Creating Attractive and Functional HTML5 Canvas Gauges

A Guide to Using CanvasDial.js

Introduction

With the proliferation of smartphones and tablets running native apps containing beautiful and dynamic graphics, expectations from users are high. At the same time, Web technology is improving rapidly, and the capabilities of Web applications are getting closer to native apps all the time, even while both continue to advance. Mediocre Web pages and Web apps have no hope of competing against native apps with a user population that doesn’t know or care why — they will prefer and use what works, what is easy to view, and what conveys information in the best manner possible.

The subject of user interaction with design, color, graphics, and interfaces is vast, and the ongoing debate over native apps vs. Web apps will not be resolved anytime soon. What is clear is that there is a need to present dynamic, colorful, and high-quality data visualization graphics within any Web browser on any operating system without requiring an extra plugin. That’s a pretty tall order, and comprises a wide-ranging domain, so this Javascript class is focused narrowly on the creation of dynamic circular dials and gauges.

Motivation

The HTML5 Canvas has been around for over six years at the time of this writing, but the initially slow adoption by the leading browser of the time, and the fact that the programatic creation of canvas graphics is non-trivial has slowed the appearance of such graphics. All modern browsers now support the HTML5 Canvas, so the only remaining speedbump is the difficulty of creating complex drawings.

This Javascript class attempts to make it easy to produce attractive HTML5 canvas dials and circular gauges, abstracting some of the gory details such as the mathematics of arcs, polar coordinates, relative widths and heights, and placement and rotation of all the various elements. In doing so, it by necessity eliminates some freedom, but gains consistency, repeatability, and ease of use.

CanvasDial.js allows the Web developer to create a dial or gauge representing any unit of measure, with any range of values, with complete control over all visual elements, while removing the tedium of the canvas details.

How To Use CanvasDial.js

As with any Javascript code, it needs to be referenced in the page by a script element:
<script src="path-to-javascript-directory/CanvasDial.js"></script>
The code does not require jQuery, although it plays nicely with jQuery, and should not interfere with any other namespaces or programs.
In the HTML code, a <canvas> element needs to appear with an id attribute as the only required element:
<canvas id="temperature" width="200" height="200"></canvas>
Here the optional attribute width is included to limit the size of the canvas. Most canvas implementations default to a width of 300 (usually pixels).

At this point, the CanvasDial code has been loaded, and a canvas element has been included on the page, but there is no connection or reference to the code from the canvas. Instantiating a CanvasDial object with the required parameter id of the canvas element makes this connection:
<script>var tempGauge = new CanvasDial("temperature")</script>
The above code represents the bare minimum required to get a canvas element on the page and make the connection to the javascript code, but nothing has been drawn yet.

Creating the Base Dial

CanvasDial.js allows for three different methods to pass parameter values to the object. In most circumstances there is no perceivable performance advantage to any one of the methods. The variety is provided so that the developer may use whichever method makes the most sense for the application or even mix and match among them. The three methods are:

  • Pass the values in specific data- attributes within the canvas element.
  • Pass the values in an key/value object to the setBaseParameters function.
  • Set individual object attributes.

Therefore, the following methods are all functionally equivalent:

<canvas id="temperature"
data-value="22"
data-units="&deg;C"
data-sweep="270"
data-rotation="135"
data-minvalue="-40"
data-maxvalue="50"
data-tickmarks="9"
data-fontfamily="Helvetica, Arial, sans-serif"
data-bgcolor="#224"
></canvas>


<canvas id="temperature"></canvas>
<script>
var tempGauge = new CanvasDial("temperature");
var gaugeParams = {value:22, units:'\xB0'+'C', sweep:270, rotation:135, minvalue:-40, maxvalue:50, tickmarks:9, fontfamily:'Helvetica, Arial, sans-serif', bgcolor:'#224'};
tempGauge.setBaseParameters(gaugeParams);
</script>


<canvas id="temperature"></canvas>
<script>
var tempGauge = new CanvasDial("temperature");
tempGauge.value = 22;
tempGauge.units = '\xB0'+'C';
tempGauge.sweep = 270;
tempGauge.rotation = 135;
tempGauge.minvalue = -40;
tempGauge.maxvalue = 50;
tempGauge.tickmarks = 9;
tempGauge.fontfamily = 'Helvetica, Arial, sans-serif';
tempGauge.bgcolor = '#224';
</script>

There is an advantage to be gained by using the second method, where an object is defined containing all the gauge parameter: the object variable may be used over and over in other gauges, even if values in the object are modified along the way.

Alternatively, all base parameters have default values, so a final option is simply to do nothing and accept the defaults.

Drawing the Elements of a Gauge

In the preceding section, an instance of a CanvasDial was created and connected to a canvas element on the page. However, nothing has been drawn, so nothing will appear on the page without creating the various components of a gauge. Any and all of these components are optional, and most can even be created multiple times if needed. There are six main components available in CanvasDial, including the background or face, the sweep or arc, the tick marks,the numerical scale, the pointer, and the textual value.

Dial Background

The simplest component is the background. It is a solid circle the width of the canvas. The color of the background is the given bgcolor, or the default black. Drawing the background is performed by a simple function call with no arguments:
tempGauge.drawBackground();
The bgcolor given in the instantiation phase can be an rgba color, so the dial face may be transparent. Additionally, if the drawBackground() function is not called, the the gauge will have no background, which is the equivalent of a completely transparent background.

Arcs, Segments, and Sweeps

An arc is a segment drawn on the face of the dial, which is usually employed to make a range of the gauge stand out, create a complete contrasting sweep, or draw a short segment which highlights a portion of the gauge range. The function with the most freedom, therefore the most options, is drawArc. drawSweep is a convenience function which draws an arc over the entire range, and also draws the tick marks if they have been defined. drawSegment simply draws an arc with butt ends, and is meant to create partial arc segments. Here are three code snippets which illustrate the drawing of arcs, a sweep, and a segment:

Left Gauge:
tempGauge.drawArc({start:-40, stop:0, radiusPercent:85, widthPercent:10, color:'blue', lineCap:'round'});

Middle Gauge:
tempGauge.drawSweep({radiusPercent:85, widthPercent:10, color:'red'});

Right Gauge:
tempGauge.drawArc({start:-40, stop:0, radiusPercent:85, widthPercent:10, color:'blue', lineCap:'round'});
tempGauge.drawArc({start:30, stop:50, radiusPercent:85, widthPercent:10, color:'red', lineCap:'round'});
tempGauge.drawSegment({start:0, stop:30, radiusPercent:85, widthPercent:10, color:'orange'});

Tick Marks

If tickmarks has been defined and the drawSweep function is called, then tick marks will automatically be drawn on the sweep as seen in the middle example above. However, often more freedom over the number, size, and placement of tick marks is desired. The drawTicks function is provided for just this reason.

Continuing the example with the furthest rightbottom gauge from above, any number of tick marks can be drawn. This example shows main tick marks drawn at every 10 division, smaller ticks drawn at every 5 division, and finally a very small tick drawn at every integer.
tempGauge.drawTicks({radiusPercent:92, lengthPercent:8, tickWidth:1, color:'#220'});
tempGauge.drawTicks({numTicks:20, radiusPercent:85, lengthPercent:5, tickWidth:1, color:'#220'});
tempGauge.drawTicks({numTicks:100, radiusPercent:80, lengthPercent:2, tickWidth:1, color:'#220'});

Note that tick marks spacing is directly proportional to the number of ticks parameter numTicks. Tick marks will be evenly distributed across the entire sweep. In the above example, the numTicks values of 18 and 90 were chosen so that tick marks would appear on every 5th number and every single number respectively. Also note that in the first function call to drawTicks, numTicks was not defined. That is because the main object tickmarks had already been set to 9. In this case, if numTicks is not defined, then the object tickmarks value will be used. If neither have been defined, then only a single tick mark at the low range will be drawn, which is probably not what is desired. In general, it is usually best to simply define all desired values to avoid surprises.

The Scale

Drawing the scale numbers is mostly automatic. This is usually a good thing, since trying to figure out how to space numbers around a dial is not easy. The drawScale function does all the work. If numTicks has not been set, then the total range will be divided by 10 and the numbers and spacing will follow, regardless of how strange it may appear (if for instance, your total range is something like 73, even though 73 is the best number, followed closely by 42).

This simple function call will draw a scale on the outer edge of the dial:

tempGauge.drawScale({radiusPercent:98, fontSizePercent:15, color:'white'});

The available options allow for setting both the position and font size of the numbers, as well as the color of the numbers.

Current Value Text

Most often, a textual representation of the current value is helpful to those people who demand to know exactly what the value is. Again, for the most part, the hard work is done in the drawValue function, with control over colors and whether or not the value is centered on the dial face.

If a pointer will not be used, the value should probably be centered. Setting the option centered to true will center the text.

If the option centered is omitted or set to false, the function will automatically figure out the best position for the text, by choosing a space along the edge of the dial not occupied by the sweep, which will also not be covered by a pointer.

The gauge on the left centers the value. Of course without a pointer, the scale doesn’t make much sense, so in this example, the value is represented by a short segment inside the sweep.
tempGauge6.drawSegment({start:21, stop:23, radiusPercent:63, widthPercent: 10, color:'#8ff'});
tempGauge.drawValue({centered: true});

The middle gauge accepts all defaults, and positions the value in the blank area between the sweep.
tempGauge.drawValue();

As an example of automatic positioning, if the gauge rotation is changed before calling the drawValue function, the position of the value will change accordingly as shown on the right gauge.
tempGauge.rotation = 0;
// ... other drawing functions ...
tempGauge.drawValue();

Pointer

The pointer is the element which visually indicates the value on a gauge. Drawing the pointer is simple by calling the drawPointer function. At this time there is a single style of pointer, so the only options are the size and color of the pointer, supplied by radiusPercent and color respectively.

This example sets the length of the pointer to 70% of the radius, uses the default red color, and will automatically point to the previously given value:

tempGauge10.drawPointer({radiusPercent: 70});

The pointer value may be any integer or float within the range of the gauge. If a value such as 22.3578 were passed to the drawPointer function, it would happily position the pointer at that value, although a user would be hard-pressed to discern any difference between that and 22.

All together now

That’s it then! Simple, yes? From beginning to end, taken all together then, the code required to create the above sample gauge is:

<script src="path-to-javascript-directory/CanvasDial.js"></script>
<canvas id="temperature" width="200"></canvas>
<script>
var tempGauge = new CanvasDial("temperature");
var gaugeParams = {value:22, units:'\xB0'+'C', sweep:270, rotation:135, minvalue:-40, maxvalue:50, tickmarks:9, fontfamily:'Helvetica, Arial, sans-serif', bgcolor:'#224'};
tempGauge.setBaseParameters(gaugeParams);
tempGauge.drawBackground();
tempGauge.drawArc({start:-40, stop:0, radiusPercent:85, widthPercent:10, color:'blue', lineCap:'round'});
tempGauge.drawArc({start:30, stop:50, radiusPercent:85, widthPercent:10, color:'red', lineCap:'round'});
tempGauge.drawSegment({start:0, stop:30, radiusPercent:85, widthPercent:10, color:'orange'});
tempGauge.drawTicks({radiusPercent:82, lengthPercent:8, tickWidth:1, color:'#220'});
tempGauge.drawTicks({numTicks:18, radiusPercent:75, lengthPercent:5, tickWidth:1, color:'#220'});
tempGauge.drawTicks({numTicks:90, radiusPercent:70, lengthPercent:2, tickWidth:1, color:'#220'});
tempGauge.drawScale({radiusPercent:98, fontSizePercent:15, color:'white'});
tempGauge.drawValue();
tempGauge.drawPointer({radiusPercent: 70});
</script>

It is worthwhile to note that most options are indeed optional, and an error in the specification or value of an option will usually result in that option simply not being used. In the few cases where an option is mandatory (such as a radiusPercent), a failure to supply this option or an error in the value will result in an error message sent to the console, but the rest of the code will not be affected.

Changing the Value

In other programming environments, most of the various elements of a gauge would be their own object, or at least attributes of an object. If this were the case, then updating a value would likely be reflected immediately without the need to redraw the entire gauge. This is not the case with the HTML5 canvas element.

The creation and functions of the HTML5 canvas make it appear to be vector-based. However, an HTML5 canvas graphic is rendered as a raster image on the browser, usually as a PNG image. What this means for changing values is that if the position of the pointer and the text value need to change, a way to do so graphically would involve "erasing" the pixels by painting over them with the background color. A more reliable and easier way is to simply redraw the entire gauge when the value changes.

It is for this reason that there is no function to update or change the value or pointer. Simply redraw the gauge with the new value. For efficiency, create all the elements of a gauge except the value and pointer, and then use the canvas function save(). Then the drawValue and drawPointer functions can be called. When changing the value, change the value variable, then call the canvas function restore(), and finally draw the value and pointer.

Parameter Options Explained

Base parameters

  • value : The value to display on the dial or gauge.
  • units : The units of measure to display if the text is drawn.
  • sweep : The degrees which the sweep of the dial will span. Given as an integer from 0-360.
  • rotation : The degrees to which the starting point is rotated. Given as an integer from 0-360.
  • minvalue : The gauge minimum value. Given as an integer which may be negative.
  • maxvalue : The gauge maximum value. Given as an integer which may be negative.
  • tickmarks : The default number of tick marks to draw on the sweep. Given as a positive integer.
  • fontfamily : The font to be used on gauge text. Given as a string confirming to W3C font-family property.
  • bgcolor : The background color of the dial face. Given as a hex color number, or an rgba list.

Option parameters in class functions

  • start : The starting point for an arc or segment, given as a value within the numerical range of the gauge.
  • stop : The stopping point for an arc or segment, given as a value within the numerical range of the gauge.
  • radiusPercent : The position of an arc, tick marks, scale numbers, or length of a pointer given as a percentage of the radius, where 0 is center, and 100 is the edge.
  • widthPercent : The width of an arc or segment as a percentage of the total radius.
  • lengthPercent : The length of the tick mark as a percentage of the total radius.
  • fontSizePercent : The size of the numbers font as a percentage of the total radius.
  • numTicks : The number of tick marks to be divided evenly across the sweep.
  • tickWidth: The thickness of the tick mark. 1 is a line, where 5 would be a fat mark.
  • centered : A boolean value that determines whether the value is placed in the dial center.
  • lineCap : The end style of a line. One of “butt”, “round”, or “square”.
  • color, fgcolor, bgcolor : All color values are given as #hex, rgba, or color strings.

A Wide Variety of Styles

A class of this nature by necessity limits the freedom when compared to coding everything from scratch. However, there is considerable variety still available. For example, any number of arcs of any color, and any width can be drawn. Fonts and their position are as unlimited as in any other browser environment. A single gauge can even have multiple scales if needed. The possibilities are limitless, and the main purpose of he class is to make programming
easier.

Conclusion

Creating impactful and attractive data visualization is not a simple matter, but this CanvasDial.js code attempts to make the task of creating dials and circular gauges a little easier. While a circular dial or gauge is sometimes not the best visualization tool, it is one very familiar to most people. HTML5 and the associated Canvas element are here to stay, and can be safely used in all browser environments released in this decade.

There are several other dynamic gauge and dial classes and libraries available with various levels of quality, license terms, and ease-of-use, but CanvasDial.js attempts to maximize these characteristics.

The code can be found on GitHub at: github.com/CayuseConcepts/CanvasDial

Future Development

This code is being released as version 0.9.2. That means it has some known shortcomings before it is ready for an honest 1.0 release. It is my intent to improve the code, add features, add more style choices, and make more types of dials and circular gauges possible without adding much to the complexity and weight of the code.

Feel free to comment or send me an email with ideas, feature requests, and bug discoveries.

About the Author

Scott Erholm is a Freelance Developer with over 20 years experience in many different software engineering and architecture domains. His passion is data manipulation and visualization from raw collection to user interfaces on Web, iOS and Android platforms. Scott may be contacted at scott@agilatech.com

4 Comments. Leave new

Okay. That makes a lot more sense than the mess I was creating to change the value of the gauge. I’ll try out the save()/restore() and let you know how it works on a SCADA display.

Reply

    Nope. The save() and restore() functions only apply to the base canvas, not the drawing placed on top of the canvas after it has been created. Therefore, the re-painting of the text value area and the pointer have to be done in such a way as to clear out the previous rasterized image before a new image (value) can be painted.

    I’ll get the details sorted out and leave you a note when it’s ready.

    Reply

Sorry it took me so long to get back to you. I actually had all this running over a year ago, but the whole SCADA instrumentation thing has been on and off so much that I lost track of what I had going on. Heh. Happens, man.

Anyway, here is a demo:
http://parsecsystemsinc.com/SCADA/Instrument/SCADADial/SCADADial%20demo.html

Here is the dial in a mock-up environment:
http://parsecsystemsinc.com/SCADA/Sight/Dashboard.html

Here’s the JS source:
http://parsecsystemsinc.com/SCADA/Instrument/SCADADial/SCADADial.js

Very much appreciate all the work that went into the original CanvasDial facility. It would have taken me ages to create this stuff from scratch, and all the contemporary alternatives I looked at smacked or were commercial products.

By the way, here’s a chart mechanism I created from scratch:
http://parsecsystemsinc.com/SCADA/Instrument/SCADAChart/SCADAChart%20demo.html

The JS is here:
http://parsecsystemsinc.com/SCADA/Instrument/SCADAChart/SCADAChart.js

It could stand some more documentation, but I’ll expand as time goes sloooowly by.

Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu