Gauges, the types of which I discussed in my last post, are pretty useless without target zones showing people where the needle should be pointing. With that in mind, it's probably a good idea that we add them to our gauges before they become part of the dashboard. This, of course, means drawing SVG arcs. Which means I need to know where a particular point is on the circumference of a circle.
Which means trigonometry.
I really enjoyed maths at school - two really great teachers helped - even going so far as to enjoy trigonometry. Sadly, as I only use a small portion of the maths I used to be quite fluent in at school on a day-to-day basis, most of the things I used to know now escape me, without a quick trip down Google's memory lane.
Even a fairly basic task - finding the x and y coordinates of a point at a given angle, on the circumference of a circle of radius r, centred at the coordinates cx,cy - saw me grind to a halt for a minute or two this morning. Then you find what you were looking for, and you remember it was dead easy all along:
Adding the zones to a circular gauge means I need to know where the arc starts, and where it finishes. Fortunately, SVG will worry about the rest for me, but I need the x,y co-ordinate pairs for four points on the zone.
Sure, we could calculate these by hand and hard-code them in, but given that the gauge is going to change and be re-used, it's better - and easier - to get some code to do the calculations for us. Good job that SVG supports JavaScript, then:
function c_xy(r, a, cx, cy) {
var cx = 110 + (r * Math.cos(a * (Math.PI/180)));
var cy = 110 - (r * Math.sin(a * (Math.PI/180)));
return {
x: cx,
y: cy
};
}
This function will return an object with the x and y coordinate of a point at angle a on the circumference of a circle of radius r. All it's doing is exactly what the equations on the chart above show.
So now we have a way of finding the co-ordinates of the points on the zone, let's create a function that will add a zone to the gauge:
function gauge_addZone(pct1, pct2, fill, className, r1, r2) {
var a1 = (-270*(pct1/100))+225;
var a2 = (-270*(pct2/100))+225;
var c1s = c_xy(r1, a1 % 360, 110, 110);
var c1f = c_xy(r1, a2 % 360, 110, 110);
var c2s = c_xy(r2, a2 % 360, 110, 110);
var c2f = c_xy(r2, a1 % 360, 110, 110);
var zone = document.createElementNS("http://www.w3.org/2000/svg", "path");
var d = " M " + c1s.x + "," + c1s.y +
" A " + r1 + "," + r1 + " " + (pct2-pct1 > (200/3) ? "0 1" : "1 0") + " 1 " + c1f.x + "," + c1f.y +
" L " + c2s.x + "," + c2s.y +
" A " + r2 + "," + r2 + " " + (pct2-pct1 > (200/3) ? "0 1" : "1 0") + " 0 " + c2f.x + "," + c2f.y +
" Z";
zone.setAttribute("d", d);
zone.setAttribute("fill", fill);
if (className != null) {
zone.setAttribute("class", className + " gaugeZone");
} else {
zone.setAttribute("class", "gaugeZone")
}
document.getElementById("gaugeBack").appendChild(zone);
}
This function does all the heavy lifting. The gauges I'm creating all work on the theory of ranges from 0% to 100%, so by passing in the starting and finishing percentages - pct1 and pct2 - of the target zone, I've a good idea where I'm expecting the zone to be drawn.
The fill value allows me to pass in a colour, if I so desire, and I'm hoping className should be fairly self-explanatory too. The r1 and r2 values allow me to set the thickness of the zones. For the zone in the example above, I've used 80 and 55, but if I want a thin zone to be drawn, I could specify radii of 80 and 75, resulting in something like this:
Now it's time to use our c_xy function to get our co-ordinate objects for the four points. The zone is created as a path element, and we draw using the d attribute. I won't go into the actual syntax, but it's safe to say that it's fairly simple to pick up. SVG Basics has some good tutorials on paths and arcs that you can read if you're so inclined.
Finally, because we don't really want to hard-code the position of the pointer, let's create a function that will move the pointer for us. This one's really basic, but you could extend it to make a smoothly animated pointer with minimal fuss:
function gauge_setPointer(pct) {
var a = (270 * (pct/100));
document.getElementById("gaugeNeedle").setAttribute("transform", "rotate(" + a + ",110,110)");
}
All we're doing, because we know that the angular range from 0% to 100% spans 270°, is multiplying that angle by the percentage, and then applying a transformation to the gauge needle. Very simple.
Finally, we actually call the functions:
gauge_addZone(80, 100, "#0f0", "zoneGood", 80, 55);
gauge_setPointer(83.5);
Which gives us exactly what you've seen above:
The code I've used above means that we can also add multiple zones, should we wish. If, as well as showing a safe zone, you want to also add a zone which should be avoided, you can do that. All we'd need to do would be to add another gauge_addZone line, and we're done:
gauge_addZone(80, 100, "#0f0", "zoneGood", 80, 55);
gauge_addZone(0, 40, "#f00", "zoneBad", 80, 55);
gauge_setPointer(36.8);
This would result in the following example:
I'm really enjoying using SVG, and I'll probably post more on it as time goes by.