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 


An introduction to SVG - part 2

Part 1 explored creating SVG graphics using XML embedded in a web page and then followed that up with some JavaScript that generated the SVG to duplicate the original output. This part will follow on from those simple <text> and <line> tags with some more SVG goodies starting with some shapes.

Assuming that you are following on from part 1 we can keep the current HTML, CSS and svg.js files and try out some SVG shapes with changes to the main.js file.

var div = document.getElementById("dvScale"); function initialise(){     let svgAttr = {"viewBox": "0 0 510 30"};     let svg = createSvgElem("svg", svgAttr, null, div);     svgAttr = {x: 2, y: 2, width: 30, height: 20};     createSvgElem("rect", svgAttr, null, svg); } initialise();

Which gets us a solid black rectangle. Looks like we need some style.

var div = document.getElementById("dvScale"); function initialise(){     let svgAttr = {"viewBox": "0 0 510 30"};     let svg = createSvgElem("svg", svgAttr, null, div);     svgAttr = {x: 2, y: 2, width: 30, height: 20};     let svgStyle = {stroke: "lime", fill: "white"};     createSvgElem("rect", svgAttr, svgStyle, svg); } initialise();

We could also set the stroke-width and you are likely to often want a transparent fill. Try using:

let svgStyle = {stroke: "lime", fill: "transparent", "stroke-width": 3};

Rounded corners? Specify the corner in the style object.

let svgStyle = {stroke: "black", fill: "transparent", "stroke-width": 3, rx: 5, ry: 5};

For a circle we are going to have to specify the centre and the radius while an ellipse effectively specifies two radii (one for the vertical y dimension and the other for the horizontal).

var div = document.getElementById("dvScale"); function initialise(){     let svgAttr = {"viewBox": "0 0 510 30"};     let svg = createSvgElem("svg", svgAttr, null, div);     svgAttr = {cx: 20, cy: 15, r: 12};     let svgStyle = {stroke: "hotpink", fill: "transparent", "stroke-width": 1};     createSvgElem("circle", svgAttr, svgStyle, svg);     svgAttr = {cx: 60, cy: 15, rx: 12, ry: 6};     createSvgElem("ellipse", svgAttr, svgStyle, svg); } initialise();

A polyline has multiple x/y coordinates expressed as a single attribute. You will need to decide how you will ensure that the specification is easy to maintain, particularly if a given line is long. I chose to have the x and y values separated by a space and each point separated by a comma (OK and a space).

var div = document.getElementById("dvScale"); function initialise(){     let svgAttr = {"viewBox": "0 0 510 30"};     let svg = createSvgElem("svg", svgAttr, null, div);     svgAttr = {points: "0 0, 10 10, 0 20, 30 20, 40 10"};     let svgStyle = {stroke: "teal", "stroke-width": 1};     createSvgElem("polyline", svgAttr, svgStyle, svg); } initialise();

But that code draws:

The polyline is (perhaps unexpectedly) filled, so we probably need to be more specific. Change the style object as follows and give it another run.

let svgStyle = {stroke: "teal", "stroke-width": 1, fill: "transparent"};

The SVG polygon looks just like a <polyline> but there is an implicit line drawn from the last specified point back to the first, to enclose the shape.

var div = document.getElementById("dvScale"); function initialise(){     let svgAttr = {"viewBox": "0 0 510 30"};     let svg = createSvgElem("svg", svgAttr, null, div);     svgAttr = {points: "15 0, 0 10, 5 30, 25 30, 30 10"};     let svgStyle = {stroke: "red", "stroke-width": 1, fill: "transparent"};     createSvgElem("polygon", svgAttr, svgStyle, svg); } initialise();

The <polyline> and <polygon> tags bring us to the even more flexible but potentially complex <path>.

The specification for a <path> tag "d" attribute is akin to a language as it can contain a sequence of commands. The command sequence is used to define a set of lines and curves. For an explanation of the individual command options I recommend reading the Mozilla page here.

In brief. The command designated by the uppercase letter "M" is a "move to" command with a specified x/y coordinate. A lower case "m" specifies a relative movement from the current location. L and l define drawn lines to an absolute or relative location. H and h draws a horizontal line with just an x location specified. Similarly V and v draw vertical lines with just a y value. Z or z closes a path by drawing a line back to the start point. C and c commands can be used to draw Cubic Bezier curves. Similarly S, Q and T will draw alternate Bezier curve types. A and a can be used to draw arcs. All of the path components form a contiguous whole.

You are probably not going to hand code (or edit XML for) lengthy <path> tags. However there are a number of SVG editors around that can be used to create and maintain complex shapes. Once a shape has been defined then you can scale it and apply styles.

You might like to copy and paste the following example paths and then maybe play around with the <div> dimensions, viewBox attributes and the style object.

var div = document.getElementById("dvScale"); function initialise(){     let svgAttr = {"viewBox": "0 0 510 60"};     let svg = createSvgElem("svg", svgAttr, null, div);     let svgStyle = {stroke: "red", "stroke-width": 1, fill: "transparent"};     svgAttr = {d: "m31.847256,20.147449c7.627459,-16.538212 37.512093,0 0,21.263415c-37.512093,-21.263415 \      -7.627459,-37.801627 0,-21.263415z"};     createSvgElem("path", svgAttr, svgStyle, svg);     svgAttr = {d: "m74.805598,52.920259c-1.325839,-1.078225 2.076708,-1.107086 \     1.542153,-2.302318c0.977352,-1.583842 3.518568,-2.542536 3.892802,-4.342332c-1.030665,-0.500215 \      -1.623434,-1.709964 0.342901,-1.566342c1.44199,-0.756148 1.261771,-2.303513 1.695423,-3.439055 \      c0.40623,-1.83279 0.498224,-3.687829 0.67271,-5.535677c-1.668471,-0.089279 -3.378213,-0.027091 \      -5.003929,-0.324922c0.046763,-1.228895 3.354712,-0.757366 3.50527,-1.916973c2.40519,-0.030278 \      0.133222,-2.53291 -0.84626,-3.129996c-1.253728,-1.007249 -2.088776,-2.597297 -1.135676,-3.841233 \      c1.363243,-0.630993 4.445648,-0.246902 4.306324,-1.90514c-0.92712,-0.368159 -2.104238,-0.134364 \      -3.143361,-0.199472c0.063904,-0.773238 0.127806,-1.546474 0.19171,-2.319711c1.374225,-0.073638 \      2.748453,-0.147281 4.122678,-0.220925c-0.697538,-0.505036 -1.741481,-0.876715 -2.002715,-1.591114 \      c0.888562,-1.017564 3.03006,-0.927739 4.564204,-0.918118c1.491783,-0.071682 4.211677,0.621513 \      2.178285,1.738058c-0.164809,0.274194 -1.76877,0.955962 -0.777544,0.881636c1.269146,0 2.538289,0 3.807432,0 \      c-0.003647,0.755909 -0.032817,1.519331 0.222013,2.259079c-0.968779,0.50261 -4.110267,-0.42933 \      -3.215785,1.121467c1.00718,0.648374 2.549715,0.631263 3.827187,0.916809c0.745128,0.05186 1.337877,0.187126 \      1.06936,0.793888c0.140053,1.12655 -0.267946,2.259795 -1.379505,3.114961c-0.78685,0.892343 -2.554104,2.009454 \      -2.030939,3.04235c0.989808,-0.112243 1.784926,0.34051 0.994348,0.849666c0.811317,0.418481 3.668275,0.322648 \      3.050353,1.370331c-1.44748,0.333793 -3.002438,0.194731 -4.504179,0.228793c0.399856,2.859806 0.952729,5.719883 \      1.876543,8.520375c0.539773,0.621425 2.683955,1.099034 0.92425,1.909172c-0.656721,1.508686 1.44088,2.638331 \      2.758263,3.606642c1.448451,0.755733 0.745692,2.276334 2.794824,2.518852c1.613781,1.089034 -2.215606,1.06004 \      -3.198757,1.062415c-6.508579,0.04889 -13.024179,0.11729 -19.530211,-0.028817c-0.532596,-0.046826 -1.190603,-0.036114 \      -1.57017,-0.35235l-0.000002,0.000001z"};     createSvgElem("path", svgAttr, svgStyle, svg); } initialise();

I am sure that the second drawn object (a chess piece) underlines the suggestion that you might like to do an Internet search for an SVG editor when faced with some sorts of SVG drawing challenges.

A graduated fill would be fun although it is just a little bit more fiddly than the <canvas> equivalent. This is because when building SVG graphics we are actually constructing XML. With that in mind, the following should seem straightforward. I did edit the main.css file to increase the size of the dvScale <div> to create a bit more display space though. I went for 500px wide and 300px high and that will be reflected in the dimensions of the viewBox attribute.

The code comments document the steps.
var div = document.getElementById("dvScale"); function initialise(){     let svgAttr = {"viewBox": "0 0 500 300"};     let svg = createSvgElem("svg", svgAttr, null, div);     // gradients are located in a "defs" element in the XML     let defs = createSvgElem("defs", null, null, svg);     // now we define the gradient attributes including an id     svgAttr = {x1: 0, x2: 0, y1: 0, y2: 1, id: "grad1"}; // vertical gradient     let grad = createSvgElem("linearGradient", svgAttr, null, defs);     // we now create 3 stops for the gradient     svgAttr = {offset: "0%", "stop-color": "red"};     createSvgElem("stop", svgAttr, null, grad);     svgAttr = {offset: "50%", "stop-color": "black", "stop-opacity": 0};     createSvgElem("stop", svgAttr, null, grad);     svgAttr = {offset: "100%", "stop-color": "blue"};     createSvgElem("stop", svgAttr, null, grad);     // now a rectangle to show the gradient off     svgAttr = {x: 20, y: 20, width: 100, height: 100, rx: 10, ry: 10};     createSvgElem("rect", svgAttr, {fill: "url(#grad1)"}, svg); // the style adds the gradient fill } initialise();

The result is quite pleasing:

There are also radial gradients with some additional features but the ability to create a pattern and use that as a fill is a standout feature.

var div = document.getElementById("dvScale"); function initialise(){     let svgAttr = {"viewBox": "0 0 500 300"};     let svg = createSvgElem("svg", svgAttr, null, div);     // patterns like gradients are inside a defs element     let defs = createSvgElem("defs", null, null, svg);     // now a horizontal gradient     svgAttr = {id: "grad1"};     let grad = createSvgElem("linearGradient", svgAttr, null, defs);     // with 2 stops     svgAttr = {offset: "10%", "stop-color": "white"};     createSvgElem("stop", svgAttr, null, grad);     svgAttr = {offset: "95%", "stop-color": "blue"};     createSvgElem("stop", svgAttr, null, grad);     // now we define the pattern     svgAttr = {x: 0, y: 0, width: 0.25, height: 0.25, id: "patt1"};     let patt = createSvgElem("pattern", svgAttr, null, defs);     // we can now create the components of the pattern     svgAttr = {x: 0, y: 0, width: 50, height: 50};     createSvgElem("rect", svgAttr, {fill: "cyan"}, patt); // a square     svgAttr = {cx: 25, cy: 25, r: 20}; // with a circle inside it     createSvgElem("circle", svgAttr, {fill: "url(#grad1)", "fill-opacity": 0.5}, patt);     // and a rectangle to show the repeating pattern off     svgAttr = {x: 20, y: 20, width: 200, height: 200, rx: 10, ry: 10};     createSvgElem("rect", svgAttr, {fill: "url(#patt1)"}, svg); } initialise();

The pattern repeats as it fills the rectangle. Patterns can be created using any combination of SVG drawing elements.

SVG graphics includes transformations and if you are mathematically inclined you can apply matrices but for the moment we could try a rotation. You will need to create a bit more screen space for this one but start by just changing the svgAttr object for the pattern filled rectangle to read:

svgAttr = {x: 120, y: 0, width: 200, height: 200, rx: 10, ry: 10, transform: "rotate(30)"};

Not bad for just a few lines of code.

A prowl around the Mozilla web site will reveal many other SVG features but I hope that this two part introduction has shown how to apply at least the basics from JavaScript. I think though that we should end with an animation.

Change the final rectangle attribute object as shown below and then add the two extra lines that define an animation that repeatedly adjusts the radius applied to the corners over a 10 second time-frame. Enjoy.

svgAttr = {x: 120, y: 0, width: 200, height: 200, transform: "rotate(30)"}; let rect = createSvgElem("rect", svgAttr, {fill: "url(#patt1)"}, svg); svgAttr = {attributeName: "rx", values: "0;100;0", dur: "10s", repeatCount: "indefinite"}; createSvgElem("animate", svgAttr, null, rect);


Tuesday, 12 May 2020

Draw diagrams in VS Code

This is another great Visual Studio Code extension and it allows you to draw diagrams using Draw.io. This makes it easy to include a diagram in a project folder and maintain it in just the same way as you would create and maintain JavaScript, HTML or CSS files (or indeed any other language file) using VS Code.


The image shows the extension in action.

You can use the extensions feature of VS Code to download and install Draw.io integration. Search for "draw.io" and select the Henning Dieterichs offering from any presented.

Once installed, restart VS Code and you are ready to go. To create a diagram, start with a new file and then save it as an XML file but with the .drawio extension. [VS code might attach .xml onto the end but you can rename it directly in the VS Code explorer view.] Now if you click on your xml/drawio file the newly installed extension will load and you can start adding drawing objects.

There is more information to be found at the project GitHub repository.

Wednesday, 22 April 2020

Intersections

It is common when writing JavaScript games to need to calculate if a line will intersect with a given rectangle, circle or another line. In the chapter on Ray Casting in my book on learning JavaScript programming the code required to test for an intersection between a "ray" and a game world wall or sprite proved straightforward as the problem was deliberately simplified by the overall structure. In other projects some more general functions might prove helpful.

Interactive demo web page here

Demo code available for download here

The demo page features three intersection functions. All three functions are based upon Internet research and I am grateful for all of the very useful suggestions I found for each.

1. Line intersection with a rectangle

function lineIntersectsRect(lStart, lEnd, rect) { let minX = lStart.x; let maxX = lEnd.x; if(lEnd.x < lStart.x) { maxX = lStart.x; minX = lEnd.x; } maxX = Math.min(maxX, rect.x + rect.width); minX = Math.max(minX, rect.x); if(minX > maxX) {return false;} let minY = lStart.y; let maxY = lEnd.y; let dx = lEnd.x - lStart.x; if(dx) { // there is a horizontal slope let a = (lEnd.y - lStart.y)/dx; let b = lStart.y - a * lStart.x; minY = a * minX + b; maxY = a * maxX + b; } if(minY > maxY) { [minY, maxY] = [maxY, minY]; // ES6 Destructuring Assignment swaps values } if(Math.max(minY, rect.y) > Math.min(maxY, rect.y + rect.height)){ return false; } return true; }























2. Line intersection with a circle

function lineIntersectsCircle(lStart, lEnd, center, radius) { let dist; const dx1 = lEnd.x - lStart.x; const dy1 = lEnd.y - lStart.y; const dx2 = center.x - lStart.x; const dy2 = center.y - lStart.y; // get the unit distance along the line of the closest point to circle center const u = (dx2 * dx1 + dy2 * dy1) / (dy1 * dy1 + dx1 * dx1); // if the point is on the line segment get the distance squared to circle center if(u >= 0 && u <= 1){ dist = Math.pow((lStart.x + dx1 * u - center.x), 2) + Math.pow((lStart.y + dy1 * u - center.y), 2); } else { // use the u distance to determine which end is closest and get dist squared to circle dist = u < 0 ? Math.pow((lStart.x - center.x), 2) + Math.pow((lStart.y - center.y), 2) : Math.pow((lEnd.x - center.x), 2) + Math.pow((lEnd.y - center.y), 2); } return dist < Math.pow(radius, 2); }

















3. line intersection with a line

function lineIntersectsLine(p0, p1, p2, p3) { let dx1 = p1.x - p0.x; let dy1 = p1.y - p0.y; let dx2 = p3.x - p2.x; let dy2 = p3.y - p2.y; let d = dx1 * dy2 - dx2 * dy1; if(!d) { return false;} let dIsPositive = d > 0; let xd1 = p0.x - p2.x; let yd1 = p0.y - p2.y; let s_numer = dx1 * yd1 - dy1 * xd1; if((s_numer < 0) === dIsPositive) { return false; // watch logic = both true or both false returns false } let t_numer = dx2 * yd1 - dy2 * xd1; if((t_numer < 0) === dIsPositive) { return false; } if((s_numer > d) === dIsPositive || (t_numer > d) === dIsPositive) { return false; } return true; }