Scatterplot Matrix
Scatterplot matrix design invented by J. A. Hartigan; see also R and GGobi. Data on Iris flowers collected by Edgar Anderson and published by Ronald Fisher.
Source Code
1 d3.json("flowers.json", function(flower) {
2
3 // Size parameters.
4 var size = 150,
5 padding = 20;
6
7 // Color scale.
8 var color = d3.scale.ordinal().range([
9 "rgb(50%, 0%, 0%)",
10 "rgb(0%, 50%, 0%)",
11 "rgb(0%, 0%, 50%)"
12 ]);
13
14 // Position scales.
15 var position = {};
16 flower.traits.forEach(function(trait) {
17 function value(d) { return d[trait]; }
18 position[trait] = d3.scale.linear()
19 .domain([d3.min(flower.values, value), d3.max(flower.values, value)])
20 .range([padding / 2, size - padding / 2]);
21 });
22
23 // Root panel.
24 var svg = d3.select("#chart")
25 .append("svg:svg")
26 .attr("width", size * flower.traits.length)
27 .attr("height", size * flower.traits.length);
28
29 // One column per trait.
30 var column = svg.selectAll("g")
31 .data(flower.traits)
32 .enter().append("svg:g")
33 .attr("transform", function(d, i) { return "translate(" + i * size + ",0)"; });
34
35 // One row per trait.
36 var row = column.selectAll("g")
37 .data(cross(flower.traits))
38 .enter().append("svg:g")
39 .attr("transform", function(d, i) { return "translate(0," + i * size + ")"; });
40
41 // X-ticks. TODO Cross the trait into the tick data?
42 row.selectAll("line.x")
43 .data(function(d) { return position[d.x].ticks(5).map(position[d.x]); })
44 .enter().append("svg:line")
45 .attr("class", "x")
46 .attr("x1", function(d) { return d; })
47 .attr("x2", function(d) { return d; })
48 .attr("y1", padding / 2)
49 .attr("y2", size - padding / 2);
50
51 // Y-ticks. TODO Cross the trait into the tick data?
52 row.selectAll("line.y")
53 .data(function(d) { return position[d.y].ticks(5).map(position[d.y]); })
54 .enter().append("svg:line")
55 .attr("class", "y")
56 .attr("x1", padding / 2)
57 .attr("x2", size - padding / 2)
58 .attr("y1", function(d) { return d; })
59 .attr("y2", function(d) { return d; });
60
61 // Frame.
62 row.append("svg:rect")
63 .attr("x", padding / 2)
64 .attr("y", padding / 2)
65 .attr("width", size - padding)
66 .attr("height", size - padding)
67 .style("fill", "none")
68 .style("stroke", "#aaa")
69 .style("stroke-width", 1.5)
70 .attr("pointer-events", "all")
71 .on("mousedown", mousedown);
72
73 // Dot plot.
74 var dot = row.selectAll("circle")
75 .data(cross(flower.values))
76 .enter().append("svg:circle")
77 .attr("cx", function(d) { return position[d.x.x](d.y[d.x.x]); })
78 .attr("cy", function(d) { return size - position[d.x.y](d.y[d.x.y]); })
79 .attr("r", 3)
80 .style("fill", function(d) { return color(d.y.species); })
81 .style("fill-opacity", .5)
82 .attr("pointer-events", "none");
83
84 d3.select(window)
85 .on("mousemove", mousemove)
86 .on("mouseup", mouseup);
87
88 var rect, x0, x1, count;
89
90 function mousedown() {
91 x0 = d3.svg.mouse(this);
92 count = 0;
93
94 rect = d3.select(this.parentNode)
95 .append("svg:rect")
96 .style("fill", "#999")
97 .style("fill-opacity", .5);
98
99 d3.event.preventDefault();
100 }
101
102 function mousemove() {
103 if (!rect) return;
104 x1 = d3.svg.mouse(rect.node());
105
106 x1[0] = Math.max(padding / 2, Math.min(size - padding / 2, x1[0]));
107 x1[1] = Math.max(padding / 2, Math.min(size - padding / 2, x1[1]));
108
109 var minx = Math.min(x0[0], x1[0]),
110 maxx = Math.max(x0[0], x1[0]),
111 miny = Math.min(x0[1], x1[1]),
112 maxy = Math.max(x0[1], x1[1]);
113
114 rect
115 .attr("x", minx - .5)
116 .attr("y", miny - .5)
117 .attr("width", maxx - minx + 1)
118 .attr("height", maxy - miny + 1);
119
120 var v = rect.node().__data__,
121 x = position[v.x],
122 y = position[v.y],
123 mins = x.invert(minx),
124 maxs = x.invert(maxx),
125 mint = y.invert(size - maxy),
126 maxt = y.invert(size - miny);
127
128 count = 0;
129 svg.selectAll("circle")
130 .style("fill", function(d) {
131 return mins <= d.y[v.x] && maxs >= d.y[v.x]
132 && mint <= d.y[v.y] && maxt >= d.y[v.y]
133 ? (count++, color(d.y.species))
134 : "#ccc";
135 });
136 }
137
138 function mouseup() {
139 if (!rect) return;
140 rect.remove();
141 rect = null;
142
143 if (!count) svg.selectAll("circle")
144 .style("fill", function(d) {
145 return color(d.y.species);
146 });
147 }
148
149 });
150 function cross(a) {
151 return function(d) {
152 var c = [];
153 for (var i = 0, n = a.length; i < n; i++) c.push({x: d, y: a[i]});
154 return c;
155 };
156 }
This example uses a helper function, cross
, to allow property functions to access both parent and child data. We’re looking for simpler ways to do this in future versions of D3:
1 function cross(a) {
2 return function(d) {
3 var c = [];
4 for (var i = 0, n = a.length; i < n; i++) c.push({x: d, y: a[i]});
5 return c;
6 };
7 }