Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

A Universal Gauge for Your Web Dashboard

4.95/5 (20 votes)
16 Dec 2014CPOL8 min read 99K   4.5K  
JavaScript plugin gaugeSVG to generate widely configurable SVG gauge for a web dashboard

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.

Image 1

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:

  1. The gaugeSVG JavaScript must be included into the HTML document.
    HTML
    <script src="javascript/gaugeSVG.js"></script>
  2. The HTML document must contain a div element with id, width and height that can be used as container for gaugeSVG.
    HTML
    <div id="container1" style="width:350px; height:300px"></div> 
  3. The gaugeSVG JavaScript must be initialized with the id of the HTML document's div container.
    JavaScript
    <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.

Image 2

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:

JavaScript
<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,  // Use '0' to disable. A '-1' is obsolete with version 2.0.
            lowerWarningLimit: 0, // Use '0' to disable. A '-1' is obsolete with version 2.0.
            upperWarningLimit: 0, // Use '0' to disable. A '-1' is obsolete with version 2.0.
            upperActionLimit: 0,  // Use '0' to disable. A '-1' is obsolete with version 2.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.

JavaScript
// Determine drawing container.
var container = document.getElementById(params.id);
 
...
 
// Create SVG canvas.
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.

JavaScript
// Draw canvas background.
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.

JavaScript
// Draw current value.
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.

JavaScript
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.

JavaScript
// At that time, the timed out function will be called, the context,
// where 'this' pointer is known to, is no longer valid. 
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")

JavaScript
// Create 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.

JavaScript
// Draw gauge background.
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)