Skip to content

Instantly share code, notes, and snippets.

@emilyinamillion
Created June 21, 2016 23:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save emilyinamillion/3b1a21059920e1fe700c12e9d019f6c5 to your computer and use it in GitHub Desktop.
Save emilyinamillion/3b1a21059920e1fe700c12e9d019f6c5 to your computer and use it in GitHub Desktop.
Local Variables

It’s often desirable when using D3 to define local behavior, that is, behavior that is specific to an individual element, rather than the same for all elements in a selection. The simplest example of this is passing a function to selection.attr to compute an attribute value for each element.

But what happens if your local behavior is more complicated? What if you want multiple operations (multiple attributes, or elements) to have local behavior, but still share local state between them? For instance, when rendering small multiples of time-series data, you might want the same x-scale for all charts but distinct y-scales to compare the relative (not absolute) performance of each metric.

There are several ways to do this in D3:

  1. Make the y-scale global, but set the domain on the y-scale before use. (Example.)

  2. Use selection.each to create a local context which defines the y-scale. (Example.)

  3. Make the y-scale part of the data. (Example.)

Sadly, none of these approaches are elegant. D3 encourages you to write code that renders all the charts simultaneously, whereby you only have access to in-scope (such as global) variables and the data bound to each element. This structure is typically helpful, by reducing control flow and forcing simpler code, but causes friction here.

Approach 1 is error-prone because you must remember to set the y-scale’s domain before using it; if you don’t, you’ll get the wrong y-values. Approach 2 is cleaner, but the local state is discarded after selection.each returns, so it must be recreated from scratch to update the chart with new data or on interaction. Approach 3 is arguably the most robust, but it’s a little icky to decorate the data. And in more complex cases, it can be awkward to access local state defined by an ancestor (e.g., this.parentNode.__data__).

So here’s an idea for a new approach, inspired partly by ES6 Symbols. What if you could declare a local variable, similar to a standard var, but whose value is scoped by the DOM? Like so:

var foo = d3.local();

On set, the value is stored on the given element:

foo.set(element, value);

On get, the value is retrieved from given element, or the nearest ancestor that defines it:

var value = foo.get(element);

The code to implement this approach is tiny; see d3-local.js below.

What do you think? Good idea? Bad? Let me know.

forked from mbostock's block: Local Variables

<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
margin: 0;
}
.line {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
.area {
fill: #e0e0e0;
}
text {
text-anchor: end;
}
</style>
<body>
<script src="//d3js.org/d3.v4.0.0-alpha.49.min.js"></script>
<script>
var margin = {top: 8, right: 10, bottom: 2, left: 10},
width = 960 - margin.left - margin.right,
height = 69 - margin.top - margin.bottom;
var parseDate = d3.timeParse("%b %Y");
var x = d3.scaleTime()
.range([0, width]);
var y = d3.local();
var area = d3.local();
var line = d3.local();
d3.tsv("stocks.tsv", type, function(error, data) {
if (error) throw error;
var symbols = d3.nest()
.key(function(d) { return d.symbol; })
.entries(data);
x.domain([
d3.min(symbols, function(symbol) { return symbol.values[0].date; }),
d3.max(symbols, function(symbol) { return symbol.values[symbol.values.length - 1].date; })
]);
var svg = d3.select("body").selectAll("svg")
.data(symbols)
.enter().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 + ")")
.each(function(d) {
var ty = y.set(this, d3.scaleLinear()
.domain([0, d3.max(d.values, function(d) { return d.price; })])
.range([height, 0]));
area.set(this, d3.area()
.x(function(d) { return x(d.date); })
.y0(height)
.y1(function(d) { return ty(d.price); }));
line.set(this, d3.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return ty(d.price); }));
});
svg.append("path")
.attr("class", "area")
.attr("d", function(d) { return area.get(this)(d.values); });
svg.append("path")
.attr("class", "line")
.attr("d", function(d) { return line.get(this)(d.values); });
// svg.append("text")
// .attr("x", width - 6)
// .attr("y", height - 6)
// .text(function(d) { return d.key; });
});
function type(d) {
d.price = +d.price;
d.date = parseDate(d.date);
return d;
}
</script>
symbol date price
S&P 500 Jan 2000 1394.46
S&P 500 Feb 2000 1366.42
S&P 500 Mar 2000 1498.58
S&P 500 Apr 2000 1452.43
S&P 500 May 2000 1420.6
S&P 500 Jun 2000 1454.6
S&P 500 Jul 2000 1430.83
S&P 500 Aug 2000 1517.68
S&P 500 Sep 2000 1436.51
S&P 500 Oct 2000 1429.4
S&P 500 Nov 2000 1314.95
S&P 500 Dec 2000 1320.28
S&P 500 Jan 2001 1366.01
S&P 500 Feb 2001 1239.94
S&P 500 Mar 2001 1160.33
S&P 500 Apr 2001 1249.46
S&P 500 May 2001 1255.82
S&P 500 Jun 2001 1224.38
S&P 500 Jul 2001 1211.23
S&P 500 Aug 2001 1133.58
S&P 500 Sep 2001 1040.94
S&P 500 Oct 2001 1059.78
S&P 500 Nov 2001 1139.45
S&P 500 Dec 2001 1148.08
S&P 500 Jan 2002 1130.2
S&P 500 Feb 2002 1106.73
S&P 500 Mar 2002 1147.39
S&P 500 Apr 2002 1076.92
S&P 500 May 2002 1067.14
S&P 500 Jun 2002 989.82
S&P 500 Jul 2002 911.62
S&P 500 Aug 2002 916.07
S&P 500 Sep 2002 815.28
S&P 500 Oct 2002 885.76
S&P 500 Nov 2002 936.31
S&P 500 Dec 2002 879.82
S&P 500 Jan 2003 855.7
S&P 500 Feb 2003 841.15
S&P 500 Mar 2003 848.18
S&P 500 Apr 2003 916.92
S&P 500 May 2003 963.59
S&P 500 Jun 2003 974.5
S&P 500 Jul 2003 990.31
S&P 500 Aug 2003 1008.01
S&P 500 Sep 2003 995.97
S&P 500 Oct 2003 1050.71
S&P 500 Nov 2003 1058.2
S&P 500 Dec 2003 1111.92
S&P 500 Jan 2004 1131.13
S&P 500 Feb 2004 1144.94
S&P 500 Mar 2004 1126.21
S&P 500 Apr 2004 1107.3
S&P 500 May 2004 1120.68
S&P 500 Jun 2004 1140.84
S&P 500 Jul 2004 1101.72
S&P 500 Aug 2004 1104.24
S&P 500 Sep 2004 1114.58
S&P 500 Oct 2004 1130.2
S&P 500 Nov 2004 1173.82
S&P 500 Dec 2004 1211.92
S&P 500 Jan 2005 1181.27
S&P 500 Feb 2005 1203.6
S&P 500 Mar 2005 1180.59
S&P 500 Apr 2005 1156.85
S&P 500 May 2005 1191.5
S&P 500 Jun 2005 1191.33
S&P 500 Jul 2005 1234.18
S&P 500 Aug 2005 1220.33
S&P 500 Sep 2005 1228.81
S&P 500 Oct 2005 1207.01
S&P 500 Nov 2005 1249.48
S&P 500 Dec 2005 1248.29
S&P 500 Jan 2006 1280.08
S&P 500 Feb 2006 1280.66
S&P 500 Mar 2006 1294.87
S&P 500 Apr 2006 1310.61
S&P 500 May 2006 1270.09
S&P 500 Jun 2006 1270.2
S&P 500 Jul 2006 1276.66
S&P 500 Aug 2006 1303.82
S&P 500 Sep 2006 1335.85
S&P 500 Oct 2006 1377.94
S&P 500 Nov 2006 1400.63
S&P 500 Dec 2006 1418.3
S&P 500 Jan 2007 1438.24
S&P 500 Feb 2007 1406.82
S&P 500 Mar 2007 1420.86
S&P 500 Apr 2007 1482.37
S&P 500 May 2007 1530.62
S&P 500 Jun 2007 1503.35
S&P 500 Jul 2007 1455.27
S&P 500 Aug 2007 1473.99
S&P 500 Sep 2007 1526.75
S&P 500 Oct 2007 1549.38
S&P 500 Nov 2007 1481.14
S&P 500 Dec 2007 1468.36
S&P 500 Jan 2008 1378.55
S&P 500 Feb 2008 1330.63
S&P 500 Mar 2008 1322.7
S&P 500 Apr 2008 1385.59
S&P 500 May 2008 1400.38
S&P 500 Jun 2008 1280
S&P 500 Jul 2008 1267.38
S&P 500 Aug 2008 1282.83
S&P 500 Sep 2008 1166.36
S&P 500 Oct 2008 968.75
S&P 500 Nov 2008 896.24
S&P 500 Dec 2008 903.25
S&P 500 Jan 2009 825.88
S&P 500 Feb 2009 735.09
S&P 500 Mar 2009 797.87
S&P 500 Apr 2009 872.81
S&P 500 May 2009 919.14
S&P 500 Jun 2009 919.32
S&P 500 Jul 2009 987.48
S&P 500 Aug 2009 1020.62
S&P 500 Sep 2009 1057.08
S&P 500 Oct 2009 1036.19
S&P 500 Nov 2009 1095.63
S&P 500 Dec 2009 1115.1
S&P 500 Jan 2010 1073.87
S&P 500 Feb 2010 1104.49
S&P 500 Mar 2010 1140.45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment