A node-RED inspired D3 Dashboard Gauge

node-red inspired d3 dashboard gauge

I recently works on an IoT project which involving of rendering a dashboard using node-RED. I was wondering whether I could build a node-RED inspired dashboard gauge using D3.js JavaScript library, so I built one as a side project.

About Node-RED

Node-RED is browser-based programming tool for wiring together hardware devices, APIs and online services with drag and drop of pre-configured "nodes" and wiring them together. There are programming tools with similar concepts available in the market, such as Scatch from MIT, focusing on general programming and education, or labVIEW from National Instruments for industrial applications and programming.

node-RED dashboard gauge example
A Typical node-RED Dashboard with a gauge and a line chart

What's appealing to many Raspberry Pi users, especially those who are new to programming, are node-RED's drag-and-drop without the need of programming in most of the time, and the well-crafted dashboard UI widgets, such as gauge, charts, etc..

customise node-RED gauge settings
Node-RED gauge configuration settings

About D3.js

D3.js or just D3, stands for Data-Driven Documents, is a JavaScript library for producing dynamic, interactive data visualisation in web browsers using SVG, HTML and CSS standards. D3.js is often rendering the data using SVG because SVG is valid HTML tags and can be easily add or remote from DOM of a web page. In fact if you look closely at node-RED gauge implementation, itself also implemented the gauge using SVG.

D3 dashboard gauge design considerations

For no better name, I called my project as D3 Dashboard Gauge. My goal for D3 Dashboard Gauge is to create a dashboard gauge that is resemble the node-RED UI gauge widget with the following design considerations:

  • a self-contain re-usable library;
  • high customisable just like the node-RED gauge for different IoT projects and sensors;
  • simple API for creating, rendering and updating the data;
  • Build with d3.js library.

Gauge Class and instantiation

I decided to wrap the entire code within a ESMAScript 2015 style JavaScript Class which can be instantiated to create a gauge or multiple of it. As a self-contain library, to use it, just referring it on your html page:

<head>
  <link rel="stylesheet" href="dist/style.min.css">
  <script src="https://unpkg.com/d3@5.7.0/dist/d3.min.js"></script>
  <script src="dist/gauge.min.js"></script>
</head>

During the instantiation of the class, you could either use the default configuration settings or customise the configuration by passing in a configuration object as argument.

    
// using default configuration settings
const myGauge = new Gauge();

// customise configuration
const tempGauge = new Gauge({
    minValue: -20,
    maxValue: 50,
    lowThreshhold: 0,
    highThreshhold: 40,
    displayUnit: 'Degree Celcius'
});

The default configuration is set with an internal configobject with the following values during the instantiation of the Gauge class. The detail API documentation is available at the github page, the picture below help to visualise the meaning of each parameter.

config = {
  size : 200,
  margin : 10,
  minValue : 0,
  maxValue : 10,
  majorTicks : 5,
  lowThreshhold : 3,
  highThreshhold : 7,
  lowThreshholdColor : '#009900',
  defaultColor : "#ffe500',
  highThreshholdColor: '#cc0000',
  transitionMs : 1000,
  displayUnit : 'Value'
};
configurable d3 dashboard gauge parameter
Configurable d3 dashboard gauge parameters

Render method

D3 renders data by inserting the rendered object into a specific html tag, often specified by a html idtag.

<div class="gauge-container">
  <div id="gauge">
    <!-- D3 Dashboard Gauge to be inserted here -->
  </div>
</div>

We can render the instantiated gauge object by calling the Gauge.render()function. Multiple gauges can be added with different idtag. The #gauge-containeris styled as a flex-boxcontainer so that multiple gauges will be align side-by-side based on the viewport setting of the browser.

myGauge.render("#gauge");

Update method

To update the gauge display, you simply pass-in the new value to the Gauge.update()method.

myGauge.update(newValue);

How often you should update the display can be set using Javascript setIntervalfunction. For example, here is an demonstration code for updating the gauge with a randomly generated value at every 3 seconds.

setInterval(function() {
    myGauge.update(Math.random()*10);
}, 3*1000);

Summary

And that's is. Is this simple enough to use it in your projects? D3 Dashboard Gauge provides an alternative to node-RED gauge if all you need a gauge for your IoT project and your prefer to write your own programming than using node-RED's drag-and-drop programming style.

I'd love to see how you use the D3 Dashboard Gauge in your project, add a comment below and tell me how you use it.

Github Repository

D3 Dashboard Gauge is an open source project and is covered by MIT license. Github repository is available at e-tinkers github, there is also a live demo at github.io page.

13 comments by readers

  1. Wonderful! Easy enough and (almost) exactly what I needed. Because I need a logarithmic base10 scale, going 1, 10, 100, 1000, 10000.

    When I take the log of my values, I am back to a linear scale, obviously. So what I need is “only” the ability to label the ticks with the values 1, 10, 100, 1000, 10000.

    What settings or changes do I need to make?

  2. Unfortunately, this is a lot more involving than just replacing with scaleLog :-((

    When I do this the scales disappear, and only the pointer hand is visible, but stays on the very same spot.

    Log scale are of interest in various disciplines, like geology (earthquake), acoutic (music, noise), light intensity (photography), and, in my case, measuring radioactivity. Perhaps this can make you interested in thinking about the log scale issue?

    1. I’ve added the log base 10 scale support just now. Now you can config the dashboard through config {scale: 'log'}, I’ve updated the github as well the live demo code. You can see the demo here.

  3. Well, that was prompt — I put it into my code and it worked! Nice compact code, fantastic! And thanks for publishing with some comments in it, made some adaptation a whole lot easier.

    But I am struggling with some things. I tried to show a single dashboard centered in a div, but it was always misaligned (right-shifted). I noticed that class ‘gauge’ in the css is missing, as used in

    const svg = d3.select(container)     
                .append('svg')             
                .attr('class', 'gauge')
    

    would that be the place to properly position it?

    Another struggle was the setting of the Ticks. I now use

    if (config.scale === 'log') {      
        ticks = Math.log10(config.majorTicks); ...
    

    (note the use of Log Base 10!) with majorTicks being defined as the ratio maxValue/minValue. This makes majorTicks==5 for a range 1 … 100000. Other values give odd results.

    1. On css, the style.css provided all the styling for the dashboard gauge widget. It is however does not including any styling required to your overall <html> or <body>, the reason being that each user case (such as position, number of widgets, etc. on screen) is different. See example.html which has an inline style on <body> to center the two widgets.

      For ticks, yes, it is bit try-and-error now. On Linear, with a majorTicks of 5, and a scale of 20 – 50 for example, it will means that the value between each tick will be (50-20)/(5 + 1)=5 (i.e, 20, 25, 30, 35, 40, 45, 60). In Logarithmic scale, this no longer hold, I can’t find an elegant way to handle it yet (I only had a couple of hours spare time in my weekend to add the Log scale based on your requirements, and decided to push it out so that you could have it first).

    2. BTW, I didn’t know that JavaScript has a built-in Math.log10() function, the Math.log(val)/Math.LN10 that I used convert the natural log to log10 base is basically same as Math.log10(), but it seems that Math.log10() produce better precision, e.g. Math.log(1000) result in 3, while my conversion formula produced 2.9999999999999996. I have updated the github to use log10().

  4. I am surprised about the differences in log calcs, as I think internally they’ll do the ln anyway, strange.

    My suggestion for the ticks is to use a default of 5, which is applied to lin scale only, and on log scale force ticks to the 10x ticks only, using in the gauge.js:

    let ticks = config.majorTicks;
    if (config.scale === 'log') {
        ticks = Math.log10(config.maxValue/config.minValue);
    }
    

    Works well for me even when selecting a log e.g. from 7 … 300000, so not even using exact powers to 10.

    This also solves one minor issue: when the max value is large, and a power to ten (like 1 mio), then the last zeros are being cut off. When I use 3 mio, then the 3,000,000 gets no tick, and the 1,000,000 is higher up and fully visible :-).

    Also, I am using formatting of the numbers as “.text(d3.format('1,.0f'));” (note the comma after the 1), which gives commas as thousand separators, as the many zeros become difficult to read.

    And I also figured out how to change the settings after the website has loaded, so, I am all set! Thanks!

    1. Good suggestion. I will take a look and adjust github accordingly.

      To avoid this comment section became tech support, bug fix and feature request page, going forward, please submit your comment regarding bugs, issues, etc. on github page. Thanks.

  5. Ooops, better use:

    if (newValue < this.config.minValue) newValue = this.config.minValue;

    (not sure why the other even worked?)

  6. Do you have an example node-red flow you could share with this in?

    Thanks!

    1. I have tried using two template nodes to begin with, to contain the head and content, I can run the demo in the “public” folder so I copied the dist files into there..

      [{"id":"78a2d84c.7dc268","type":"ui_template","z":"38f25409.5acdfc","group":"ca76c572.b59678","name":"","order":2,"width":0,"height":0,"format":"  \n  \n  \n  ","storeOutMessages":true,"fwdInMessages":true,"templateScope":"global","x":600,"y":220,"wires":[[]]},{"id":"c03170a0.877ea","type":"ui_template","z":"38f25409.5acdfc","group":"ca76c572.b59678","name":"","order":1,"width":0,"height":0,"format":"\n  Test\n      \n      \n    \n  \n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":600,"y":300,"wires":[[]]},{"id":"ca76c572.b59678","type":"ui_group","name":"Group 2","tab":"760b816.280d88","order":2,"disp":true,"width":6},{"id":"760b816.280d88","type":"ui_tab","z":"","name":"Home","icon":"dashboard","order":1,"disabled":false,"hidden":false}]
      1. Not sure the node-RED code you pasted-in is about. The D3 Dashboard Gauge is a stand-alone dashboard widget that do not require node-RED, just pure JavaScript with d3.js. Please read this article and see my github for complete documentation on how to use it. If you are interested of using node-RED as your dashboard, you can read my another article on control Raspberry Pi GPIO using node-RED.

Comments are closed.