Friday, 5 June 2020

An introduction to SVG - part 1

I little while back I blogged about recreating the Concentrichon using JavaScript and canvas drawing rather than the SVG of the original. In that post I mentioned that my JavaScript book had only built games using canvas drawing and I wondered now if readers of that book would be interested in a general introduction to SVG. There are many instances where SVG rather than canvas drawing would be the most appropriate tool for displaying and animating particular images.

SVG stands for Scalable Vector Graphics and it is a 2D image format that uses XML (Extensible Markup Language) syntax. Two TLAs in that sentence but there you go. Vector Graphic images are defined in terms of points on a plane with lines and curves joining the points. As the relative position of each point can be adjusted then the image can be scaled without degradation. Individual lines and curves can have defined colour, thickness and fill. Each time such an image is rendered then it is drawn afresh from the XML definition. SVG animations are baked into the specification and many effects can be achieved without program code.

My introduction to the JavaScript programming of SVG images starts with a project but veers off (many, probably most, will be glad to know) to cover some wider aspects of this graphics format than would be exercised by my wanting to reproduce an interactive version of that analogue calculating device known as a slide rule.

<Aside topic=”Slide Rules”>

Feel free to skip this section if you just want to start in on some SVG.

I was looking through some bits and bobs that came from a draw in my Father’s house when it was being cleared for sale. One of the items was a slide rule and it was found alongside the early “Sinclair Executive” calculator that replaced it. It is small (nominally 6” probably) and pretty similar to the one I remember buying with my first student grant money in the very late 1960s when they were still pretty much the state of the art for calculators. Around that time though, I did come into contact with an electronic Anita calculator but they were staggeringly expensive and you could hardly slip one in your pocket or even carry one very far.

The Sinclair Executive calculator came out in 1972 and cost £79.95 (over £1000 in 2020 money) and I probably paid £5 or £6 for the slide rule three or four years before that (still an appreciable cost that needed some internal debate to justify).

The slide rule was invented sometime between 1620 and 1630 with new functions developed and added over time until the device became the tool of choice for the developing field of engineering.

How do they work? It is pretty easy to see how two rulers could be used to do simple addition and subtraction. We can try adding 3 and 8.


We position the start on the lower ruler’s scale at one of the two values (in this case 3) and read off the sum of 3 and 8 on the scale of the upper ruler (see the red line). It should also be obvious that the same positioning could be used to calculate 11 minus 8 or any other pair of values on the two ruler scales.

Now such a simple mechanical device for adding two numbers together would not be terribly useful even if they were decimal fractions such as 3.4 and 8.7 which would be easy to do with the rulers shown above. However being able to multiply (say) 1.65 by 3.45 would be more challenging mental arithmetic for most. When I was at school, we used logarithms for such calculations.

The logarithm (base 10) of a number is the number expressed as a power of 10.

1.65 = 100.2175  so log(1.65) = 0.2175

3.45 = 100.5378 so log(3.45) = 0.5378

0.5378 + 0.2175 = 0.7553 (sum the logs)

100.7553 = 5.6925 which is the product of 1.65 and 3.45.

Thus simple addition can be used to multiply two decimal fractions.

Fortunately, when I was at school, we did not need to calculate these powers of 10 – we were issued with books of tables for looking them up. The tables included a host of trigonometric tables as well as the vital “antilogarithms” needed to establish that 100.7553 was 5.69 (which was about as accurate as the ones I had could get.

If instead of having pages of tables we were to draw a logarithmic scale on a pair or rulers instead of the linear scale illustrated above we could use them to multiply two values by adding the logs. Indeed we could also do division by subtracting one logarithmic scale position from another. So, what does a logarithmic scale look like in action?

You will notice that as the values increase (from 1 to 10 in this instance) the distance between the log of those values decreases. You can probably also see that two rules with logarithmic scales can be used to make our calculation. Slide rules were fast and accurate enough for most purposes.

Why does the log scale start at 1? Well ten to the power of zero is 1 and zero is a good place to start. In fact any number to the power of zero is 1. Feel free to debate about zero to the power of zero; Wikipedia has a page on it.

As it was way easier to use JavaScript and SVG to create the illustrations in this “aside” than to draw them using something like Paint.NET it is probably time now to cut back to the chase as they say.

</Aside>

We could start some SVG development with an HTML page and a short CSS file.

main.css looks like:

html { padding: 0; margin: 0; border: 0;} #dvViewer { width: 300px; height: 200px; } svg { height: 100%; width: 100%; }

and the index.html file has all the SVG action and plagiarises the Mozilla documentation (at least in part).

<!DOCTYPE html> <html> <head>     <meta charset="utf-8" />     <meta http-equiv="X-UA-Compatible">     <title>Basic SVG Demo</title>     <meta name="viewport" content="width=device-width, initial-scale=1">     <link rel="stylesheet" type="text/css" media="screen" href="main.css" /> </head> <body>     <div id="dvViewer">         <svg viewBox="0 0 200 100" xmlns="http://www.w3.org/2000/svg">             <style>              .normal { font: normal 15px sans-serif; }              .bold { font: bold 30px sans-serif; }              .bright { font: italic 40px serif; fill: red; }             </style> <!-- font colour is the fill -->             <text x="10" y="35" class="normal">Welcome</text>             <text x="75" y="35" class="bold">readers</text>             <text x="55" y="65" class="normal">to</text>             <text x="70" y="65" class="bright">SVG!</text>         </svg>     </div> </body> </html>

The HTML <body> contains a single <div>. Within the <div> there is an <svg> element that is structured in a pretty familiar way. XML is a “markup language” just like HTML. The <svg> element contains a <style> tag that defines three classes that can be used for CSS type styling. The <style> object is followed by four text objects with the “x” and “y” attributes setting the bottom left for the text content. The “x” and “y” units relate to the “viewBox” attribute that sets the dimensions of the viewport.

If you load the HTML file into a browser then you should see:

The <svg> viewBox attribute read   viewBox="0 0 200 100". This defined the upper left hand corner as having coordinates 0,0 with a width of 200 units and a height of 100 units. This is not the same as the dimensions of the <svg> object set by the CSS. Try resetting the viewBox values so that they match the size (300 by 200) set in the CSS and reload the page. You should observe some instant scaling.

Next step is to try drawing something just a little bit technical. Add another <div> and some enclosed <svg> to the HTML file.

<div id="dvScale">     <svg viewBox="0 0 510 30" xmlns="http://www.w3.org/2000/svg">         <style>             .normals { font: normal 10px sans-serif; text-anchor: middle;}             .tl {stroke: black; stroke-width: 0.5;}         </style>         <line x1="5" y1="20" x2="5" y2="30" class="tl" />         <text x="5" y="19" class="normals" >1</text>         <line x1="155.5" y1="20" x2="155.5" y2="30" class="tl" />         <text x="155.5" y="19" class="normals" >2</text>         <line x1="243.5" y1="20" x2="243.5" y2="30" class="tl" />         <text x="243.5" y="19" class="normals" >3</text>         <line x1="306" y1="20" x2="306" y2="30" class="tl" />         <text x="306" y="19" class="normals" >4</text>         <line x1="354.5" y1="20" x2="354.5" y2="30" class="tl" />         <text x="354.5" y="19" class="normals" >5</text>         <line x1="394" y1="20" x2="394" y2="30" class="tl" />         <text x="394" y="19" class="normals" >6</text>         <line x1="427.5" y1="20" x2="427.5" y2="30" class="tl" />         <text x="427.5" y="19" class="normals" >7</text>         <line x1="456.5" y1="20" x2="456.5" y2="30" class="tl" />         <text x="456.5" y="19" class="normals" >8</text>         <line x1="482" y1="20" x2="482" y2="30" class="tl" />         <text x="482" y="19" class="normals" >9</text>         <line x1="505" y1="20" x2="505" y2="30" class="tl" />         <text x="505" y="19" class="normals" >10</text>     </svg>     </div>

and back that up with a small addition to the CSS file.

#dvScale {     width: 510px;     height: 30px; }

Reload the HTML file in your browser and you should see the new addition rendered like.

Clearly that was a lot of XML added to the web page and it should also be fairly clear that some calculations had been hand cranked to place the vertical lines and numbers. This would be much better achieved using code so the next step will be to switch to creating our SVG graphical output using JavaScript. Before that though, take a look at the two new classes added between the <style> tags. The text style (normals) has a text-anchor attribute to centres the numbers over their defined "x" position. The <line> tags are probably what you would expect with a defined start and end point. One thing though, the style class applied defines the stroke colour as if this is omitted then the line will not be drawn.

The next step minimises the HTML but adds a couple of short JavaScript files.

<!DOCTYPE html> <html> <head>     <meta charset="utf-8" />     <meta http-equiv="X-UA-Compatible">     <title>SVG and JavaScript</title>     <meta name="viewport" content="width=device-width, initial-scale=1">     <link rel="stylesheet" type="text/css" media="screen" href="main.css" />     <script src="svg.js"></script> </head> <body>     <div id="dvScale">     </div>     <script src="main.js"></script> </body> </html>

There is a very good chance that you will decide to use a JavaScript library to manipulate SVG graphics. [If there is a lot of HTML to manipulate then you might well decide to make use of jQuery so similarly, if a lot of SVG is going to be created then a library designed to make that simpler is likely to appeal.] As a placeholder for such a library, I created a JavaScript file called svg.js with one function,

function createSvgElem(svgTag, svgAttr, svgStyle, addTo) {     const nSpace = "http://www.w3.org/2000/svg";     let nElem = document.createElementNS(nSpace, svgTag);     for (let atrib in svgAttr) {         nElem.setAttributeNS(null, atrib, svgAttr[atrib]);     }     if (svgStyle) {         for (let atrib in svgStyle) {             nElem.style[atrib] = svgStyle[atrib];         }     }     if (addTo) {         addTo.appendChild(nElem);     }     return nElem; }

That function is passed an SVG tag name, an object containing relevant attributes, an object containing style attributes (or null if there are none) and an object to which the new SVG object created should be added. For the main <svg> object that will be an HTML element but things like <line> and <text> tags will be added to a preexisting <svg> element.

All the creative action is in the main.js file.

var div = document.getElementById("dvScale"); function initialise(){     let svgStyle = {         small: { "font": "normal 10px sans-serif", "text-anchor": "middle"},         tl: {"stroke": "black", "stroke-width": "0.5"}     };     let svgAttr = {"viewBox": "0 0 510 30"};     let svg = createSvgElem("svg", svgAttr, null, div);     for(let i = 1; i < 11; i++){         let x = (Math.log10(i) * 500 + 5).toFixed(1); // log of number * scale length + scale start         svgAttr = {"x1": x, "x2": x, "y1": "20", "y2": "30"};         createSvgElem("line", svgAttr, svgStyle.tl, svg);         let lab = createSvgElem("text", {"x": x, "y": "19"}, svgStyle.small, svg);         lab.innerHTML = i.toString();     } } initialise();

That code takes a bit of a  scatter gun approach to show a couple of techniques for attributes and styles. The <svg> object is created with an attribute detailing the viewBox. Then a sequence of <line> and <text> tags are added within the for loop but note that the content of each <text> tags is added after it is created.

If you open the revised web page in your browser and then open the "Developer Tools" (<F12>) you can take a look at the content of the <div> with the id dvScale using the "elements" tab in Chrome and Edge or the "Inspector" tab in FireFox. You should see the nicely laid out <svg> tag that starts:

<svg viewBox="0 0 510 30">   
    <line x1="5.0" x2="5.0" y1="20" y2="30" style="stroke: black; stroke-width: 0.5;"></line>
    <text x="5.0" y="19" style="font: 10px sans-serif; text-anchor: middle;">1</text>
    <line x1="155.5" x2="155.5" y1="20" y2="30" style="stroke: black; stroke-width: 0.5;"></line>
    <text x="155.5" y="19" style="font: 10px sans-serif; text-anchor: middle;">2</text>
    <line x1="243.6" x2="243.6" y1="20" y2="30" style="stroke: black; stroke-width: 0.5;"></line>
    <text x="243.6" y="19" style="font: 10px sans-serif; text-anchor: middle;">3</text>
    <line x1="306.0" x2="306.0" y1="20" y2="30" style="stroke: black; stroke-width: 0.5;"></line>
    <text x="306.0" y="19" style="font: 10px sans-serif; text-anchor: middle;">4</text>
    <line x1="354.5" x2="354.5" y1="20" y2="30" style="stroke: black; stroke-width: 0.5;"></line>
    <text x="354.5" y="19" style="font: 10px sans-serif; text-anchor: middle;">5</text>
...continues...

Looking just as it might have been typed in the hard way.

Enough of these logarithmic scales - time to sample some of the other things we can do with SVG - all in part 2.


Further reading:



SVG JavaScript Libraries (web search for others):

Snap.svg 


No comments:

Post a comment