Writing the Script - Part 5

That's enough to get a basic Infographic working.

But with live data, there's an issue - we keep adding more content to the page every time we call draw.

The simplest way to avoid this is by re-drawing the image every time. We could do this with the line…

$('#reportsvg').html("");

This is…OK. But it's not great, either for performance or for your users experience.

Let's use some of what makes D3 great, and make it transition.

This can be done in a couple of steps…

1) Smoothly transitioning our bars when we first create them,
2) Avoid re-creating items that can never update, and
3) Smoothly transitioning our bars when data changes


Starting Our Bars Out Small

Our join command creates our bars already at their full size.

If we want to make a pretty opening transition, it'd be nice to have them animate from 0 up to their final value.

We can do this quite simply, by changing the initial width to '0' and using the transition and duration functions.

We can change our 'enter' function to look like this…

this.svgbase.selectAll(".bar").data(finaldata)
   .join(
      enter => enter.append("rect")
                     .attr("class","bar element")
                     .attr("x",function (d) { return x(0); })
                     .attr("y",function (d) { return y(d.name); })
                     .attr("width", 0)
                     .attr("height",y.bandwidth)
                     .attr("fill","cyan")
                     .attr("name",function(d) { return d.name; })
		     .attr("value",function(d) { return d.value; })					
		     .attr("units"," Deg C")
		     .call(this.tip)
                     .transition()
                     .duration(1000)
                     .attr("width", function(d) { return x(d.value);})
   );

They key changes here are….

  • The width now starts at 0,
  • We've told d3 to transition our value over 1000ms (1 second), and
  • The width we used to have previously is now after the transition - the width will animate to this value.

Only Creating the Axes Once

We don't want to constantly re-create our report axes every time an update comes in, so let's write a simple condition.

When we initialise our report, we'll set a flag called 'first'.

initialise() {
    super.initialise();
    this.first = true;
};

Then, we only draw our X and Y axes if the value of first is true.

if (this.first == true)
{
    ....draw x and y axes...
}
this.first = false;

This ensures that the elements that don't need to be updated when new live data arrives, don't get updated.


Animate On Updates

In our join function, we gave a function that is called on entry - when the object first appears in the image.

Next, we need to define a function that is called on update - when there is an existing bar to use.

We add this after the 'enter' parameter to the join function. The code is almost exactly the same as the code we just added to our bars during entry…

                            update => update
                                        .attr("value",function(d) { return d.value;})
					.transition()
					.duration(500)	
					.attr("width",function(d) {
						return x(d.value);
					})

This tells the system to leave all of the existing properties alone, but immediately set the 'value' attribute (to change the value shown on the tooltips) to it's new value, and animate the width of the object to the new value.

Final Code & Example

For a version of this graphic with a little bit of extra polish, see the code example

In addition to the basics we've explored here, this example…

  • Places the set of axes in a group and translates it, creating a top and left margin,
  • Deals properly with negative values (the simple code shown above will struggle with negatives)
  • Includes a text value on each of the bars,
  • Dynamically colours the bars using ARDI colour lookup tables, and
  • Highlights the minimum and maximum values