While simple circles and paths are easy enough to generate by hand-coding or a vector application, complex illustrations with regular geometry are often easiest to generate with some programming. A good example of the latter is a compass rose, shown above, which was mostly created using JavaScript.

The Markup

I started the design on the page with a element that contains only symbols:

<svg xmlns="http://www.w3.org/2000/svg" 
    xmlns:xlink="http://www.w3.org/1999/xlink" 
    viewBox="0 0 500 500" id="compassrose">
<defs>  
  <symbol>
    <line x1="40" y1="250" x2="50" y2="250" id="roseline" />
    <line x1="40" y1="250" x2="60" y2="250" id="majline" />
    <path d="M10,250a240,240 0 1,0 480,0a240,240 0 1,0 -480,0" 
        id="rosecircle" />
  </symbol>
  </defs>
</svg>

Because <symbol> elements do not render by themselves, the SVG won’t be “seen” until it is completed with JavaScript. The <path> will be used as a guide to place elements on a circle, including the short and long degree lines (roseline and majline, respectively).

The CSS

The SVG is styled so that it is in the center of the page, and the lines and text provided with an appearance with :

#compassrose { 
    width: 40%;
    border: 1px solid rgba(255,255,255,0.3);
    margin-left: 30%;
    border-radius: 50%;
}
#roseline, #majline {
    stroke: #eee;
    stroke-width: .5;
}
#compassrose text {
    font-family: Montserrat, sans-serif;
    font-size: 10;
    fill: #eee;
}

Note the slightly unusual appearance of the svg element, which is given a border-radius to make it appear as the outside of the compass: the SVG element itself can be made to look circular.

Creating the Lines

The script to make the compass lines is added to the end of the document:

var lineInc = 2,
majMarkDegree = 10,
degreeInc = 30,
compassrose = document.getElementById("compassrose"),
xmlns = "http://www.w3.org/2000/svg",
xlink = "http://www.w3.org/1999/xlink";
if (lineInc > 0) {
    for (var i=0; i < 360; i+=lineInc) {
        var newline = document.createElementNS(xmlns,'use'); 
        if (i % majMarkDegree == 0) {           
            newline.setAttributeNS(xlink,'xlink:href','#majline');
        } else {
            newline.setAttributeNS(xlink,'xlink:href','#roseline');
        }
newline.setAttributeNS(null,'transform','rotate('+i+' 250 250)');
compassrose.appendChild(newline);
}

The variables are:

  • lineInc: how many degrees apart the markings are
  • majMarkDegree: how many degrees apart the major markings are
  • degreeInc: the numerical separation between the degrees printed around the edge of the circle

The for loop increments by the amount specified by lineInc. At every increment, a <use> element is created. If the incremented amount is divisible by majMarkDegree via a modulus operator, then the majline is used; otherwise, roseline is added instead. Each line is rotated into the orientation provided by i.

The Degree Markers

The degree markers use startOffSet to position the text around the edge of the compass. startOffSet takes values from 0 to 100, so the loop is based on that.

Above 0 - 9 degrees, the printed numeral will be slightly out of alignment on the circle, since the text starts at the degree point. I’ve used a somewhat unusual equation with log to determine how many numerals are in the number, and (if it is longer than a single digit), the script pulls the rotation of the number back by a degree:

var writeDegs = document.createElementNS(xmlns,'text'),
currentDeg = 0,
writeOffset = 0;
for (var i=0; i < 99; i+=(degreeInc/360)*100) {
    var degree = document.createElementNS(xmlns,'textPath');
    degree.setAttributeNS(xlink,'xlink:href','#rosecircle');
    var length = Math.log(i) * Math.LOG10E + 1 | 0;
    if (length > 1) { writeOffset = 1; } 
    degree.setAttributeNS(null,'startOffset',(i - writeOffset)+"%");
    degree.textContent = currentDeg;
    writeDegs.appendChild(degree);
    currentDeg += degreeInc;
}
compassrose.appendChild(writeDegs);

This isn’t perfectly accurate mathematically (to achieve that would require a bit more JavaScript) but it’s close enough for our purposes.

The Animation

I’ve also animated the compass to sway back and forth using the Web Animation API, using a similar technique to my “Random Walk” article. (Note that this animation will only work in Chrome and Firefox, at least currently).

function randomRot() {
    var oldOrientation = newOrientation;
    newOrientation =  Math.floor(Math.random() * 240);
    compassrose.animate([
    { transform: 'rotate('+ oldOrientation+'deg)' },
    { transform: 'rotate('+ newOrientation+'deg)' }
    ], {
    duration: Math.abs(oldOrientation - newOrientation) * 30,
    fill: 'forwards'
    }).onfinish = function() {
        randomRot();
    }
}
newOrientation = 0;
randomRot();

The function compares the oldOrientation of the compass with the newOrientation of the element (a random number between 0 and 240, interpreted as degrees) and animates between them, with a duration calculated as the difference between the orientations multiplied by 30 (interpreted as time in milliseconds).

Conclusion

There are many other ways to create SVG with JavaScript, which I’ll go into more depth in future articles; for now, I hope this might prove a useful starting point for your own experiments.

Photograph by Mel Foody, licensed under Creative Commons.

Enjoy this piece? I invite you to follow me at twitter.com/dudleystorey to learn more.
Check out the CodePen demo for this article at https://codepen.io/dudleystorey/pen/yOyzpy