Skip to content

Instantly share code, notes, and snippets.

@eoiny
Last active January 4, 2016 01:29
Show Gist options
  • Save eoiny/8548406 to your computer and use it in GitHub Desktop.
Save eoiny/8548406 to your computer and use it in GitHub Desktop.
Multi-line Chart with Info
date series1 series2 series3 series4 series5 series6 series7 series8
2005-01-01 100 100 100 100 100 100 100 100
2005-02-01 100.4 100.5 100.1 100.1 100.3 101.1 100.8 101.7
2005-03-01 100.6 100.8 99.9 100.3 100.6 101.2 101.2 101.3
2005-04-01 101.3 101.6 99.7 100.9 101.2 102.2 102.4 101.5
2005-05-01 102 102.3 99.7 101.5 101.9 102.8 103.4 101.1
2005-06-01 102.9 103.4 99.6 102.6 103.1 103.5 104.2 101.5
2005-07-01 104.3 105 100 104 104.7 104.7 105.6 102
2005-08-01 105.9 106.6 101.5 105.6 106.2 106.4 107.3 103.5
2005-09-01 107.2 107.8 103.8 106.5 107.1 108.5 109.4 106
2005-10-01 109 109.5 106.2 107.9 108.3 111.1 112 108.3
2005-11-01 110 110.5 106.9 108.6 109.1 112.6 113.7 109.2
2005-12-01 111.5 112 108.3 110.3 110.7 113.7 114.9 110
2006-01-01 111.8 112.4 108.4 110.7 111.1 113.9 115.2 109.9
2006-02-01 112.6 113.1 109.1 111.2 111.6 114.9 116.4 110.5
2006-03-01 113.1 113.8 109 111.6 112 116 117.8 110.6
2006-04-01 114.6 115.2 111 112.7 113.2 117.9 119.4 113.4
2006-05-01 116.8 117.4 112.9 114.7 115.3 120.5 122.1 115.3
2006-06-01 119 119.7 114.7 116.4 117 123.6 125.6 117.4
2006-07-01 121.3 122.2 115.7 118.1 118.7 127 129.9 118.2
2006-08-01 123.5 124.4 117.8 119.8 120.4 130.3 133.3 120.9
2006-09-01 125.1 125.9 119.9 121.5 122.1 131.6 134.4 123.1
2006-10-01 126 126.8 120.9 122.5 123 132.4 135.3 123.7
2006-11-01 126.1 126.9 120.7 122.9 123.4 131.9 134.6 123.4
2006-12-01 127.3 128.3 120.8 124 124.7 133.1 136.2 123.7
2007-01-01 128.4 129.3 122.5 125.6 126.4 133.4 135.7 125.9
2007-02-01 129.6 130.5 123.9 126.9 127.6 134.5 136.7 127.4
2007-03-01 129.9 130.9 123.2 127.7 128.4 133.9 136.4 126
2007-04-01 130.3 131.7 121.6 128.1 128.9 134 137.8 123.3
2007-05-01 130.2 131.5 121.7 128.7 129.4 132.7 135.9 123.1
2007-06-01 130.2 131.4 122.5 128.4 129.1 133.4 136.6 123.9
2007-07-01 130.2 131.4 123 128.5 129.2 133.3 135.9 125.4
2007-08-01 130.4 131.7 122.1 128.5 129.3 133.6 136.8 124.1
2007-09-01 130.5 132 121.5 128.8 129.7 133.5 136.9 123.4
2007-10-01 130.1 131.4 121.5 128.3 129 133.1 136.6 122.6
2007-11-01 130.1 131.6 120.7 128.6 129.3 132.7 136.7 121.3
2007-12-01 129.5 130.8 121.3 128.4 129.1 131.2 134.1 122.6
2008-01-01 128.7 130.1 120.3 128.5 129.5 128.6 130.6 122.3
2008-02-01 127.6 128.8 120.1 127.3 128.4 127.7 128.9 123.4
2008-03-01 126.6 127.7 119.5 126.3 127.3 126.9 128.2 122.2
2008-04-01 125.7 126.9 118.5 124.8 125.6 127.4 129.6 121.1
2008-05-01 124.6 126 116 123.3 124.2 126.9 130.6 117.8
2008-06-01 123.4 125.1 113.5 122.1 122.9 125.9 130.7 114.7
2008-07-01 122.5 124.3 112.4 121.4 122.2 124.4 129.3 112.9
2008-08-01 121.5 123.2 111.7 120.8 121.5 122.5 127.1 111.8
2008-09-01 120.1 121.9 109.9 120.2 120.9 119.5 123.9 109.1
2008-10-01 117.7 120 105.3 118.4 119.2 116 121.4 103.6
2008-11-01 115.7 118.2 102.1 117 117.9 112.5 118.3 99.5
2008-12-01 113.5 116.2 99.3 114.7 115.7 110.6 116.7 97
/* Normalize */
body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, figure, form, fieldset, input, p, blockquote, th, td, legend {
margin: 0;
padding: 0;
}
body {
font-family: 'Cardo', serif;
}
p { line-height: 1.4em; margin-bottom: 1.0em; }
a { color: #821122; }
table {
width: 925px;
border-collapse: collapse;
border-top: 0px solid #ccc;
margin-right: 2px;
}
table td { padding: 15;
margin: 0;
vertical-align:top;
padding-top: 40px;
}
#chart {
min-height: 500px;
width: 960px;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.dot {
stroke: #000;
}
.line {
fill: none;
stroke: #e2e2e2;
stroke-width: 1.5px;
}
.lineOver {
fill: none;
stroke: orange;
stroke-width: 2.5px;
}
text {
font-family: Helvetica, Arial, sans-serif;
font-size: 12px;
stroke: #000;
}
//create svg container + inner drawing space using the d3 conventional margin
var margin = {top: 20, right: 80, bottom: 30, left: 30},
width = 960 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
//create time formatting function
var parseDate = d3.time.format("%Y-%m-%d").parse;
//create a time scaling function for the x-axis data, from 0 to width of the inner drwg space
var x = d3.time.scale()
.range([0, width]);
//create a linear scaling function for the y-axis data, from height of inner drwg space to 0
var y = d3.scale.linear()
.range([height, 0]);
//create ordinal scaling function for the colours
var color = d3.scale.category10();
//******testing
var places = d3.scale.ordinal();
//createe x- & y-axes functions, bring in scaling functions & orientate
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
//create d3 path generation function
var line = d3.svg.line()
.interpolate("basis")
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.pindex); });
//create svg drawing container & inner drwg space
var svg = d3.select("#chart").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 + ")");
//asynchrous callback function to retrieve data from tsv file
d3.tsv("data.tsv", function(error, data) {
//look at the 1st data element, gets the object keys,
//data[0] returns the 1st js object, d3.keys functionality looks at eack key:value pair in the object
//& returns only the keys, as an array. Then the js array.filter method is applied to each element in the
//the array and returns the element as long as it does not equal "date". This array of keys is used to set
//the domain of the ordinal scale function.
//data is an array of 106 objects in the form:
// [{
// series1: "100",
// series2: "100",
// series3: "100",
// series4: "100",
// series5: "100",
// series6: "100",
// series7: "100",
// series8: "100",
// Date: "2005-01-01",
// } ..... 105 other objects for each date value..]
//
//console.log(data);
places.domain(d3.keys(data[0]).filter(function(key) { return key !== "date"; }));
//iterate thru the js objects & for each object convert the date str into a js date object
//& assign it back to the d.date key
data.forEach(function(d) {
d.date = parseDate(d.date);
});
//reorganise data for use by the d3 pattern: i.e. Nesting data
//1. apply a function to every element in the ordinal domain i.e. each pindex
//2. return an object with 2 key:value pairs
//3. end up with an array of 8 objects for each pindex series, consisting of an array
// of names and a value, where the value is an array of 106 objects, consisting of a
//date and a pindex value:
// [{
// name: "series1",
// values: [{date: "2005-01-01",
// pindex: "100"},
// {date: "2005-02-01"
// pindex: "100.4"}, ...etc for all 106 data points i.e. dates
// ]
// },
// ....+7 more objects for each series
// ]
//
var cities = places.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {date: d.date, pindex: +d[name]};
})
};
});
//console.log(cities);
//create an array using d3.extent, & use it to set the x-scale fuction's input domain,
// returns an array of two elements - min & max dates
x.domain(d3.extent(data, function(d) { return d.date; }));
//as with x-axis, create an array to setup the y-scale function input domain,
//as this is a nested array we need the max & min values for the entire data set
//but also for each individual series
y.domain([
d3.min(cities, function(c) { return d3.min(c.values, function(v) { return v.pindex; }); }),
d3.max(cities, function(c) { return d3.max(c.values, function(v) { return v.pindex; }); })
]);
//create a svg group element for the x axis & for the y axis, append labels
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("House Price Index¹");
//create an svg g-element for each of the 8 series
var city = svg.selectAll(".city")
.data(cities)
.enter().append("g")
.attr("class", "city");
//and generate a path for each series using the line function
city.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
//****testing
// .style("stroke", "lightgrey")
//...testing
//.style("stroke-dasharray", (3,3) )
.style("stroke", function(d){
if (d.name === "series1") {return "black"}
else {return "lightgrey"}
;})
//set an mouseover event listener for when the mouseover occurs on any give path g-element
//using d3.select(this)
//and also select the corresponding text g-element,
//in both selections make path and text go orange
.on("mouseover", function(d){
//console.log(d.name);
d3.select(this)
.style("stroke", "orange");
//use the following to select the specific path g-element & bring it to the top
//of the svg drawing container
this.parentNode.parentNode.appendChild(this.parentNode);
//selection for grabbing corresponding text
d3.select('#text-' + d.name)
.attr("visibility","visible")
.style("stroke", "orange")
.text(function(d) {return d.name;});
})
//when mouseout occurs revert the path to grey & the text to invisible
.on("mouseout", function(d){
d3.select(this)
.style("stroke", function(d){
if (d.name === "series1") {return "black"}
else {return "lightgrey"}
;})
d3.select('#text-' + d.name)
.attr("visibility","hidden")
})
//create an id for the path DOM element that has the name of the data series in it
.attr("id", function(d, i) { return "path-" + d.name; })
;
//create the text label for each data series,
//find the position the apppend it to at the end each path using the array.length method
city.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.pindex) + ")"; })
.attr("x", 5)
.attr("dy", ".35em")
//.attr("visibility","hidden")
//****testing
.attr("visibility",function(d){
if (d.name === "series1") {return "visible"}
else {return "hidden"}
;})
//as with the path mouseover event above, set an event listener for the text.
.on("mouseover", function(d){
d3.select('#path-' + d.name)
.style("stroke", "orange")
this.parentNode.parentNode.appendChild(this.parentNode);
d3.select(this)
.attr("visibility", "visible")
.style("stroke", "orange");
})
.on("mouseout", function(d) {
d3.select('#path-' + d.name)
//.style("stroke", "lightgrey")
.style("stroke", function(d){
if (d.name === "series1") {return "black"}
else {return "lightgrey"}
;})
d3.select(this)
.attr("visibility",function(d){
if (d.name == "series1") {return "visible"}
else {return "hidden"}
;})
})
.text(function(d) { return d.name; })
.attr("id", function(d, i) { return "text-" + d.name; });
//filter the "series1" series
//and append "points of interest" i.e. circles visible at points: 1.max, 2.min, 3.first, 4.last,
//& set them as mouseover points for further explanations
var filtered = city
.filter(function(d){
return d.name == "series1"
})
filtered
.selectAll('circle')
.data(
function(d){return d.values}
)
.enter().append('circle')
.attr({
cx: function(d,i){
return x(d.date)
},
cy: function(d,i){
return y(d.pindex)
},
r: 5
})
//at this point all the data points have circles appended
//but need to set all visibility attrs to "hidden"
//except for : 1.max, 2.min, 3.first, 4.last
/*.attr("visibility", function(f) {
// if (d.pindex == d3.max(filtered, function(v) {return d.pindex;}) {return "visible"}
//if (d.pindex == d3.max(d3.values(filtered))) {return "visible"}
if (+f.pindex == d3.max(filtered, function(d,i) {return +d.pindex;})) {return "visible"}
// <== max
// if (d.pindex == d3.min(filtered) {return "visible"} // <== min
else { return "hidden" } // <== Add these
;})*/
.style("fill", "orange")
//mouseover cirlces
.on('mouseover', function(d){
filtered.append("text")
.attr({
x: function(dd){
return x(d.date)
},
y: function(dd){
return y(d.pindex)
},
dx:-3,
dy:".35em",
"text-anchor":"end"
})
.style("fill", "black")
.text(
function(dd){
var formatDate = d3.time.format("%d-%B-%Y")
return 'Date:' + formatDate(d.date) + ',index:' + d.pindex
}
)
})
.on('mouseout', function(dd){
d3.select(this.parentElement)
.selectAll('text').remove()
})
;
});
<!DOCTYPE html>
<meta charset="utf-8">
<html>
<head>
<title>House Price Index</title>
<link rel="stylesheet" type="text/css" href="houses.css" />
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body>
<table class="left">
<tr>
<td><div id="chart"></div> </td>
</tr>
</table>
<script type="text/javascript" src="houses.js"></script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment