source: Dev/branches/jQueryUI/client/d3/src/chart/box.js @ 249

Last change on this file since 249 was 249, checked in by hendrikvanantwerpen, 13 years ago

This one's for Subversion, because it's so close...

First widget (stripped down sequencer).
Seperated client and server code in two direcotry trees.

File size: 8.5 KB
Line 
1// Inspired by http://informationandvisualization.de/blog/box-plot
2d3.chart.box = function() {
3  var width = 1,
4      height = 1,
5      duration = 0,
6      domain = null,
7      value = Number,
8      whiskers = d3_chart_boxWhiskers,
9      quartiles = d3_chart_boxQuartiles,
10      tickFormat = null;
11
12  // For each small multiple

13  function box(g) {
14    g.each(function(d, i) {
15      d = d.map(value).sort(d3.ascending);
16      var g = d3.select(this),
17          n = d.length,
18          min = d[0],
19          max = d[n - 1];
20
21      // Compute quartiles. Must return exactly 3 elements.
22      var quartileData = d.quartiles = quartiles(d);
23
24      // Compute whiskers. Must return exactly 2 elements, or null.
25      var whiskerIndices = whiskers && whiskers.call(this, d, i),
26          whiskerData = whiskerIndices && whiskerIndices.map(function(i) { return d[i]; });
27
28      // Compute outliers. If no whiskers are specified, all data are "outliers".
29      // We compute the outliers as indices, so that we can join across transitions!
30      var outlierIndices = whiskerIndices
31          ? d3.range(0, whiskerIndices[0]).concat(d3.range(whiskerIndices[1] + 1, n))
32          : d3.range(n);
33
34      // Compute the new x-scale.
35      var x1 = d3.scale.linear()
36          .domain(domain && domain.call(this, d, i) || [min, max])
37          .range([height, 0]);
38
39      // Retrieve the old x-scale, if this is an update.
40      var x0 = this.__chart__ || d3.scale.linear()
41          .domain([0, Infinity])
42          .range(x1.range());
43
44      // Stash the new scale.
45      this.__chart__ = x1;
46
47      // Note: the box, median, and box tick elements are fixed in number,
48      // so we only have to handle enter and update. In contrast, the outliers
49      // and other elements are variable, so we need to exit them! Variable
50      // elements also fade in and out.
51
52      // Update center line: the vertical line spanning the whiskers.
53      var center = g.selectAll("line.center")
54          .data(whiskerData ? [whiskerData] : []);
55
56      center.enter().insert("svg:line", "rect")
57          .attr("class", "center")
58          .attr("x1", width / 2)
59          .attr("y1", function(d) { return x0(d[0]); })
60          .attr("x2", width / 2)
61          .attr("y2", function(d) { return x0(d[1]); })
62          .style("opacity", 1e-6)
63        .transition()
64          .duration(duration)
65          .style("opacity", 1)
66          .attr("y1", function(d) { return x1(d[0]); })
67          .attr("y2", function(d) { return x1(d[1]); });
68
69      center.transition()
70          .duration(duration)
71          .style("opacity", 1)
72          .attr("y1", function(d) { return x1(d[0]); })
73          .attr("y2", function(d) { return x1(d[1]); });
74
75      center.exit().transition()
76          .duration(duration)
77          .style("opacity", 1e-6)
78          .attr("y1", function(d) { return x1(d[0]); })
79          .attr("y2", function(d) { return x1(d[1]); })
80          .remove();
81
82      // Update innerquartile box.
83      var box = g.selectAll("rect.box")
84          .data([quartileData]);
85
86      box.enter().append("svg:rect")
87          .attr("class", "box")
88          .attr("x", 0)
89          .attr("y", function(d) { return x0(d[2]); })
90          .attr("width", width)
91          .attr("height", function(d) { return x0(d[0]) - x0(d[2]); })
92        .transition()
93          .duration(duration)
94          .attr("y", function(d) { return x1(d[2]); })
95          .attr("height", function(d) { return x1(d[0]) - x1(d[2]); });
96
97      box.transition()
98          .duration(duration)
99          .attr("y", function(d) { return x1(d[2]); })
100          .attr("height", function(d) { return x1(d[0]) - x1(d[2]); });
101
102      // Update median line.
103      var medianLine = g.selectAll("line.median")
104          .data([quartileData[1]]);
105
106      medianLine.enter().append("svg:line")
107          .attr("class", "median")
108          .attr("x1", 0)
109          .attr("y1", x0)
110          .attr("x2", width)
111          .attr("y2", x0)
112        .transition()
113          .duration(duration)
114          .attr("y1", x1)
115          .attr("y2", x1);
116
117      medianLine.transition()
118          .duration(duration)
119          .attr("y1", x1)
120          .attr("y2", x1);
121
122      // Update whiskers.
123      var whisker = g.selectAll("line.whisker")
124          .data(whiskerData || []);
125
126      whisker.enter().insert("svg:line", "circle, text")
127          .attr("class", "whisker")
128          .attr("x1", 0)
129          .attr("y1", x0)
130          .attr("x2", width)
131          .attr("y2", x0)
132          .style("opacity", 1e-6)
133        .transition()
134          .duration(duration)
135          .attr("y1", x1)
136          .attr("y2", x1)
137          .style("opacity", 1);
138
139      whisker.transition()
140          .duration(duration)
141          .attr("y1", x1)
142          .attr("y2", x1)
143          .style("opacity", 1);
144
145      whisker.exit().transition()
146          .duration(duration)
147          .attr("y1", x1)
148          .attr("y2", x1)
149          .style("opacity", 1e-6)
150          .remove();
151
152      // Update outliers.
153      var outlier = g.selectAll("circle.outlier")
154          .data(outlierIndices, Number);
155
156      outlier.enter().insert("svg:circle", "text")
157          .attr("class", "outlier")
158          .attr("r", 5)
159          .attr("cx", width / 2)
160          .attr("cy", function(i) { return x0(d[i]); })
161          .style("opacity", 1e-6)
162        .transition()
163          .duration(duration)
164          .attr("cy", function(i) { return x1(d[i]); })
165          .style("opacity", 1);
166
167      outlier.transition()
168          .duration(duration)
169          .attr("cy", function(i) { return x1(d[i]); })
170          .style("opacity", 1);
171
172      outlier.exit().transition()
173          .duration(duration)
174          .attr("cy", function(i) { return x1(d[i]); })
175          .style("opacity", 1e-6)
176          .remove();
177
178      // Compute the tick format.
179      var format = tickFormat || x1.tickFormat(8);
180
181      // Update box ticks.
182      var boxTick = g.selectAll("text.box")
183          .data(quartileData);
184
185      boxTick.enter().append("svg:text")
186          .attr("class", "box")
187          .attr("dy", ".3em")
188          .attr("dx", function(d, i) { return i & 1 ? 6 : -6 })
189          .attr("x", function(d, i) { return i & 1 ? width : 0 })
190          .attr("y", x0)
191          .attr("text-anchor", function(d, i) { return i & 1 ? "start" : "end"; })
192          .text(format)
193        .transition()
194          .duration(duration)
195          .attr("y", x1);
196
197      boxTick.transition()
198          .duration(duration)
199          .text(format)
200          .attr("y", x1);
201
202      // Update whisker ticks. These are handled separately from the box
203      // ticks because they may or may not exist, and we want don't want
204      // to join box ticks pre-transition with whisker ticks post-.
205      var whiskerTick = g.selectAll("text.whisker")
206          .data(whiskerData || []);
207
208      whiskerTick.enter().append("svg:text")
209          .attr("class", "whisker")
210          .attr("dy", ".3em")
211          .attr("dx", 6)
212          .attr("x", width)
213          .attr("y", x0)
214          .text(format)
215          .style("opacity", 1e-6)
216        .transition()
217          .duration(duration)
218          .attr("y", x1)
219          .style("opacity", 1);
220
221      whiskerTick.transition()
222          .duration(duration)
223          .text(format)
224          .attr("y", x1)
225          .style("opacity", 1);
226
227      whiskerTick.exit().transition()
228          .duration(duration)
229          .attr("y", x1)
230          .style("opacity", 1e-6)
231          .remove();
232    });
233    d3.timer.flush();
234  }
235
236  box.width = function(x) {
237    if (!arguments.length) return width;
238    width = x;
239    return box;
240  };
241
242  box.height = function(x) {
243    if (!arguments.length) return height;
244    height = x;
245    return box;
246  };
247
248  box.tickFormat = function(x) {
249    if (!arguments.length) return tickFormat;
250    tickFormat = x;
251    return box;
252  };
253
254  box.duration = function(x) {
255    if (!arguments.length) return duration;
256    duration = x;
257    return box;
258  };
259
260  box.domain = function(x) {
261    if (!arguments.length) return domain;
262    domain = x == null ? x : d3.functor(x);
263    return box;
264  };
265
266  box.value = function(x) {
267    if (!arguments.length) return value;
268    value = x;
269    return box;
270  };
271
272  box.whiskers = function(x) {
273    if (!arguments.length) return whiskers;
274    whiskers = x;
275    return box;
276  };
277
278  box.quartiles = function(x) {
279    if (!arguments.length) return quartiles;
280    quartiles = x;
281    return box;
282  };
283
284  return box;
285};
286
287function d3_chart_boxWhiskers(d) {
288  return [0, d.length - 1];
289}
290
291function d3_chart_boxQuartiles(d) {
292  return [
293    d3.quantile(d, .25),
294    d3.quantile(d, .5),
295    d3.quantile(d, .75)
296  ];
297}
Note: See TracBrowser for help on using the repository browser.