Skip to content

Instantly share code, notes, and snippets.

@sathomas
Last active August 29, 2015 14:11
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sathomas/f00d5205ba4d661540c5 to your computer and use it in GitHub Desktop.
Save sathomas/f00d5205ba4d661540c5 to your computer and use it in GitHub Desktop.
Line chart with gradient range

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.

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