Skip to content

Instantly share code, notes, and snippets.

@headwinds
Last active August 27, 2016 18:15
Show Gist options
  • Save headwinds/7dbc789cdf32572aef3596fbb22748d8 to your computer and use it in GitHub Desktop.
Save headwinds/7dbc789cdf32572aef3596fbb22748d8 to your computer and use it in GitHub Desktop.
D3 v3 Multi-Series Line
license: gpl-3.0

This line chart is constructed from a TSV file storing the daily average temperatures of New York, San Francisco and Austin over the last year. The chart employs conventional margins and a number of D3 features:

forked from mbostock's block: Multi-Series Line Chart

forked from fengqingli's block: D3 Multi-Series Line

date New York San Francisco Austin
20161001 432 624 724
20161002 582 594 674
20161003 999 599 694
20161004 575 584 685
20161005 200 399 360
20161006 758 757 770
20161007 257 526 282
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #ddd;
shape-rendering: crispEdges;
}
.x.axis path {
//fill: #ddd;
stroke: #ddd;
}
.tick {
fill: #999;
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1px;
}
.selectedline {
fill: none;
stroke: steelblue;
stroke-width: 8px;
}
.lineSelected {
}
circle {
pointer-events: all;
cursor: points;
}
.d3-tip {
line-height: 1;
font-weight: bold;
padding: 12px;
background: rgba(0, 0, 0, 0.8);
color: #fff;
border-radius: 2px;
}
/* Creates a small triangle extender for the tooltip */
.d3-tip:after {
box-sizing: border-box;
display: inline;
font-size: 10px;
width: 100%;
line-height: 1;
color: rgba(0, 0, 0, 0.8);
content: "\25BC";
position: absolute;
text-align: center;
}
/* Style northward tooltips differently */
.d3-tip.n:after {
margin: -1px 0 0 0;
top: 100%;
left: 0;
}
</style>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="http://labratrevenge.com/d3-tip/javascripts/d3.tip.v0.6.3.js"></script>
<script>
var margin = {top: 20, right: 80, bottom: 30, left: 50},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var parseDate = d3.time.format("%Y%m%d").parse;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
.interpolate("cardinal")
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.value); });
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(d) {
return "<strong>Frequency:</strong> <span style='color:pink'>" + d.value + "</span>";
})
var reorder = function(){
// make sure the selected line is on top...
// with D3 v3, ordering is easiser with jquery but v4 has better ordering
var selectedLineGroup = $(".lineSelected");
selectedLineGroup.remove();
// add it back first
$('#chart').append(selectedLineGroup);
}
var getLineColor = function(d){
return ( d.selected ) ? "pink" : "lightgrey";
}
var getLineWidth = function(d){
return ( d.selected ) ? "4px" : "1px";
}
var getCityClass = function(d){
return ( d.selected ) ? "city lineSelected" : "city";
}
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("id","chart")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.call(tip);
var tabProcessData = function(data){
color.domain(d3.keys(data[0]).filter(function(key) { return key !== "date"; }));
data.forEach(function(d) {
d.date = parseDate(d.date);
});
return data;
}
var createCitiesData = function(data){
return color.domain().map(function(name) {
let bSelected = (name === "San Francisco") ? true : false;
return {
name: name,
values: data.map(function(d) {
return {date: d.date, value: +d[name], selected: bSelected};
}),
selected: bSelected,
};
});
}
var createChart = function(data){
var cities = createCitiesData(data);
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([
d3.min(cities, function(c) { return d3.min(c.values, function(v) { return v.value; }); }),
d3.max(cities, function(c) { return d3.max(c.values, function(v) { return v.value; }); })
]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text(""); // obviously a value - label isn't necessary
var city = svg.selectAll(".city")
.data(cities)
.enter().append("g")
.attr("class", "city");
let path = city.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return getLineColor(d) })
.style("stroke-width", function(d) { return getLineWidth(d) });
var totalLength = path.node().getTotalLength();
let onUpdate = function(){
};
let pathLengths = [];
path.each( function() {
var len = d3.select(this).node().getTotalLength();
pathLengths.push(len);
});
let getTotalLengthPerNode = function(d,i){
let totalLength = pathLengths[i];
return totalLength + " " + totalLength
}
path
.attr("stroke-dasharray", function(d,i){return getTotalLengthPerNode()})
.attr("stroke-dashoffset", totalLength)
.transition()
.duration(2000)
.ease("linear")
.each("end", onUpdate)
.attr("stroke-dashoffset", 0)
// add the points
var point = city.append("g")
.attr("class", "line-point");
point.selectAll('circle')
.data(function(d){ return d.values})
.enter().append('circle')
.on("mouseover", tip.show)
.on("mouseout", tip.hide)
.attr("cx", function(d) { return x(d.date) })
.attr("cy", function(d) { return y(d.value) })
.attr("r", 0)
.style("opacity", function(d){ return (d.selected) ? 1 : 0})
.style("fill", "white")
.style("stroke", function(d) { return getLineColor(d); })
.transition()
.duration(1000)
.delay( function(d,i){ return 250 + ( 750 * i) })
.attr("r", 5);
// city label
cityLabelContainer = city
.append("g")
.attr("class", "cityLabelContainer");
cityLabelContainer
.append("text")
.datum(function(d) { return {name: d.name, value: d.values[d.values.length - 1]}; })
.attr("transform", function(d) { return "translate(" + x(d.value.date) + "," + y(d.value.value) + ")"; })
.attr("x", 5)
.attr("dy", ".35em")
.text(function(d) { return d.name; });
reorder();
}
var getDummyData = function(){
/*
date New York San Francisco Austin
20161001 432 624 724
20161002 582 594 674
20161003 999 599 694
20161004 575 584 685
20161005 200 399 360
20161006 758 757 770
20161007 257 526 282
0bject
Austin:"724"
New York:"432"
San Francisco:"624"
date:"20161001"
*/
let cityNames = ["new york","toronto","San Francisco","bangalore","tokyo"];
let dates = ["20161001", "20161002","20161003","20161004","20161005","20161006","20161007"];
let data = [];
dates.forEach((dateStr) => {
let dataObj = {
"new york" : Math.floor( Math.random() * 1500 ),
"toronto" : Math.floor( Math.random() * 1500 ),
"San Francisco" : Math.floor( Math.random() * 1500 ),
"bangalore" : Math.floor( Math.random() * 1500 ),
"tokyo" : Math.floor( Math.random() * 1500 ),
date: dateStr,
}
data.push(dataObj);
});
//console.log(data)
return data;
}
var onTabLoaded = function(data){
data = tabProcessData(data);
createChart(data);
// make sure the selected line is on top...
// D3 v3 isnt easiser with jquery but v4 has better ordering
let selectedLineGroup = $(".lineSelected");
selectedLineGroup.remove();
// add it back first
$('svg g').append(selectedLineGroup);
}
var dummyData = getDummyData();
onTabLoaded(dummyData);
//d3.tsv("data.tsv", onTabLoaded);
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment