A standard line chart with the addition of a gradient to show the range of values at each point. (In this case the range is ± 2 standard deviations.) Because the center point of the gradient varies with position on the x-axis, a single SVG area is not sufficient. Instead, the code creates a separate area (referred to as a slice) for each data point.
Last active
July 27, 2024 20:56
-
-
Save sathomas/f00d5205ba4d661540c5 to your computer and use it in GitHub Desktop.
Line chart with gradient range
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset='utf-8'> | |
<title>Line chart with gradient range</title> | |
<link href='http://fonts.googleapis.com/css?family=Varela' rel='stylesheet' | |
type='text/css'> | |
<style> | |
body { font-family: Varela,sans-serif; } | |
</style> | |
</head> | |
<body> | |
<script src='http://d3js.org/d3.v3.min.js'></script> | |
<!-- | |
Because of cross-origin restrictions, we have to use | |
a hack to load our dataset. | |
--> | |
<script src='http://jsDataV.is/data/atlweather.js'></script> | |
<script> | |
// Convenience functions that provide parameters for | |
// the chart. In most cases these could be defined as | |
// CSS rules, but for this particular implementation | |
// we're avoiding CSS so that we can easily extract | |
// the SVG into a presentation. | |
var color = "#007979"; | |
// Define the dimensions of the visualization. | |
var margin = {top: 80, right: 50, bottom: 50, left: 50}, | |
width = 636 - margin.left - margin.right, | |
height = 436 - margin.top - margin.bottom; | |
// Since this is a line chart, it graphs x- and y-values. | |
// Define scales for each. Both scales span the size of the | |
// chart. The x-scale is time-based (we're assuming months) | |
// and the y-scale is linear. Note that the y-scale | |
// ranges from `height` to 0 (opposite of what might be | |
// expected) because the SVG coordinate system places a | |
// y-value of `0` at the _top_ of the container. | |
// At this point we don't know the domain for either of | |
// the x- or y-values since that depends on the data | |
// itself (which we'll retrieve in a moment) so we only | |
// define the type of each scale and its range. We'll | |
// add a definition of the domain after we retrieve the | |
// actual data. | |
var x = d3.time.scale() | |
.range([0, width]); | |
var y = d3.scale.linear() | |
.range([height, 0]); | |
// Define the axes for both x- and y-values. For the | |
// x-axis, we specify a format for the tick labels | |
// (just the month abbreviation). | |
var xAxis = d3.svg.axis() | |
.scale(x) | |
.tickSize(0, 0, -height) | |
.tickPadding(10) | |
.tickFormat(d3.time.format("%b")) | |
.orient("bottom"); | |
// For the y-axis we add grid lines by specifying a | |
// negative value for the major tick mark size. We | |
// set the size of the grid lines to be the entire | |
// width of the graph. | |
var yAxis = d3.svg.axis() | |
.scale(y) | |
.tickSize(-width, 0, -width) | |
.tickPadding(10) | |
.orient("left"); | |
// Define a convenience function to create a line on | |
// the chart. The line's x-values are dates and the | |
// y-values are the temperature values. The result | |
// of this statement is that `line` will be a | |
// function that, when passed a selection with an | |
// associated array of data points, returns an SVG | |
// path whose coordinates match the x- and y-scales | |
// of the chart. | |
var line = d3.svg.line() | |
.x(function(d) { return x(d.date); }) | |
.y(function(d) { return y(d.temp); }); | |
// A similar convenience function generates an SVG | |
// area. Our area is two standard deviations above | |
// and below the average temperature. | |
var area = d3.svg.area() | |
.x(function(d) { return x(d.date); }) | |
.y0(function(d) { return y(d.temp - 2*d.stdv); }) | |
.y1(function(d) { return y(d.temp + 2*d.stdv); }); | |
// Create the SVG container for the visualization and | |
// define its dimensions. Within that container, add a | |
// group element (`<g>`) that can be transformed via | |
// a translation to account for the margins. | |
var svg = d3.select("body").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 + ")"); | |
// Add the gradient definition to the SVG element. | |
svg.append("linearGradient") | |
.attr("id", "temperature-gradient") | |
.attr("x1", 0).attr("y1", 0) | |
.attr("x2", 0).attr("y2", "100%") | |
.selectAll("stop") | |
.data([ | |
{offset: "0%", color: "#ffffff"}, | |
{offset: "50%", color: color}, | |
{offset: "100%", color: "#ffffff"} | |
]) | |
.enter().append("stop") | |
.attr("offset", function(d) { return d.offset; }) | |
.attr("stop-color", function(d) { return d.color; }); | |
// Define a convenience function to calculate the | |
// path for a slice of the data. | |
var slice = function(d,i) { | |
var date = i ? dataset[i-1].date : d.date, | |
temp = i ? dataset[i-1].temp : d.temp, | |
stdv = i ? dataset[i-1].stdv : d.stdv, | |
x0 = x(date) | |
x1 = x(d.date), | |
y0min = y(temp - 2*stdv), | |
y0max = y(temp + 2*stdv), | |
y1min = y(d.temp - 2*d.stdv), | |
y1max = y(d.temp + 2*d.stdv); | |
return "M" + x0 + "," + y0min + | |
"L" + x0 + "," + y0max + | |
"L" + x1 + "," + y1max + | |
"L" + x1 + "," + y1min + | |
"L" + x0 + "," + y0min; | |
} | |
// Convert the data into a more "understandable" | |
// JavaScript object. | |
dataset = dataset.map(function(d,i) { | |
var dt = +d["DATE"], // "20100101" | |
yr = Math.floor(dt/10000), | |
mn = Math.floor((dt%10000)/100) - 1, | |
dy = ((dt%10000)%100), | |
date = new Date(yr,mn,dy), | |
temp = (+d["DLY-TAVG-NORMAL"])/10, | |
stdv = (+d["DLY-TAVG-STDDEV"])/10; | |
return { | |
"date": date, | |
"temp": (+d["DLY-TAVG-NORMAL"])/10, | |
"stdv": (+d["DLY-TAVG-STDDEV"])/10, | |
}; | |
}); | |
// Now that we have the data, we can calculate | |
// the domains for our x- and y-values. The x-values | |
// are a little tricky because we want to add additional | |
// space before and after the data. We start by getting | |
// the extent of the data, and then extending that range | |
// 16 days before the first date and 15 days after the | |
// last date. | |
var xMin = dataset[0].date, | |
xMax = dataset[dataset.length-1].date; | |
x.domain([d3.time.day.offset(xMin,-0), | |
d3.time.day.offset(xMax,0)]); | |
// For the y-values, we want the chart to show the minimum | |
// and maximum values from all the datasets. | |
var yMin = d3.min(dataset, function(d) { | |
return d.temp - 2*d.stdv; | |
}); | |
var yMax = d3.max(dataset, function(d) { | |
return d.temp + 2*d.stdv; | |
}); | |
// The `.nice()` function gives the domain nice | |
// rounded limits. | |
y.domain([yMin, yMax]).nice(); | |
// Now that the domains are defined, we can determine | |
// the width of each x-value. We'll need this to | |
// create the area paths corresponding to each value's | |
// range. | |
var xDelta = x(dataset[1].date) - x(dataset[0].date); | |
// With the domains defined, we also have enough | |
// information to complete the axes. We position | |
// the x-axis by translating it below the chart. | |
svg.append("g") | |
.attr("class", "x axis") | |
.attr("transform", "translate(0," + height + ")") | |
.call(xAxis); | |
// For the y-axis, we add a label. | |
svg.append("g") | |
.attr("class", "y axis") | |
.call(yAxis) | |
.append("text") | |
.attr("transform", "rotate(-90)") | |
.attr("x", -4) | |
.attr("y", 9) | |
.attr("dy", ".71em") | |
.attr("text-anchor", "end") | |
.text("Temperature (°F)"); | |
// Style the axes. As with other styles, these | |
// could be more easily defined in CSS. For this | |
// particular code, though, we're avoiding CSS | |
// to make it easy to extract the resulting SVG | |
// and paste it into a presentation. | |
svg.selectAll(".axis line, .axis path") | |
.attr("fill", "none") | |
.attr("stroke", "#bbbbbb") | |
.attr("stroke-width", "2px") | |
.attr("shape-rendering", "crispEdges"); | |
svg.selectAll(".axis text") | |
.attr("font-size", "14"); | |
svg.selectAll(".x.axis text") | |
.attr("dx", "18"); | |
svg.selectAll(".axis .tick line") | |
.attr("stroke", "#f0f0f0") | |
.attr("stroke-width", "1"); | |
// Add slices for each data point (except the | |
// first). These slices will show the range of | |
// values. | |
svg.selectAll(".slice.dataset") | |
.data(dataset) | |
.enter().append("path") | |
.attr("class", "slice dataset") | |
.attr("fill", "url(#temperature-gradient)") | |
.attr("fill-opacity", "0.4") | |
.attr("stroke", "none") | |
.attr("d", slice); | |
// Add a single area for the entire range of | |
// values. Since the mean varies, we can't | |
// really us a gradient for this path. | |
// svg.append("path") | |
// .datum(dataset) | |
// .attr("class", "area dataset") | |
// .attr("fill", color) | |
// .attr("fill-opacity", "0") | |
// .attr("stroke", "none") | |
// .attr("d", area); | |
// Graph the dataset as a single line | |
svg.append("path") | |
.datum(dataset) | |
.attr("class", "line dataset") | |
.attr("fill", "none") | |
.attr("stroke", color) | |
.attr("stroke-width", "2") | |
.attr("d", line); | |
// Chart decoration. Once more we're avoiding | |
// CSS for styling, but usually that would be | |
// a better approach. | |
d3.select("svg").append("text") | |
.attr("transform", "translate(" + | |
(margin.left + width/2) + ",20)") | |
.attr("class", "title") | |
.attr("font-size", "20") | |
.attr("text-anchor", "middle") | |
.text("Average Daily Temperature - Atlanta"); | |
d3.select("svg").append("text") | |
.attr("transform", "translate(" + | |
(margin.left + width/2) + ",44)") | |
.attr("class", "subtitle") | |
.attr("font-size", "15") | |
.attr("text-anchor", "middle") | |
.text("Source: www.noaa.gov"); | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
http://jsdatav.is/img/thumbnails/weather.png |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment