Skip to content

Instantly share code, notes, and snippets.

@phillipweston
Last active September 17, 2016 09:18
Show Gist options
  • Save phillipweston/e559012fa66e51a025eac647aaf1c0fe to your computer and use it in GitHub Desktop.
Save phillipweston/e559012fa66e51a025eac647aaf1c0fe to your computer and use it in GitHub Desktop.
animated zoomed multi line chart
license: mit

Built with blockbuilder.org

Animated Multi Line Chart

Features

  • Animated
  • Zoomable
  • Pannable
  • Dynamic Legend
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
#linechart {
height: 300px;
width: 500px;
margin: 0 auto;
margin-top: 100px;
}
.overlay {
fill: none;
pointer-events: all;
}
text {
font-size: 10px;
font-family: sans-serif;
}
.legend-text {
font-size: 11px;
}
</style>
</head>
<body>
<div id="linechart"></div>
<script>
function num(max) {
return Math.abs(Math.floor(Math.random() * max) + (max - 200));
}
var data = [];
d3.timeDay.range(new Date(2015, 0, 1), new Date(2016, 0, 1), 1).forEach(function(date) {
data.push({
date: date,
similarityRed: num(100),
similarityOrange: num(200),
similarityYellow: num(300),
similarityBlue: num(400),
similarityGreen: num(700)
});
});
var svg = d3.select('#linechart').append('svg').attr('height', '300px').attr('width', '500px');
var xExtent = d3.extent(data, function(d, i) { return d.date; });
var yValues = [];
data.forEach(function(d) {
for (key in d) {
if (key !== 'date') {
yValues.push(d[key]);
}
}
});
var yMin = d3.min(yValues, function(d, i) { return d; });
var yMax = d3.max(yValues, function(d, i) { return d; });
var xOrigScale = d3.scaleTime()
.domain([ new Date(xExtent[0]), new Date(xExtent[1]) ])
.range([40,495]);
var xScale = xOrigScale.copy();
var yScale = d3.scaleLinear()
.domain([yMin, yMax])
.range([220,2]);
var xAxis = d3.axisBottom(xScale).ticks(6);
var yAxis = d3.axisLeft(yScale);
var xAxisG = svg.append('g')
.attr('id', 'xAxisG')
.attr('transform', 'translate(0,220)')
.call(xAxis);
var yAxisG = svg.append('g')
.attr('id', 'yAxisG')
.attr('transform', 'translate(40,0)')
.call(yAxis);
var color = d3.scaleOrdinal()
.range(['#FF4848', '#FF9C42', '#FFF06A', '#24E0FB', '#36F200']);
var linesG = svg.append('g');
var legend = svg.append('g')
.attr('id', 'legend')
.attr('transform', 'translate(60, 250)')
.style('opacity', '0');
var dateText = legend.append('text')
.attr('id', 'date-text')
.attr('class', 'legend-text')
.attr('x', 0)
.attr('y', 20);
var lines = {};
var paths = {};
var index = Object.keys(data[0]).length - 1;
for (key in data[0]) {
if (key !== 'date') {
var line = d3.line()
.x(function(d) {
return xScale(new Date(d.date));
})
.y(function(d) {
return yScale(d[key]);
})
.curve(d3.curveCatmullRom.alpha(0.5));
lines[key] = line;
path = linesG.append('path')
.attr('d', line(data))
.attr('id', key)
.attr('fill', 'none')
.attr('stroke', function(d) { return color(key); })
.attr('stroke-width', 2);
var totalLength = path.node().getTotalLength();
path
.attr('stroke-dasharray', totalLength + ' ' + totalLength)
.attr('stroke-dashoffset', totalLength)
.transition()
.duration(2000)
.ease(d3.easeCubicInOut)
.attr('stroke-dashoffset', 0);
paths[key] = path;
legend.append('rect')
.attr('class', 'legend-text')
.attr('fill', color(key))
.attr('x', function() {
return 60 * index + 70;
})
.attr('height', 20)
.attr('width', 20)
.attr('y', 5);
legend.append('text')
.attr('id', 'text' + key)
.attr('class', 'legend-text')
.attr('fill', 'black')
.attr('x', function() {
return 60 * index + 93;
})
.attr('y', 20);
index--;
}
}
var clipPath = svg.append('clipPath')
.attr('id', 'clip')
.append('rect')
.attr('x', 40)
.attr('y', 0)
.attr('height', 240)
.attr('width', 500);
var focus = svg.append('g')
.attr('class', 'focus')
.style('display', 'none');
focus.append('line')
.attr('id', 'dotted-line')
.attr('x1', 0)
.attr('y1', 0)
.attr('x2', 0)
.attr('y2', 220)
.attr('stroke', 'black')
.attr('stroke-dasharray', '1, 2');
svg.append("rect")
.attr("class", "overlay")
.attr("width", 500)
.attr("height", 240)
.on("mouseout", function() {
focus.style('display', 'none');
legend
.transition()
.duration(100)
.style('opacity', '0');
})
.on("mousemove", mousemove)
.style('cursor', 'move')
.attr('clip-path', 'url(#clip)');
var zoom = d3.zoom()
.scaleExtent([1, 10])
.on('zoom', zoomed);
svg.call(zoom);
function zoomed() {
console.log('zoomed');
xScale = d3.event.transform.rescaleX(xOrigScale);
xAxisG.call(xAxis.scale(d3.event.transform.rescaleX(xOrigScale)));
for (key in data[0]) {
if (key !== 'date') {
line = lines[key];
path = paths[key];
totalLength = path.node().getTotalLength();
path
.attr('stroke-dasharray', totalLength + ' ' + totalLength)
.attr('stroke-dashoffset', totalLength)
.attr('stroke-dashoffset', 0);
path.attr('d', line(data));
path.attr('clip-path', 'url(#clip)');
}
}
}
var bisectDate = d3.bisector(function(d) { return new Date(d.date); }).left;
function mousemove() {
var x = d3.mouse(this)[0];
var x0 = xScale.invert(d3.mouse(this)[0]),
i = bisectDate(data, x0, 1),
d0 = data[i - 1],
d1 = data[i],
d = x0 - d0.date > d1.date - x0 ? d1 : d0;
focus.style('display', 'block');
dateText.style('display', 'block');
legend
.transition()
.duration(90)
.style('opacity', '1');
focus.attr("transform", "translate(" + xScale( new Date(d.date)) + ",0)");
var date = new Date(d.date);
date = date.toUTCString().split(' ');
date = date[0] + ' ' + date[1] + ' ' + date[2] + ' ' + date[3];
dateText.text(date);
for (key in data[0]) {
if (key !== 'date') {
svg.select('#text' + key)
.text(Number(d[key]).toLocaleString());
}
}
}
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment