|
<!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> |