D3.js – using an advanced chart library in Tizen

Introduction

 

                In order to create modern mobile applications a web mobile developer needs to know at least one graph / chart / document manipulation and generation library. It is crucial especially when creating business web applications based upon downloaded data like in AngularJS, React, Meteor and many more. In this article we will show you how to use the D3.js library to generate a custom chart for your Tizen web applications. As the D3 library is a very big and powerful tool we will focus in this article on showing you the fastest way from importing the library to generating an SVG chart, customizing axes, animating and handling click events. The chart we are creating in this article can be seen on Figure 1. Please note that the subject of this article is not simple. We encourage you to read this tutorial simultaneously with the application code attached to this article and change the parameters at will so you will get a grip what is the D3 work philosophy. 

 

 

Figure 1 – An animated SVG chart with interactivity using D3.js

 

Prerequisites

 

                First of all you should download the latest D3.js (4.3.0) library version from here. If that link is not working then please check the D3.js website and there you will find a link to the up to date version. After downloading the library you need to add it to your document using the <script></script> tag. 

 

<script src="d3.min.js" type="text/javascript"></script>

 

After that everything is settled and you are ready to go with coding your first data driven chart in D3.js.  

 

Preparing the data

 

                In order to plot any charts or graphs we need to have some data provided, whether it will be data loaded up from a web server or hardcoded data we need a way to get them into our chart. D3.js can use almost all kind of data. You can import CSV data collections or TSV data collections as well as JSON data or plain JavaScript objects or arrays. In our case we will use predefined data in an array. But before we will show you how our data collection will look like we urge you to place the whole code of your tutorial inside the window.onload function.

 

window.onload() = function {

    // here goes the code	

}

 

As we wrote earlier we will be creating an SVG chart and you have seen a preview of it on Figure 1. The points on our chart are also SVG graphics. To be precise they are SVG rounded rectangles. As we know an SVG rounded rectangle needs several properties to be rendered, like the x and y positions, width and height, color and finally the rx and ry variables which are responsible for the radius of the rounded corners both in the x and y axes.

Knowing all of that we have created a dotData array which will hold all these values for our points as well some additional string data in the description key.

 

var dotData = [
    { "x": 20, "y": 20, "rx": 3, "ry": 3, "width": 15, "height": 15, "color" : "crimson", "description" : "Low standings :(" },
    { "x": 22, "y": 40, "rx": 3, "ry": 3, "width": 15, "height": 15, "color" : "green", "description" : "Better standings :|" },
    { "x": 42, "y": 52, "rx": 3, "ry": 3, "width": 15, "height": 15, "color" : "red", "description" : "Nice standings ;)" }, 
    { "x": 60, "y": 60, "rx": 3, "ry": 3, "width": 15, "height": 15, "color" : "orange", "description" : "Great standings :D" },
    { "x": 90, "y": 90, "rx": 3, "ry": 3, "width": 15, "height": 15, "color" : "teal", "description" : "SUPERB standings!" }];

 

Creating the main SVG

 

As we have stated in the introduction, we will be creating an SVG chart, so first we need to create some kind of “canvas” which we will be drawing upon. We are writing “canvas” on purpose, because we are not writing here about the HTML5 <canvas> element. So as you see below, we first describe the base_width and base_height of our main SVG element. In our case the width will be 250 pixels and height - 500 pixels. 

 

var base_width = 250;
var base_height = 500;

 

Next we need to specify the margins of our chart. It is needed because if we don’t do that then later we will have problems with viewing the axes description of our chart. We specify the top, right, bottom and left margin parameters. 

 

var margin = {top: 25, right: 25, bottom: 100, left: 70}

 

After that we can start writing our first code using the D3.js library. The library uses the jQuery-like syntax. Especially you can see it by an extensive usage of the chaining concept. Below you can see what we are doing step by step. First we fetch the <body> element and we append a new <svg> element to it. We set the attributes of that element by changing the width and height of it by assigning our earlier described base_width and base_height variable values with additional margins from our margins object. Then we create an SVG group element <g> and after that we translate the whole SVG group by moving it in both axes by the value of margins.top and margins.left values. 

 

var svg = d3.select("body").append("svg")
                           .attr("width", base_width + margin.left + margin.right)
	  		   .attr("height", base_height + margin.top + margin.bottom)
	  		   .append("g")
	  		   .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

 

Setting the scales and formatting data for the axes

 

In order to present our data on a chart properly we first need to set the scales of the chart. We have to set the type of scaling, the domain and the range in which our data will be presented. Below we have showed how to perform such an operation while building our chart. As you can see we are creating variables for both axes containing linear scaling scaleLinear(), with a set domain between 0 and 100 and the pixel range of our chart will be between 0 and the base_width specified earlier. 

 

var x_axis = d3.scaleLinear().domain(0,100).range([0, base_width]);      
var y_axis = d3.scaleLinear().domain(100,0).range([base_height, 0]);

 

You probably have observed that in the y_axis variable the domain values have switched places and in the range the base_width also is switched with the 0. This is because we want our chart to have the starting point in the lower left corner - the (0, 0) point of our chart. After completion of our chart you can play around with changing both the domain and range. It will have a direct impact on the presented data.

Another issue which needs to be addressed is the setting of the amount of the data from the domain that can be seen to the user. We can for example tell our axes to show a specific spectrum of domain values in our pixel range. Making a long talk short, the example beneath shows how to get the effect of fitting axes from 0 to the last value in our data in the earlier set pixel range.

 

x_axis.domain([0, d3.max(dotData, function(d) { return d.x; })]);
y_axis.domain([0, d3.max(dotData, function(d) { return d.y; })]);

 

After applying the code above we can be sure that our chart will cover the range between 0 and 90. Our last data point has coordinates (90, 90). The d3.max function will take in our dotData and in the return statement it will give us back the highest data value in our data. Using an anonymous function with the d parameter in D3.js tells the library to fetch data from the data collection. Then in the return statement we can refer to our collection as d.ourDataParameter. This is a very simple way to get to our data collection. You will be using this scheme all the time while working with D3.

 

Sometimes you may want to show on the axes only the values between the lowest point and the highest point value in your data. And then comes in handy the d3.extent function. Below we are applying such behavior to our axes. 

 

x_axis.domain(d3.extent(dotData, function(d) { return d.x; }));  
y_axis.domain(d3.extent(dotData, function(d) { return d.y; }));

 

In our case (with our dotData) we will get on our axes only values between 20 and 90. In some cases it can be useful. Of course there are also other possibilities to show the value range on the axes. We are just showing here the most basic examples. Also please note that setting the scaling and domains and ranges doesn’t mean that we have created the axes themselves. It means only that we have set the rules on which the axes, data points and grids will rely on. The rendering of the axes will be covered later in this article.

After performing the right scaling we need to do one last thing, so we can have easy access to it later. We will attach both the x_axis and y_axis values to the window object like in the example below.

 

window.x_axis = x_axis;
window.y_axis = y_axis;

 

Drawing the chart – data point labels

 

Finally we got to the best part of the article where we actually draw the chart in our Tizen application. We will start from the labels marking the coordinates of our points. The code below does the preliminary job for that.

 

var pointLabels = svg.selectAll("text")
                           .data(dotData)
	                       .enter()
	                       .append("text");

 

Ok, but here we need to explain something. You are probably baffled now about the svg.selectAll(“text”) thing. What are we selecting here? The text variable does not exist in our project. The answer is simple. D3.js works by defining things in advance which will be placed at the end on the web page. So as you see first we select all the text. Then we specify the dotData, then we enter() and iterate through the dotData sequence and append all those texts based on the provided data.

Next we are creating a textLabels variable which will be essentialy the same as the pointLabels but with addition of attribute changes. We use the attr function to change the x and y positions according to the data provided in the dotData collection. As you can see below we are returning like in the earlier usage example the d.x and d.y positions, but this time we are also converting them through our x_axis and y_axis scales. This is because our labels need to be placed in the right place on the chart according to the scales we have created earlier. We are also adjusting their position to be on an offset -20 on the X axis and -15 on the Y axis, which will put them above the chart value markers. 

 

var textLabels = pointLabels
                       .attr("x", function(d) { return window.x_axis(d.x) - 20; })
	                   .attr("y", function(d) { return window.y_axis(d.y) - 15; })
	                   .text( function (d) { return "( " + d.x + " , " + d.y + " )"; })
	                   .attr("font-family", "arial")
	                   .attr("font-size", "10px")
	                   .attr("fill", "black");

 

We are also wiritng down the coordinates on the labels themselves. That is why we are extracting their values from our data collection and then rendering, using the text function the d.x and d.y coordinates. The last two lines of code are again changing the attributes. We are changing both the font-family to arial and font-size to 10px.

 

Drawing the chart – grid lines

 

The next thing we are doing is creating the gridlines. In order to create the horizontal (for the x axis) grid line we will be appending a <g> SVG group then we will add a CSS class to it named the grid (inside style.css). After that we are translating (moving) the start point of creation of that grid line to the left-bottom corner. Then we are calling the make_x_gridlines() function which returns a d3 object. That object has a set alignment of axisBottom and takes in as a parameter the earlier set x_axis scale of the chart. AxisBottom means that we want our labels aligned on the bottom, relative to the X axis. But since next in the <g> group we are setting the tickSize attribute to –base_height then in order to invert it we need to choose axisBottom instead of axisTop. In the make_x_gridlines() function we are also setting the amount of ticks. In our case it is 10. This is the actual amount of grid lines distributed evenly on the chart. The last thing is the tickFormat. We leave it as an empty string. We do not want any text on the grid lines. Below we present the complete JavaScript code for the D3 grid lines. If the solution sounds to you too complicated then we encourage you to play around (change parameters and axis alignment) with the sample code application attached to this article. 

 

  svg.append("g")    		
	      .attr("class", "grid")
	      .attr("transform", "translate(0," + base_height + ")")
	      .call(make_x_gridlines()
	          .tickSize(-base_height)
	          .tickFormat("")
	      )

	  function make_x_gridlines() {		
			   return d3.axisBottom(x_axis).ticks(10)
	  }

 

And below is the CSS style (grid class) for the grid lines. We want the grid lines to be very delicate that is why we set their color to lightgrey, we have also decreased the stroke-opacity and we have set the shape-rendering to crispEdges. This will ensure us that the grid lines won’t be blurred. The last thing to style is the stroke-width of the grid path. We have set it to 0 because we do not want to see a border around our grid lines.

 

.grid line {
  stroke: lightgrey;
  stroke-opacity: 0.7;
  shape-rendering: crispEdges;
}

.grid path {
  stroke-width: 0;
}

 

The same concept goes for creation of the Y grid lines. The only difference will be in the .tickSize. For Y grid lines it will be distributed over –baseWidth. And therefore the make_y_gridlines() function will return a d3 object with an axisLeft align and basing on the y_axis scale. 

 

svg.append("g")    		
        .attr("class", "grid")
        .call(make_y_gridlines()
		          .tickSize(-base_width)
		          .tickFormat("")
	     )

	function make_y_gridlines() {		
	      return d3.axisLeft(y_axis).ticks(10)
	}

 

Drawing the chart – axes

 

After creating the horizontal and vertical gird lines we will now create both x and y axes. To do so we will use the same technique we have facilitated while creating the grid lines. For the x axis we will create an SVG <g> group with a transform translate attribute set to 0 for the x position and base_height for the y position. This will put our x axis in the left-bottom corner of the chart. The last thing to do is to call the d3.axisBottom(x_axis) function. We align the x axis labels to the bottom of it and set the scale basing on the x_axis scale. 

 

svg.append("g")
          .attr("transform", "translate(0," + base_height + ")")
	  .call(d3.axisBottom(x_axis));

   
svg.append("text")             
	  .attr("transform", "translate(" + (base_width / 2) + " ," + (base_height + margin.top + 25) + ")")
	  .style("text-anchor", "middle")
	  .text("X axis");

 

The second thing we want to do is to append the text label describing the X axis. We use the append function and apply the “text” as parameter. Then we position the text by using the transform translate  again. We put the label text in the middle of our base_width by dividing it by 2. We do not have to worry that it won’t be centered because in the style parameter we are assigning its text-anchor to middle. As for the vertical position of our text we are placing it a little beneath the X axis. So we need to add up the base_height with the margin.top and add some value, like 25, to put the label lower than the axis. The last thing to do is to provide the text itself. In our case it will be just a simple label saying “X axis”.

As you might expect the Y axis has an almost same route of creation. And on the example of this axis we will show you how to style all of its elements. It will make the example more helpful and will give you the knowledge how to totally customize your axes  visually.

The Y axis is starting in the top corner of our chart so we do not need to position it specifically like we did it with the X axis (bottom-left corner using transform translate). We are creating a <g> SVG group. Then we are assigning a CSS greenAxis class specified in our style.css file. We will get back to describe the CSS after the creation and positioning of the Y axis and its label. The last things we need to do in order to create the Y axis is calling the d3.axisLeft function to put the labels on the left from our Y axis and providing the scaling for our axis via the y_axis scale value.

 

svg.append("g")
	   .attr("class", "greenAxis")
	   .call(d3.axisLeft(y_axis));

svg.append("text")
	   .attr("transform", "rotate(-90)")
	   .attr("y", 0 - margin.left)
	   .attr("x", 0 - (base_height / 2))
	   .attr("dy", "1em")
	   .style("text-anchor", "middle")
	   .text("Y axis");

 

As seen in the above code snippet we are also creating the Y axis label. So we are again appending a text element. Then we transform and rotate that element -90 degrees. The next thing is to position it on the margin, that is why we are subtracting the margin.left value from 0. And we need to position the text in the half of the y axis, that is why we subtract half of the base_height from 0. Then  in order to adjust the text we tell the SVG text to move 1em, which stands for 1 font height, to move it towards the Y axis, so it doesn’t disappear on the left edge of the screen. We also need to set the text-anchor parameter to middle in order to center the text properly. The last thing to do is to specify the text itself, which will be “Y axis”. Again, at some point the explanation here can be confusing, that is why please play around with the code. Change values, positioning, text, etc. to see for yourself how everything works.

We also promised to show how to style the Y axis with the greenAxis CSS class. Below you can find the CSS code which is responsible for that

 

.greenAxis line{
  stroke: crimson;
  stroke-width: 4px;
}

.greenAxis path{
  stroke: green;
  stroke-width: 4px;
}

.greenAxis text{
  fill: blue;
  font-size: 16px;
}  

 

So we have the greenAxis line parameters. Those are responsible for the color of the little lines marking the values on our Y axis. We changed their color to crimson and set their stroke-width to 4 pixels. The next one, which are the greenAxis parth parameters change the color of the line which is actually the Y axis. We changed its color to green and stroke-width to 4 pixels also. The last thing to style is the values on the Y axis. We have set their color to blue and the font-size to 16 pixels. Of course as usual feel free to add your own properties and change the existing ones.

 

Creating interactive data points

 

We are now prepared to visualize the data itself on the chart. We will create interactive, clickable data points on the chart and a dynamic text field which will show us some information based on the clicked point.

First we will create the text which will serve us as the information about the data stored under a data point. As you can see below there is nothing special about it. We just create a plain SVG text like we used to do earlier. We are setting x and y position values, then we set the dy vertical adjustment to the text and supply it with the value of 1em (one font height). Then we set the anchor and color fill, font-weight and finally the string in the text.

 

var dynamicText = svg.append("text")
    			    .attr("x", base_width / 2)
			    .attr("y", base_height + 70)
			    .attr("dy", "1em")
			    .style("text-anchor", "middle")
			    .style("fill", "red")
			    .style("font-weight", "bold")
			    .text("Click a point on the chart to get info");    

 

Having done that, we can finally create our data points on the chart. As in the beginning of the article we were creating pointLabels by defining them with the selectAll() method, now we will do the same for the SVG rect figures, which will serve us as the graphical representation of our dotData. The code below shows how to accomplish that.

 

var rects = svg.selectAll("rect")
                         .data(dotData)
	                 .enter()
	                 .append("rect")
	                 .on("click", onClick);
	  	  
				  function onClick(d) {	  
					  dynamicText.text("X = " + d.x + ", Y = " + d.y + "  ---   data: " + d.description);   
				  }

 

There is nothing new here except the .on(“click”, onClick); part, which is responsible for adding a click listener to our graphical objects. Inside the onClick function we are setting the text to be displayed in our dynamicText SVG field. For this case we use the data d object holding the dotData data passed in to the onClick function as a parameter. The dynamic text will display both x and y coordinates of the data points as well as a description string attached to the data point.

Still we need to set the attributes of the rects, so they will be placed and displayed as we wish. Beneath we are setting up all the needed parameters for displaying an SVG rect properly. So first of all we are setting the right position of the data point. As you can see we need to recalculate the position of our data objects using the x_axis and y_axis scales in order to render them in the right places on the chart. We also need to subtract the half of the width and height of a rect in order to center the rect on the data point coordinates. We are also specifying the arcs of the rect corners by setting the rx and ry values. Then we set the width and height of the rectangles. And finally we set a fill color for them. All the values are taken from the dotData since they are returned from the anonymous functions as a d object.

 

var rectAttr = rects.attr("x", function (d) { return window.x_axis(d.x) - d.width/2; })
                              .attr("y", function (d) { return window.y_axis(d.y) - d.height/2; })
	                      .attr("rx", function (d) { return d.rx; })
	                      .attr("ry", function (d) { return d.ry; })
	                      .attr("width", function (d) { return d.width; })
	                      .attr("height", function (d) { return d.height; })
	                      .style("fill", function (d) { return d.color; });

 

Creating an animated line between data points

 

So we have placed our data points on the chart and you can even click them and then you get the information based on their data in a dynamic text field. The next thing we want to do is to create a line between all the data points to indicate a trend for example. Also we will make that line animated by drawing it between the points, starting from the first and ending on the last one.

The first thing we need to do is to plot the line itself. We do that by appending a path to our svg. We specify the datum for the path from our dotData again. We set the style of the fill to none, because we don’t want any color between the path parts. We set the stroke color of the path to black and stroke-width to 2 pixels. The next line with the changing d attribute is used for generating the SVG d3.line path data for plotting our path. We are again providing the x and y data converted with the earlier prepared scale. The last line calls the animate function which will serve us for animating the path between the data points.

 

var linePath = svg.append("path")
    			 .datum(dotData)
			 .style("fill", "none")
			 .style("stroke", "black")
			 .style("stroke-width", "2px")
		         .attr("d", d3.line()
				        .x(function(d) { return window.x_axis(d.x); })
				        .y(function(d) { return window.y_axis(d.y); }))
		         .call(animate); // <-- call the animate() function

 

Next the animate function uses the generated path and invokes a transition() function on it. We need to set the duration for the transition. In our case it will be 6 seconds (6000 miliseconds). Then in the next line in the attrTween() function we are changing the value of the stroke-dasharray of our path by invoking our own tween function. In the last line we are adding a listener to check for the moment where the path is drawn and then we select the same path and order it to animate again by invoking the calling the animate function.

 

function animate(path) {
    		          path.transition()
			      .duration(6000)
			      .attrTween("stroke-dasharray", tween)
			      .on("end", function() { d3.select(this).call(animate); });
	               }

 

We also have to look into the tween function. What it does first is storing in a variable the total length of the line. Then it uses the d3.interpolateString() function to interpolate the values between the start and end points of the path. Lastly it returns the current drawn length of the line based on the t progress value of the transition.

 

function tween() {
    		    var lineLength = this.getTotalLength()
		    var interpolated = d3.interpolateString("0," + lineLength, lineLength + "," + lineLength);
		    return function(t) { return interpolated(t); };
	         }

 

Lastly please remember to start defining the linePath before defining the data point SVG rects because we want the rects to overlap the animated line and not the other way around.

 

Summary

 

                In this article we have showed you how to create a complete D3.js interactive and animated chart. We have explained how to prepare the data for our chart. Next we have described how to create the scene, scales and how to create labels for the data points along with grid lines and chart axes. After that we wrote about creating the data points, plotting a path through them and finally animating the process. We have also shown how to add a click listener to the data points and how to change the value of a text field, basing on the data from our collection.

The D3.js library is far more complex than we have described it in this article. But we hope that by reading this article you have gained new exciting knowledge and that you will use D3.js in your Tizen Web project to create stunning mobile applications utilizing data visualization.

For further learning materials we encourage you to visit the official D3.js website and the official D3.js GIT repository.

첨부 파일: