Skip to content

Instantly share code, notes, and snippets.

@razum2um
Last active January 16, 2018 22:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save razum2um/bd015bfb67184acb45ba5924a564b6ef to your computer and use it in GitHub Desktop.
Save razum2um/bd015bfb67184acb45ba5924a564b6ef to your computer and use it in GitHub Desktop.
D3 v4 Line Chart
license: mit

D3 v4 Line Chart Example

This is a example for basic line chart using D3. We are using the newest version of D3, version 4. As for all visualizations, we can break down this work into a checklist.

Line Chart Checklist
  1. Add an SVG to draw our line chart on
  2. Use the D3 standard margin convetion
  3. Create an x axis
  4. Create a y axis
  5. Create an x scale
  6. Create a y scale
  7. Create a line generator
  8. Create a random dataset
  9. Create a path object for the line
  10. Bind the data to the path object
  11. Call the line generator on the data-bound path object
  12. Add circles to show each datapoint
  13. Add some basic styling to the chart so its easier on the eyes

Read through the code below to see where each part of the checklist is completed.

forked from pstuffa's block: D3 v4 Line Chart

forked from anonymous's block: D3 v4 Line Chart

<!DOCTYPE html>
<meta charset="utf-8">
<style type="text/css">
.line {
fill: none;
stroke-width: 4;
stroke-linecap: round;
stroke-linejoin: bevel;
}
.line.strength {
stroke: #875D9A;
}
.line.interest {
stroke: #61D3A5;
}
.line.dashed {
stroke-dasharray: 10 10;
}
.today {
stroke-width: 2;
stroke: #338FFC;
}
.today-text {
fill: #338FFC;
font-family: "Helvetica Neue";
font-size: 14px;
text-anchor: middle;
}
.center-y {
stroke-width: 2;
stroke: #875D9A;
}
.center-text {
fill: #875D9A;
font-family: "Helvetica Neue";
font-size: 14px;
text-anchor: middle;
}
.tick text {
font-family: "Helvetica Neue";
font-size: 16px;
text-anchor: middle;
}
</style>
<body>
</body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
// Data
const dataset1 = JSON.parse('[73.76485163027888,88.14314869551498,60.07160491671124,78.05917782144006,92.84690323913051,87.77022060741578,87.65266340207693,52.02965719713634,65.8700937116689,63.143085790431606,77.51285081290519,89.47117627132704,99.27408547811402,87.99858960412732,83.66506795366232,88.53211932576345,99.5094600639527,56.66014023625252,67.6995830391757,63.998180923002366,66.51164077564725,80.16849630600868,55.973509828607725,94.88309818299956,70.24926011032275,89.2774150560498,89.00381706016418,91.0780470510668,82.05933734906436]');
const dataset2 = JSON.parse('[80.73618473493161,88.05885666592522,98.7048816420985,74.79973316682464,67.254335813292,62.44722842133699,83.80271025274195,88.44556972808486,63.0330640574728,85.08327811406141,92.09447015491307,75.88646047806537,60.131218776193876,82.48795809853172,89.45430727528588,97.87417843831301,76.14124806290442,55.89406081061487,91.85792353943552,99.44041241846986,73.81760885821723,63.59228062243233,52.21401593797178,64.1732245543982,70.2075416481421,76.5982175249886,72.04620658856541,62.25888013762293,86.50173611465081]');
const dataset3 = JSON.parse('[11.6309347140356367,35.6430965753907,9.746423099201152,39.98647515573946,17.98027613743821,3.486469410501858,45.072967972773256,43.19067593266349,43.64312238338458,2.6638327695559627,35.49974849768752,37.76264997103447,5.305161811793269,26.743664290160083,28.447241277111203,7.442892296100901,36.07707697281577,9.159932090281785,26.74222560898597,13.918782227917227,33.00857048879784,28.656977770686787,23.286804306942678,39.61776395294071,19.231098301010363,36.82031101548148,14.719176547755753,2.399024152359308,20.279515765680955]');
const dataset4 = JSON.parse('[12.5804864395190799,4.423716487585305,44.38469228742593,22.464681279568754,49.29188761696336,20.831848000831187,22.973913573390824,47.18320458242374,47.720363830206416,26.97593329221013,13.49367755618619,34.250680716061055,41.70916366924611,47.067658950647925,37.06380506433143,43.79930466097505,26.400890061824867,30.64057803574093,29.635152109799357,47.80675224987022,14.875293828279634,2.7155919318852195,14.426866016353923,31.575105367917477,45.194747660107005,29.393420628430455,24.844235497150446,3.6585916851372025,38.88205391627133]');
const margin = {
top: 50,
right: 0,
bottom: 50,
left: 80
},
width = window.innerWidth - margin.left - margin.right,
height = window.innerHeight - margin.top - margin.bottom;
const addDays = (date, days) => {
const d = new Date(date.valueOf());
d.setDate(d.getDate() + days);
return d;
}
const n = 29;
const firstDate = new Date(2018, 0, 10);
const lastDate = addDays(firstDate, 28);
const todayDate = new Date(2018, 0, 18);
const gridColor = '#8D98A2';
const gridlineColor = '#E7ECF1';
// Scales
const xScale = d3.scaleTime()
.domain([firstDate, lastDate])
.range([0, width]);
const yScale = d3.scaleLinear()
.domain([0, 100]) // input
.range([height, 0]); // output
// Line generator
const line = d3.line()
.x(function(d, i) {
return xScale(d.x);
})
.y(function(d) {
return yScale(d.y);
})
.curve(d3.curveCatmullRom)
const getDataset = (ds) => d3.range(n).map(function(d) {
return {
"y": ds[d],
"x": addDays(firstDate, d)
}
});
const svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// X Axis
const formatTimeXSameMonth = d3.timeFormat("%B %e");
const formatTimeX = d3.timeFormat("%b %e");
const formatX = (d) => {
const left = addDays(d, -3);
const right = addDays(d, 3);
if (left.getMonth() === right.getMonth()) {
return formatTimeXSameMonth(left) + ' - ' + right.getDate()
} else {
return formatTimeX(left) + ' - ' + formatTimeX(right)
}
};
const xAxis = d3.axisBottom(xScale)
.tickValues([addDays(firstDate, 3),
addDays(firstDate, 10),
addDays(firstDate, 17),
addDays(firstDate, 24)
])
.tickFormat(formatX);
const customX = (g) => {
g.call(xAxis);
g.select(".domain").remove();
g.selectAll('.tick line').remove();
g.selectAll('.tick text').attr('fill', gridColor)
}
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(" + (width * 0.02) + "," + (height + 6) + ")")
.call(customX);
const gridlinesXAxis = () => d3.axisBottom(xScale).tickValues([
addDays(firstDate, 0),
addDays(firstDate, 7),
addDays(firstDate, 14),
addDays(firstDate, 21),
addDays(firstDate, 28)
]);
const gridlinesX = (g) => {
g.call(gridlinesXAxis()
.tickSize(-height - margin.top - 40)
.tickFormat(""));
g.select(".domain").remove();
g.selectAll('.tick text').remove();
g.selectAll('.tick line').attr('stroke', gridlineColor)
}
svg.append("g")
.attr("class", "grid")
.attr("transform", "translate(0," + (height + margin.top - 10) + ")")
.attr("stroke", gridColor)
.call(gridlinesX);
// Y Axis
const yAxis = d3.axisLeft(yScale)
.tickValues([0, 25, 50, 75, 100])
.tickFormat(d => d + '%');
const customY = (g) => {
g.call(yAxis)
g.select(".domain").remove();
g.selectAll('.tick line').remove();
g.selectAll('.tick text').attr('fill', gridColor).attr('x', -43)
}
svg.append("g")
.attr("class", "y axis")
.call(customY);
const gridlinesYAxis = () => d3.axisRight(yScale).tickValues([0]);
const gridlinesY = (g) => {
g.call(gridlinesYAxis()
.tickFormat("")
.tickSize(width + margin.right + margin.left));
g.select(".domain").remove();
g.selectAll('.tick text').remove();
g.selectAll('.tick line').attr('stroke', gridlineColor)
}
svg.append("g")
.attr("class", "grid")
.call(gridlinesY);
// Draw line
const getPastDataset = (ds) => ds.filter(d => d.x <= todayDate);
const getFutureDataset = (ds) => {
const past = getPastDataset(ds);
return [past[past.length - 1]].concat(ds.filter(d => d.x > todayDate));
}
const useDataset = (dataset, cssClass) => {
const pastDataset = getPastDataset(dataset);
const futureDataset = getFutureDataset(dataset);
svg.append("path")
.attr("class", "line " + cssClass)
.attr("d", line(pastDataset));
return svg.append("path")
.attr("class", "line dashed " + cssClass)
.attr("d", line(futureDataset));
}
const interestLine = useDataset(getDataset(dataset1), 'interest');
const strengthLine = useDataset(getDataset(dataset3), 'strength');
const animate = (n) => {
const interestDs = getFutureDataset(getDataset(n === 1 ? dataset1 : dataset2));
interestLine.transition().ease(d3.easeQuad).duration(1000).attr("d", line(interestDs));
const strengthDs = getFutureDataset(getDataset(n === 1 ? dataset3 : dataset4));
strengthLine.transition().ease(d3.easeQuad).duration(1000).attr("d", line(strengthDs));
}
// Draw today
const todayX = xScale(todayDate);
svg.append("line")
.attr("x1", todayX)
.attr("y1", 70 - margin.top)
.attr("x2", todayX)
.attr("y2", height)
.attr("class", "today");
svg.append("line")
.attr("x1", todayX)
.attr("y1", -margin.top)
.attr("x2", todayX)
.attr("y2", 30 - margin.top)
.attr("class", "today");
svg.append("text")
.text('Today')
.attr("class", "today-text")
.attr('x', todayX)
.attr('y', 50 - margin.top)
.attr('alignment-baseline', 'central');;
// Center Y Axis
const centerY = yScale(50);
svg.append("line")
.attr("x1", 0)
.attr("y1", centerY)
.attr("x2", width - 170)
.attr("y2", centerY)
.attr("class", "center-y");
svg.append("line")
.attr("x1", width - 30)
.attr("y1", centerY)
.attr("x2", width)
.attr("y2", centerY)
.attr("class", "center-y");
svg.append("text")
.text('HEALTHY TARGET')
.attr("class", "center-text")
.attr('x', width - 100)
.attr('y', centerY)
.attr('alignment-baseline', 'central');
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment