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
Edited by : Jay Ng (ngchwanlii)
Date : 02/10/2017
xxxxxxxxxx
<meta charset="utf-8">
<style>
.axis--x path {
/*display: none;*/
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
/*body {
background-color: whitesmoke;
}*/
/*svg {
background-color: whitesmoke;
}*/
/* setup plot area css */
/*rect#plot {
fill: black;
stroke: none;
}*/
/* make the y-axis label refer to its path color */
/*.city text {
fill: red;
}*/
</style>
<svg width="960" height="500"></svg>
<script src="//d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
margin = {top: 20, right: 80, bottom: 30, left: 70},
width = svg.attr("width") - margin.left - margin.right,
height = svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var parseTime = d3.timeParse("%Y%m%d");
var x = d3.scaleTime().range([0, width]),
y = d3.scaleLinear().range([height, 0]),
z = d3.scaleOrdinal(d3.schemeCategory10);
var line = d3.line()
/* level 1: lab point 4 */
.curve(d3.curveLinear)
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.temperature); });
/* loading csv data */
d3.tsv("data.tsv", type, function(error, data) {
if (error) throw error;
// console.log(data);
// id = cities name ["Austin", "San Francisco", "New York"]
var cities = data.columns.slice(1).map(function(id) {
return {
id: id,
values: data.map(function(d) {
return {date: d.date, temperature: d[id]};
})
};
});
x.domain(d3.extent(data, function(d) { return d.date; }));
/* save min and max temperature */
/* level 4: lab point 1 */
var minTemp, maxTemp;
// for the degree padding range on y axis
var yAxisPad = 5;
y.domain([
// inner loop: iterate over each cities' temperatue value and get the min and max of each cities
// then outer loop: iterate each cities min and max temperature, and find out the minimum and maximum temperature value amongst those cities
/* self explaination about these yAxisPad stuff: rounding in [roof/floor](minTemp/maxTemp +- yAxisPad) depend on condition explain below: */
/* Note:
1. if (temp +- yAxisPad) = +ve numbers, use roof
2. in min, if (temp +- yAxisPad) = -ve numbers, use floor
*/
/* level 4: lab point 2 */
(minTemp = d3.min(cities, function(c) { return d3.min(c.values, function(d) { return d.temperature; }) })) - yAxisPad,
(maxTemp = d3.max(cities, function(c) { return d3.max(c.values, function(d) { return d.temperature; }) })) + yAxisPad
]);
// debug use
// console.log(minTemp, maxTemp);
z.domain(cities.map(function(c) { return c.id; }));
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
g.append("g")
.attr("class", "axis axis--y")
/* level 1: lab point 1 */
.attr("transform", "translate(" + width + ", 0)")
// .call(d3.axisRight(y).tickFormat(function (d){return d + "%";}))
/* level 2: lab point 1: change y-axis ticks label to degree symbol º*/
.call(d3.axisRight(y).tickFormat(function(d) {return d + "º"}))
.append("text")
/* level 1: lab point 2 */
.attr("transform", "rotate(90)")
.attr("y", 6)
.attr("dy", "0.71em")
.attr("fill", "#000")
/* level 2: lab point 2 */
.text("Fahrenheit");
// here city = EACH city from cities list when use in accessing data
var city = g.selectAll("city")
.data(cities)
.enter().append("g")
.attr("class", "city");
/* temporary showing the cities structure here for circle point implementation */
// var cities = data.columns.slice(1).map(function(id) {
// return {
// id: id,
//
// values: data.map(function(d) {
// return {date: d.date, temperature: d[id]};
// })
// };
/* binding DOM tutorials: https://www.dashingd3js.com/binding-data-to-dom-elements */
// .data(theData), theData = cities = [Object1, Object2, Object3], .data(theData) = access to EACH object in the Data Array (like access through forEach loop)
/* level 3: lab point 1 */
var marks = city.append("g")
// how .selectAll works: https://bl.ocks.org/mbostock/5369146
// multiple selector on elements using selectAll(obj1, obj2);
.selectAll("circle", "text")
/** IMPORTANT DEBUG and checking here: accessing data to implement circle point on each data values **/
// console.log(cities[0].values[0].temperature);
// d = cities[i]
// values = Array[Object1, Object2], Object contain a {date: data.date, temperature: data[id]};
// .data(function(d) {return d.values;})
.data(function(d) {return d.values;})
.enter();
// tutorial abount binding circle to each data values: https://bost.ocks.org/mike/circles/
// extra info: d3 function(d,i) - the meaning of i changed as data changed explain in: https://stackoverflow.com/questions/20853402/d3-functiond-i-the-meaning-of-i-changed-as-data-changed
/* level 3: lab point 1 */
marks.append("circle")
.attr("cx", function(d) { return x(d.date);} )
.attr("cy", function(d) { return y(d.temperature);} )
.attr("r", "0.12em")
/* level 3: lab point 2 */
// Phewww!: finally! access this -> marks dataset's PARENT NODE's __data_ property = city's data which contain the city's id = "Austin", "San Francisco", "New York"
.style("fill", function (d) { return z(this.parentNode.__data__.id); });
/********************************************
* set min and max label on the data point *
********************************************/
// display min label on point
/* level 4: lab point 3 */
marks.filter(function (d) {return d.temperature == minTemp;})
.append("text")
.attr("x", function(d) {return x(d.date);})
.attr("y", function(d) {return y(d.temperature);})
.attr("dy", "1em")
.attr("dx", "-1em")
.style("font", "0.7em sans-serif")
.style("fill", function (d) { return z(this.parentNode.__data__.id); })
.text(function (d) {return d.temperature + "º";});
// display max label on point
/* level 4: lab point 3 */
marks.filter(function (d) {return d.temperature == maxTemp;})
.append("text")
.attr("x", function(d) {return x(d.date);})
.attr("y", function(d) {return y(d.temperature);})
.attr("dy", "-0.3em")
.attr("dx", "-1em")
.style("font", "0.7em sans-serif")
.style("fill", function (d) { return z(this.parentNode.__data__.id); })
.text(function (d) {return d.temperature + "º";});
city.append("path")
// here, the class=line is refer to the svg class .line above
.attr("class", "line")
// line(d.values): sub d.values as data into d3.line(arg) above
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return z(d.id); });
city.append("text")
/* see .datum documentation: https://github.com/d3/d3-selection/blob/master/README.md#selection_datum */
// d.values = each city's temperature values in Array[n1, n2, n3...]
/* original code */
// .datum(function(d) {return {id: d.id, value: d.values[d.values.length - 1]}; })
/* explaination on .datum func */
// .datum(func(d): return an object {x,y} which set the z-legend stick along with the graph line)
// d.values[0] means that the z-legend starting with the 1st temperature data
// debug with -> console.log(d.values[0])
.datum(function(d) {return {id: d.id, value: d.values[0]}; })
/* move the cities label to left side of line graph from the first starting date data point */
.attr("transform", function(d) {return "translate(" + (x(d.value.date)-margin.left) + "," + y(d.value.temperature) + ")"; })
.attr("x", 3)
// adjusting dy so that y-axis label won't overlap with y-axis .tick text (the temp value)
.attr("dy", function(d) {
/* level 1: lab point 5 */
if (d.id == "San Francisco") {v = "1.2em";}
else {v = "0.35em";}
return v;
}
)
// setting the slightly offset "Austin"
// .attr("dx", function(d) { if (d.id == "Austin") return (margin.left + offset);})
// console.log(d.id.length) -> check cities length "Austin" = 6, "San Francisco" = 13, "New York" 8 to set the offset margin
/* level 1: Optional lab point 5 */
.attr("dx", function(d) {
if(d.id == "Austin") return ("3.5em");
else if (d.id == "New York") return ("2em"); {
}
})
.style("font", "10px sans-serif")
/* level 1: lab point 3 */
.style("fill", function (d) { return z(d.id); })
.text(function(d) { return d.id; }
);
});
function type(d, _, columns) {
d.date = parseTime(d.date);
for (var i = 1, n = columns.length, c; i < n; ++i) d[c = columns[i]] = +d[c];
return d;
}
</script>
https://d3js.org/d3.v4.min.js