Introduction
I was searching the web for a free JavaScript gauge plugin to use it in a web dashboard included in Ploetz + Zeller GmbH's business process management software Symbio. The best match I found was the very good justGauge, but it has two characteristics that don't fit for me:
- It is based on the Raphaël JavaScript graphics library and i don't want to have another dependency in my web dashborard.
- It dosen't show warning limits or action limits that are required for a quality control chart.
Thus the idea originated to advance the approach of justGauge
and to create my own plugin gaugeSVG
.
This image shows how gaugeSVG
could look like. It is taken from the downloadable source and sample.
Background
The gaugeSVG
is pure SVG, resolution independent vector drawing and it works in almost any browser - IE6+, Chrome, Firefox, Safari, Opera, Android, etc.
Because I will explain the used SVG technologies, this is also a good introduction to high level SVG functionality in connection with JavaScript.
Using the Code
To use gaugeSVG
, only three requirements are to meet:
- The
gaugeSVG
JavaScript must be included into the HTML document.
<script src="javascript/gaugeSVG.js"></script>
- The HTML document must contain a
div
element with id
, width
and height
that can be used as container for gaugeSVG
.
<div id="container1" style="width:350px; height:300px"></div>
- The
gaugeSVG
JavaScript must be initialized with the id
of the HTML document's div
container.
<script>
window.onload = function(){
var gauge1 = new GaugeSVG({
id: "container1"
});
};
</script>
The initialization code above shows the minimum initialization. There are much more parameters that can be used to manipulate the gauge functionality and appearance.
This image illustrates the parts of gaugeSVG
, most of them are configurable.
The possible initialization parameters are:
title
:[string] The title text that is displayed above the gauge. It can be an empty string
to be suppressed. Default is an empty string
. titleColor
:[#rrggbb] The title color. Default is "#888888
". value
:[float] The value to display. Default is (max - min) / 2.0. Values below min
are shown as min
. Values above max
are shown as max
. valueColor
:[#rrggbb] The value text color. Default is "#000000
". The accurate value is shown in the center of the gauge as text. label
:[string] The label displayed below the value text. It can be an empty string to be suppressed. Default is empty string. Typically used to display the value's measuring unit. labelColor
:[#rrggbb] The label text color. Default is "#888888
". min
:[float] The minimum of the gauge display range. Will be displayed as text at the gauge start point, if is showMinMax
true. max
:[float] The maximum of the gauge display range. Will be displayed as text at the gauge start point, if is showMinMax
true. showMinMax
:[bool] Hide or display the min
and max
gauge display range values as text. Default is true
. minmaxColor
:[#rrggbb] The min
and max
value's text color. Default is "#888888
". canvasBackColor
:[#rrggbb] The background color of the gauge canvas. Default is "#f8f8f8
". gaugeWidthScale
:[float] The width of the gauge arc. Default is 1.0. Meaningful values range from 0.15 to 1.5. Lower values show a smaller arc, higher values show a thicker arc. gaugeBorderColor
: [float] The gauge arc border color. Default is "#cccccc
". gaugeBorderWidth
: [#rrggbb] The gauge arc border width. Default is 0
. gaugeBackColor
:[#rrggbb] The gauge arc background color. Default is "#cccccc
". showGaugeShadow
: [bool] Hide or display a gauge arc shadow. Default is true
. The gauge shadow is made of a SVG radial gradient. The gradient start color is the gaugeShadowColor
. The gradient stop color is the gaugeBackColor
. gaugeShadowColor
: [#rrggbb] The gauge arc shadow color. Default is "#000000
". gaugeShadowScale
: [float] The width of the gauge arc's shadow. Default is 1.0. Meaningful values range from 0.8 to 1.5. Lower values show a smaller shadow, higher values show a thicker shadow. lowerActionLimit
:[float] The lower action limit or a negative value, if not desired. Default is (max - min) * 0.15 + min. lowerWarningLimit
:[float] The lower warning limit or a negative value, if not desired. Default is (max - min) * 0.30 + min. upperWarningLimit
:[float] The upper warning limit or a negative value, if not desired. Default is (max - min) * 0.70 + min. upperActionLimit
:[float] The upper action limit or a negative value, if not desired. Default is (max - min) * 0.85 + min. needleColor
:[#rrggbb] The gauge needle color. Default is "#444444
". optimumRangeColor
:[#rrggbb] The optimum range color. Default is "#44ff44
". warningRangeColor
:[#rrggbb] The warning range color. Default is "#ffff00
". actionRangeColor
:[#rrggbb] The action range color. Default is "#ff4444
".
A stronger adjusted initialization code could look like this:
<script>
window.onload = function(){
var gauge2 = new GaugeSVG({
id: "container2",
value: 49,
valueColor: "#444488",
min: 30,
max: 70,
minmaxColor: "#444488",
title: "Gauge 2",
titleColor: "#8888cc",
label: "m³/h (passage)",
labelColor: "#8888cc",
gaugeWidthScale: 1.25,
gaugeBorderColor: "#222244",
gaugeBorderWidth: 1.5,
gaugeShadowColor: "#444488",
gaugeShadowScale: 1.35,
canvasBackColor: "#f8eeff",
gaugeBackColor: "#ccccff",
needleColor: "#8888cc",
lowerActionLimit: 0,
lowerWarningLimit: 0,
upperWarningLimit: 0,
upperActionLimit: 0,
});
};
</script>
Points of Interest
SVG injection
One interesting aspect of gaugeSVG
development was the dynamic injection of SVG nodes into the DOM using JavaScript. The very first step to achieve this was to create the SVG canvas into the DOM's DIV
container.
var container = document.getElementById(params.id);
...
this.canvas = document.createElementNS(svgns, "svg");
this.canvas.setAttributeNS(null, 'version', "1.1");
this.canvas.setAttributeNS(null, 'width', "100%");
this.canvas.setAttributeNS(null, 'height', "100%");
this.canvas.setAttributeNS(null, 'style', "overflow: hidden; position: relative;
left: -0.5px; top: -0.5px;");
container.appendChild(this.canvas);
The next step was to insert SVG nodes into the SVG canvas, e.g., the background rectangle.
this.rectBG = document.createElementNS(svgns, 'rect');
this.rectBG.setAttributeNS(null, 'stroke', "none");
this.rectBG.setAttributeNS(null, 'fill', this.config.canvasBackColor);
this.rectBG.setAttributeNS(null, 'x', this.config.offsetX);
this.rectBG.setAttributeNS(null, 'y', this.config.offsetY);
this.rectBG.setAttributeNS(null, 'width', this.config.canvasW);
this.rectBG.setAttributeNS(null, 'height', this.config.canvasH);
this.canvas.appendChild(this.rectBG);
Mind the calls of createElementNS
, setAttributeNS
and appendChild
and take care of the right objects to call the methods on and to pass as parameter.
SVG text
To draw text into the SVG canvas was a little challenging. This was because of the surprising nesting of a DOM node into a SVG node.
this.gaugeVAL = document.createElementNS(svgns, 'text');
this.gaugeVAL.setAttributeNS(null, 'x', this.config.offsetX + this.config.canvasW / 2.0);
this.gaugeVAL.setAttributeNS(null, 'y', this.config.offsetY + this.config.canvasH / 1.2);
this.gaugeVAL.setAttributeNS(null, 'style', "font-family:Arial,Verdana; font-size:" +
Math.floor(this.config.canvasW / 8) + "px; font-weight:bold; fill-opacity:1.0; fill:" +
this.config.valueColor + "; text-anchor:middle;");
this.gaugeVAL.appendChild(document.createTextNode(this.config.originalValue));
this.canvas.appendChild(this.gaugeVAL);
Note that a DOM text node (document.createTextNode(this.config.originalValue)
) has to be created as a SVG text nodes child (this.gaugeVAL.appendChild(...);
).
Updating
As well, the needle position as the value text might be updated during the gauge lifetime/display of the HTML document, e.g., to show the latest value of a continuous measurement. This can be done by the gaugeSVG
's method refresh()
. A life update is very easy to achieve, because the attributes of DOM nodes and SVG nodes can be manipulated using JavaScript. For the value text, the one and only DOM text child node of the SVG text node has to be set with the new value text. For the needle, the 'd'
attribute has to be updated with the new path.
this.gaugeVAL.childNodes[0].textContent = this.config.value;
this.gaugeNDL.setAttributeNS(null, 'd', this.calculateNeedlePath(...));
This approach is not only the easiest way, but it also recycles the already existing DOM and SVG nodes and avoids flicker.
Animation
The updating of the needle position can be animated, if gaugeSVG.refresh(valueNew, animated)
parameter animated
is set to true
. The animation uses JavaScript's setTimeout
. To realize a visually appealing animation, the value this.animation.startIncrementDivisor
defines the 24th part of the difference from the last needle position to the new needle position as initial incremental step. Every incremental step will be drawn with a delay this.animation.delay
of 15 milliseconds.
To emulate a declining needle speed, the incremental steps decrease by a multiplication with the value this.animation.decreaseOfIncrementValue
of 0.966. To prevent an infinite animation, the total number of incremental steps is limited to the value this.animation.maxIncrements
of 48.
These this.animation.*
values work fine for updating intervals not smaller than 2 seconds. In case of a faster updating, no animation should be done or the values must be adapted. Any adaption needs extensive testing.
Note that any function, called by setTimeout()
, will leave the context of the gaugeSVG
's this
pointer. To provide the first call of the timed out function with the gaugeSVG
's this pointer, a explicit reference must be created. All subsequent calls can hand over this reference.
var gauge = this;
setTimeout(function()
{
GaugeAnimationStep(gauge, oldValue, incrementValue, gauge.animation.maxIncrements);)
}, gauge.animation.delay);
Shadow / gradient
The gauge background can be drawn with a shadow effect. This is realized with a radial gradient. Any gradient is part of a definition SVG node: document.createElementNS(svgns, 'defs')
All definition SVG nodes are transparent through the complete document. Thus, if multiple gauges should be shown within a single document, the gradient definitions must distinguish: setAttributeNS(null, 'id', this.config.id + "_gradient")
if (this.config.showGaugeShadow == true)
{
this.gradients = document.createElementNS(svgns, 'defs');
this.gradients.setAttributeNS(null, 'id', "gradients");
this.gradient = document.createElementNS(svgns, 'radialGradient');
this.gradients.appendChild(this.gradient);
this.gradient.setAttributeNS(null, 'id', this.config.id + "_gradient");
this.gradient.setAttributeNS(null, 'cx', "50%");
this.gradient.setAttributeNS(null, 'cy', "50%");
this.gradient.setAttributeNS(null, 'r', "100%");
this.gradient.setAttributeNS(null, 'fx', "50%");
this.gradient.setAttributeNS(null, 'fy', "50%");
this.gradient.setAttributeNS(null, 'gradientTransform', "scale(1 2)");
this.grad1sub1 = document.createElementNS(svgns, 'stop');
this.gradient.appendChild(this.grad1sub1);
this.grad1sub1.setAttributeNS(null, 'offset', "15%");
this.grad1sub1.setAttributeNS(null, 'style', "stop-color:" +
this.config.gaugeShadowColor + ";stop-opacity:1");
this.grad1sub2 = document.createElementNS(svgns, 'stop');
this.gradient.appendChild(this.grad1sub2);
this.grad1sub2.setAttributeNS(null, 'offset',
this.config.gaugeShadowScale * 33 + "%");
this.grad1sub2.setAttributeNS(null, 'style', "stop-color:" +
this.config.gaugeBackColor + ";stop-opacity:1");
this.canvas.appendChild(this.gradients);
}
The radial gradient shall be drawn centered to the path it is applied to, the gauge background path. The center of the outermost color ring is set relative to the path: setAttributeNS(null, 'cx', "50%")
, setAttributeNS(null, 'cy', "50%")
The innermost color ring is also set relative to the path: setAttributeNS(null, 'fx', "50%")
, setAttributeNS(null, 'fy', "50%")
Because the path of the gauge background has a width:height ratio of 2:1, the radial gradient would look elliptic. To achive an exactly circular gradient, a scale ratio of 1:2 is applied: setAttributeNS(null, 'gradientTransform', "scale(1 2)")
The fade from innermost color to outermost color is distributed nonuniform between gradient center and gradient border. The fade starts at 15% from center to border setAttributeNS(null, 'offset', "15%")
and ends dependent from gaugeShadowScale
at setAttributeNS(null, 'offset', this.config.gaugeShadowScale * 33 + "%")
.
After the gradient's definition, it can be applied.
this.gaugeBG = document.createElementNS(svgns, 'path');
this.gaugeBG.setAttributeNS(null, 'stroke', this.config.gaugeBorderColor);
this.gaugeBG.setAttributeNS(null, 'stroke-width', this.config.gaugeBorderWidth);
if (this.config.showGaugeShadow == true)
{
this.gaugeBG.setAttributeNS(null, 'fill', "url(#" + this.config.id + "_gradient)");
}
else
{
this.gaugeBG.setAttributeNS(null, 'fill', this.config.gaugeBackColor);
}
Have fun with gaugeSVG
!
History
- The first version of the article is from June 18th 2013.
- The second version from December 15th 2014 updates the JavaScript source to support negative display ranges, e.g. from min: -80 to max: -20 with
lowerActionLimit
: -50 and upperActionLimit
: -30. - A patch for the second version from October 6th 2015 updates the JavaScript source to fix problems with display ranges from a negative to a positive value and with disabled lower warning limits.