Building Our Infographic
This is second half of our discussion of creating infographics
The example code you downloaded includes an HTML file in the Infographics folder.
Each Infographic is generated with Javascript. Like in our Python-driven reports, Javascript offers a range of different libraries you can use when creating web-based content. While this Infographic demonstrates 2D displays using D3.js, you can use other libraries such as THREE.js (3D graphics) or pure Javascript.
The visualisation begins by creating a class called ActiveReport.
For a live report, the following functions are called…
- initialise is called once the Infographic is ready to be created.
- create is called once initialisation is done.
- update is called when new live data is available, and
- draw is called when the visualisation should be re-drawn
class ActiveReport extends SVGReport { initialise() { super.initialise(); this.first = true; }; create() { this.liveReport("'Current - Armature' ALLPOINTS"); } update() { this.draw(report.livedata); } draw(data) { //Setup the Report //Draw the boxes //Draw text } }
Step 1: Create the Array
The first thing we need to is create the array we will use to power our visualisation.
The draw function is passed a set of data values in the order they were returned from your AQL query.
It also has access to the columns variable which is an array of the columns that have been returned from your AQL query.
We can use these two variables to create a new array filled with objects. Each object contains a name (the name of the AQL column) and a value (the new value).
//Create an array and fill it with the bars we want to draw. var finaldata = []; for(var q=0;q<data.length;q++) { finaldata.push({name: this.columns[q].name,value: parseFloat(data[q])}); }
Setting Up Axes
Next, we need to do the first-time setup for the image.
There are a few key steps in the process…
* Setting Up a Report Margin
* Creating the X Axis
* Creating the Y Axis
if (this.first == true) { this.first = false; }
Setting Up the Margin
You don't want to draw your image so that it stretches completely from edge-to-edge of your page. You normally add a certain amount of margin between the top, bottom, left and right.
We'll set this up in the first-time setup, with 200 pixels of margin on the left-hand side for the Y axis labels.
The script can calculate the width and height of the screen using the sizing array.
var margin = {top: 40, right: 40, bottom: 40, left: 200}; var height = this.sizing[1]- (margin.top + margin.bottom); var width = this.sizing[0] - (margin.left + margin.right);
The easiest way to add margins to our report is to build it inside an SVG group. A group is simply a collection of other objects (it doesn't have it's own appearance), so it's very useful for collecting related items together.
By adding a transform to the group, you can move, scale and rotate the items inside. Since we're going to draw our entire report inside this group, we can add a margin by simply 'nudging' the group slightly down and to the left.
The code below…
* Creates a 'g' (group) element inside the svgbase object (the empty SVG we begin with).
* Adds an attribute called 'transform' and sets the value to 'translate(200,40)' - this nudges the entire report 200 pixels left and 40 pixels down.
var group = this.svgbase.append("g") .attr("transform","translate(" + margin.left + " " + margin.top + ")"); this.svg = group;
Creating the Y Axis
You need to both create and draw the Y axis for your report.
For this report, we want to draw the different turbines on the Y axis. Luckily, D3 has a range of different scales that are used to convert pixels to data values and vice-versa.
Because the asset name is a discrete value rather than continuous one (it's made up of names rather than numbers), we can use a D3.js scaleBand, which slices a range up into even pieces.
Every D3 scale has two key elements - a range (the physical pixels it covers) and a domain (the logical/value range it covers).
//Create a band-scale for the Y axis var y = d3.scaleBand() .range([ 0, height ]) .domain(finaldata.map(d => d.name.replace(" Vibration",""))) .padding(.1); //Draw the Y axis this.svg.append("g") .call(d3.axisLeft(y)) //Keep this for later draw operations this.y = y;
Creating the X Axis
Now we do the same for the X axis.
The X-axis is a scaleLinear - this is ideal for the numeric values we'd like to show across the bottom of the page.
var min = 0; var max = 2.5; var x = d3.scaleLinear() .domain([min, max]) .range([ 0, width]); //Draw the X axis this.svg.append("g") .attr("transform", "translate(0," + height + ")") .call(d3.axisBottom(x)); //Keep this for later draw operations this.x = x;
Calling 'axisBottom' draws an axis with the correct style for a bottom axis, but doesn't actually draw it at the bottom of the image.
To bring it down to the bottom, we use the same trick we did to add a margin to the report - a group, and a transform property to push the whole thing down to the bottom of the image.
Drawing the Bars
The code below will turn our list of points into a list of SVG Rect objects - ideal for showing the bars of a bar-chart.
//Draw the bars this.svg.selectAll(".bar") .data(finaldata) .join( enter => enter.append("rect") .attr("class","bar"), update => update.attr("width",100) );
This gives the page instructions for what to do when it needs to create a bar, and when it needs to update a bar.
Filling In Details
The SVG Rect object needs several different attributes set before it appears. These include…
Name | Meaning |
---|---|
x | The horizontal position of the top-left corner |
y | The vertical position of the top-left corner |
width | The width of the rectange |
height | The height of the rectangle |
fill | The fill colour |
There are also some additional values that aren't part of the SVG standard and are used in tooltips…
Name | Meaning |
---|---|
name | Text identifying the object |
units | Measurement units |
value | The current value |
These need to be set in the enter function and updated - where needed - in the update function we added above.
Setting Static Attributes
The question is, how do we use values from our data when creating our individual bars?
You set individual attributes on your rectangles with the attr command. The first parameter is the name of the attribute you want to set (ie. 'x', 'y', 'width' etc) and the second is its value.
For example…
.enter.append('rect') .attr('class','bar') .attr('x',0) .attr('fill','cyan') .attr("units"," m/s/s") .call(this.tip)
This makes sure the rectangle has a class of 'bar', an X coordinate of 0 and a cyan fill colour, and units to display on the tooltip.
We also call the tip function on the asset to make it respond to the mouse and display tooltips to the user.
We can also set the height of the bar using the bandwidth of the d3 scaleBand class to calculate how much space there is on the chart for each channel of data.
.attr('height',y.bandwidth())
Setting Dynamic Data
If you pass a function as the value in a call to attr, something magic happens.
Internally, D3 will call that function for every rectangle that gets drawn. The function will be called with two parameters - the data that caused the rect to be created and an index number.
So to make sure our bar-chart bars all start on the left-hand side of the chart, we can do the following…
.attr('width',function(d) { return x(d.value); })
Let's quickly review the steps here….
* We create a list of objects with a name and a value.
* We join that list with each of the bars in our image.
* Since we don't actually have any bars yet, the 'entry' function is called once for each bar.
* If any of the bars have a function in their attributes, that function is called and passed the item from the array (ie. containing name and value) that caused it to be created.
* In the function for 'width', we use the X variable to map our vibration value from a number (ie, between 0 and 20) to a position along the X axis.
Again, it seems quite complex at first, but once you're used to the mechanisms involved it becomes quite useful.
We can also set the Y position using a similar technique. We have to remove the word 'Vibration' from the string, since that is what we also did when creating the Y axis…
.attr('y',function(d) { return y(d.name.replace(" Vibration","")); })
We can also set attributes such as the name and value to show on the tooltip…
.attr('name',function(d) { return d.name; }) .attr('value',function(d) { return d.value.toFixed(2); })
Transitions
You can also use the transition and duration functions to animate changes to values.
If we want to smoothly transition our bar-chart into the scene, we could create the bars with a zero width and animate it smoothly to full length over 1 second…
.attr('width',0) .transition() .duration(1000) .attr('width',function(d) { return x(d.value); })
Updates
Unlike the entry function, you only need to update the attributes that you expect to have changed between updates.
In the case of our bar-chart, this will usually be the width of the bar and the value shown in tooltips.
.attr("value",function(d) { return d.value.toFixed(2) }) .transition() .duration(1000) .attr("width",function(d) { return x(d.value); })
Extra Elements in the Example
The example also includes a few additional elements, such as…
* If live data is unavailable, the colour changes to purple and the bars become thinner
* The colour of the line is dynamic based on the colour lookup tables and gradients from ARDI
* If there are fewer than 50 lines, the report also shows the value of each bar as text
You can find some other examples here.
Continue Reading
Discover how to create alerts using ARDI live data.