For a recent project I needed to create a line chart and a bar chart. Previously I used the ASP.NET chart controls, but in the new ASP.NET Core the chart control is not available anymore, so I went looking for something new. Because of the many single page applications build in javascript these days I figured a javascript based solution would be the best investment for the future. With client computers and browsers being fast enough, there really is no need anymore to generate charts on the server. After some research I choose D3.js.
“D3.js is a javascript library for producing dynamic, interactive data visualizations in web browsers. It makes use of the widely implemented SVG, HTML5, and CSS standards. It is the successor to the earlier Protovis framework.” - Wikipedia
D3.js is a highly flexible and powerful library to build a variety of different data visualizations. But the downside of being so powerful is that it is not so easy to understand at first. That is why I wanted to make this blog post. I am going to assume you are a developer like me, so you have knowledge of HTML, CSS, Javascript and for the .NET parts also knowledge of ASP.NET. I will walk you through creating a bar chart and a line chart. And will explain how to get the data for your chart from the server. After that you should have a basic understanding of D3.js, to also be able to build other visualizations. See other visualizations here.
How to make a bar chart
To build a chart we start with a basic page, with the d3.js reference and a SVG (Scalable Vector Graphics) element. This SVG is used to draw the chart. With javascript we give the SVG a width and height and we store those in a variable, because we will need those later. We also have a set of data that we can use to draw. Later we will take the data from a JSON call.1
2
3
4
5
6
7
8
9
10
11
12<script src="https://d3js.org/d3.v3.min.js" charset="utf-8">
<svg id="barChart"></svg>
<script>
var data = [ 50,90,30,10,70,20];
var w = 500;
var h = 100;
var svg = d3.select("#barChart")
.attr("width", w)
.attr("height", h);
</script>1
2
3svg.selectAll("rect")
.data(data)
.enter()1
2
3
4.attr("x", 0)
.attr("y", 0)
.attr("width", 20)
.attr("height", 100);1
2
3.attr("x", function(d, i) {
return i * 21; //Bar width of 20 plus 1 for padding
})1
2
3 .attr("height", function(d) {
return d; //Just the data value
});1
2
3.attr("y", function(d) {
return h - d; //Height minus data value
})
I added some color to the chart with the fill attribute, just to make it a little nicer. You can use javascript or CSS to style your chart (later you will see a css example).
The example is nice, but this only works because the data matches the pixels of the chart. What if they don’t, because that is like.. always? Well then you use scales, a functions to map from an input domain to an output range. To be able to show a full bar chart we make the data a bit more complex:1
2
3
4
5
6var data = [ { Product: "Shoes", Count: 5 },
{ Product: "Shirts", Count: 9 },
{ Product: "Pants", Count: 3 },
{ Product: "Ties", Count: 1 },
{ Product: "Socks", Count: 7 },
{ Product: "Jackets", Count: 2 }];
So we make scales, an ordinal scale (more info here) for the bars, and a linear scale for the height of the bars:1
2
3
4
5
6
7//An ordinal scale, to support the bars, we choose
var x = d3.scale.ordinal()
.rangeRoundBands([width, 0], 0.1);
//I think this makes a lot of sense (just a default scale converter)
var y = d3.scale.linear()
.range([0, height]); 1
2
3
4//The x domain is a map of all the Products names
x.domain(data.map(function(d) { return d.Product; }));
//The y domain is a range from the maximal (Count) value in the array until 0
y.domain([d3.max(data, function(d) { return d.Count; }), 0]);1
2
3
4
5
6
7
8
9
10
11
12.attr("x", function(d) {
//the x function, transforms the value, based on the scale
return x(d.Product);
})
//The rangeBand() function returns the width of the bars
.attr("width", x.rangeBand())
.attr("y", function(d) {
return y(d.Count); //the y function does the same
})
.attr("height", function(d) {
return height - y(d.Count);
});
JS Bin on jsbin.com
This is a nice start, but I think most people would like to add some axis (see the inline comments for more info):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24var xAxis = d3.svg.axis() //Create an axis
.scale(x) //scale the axis
.orient("bottom"); //this is where the labels will be located
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(d3.format("d")) //Ticks are the divisions on the scale.
.tickSubdivide(0); //Here we want to see only whole numbers on the axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text") //In some cases the labels will overlap
.attr("transform", "rotate(90)") //so you might for example want to rotate the label's so they are vertical
.attr("x", 0) //After that you might have to do some finetuning to align everything the right way:
.attr("y", -6)
.attr("dx", ".6em")
.style("text-anchor", "start");
JS Bin on jsbin.com
Or as an extra example, the same data in a bar chart with horizontal bars. I wanted to share this example as well, because it turned out to be a bit more complex then I expected. Comparing them is a good way to see if you understand D3.js
How to make a line chart
Another very common chart is the line chart. I build on top of what we learned with the bar chart, so I assume you understand the scaling:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 <svg id="lineChart"></svg>
<style>
.line {
fill: none;
stroke: steelblue;
stroke-width: 2px;
}
</style>
<script>
var data = [50,90,30,10,70,20];
var width = 500;
var height = 100;
var svg = d3.select("#lineChart")
.attr("width", width)
.attr("height", height);
var x = d3.scale.linear()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
x.domain([0, data.length]);
y.domain([0, d3.max(data, function (d) { return d; })]);
var line = d3.svg.line()
.x(function (d, i) { return x(i); })
.y(function (d) { return y(d); });
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
</script>
JS Bin on jsbin.com
Very basic, but a line chart. Some things of note for the chart example:
- The scales are both linear
- The domains are set to size of the array and to the max value in the array
- The line is drawn by plotting the index of the array against the value in the array
- Then we add the line to the svg, as a path
- If you do not add any style for the line, it will look a bit strange, because it will fill the object by default (just try it out to see, remove the style block in the JSBin example), so normally you will add a style.
- Of course you can also add axis to this if you want, in the same way as with the bar chart.
Now the only thing left is styling your charts to make them look great!
Get data via an JSON call (ASP.NET MVC)
Mmost of the time the information for the chart comes from a database on the server. Although you could generate the javascript to create the data array, a cleaner solution is to make an JSON call to fetch the data. D3.js includes a perfect function to do this:1
2
3
4
5
d3.json("MyController/MyAction", function (data) {
//Here you have data available, an object with the same structure
//as the JSON that was send by the server.
});1
2
3
4
5
6
7
8
9
10
11public ActionResult MyAction()
{
return Json( new[] {
new { Product = "Shoes" , Count = 5 },
new { Product = "Shirts" , Count = 9 },
new { Product = "Pants" , Count = 3 },
new { Product = "Ties" , Count = 1 },
new { Product = "Socks" , Count = 7 },
new { Product = "Jackets" , Count = 2 }
});
}