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
xxxxxxxxxx
<meta charset="utf-8">
<style>
.axis--x path {
display: none;
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
</style>
<svg width="960" height="500"></svg>
<script src="//d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
// L1: to fit city labels
// margin = {top: 20, right: 90, bottom: 30, left: 50},
margin = {top: 20, right: 90, bottom: 30, left: 90},
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()
// L1: straight segments
// .curve(d3.curveBasis)
.curve(d3.curveLinear)
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.temperature); });
d3.tsv("data.tsv", type, function(error, data) {
if (error) throw error;
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; }));
// L4: Save the minimum and maximum temperature values as separate variables
// L4: Adjust the y-axis to pad the domain by 5 degrees. To do this, you can just subtract 5 degrees from the min value and add 5 degrees to the max temperature value when you set the domain.
var minTemp, maxTemp;
y.domain([
minTemp = d3.min(cities, function(c) {
return d3.min(c.values, function(d) {
return d.temperature;
});
}) - 5,
maxTemp = d3.max(cities, function(c) {
return d3.max(c.values, function(d) {
return d.temperature;
});
}) + 5
]);
minTemp += 5;
maxTemp -= 5;
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")
// L1: y-axis to the right
.attr("transform", "translate(" + width + ",0)")
// L2: tick marks as degree
// .call(d3.axisRight(y))
.call(d3.axisRight(y).tickFormat(function(d) {
return d + "°";
}))
.append("text")
// L1: fix rotation
// .attr("transform", "rotate(-90)")
.attr("transform", "rotate(90)")
.attr("y", 6)
.attr("dy", "0.71em")
.attr("fill", "#000")
// L2: Fahrenheit label
// .text("Temperature, ºF")
.text("Fahrenheit");
var city = g.selectAll(".city")
.data(cities)
.enter().append("g")
.attr("class", "city");
city.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return z(d.id); });
city.append("text")
.datum(function(d) {
// L1
// return {id: d.id, value: d.values[d.values.length - 1]};
return {
id: d.id,
value: d.values[0]
};
})
.attr("transform", function(d) {
// return "translate(" + x(d.value.date) + "," + y(d.value.temperature) + ")";
return "translate(" + 0 + "," + y(d.value.temperature) + ")";
})
.attr("x", 0)
// L1: non overlapping labels
// .attr("dy", "0.35em")
.attr("dy", function(d) {
if (d.id == "San Francisco") {
return "1em";
}
else {
return "0.35em";
}
})
.style("font", "10px sans-serif")
// L1: color
.style("fill", function(d) {
// console.log(d);
return z(d.id);
})
// L1: label on the left
.attr("text-anchor", "end")
.text(function(d) { return d.id; });
// L3: circles
var marks = city.append("g")
.attr("class", "points")
.style("stroke", "white")
.style("fill", function(d) {
return z(d.id);
})
.selectAll("circle")
.data(function(d) {
return d.values;
})
.enter();
marks.append("circle")
.attr("cx", function(d) {
return x(d.date);
})
.attr("cy", function(d) {
return y(d.temperature);
})
.attr("r", 3)
// .style("fill", "white");
// L4: Label the minimum and maximum values in the visualization with as little hard-coding as possible.
// To do this, you might want to filter() the marks selection you made for the circle marks such that you only keep the min and max data points. Then, only append text labels for those points. You might need to tweak the dy attribute in a similar way you had to for the line labels earlier.
minX = x(minTemp);
maxX = x(maxTemp);
minY = y(minTemp);
maxY = y(maxTemp);
// console.log(minY + " " + maxY);
// d3.selectAll("circle[cx='100']")
// var minCircle = d3.selectAll("circle[cx='" + minX + "']");
// console.log(minCircle)
// var minCircle = marks.select("circle[cy=" + minY + "]");
// console.log(minCircle);
// minCircle.append("text")
// .datum(function(d) {
// return {
// id: d.id,
// value: d.values[0]
// };
// })
// .attr("transform", function(d) {
// return "translate(" + 0 + "," + minY + ")";
// })
// .attr("x", minX)
// .attr("dy", function(d) {
// return "0.35em";
// })
// .style("font", "10px sans-serif")
// .style("fill", function(d) {
// return z(d.id);
// })
// // L1: label on the left
// .text(function(d) {
// return d.id;
// });
// var maxCircle;
// var minPt = marks.selectAll("circle").filter(function(d) {
// return 1;
// })
// console.log(minPt);
});
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