In this article, we are going to explore how to represent data with charts, using SVG and D3.js library.. D3.js is a JavaScript library for manipulating documents based on data. D3 helps you bring data to life using HTML, SVG, and CSS. Basically, it is JQuery for data.
Intro
D3 tends to be a long fluid chain of function calls.
1d3.select("body")2 .append("svg")3 .append("text")4 .text("Hello World")5 .attr("x", "100")6 .attr("y", "100");
As we can see in the example, we used a select method to select body element and append an SVG to the body. We did not have to create an SVG element, d3 did that for us, we can continue the chain and append any SVG element to it. We added text element and added text to it. Text element needs to have a position or else they are invisible.
Data
Although you can create and manipulate DOM elements with d3, the most important and powerful part of d3 is data manipulation. D3 stands for data-driven documents, implicit in this idea is that data is separate from the document or for that visualization. Data can dynamically change and visualization needs to adapt to that change.
When data is bound to a selection of nodes, each element in the data array is paired with the corresponding node in the selection. If there are a different number of nodes and data items, we can use d3 lifecycle methods enter and exit. Using those methods, you can create new nodes for incoming data and remove outgoing nodes that are no longer needed.
1// update2const selection = d3.select("body")3 .selectAll("p")4 .data([4, 8, 15, 16, 23, 42])5 .style("font-size",(d, i) => d + "px")6 .text(() => (d, i) => "I’m number " + d + "!");78// enter9selection.enter()10 .append("p")11 .style("font-size",(d, i) => d + "px")12 .text((d, i) => "I’m nmber " + d + "!");1314// exit15selection.exit().remove();
In this example, we select all p elements in the body and let's assume that there are 2 of them. We than appends text to the nodes. Because we only have 2 p elements and 6 data items, we will only update 2 elements in DOM and the rest of the data is ignored. For the rest of the data we are using enter method, in there we are appending new p elements for the rest of the data, and on exit, we are removing them if data changes to have fewer items than nodes in selection. We can see a style method similar to JQuery, what is different is that we can pass function instead of value, the first property is data and the second is index, so in this case property d is number from the array item which we can use to influence return value for style.
Example
Now we are going to go step by step in creating this simple line chart.
1const dataGroup = d32 .select(".chart")3 .append("svg")4 .attr("width", width + margin)5 .attr("height", height + 2 * margin)6 .append("g")7 .attr("transform", "translate(" + margin + ", " + margin + ")");
First, we are selecting an element from DOM with a class chart, and appending SVG to that element, we add width and height to that SVG and append group to that SVG element, we move it a bit from the edge of SVG with translate.
1const parseTime = d3.timeParse("%m/%d/%Y");23data.forEach(function(d) {4 d.date = parseTime(d.date);5});
Here we are creating dates from strings with timeParse function, and just add how date looks in a string from our data - "10/25/2018"
1const x = d32 .scaleTime()3 .domain(4 d3.extent(data, function(d) {5 return d.date;6 })7 )8 .range([0, width]);910const y = d311 .scaleLinear()12 .domain(13 d3.extent(data, function(d) {14 return d.value;15 })16 )17 .range([height, 0])18 .nice();
Functions scaleTime and scaleLinear, are part of scales inside of d3. They construct new scales with the specified domain and range. scaleTime returns representative dates from the scale’s domain. Full list of the scale functions. We specify domain and range. The domain is input values, and they are going to be mapped to output range values that are going to be shown. For a domain we are using extent function, that returns the minimum and maximum value in the given array using natural order. The first domain is for x and is mapped from 0 to width so we get values extending to the end. The second domain for y is mapped from height to 0, and it is important to not go from 0 to height. That way values are going from bottom to the top. At the end with a nice function, we are getting that ticks are shown on the edge. These are going to represent data on axes.
1dataGroup2 .append("path")3 .data([data])4 .attr("fill", "none")5 .attr("stroke", "red")6 .attr("d", lineVar);
We append the path to SVG, style the line and add previous data to d attribute so we have coordinates for the line path.
1const xAxis = d3.axisBottom(x).tickFormat(d3.timeFormat("%Y-%m-%d"));23const xAxisGroup = dataGroup4 .append("g")5 .attr("class", "xAxisGroup")6 .attr("transform", "translate(0," + height + ")")7 .call(xAxis)8 .selectAll("text")9 .attr("y", 0)10 .attr("x", 9)11 .attr("dy", ".35em")12 .attr("transform", "rotate(45)")13 .style("text-anchor", "start");
Here we are setting x-axis. After adding xAxis data to a group, we select all text nodes inside all ticks and rotate them. Because date text is long, and by rotating them we get more space.
1const yAxis = d3.axisLeft(y);2const yAxisGroup = dataGroup3 .append("g")4 .attr("class", "yAxisGroup")5 .call(yAxis);
We do the same for the y-axis. Except for the y-axis, we don't need to use any transformation, aa data provided is already a simple number.
This may be a simple example, though d3.js is a really powerful library for data visualization. Check more examples on their Github page.
That is all for now and until next time, happy coding.