source: Dev/trunk/d3/d3.chart.js @ 171

Last change on this file since 171 was 76, checked in by fpvanagthoven, 14 years ago

d3

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

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

314  function bullet(g) {
315    g.each(function(d, i) {
316      var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
317          markerz = markers.call(this, d, i).slice().sort(d3.descending),
318          measurez = measures.call(this, d, i).slice().sort(d3.descending),
319          g = d3.select(this);
320
321      // Compute the new x-scale.
322      var x1 = d3.scale.linear()
323          .domain([0, Math.max(rangez[0], markerz[0], measurez[0])])
324          .range(reverse ? [width, 0] : [0, width]);
325
326      // Retrieve the old x-scale, if this is an update.
327      var x0 = this.__chart__ || d3.scale.linear()
328          .domain([0, Infinity])
329          .range(x1.range());
330
331      // Stash the new scale.
332      this.__chart__ = x1;
333
334      // Derive width-scales from the x-scales.
335      var w0 = d3_chart_bulletWidth(x0),
336          w1 = d3_chart_bulletWidth(x1);
337
338      // Update the range rects.
339      var range = g.selectAll("rect.range")
340          .data(rangez);
341
342      range.enter().append("svg:rect")
343          .attr("class", function(d, i) { return "range s" + i; })
344          .attr("width", w0)
345          .attr("height", height)
346          .attr("x", reverse ? x0 : 0)
347        .transition()
348          .duration(duration)
349          .attr("width", w1)
350          .attr("x", reverse ? x1 : 0);
351
352      range.transition()
353          .duration(duration)
354          .attr("x", reverse ? x1 : 0)
355          .attr("width", w1)
356          .attr("height", height);
357
358      // Update the measure rects.
359      var measure = g.selectAll("rect.measure")
360          .data(measurez);
361
362      measure.enter().append("svg:rect")
363          .attr("class", function(d, i) { return "measure s" + i; })
364          .attr("width", w0)
365          .attr("height", height / 3)
366          .attr("x", reverse ? x0 : 0)
367          .attr("y", height / 3)
368        .transition()
369          .duration(duration)
370          .attr("width", w1)
371          .attr("x", reverse ? x1 : 0);
372
373      measure.transition()
374          .duration(duration)
375          .attr("width", w1)
376          .attr("height", height / 3)
377          .attr("x", reverse ? x1 : 0)
378          .attr("y", height / 3);
379
380      // Update the marker lines.
381      var marker = g.selectAll("line.marker")
382          .data(markerz);
383
384      marker.enter().append("svg:line")
385          .attr("class", "marker")
386          .attr("x1", x0)
387          .attr("x2", x0)
388          .attr("y1", height / 6)
389          .attr("y2", height * 5 / 6)
390        .transition()
391          .duration(duration)
392          .attr("x1", x1)
393          .attr("x2", x1);
394
395      marker.transition()
396          .duration(duration)
397          .attr("x1", x1)
398          .attr("x2", x1)
399          .attr("y1", height / 6)
400          .attr("y2", height * 5 / 6);
401
402      // Compute the tick format.
403      var format = tickFormat || x1.tickFormat(8);
404
405      // Update the tick groups.
406      var tick = g.selectAll("g.tick")
407          .data(x1.ticks(8), function(d) {
408            return this.textContent || format(d);
409          });
410
411      // Initialize the ticks with the old scale, x0.
412      var tickEnter = tick.enter().append("svg:g")
413          .attr("class", "tick")
414          .attr("transform", d3_chart_bulletTranslate(x0))
415          .style("opacity", 1e-6);
416
417      tickEnter.append("svg:line")
418          .attr("y1", height)
419          .attr("y2", height * 7 / 6);
420
421      tickEnter.append("svg:text")
422          .attr("text-anchor", "middle")
423          .attr("dy", "1em")
424          .attr("y", height * 7 / 6)
425          .text(format);
426
427      // Transition the entering ticks to the new scale, x1.
428      tickEnter.transition()
429          .duration(duration)
430          .attr("transform", d3_chart_bulletTranslate(x1))
431          .style("opacity", 1);
432
433      // Transition the updating ticks to the new scale, x1.
434      var tickUpdate = tick.transition()
435          .duration(duration)
436          .attr("transform", d3_chart_bulletTranslate(x1))
437          .style("opacity", 1);
438
439      tickUpdate.select("line")
440          .attr("y1", height)
441          .attr("y2", height * 7 / 6);
442
443      tickUpdate.select("text")
444          .attr("y", height * 7 / 6);
445
446      // Transition the exiting ticks to the new scale, x1.
447      tick.exit().transition()
448          .duration(duration)
449          .attr("transform", d3_chart_bulletTranslate(x1))
450          .style("opacity", 1e-6)
451          .remove();
452    });
453    d3.timer.flush();
454  }
455
456  // left, right, top, bottom
457  bullet.orient = function(x) {
458    if (!arguments.length) return orient;
459    orient = x;
460    reverse = orient == "right" || orient == "bottom";
461    return bullet;
462  };
463
464  // ranges (bad, satisfactory, good)
465  bullet.ranges = function(x) {
466    if (!arguments.length) return ranges;
467    ranges = x;
468    return bullet;
469  };
470
471  // markers (previous, goal)
472  bullet.markers = function(x) {
473    if (!arguments.length) return markers;
474    markers = x;
475    return bullet;
476  };
477
478  // measures (actual, forecast)
479  bullet.measures = function(x) {
480    if (!arguments.length) return measures;
481    measures = x;
482    return bullet;
483  };
484
485  bullet.width = function(x) {
486    if (!arguments.length) return width;
487    width = x;
488    return bullet;
489  };
490
491  bullet.height = function(x) {
492    if (!arguments.length) return height;
493    height = x;
494    return bullet;
495  };
496
497  bullet.tickFormat = function(x) {
498    if (!arguments.length) return tickFormat;
499    tickFormat = x;
500    return bullet;
501  };
502
503  bullet.duration = function(x) {
504    if (!arguments.length) return duration;
505    duration = x;
506    return bullet;
507  };
508
509  return bullet;
510};
511
512function d3_chart_bulletRanges(d) {
513  return d.ranges;
514}
515
516function d3_chart_bulletMarkers(d) {
517  return d.markers;
518}
519
520function d3_chart_bulletMeasures(d) {
521  return d.measures;
522}
523
524function d3_chart_bulletTranslate(x) {
525  return function(d) {
526    return "translate(" + x(d) + ",0)";
527  };
528}
529
530function d3_chart_bulletWidth(x) {
531  var x0 = x(0);
532  return function(d) {
533    return Math.abs(x(d) - x0);
534  };
535}
536// Implements a horizon layout, which is a variation of a single-series
537// area chart where the area is folded into multiple bands. Color is used to
538// encode band, allowing the size of the chart to be reduced significantly
539// without impeding readability. This layout algorithm is based on the work of
540// J. Heer, N. Kong and M. Agrawala in "Sizing the Horizon: The Effects of Chart
541// Size and Layering on the Graphical Perception of Time Series Visualizations",
542// CHI 2009. http://hci.stanford.edu/publications/2009/heer-horizon-chi09.pdf
543d3.chart.horizon = function() {
544  var bands = 1, // between 1 and 5, typically
545      mode = "offset", // or mirror
546      interpolate = "linear", // or basis, monotone, step-before, etc.
547      x = d3_chart_horizonX,
548      y = d3_chart_horizonY,
549      w = 960,
550      h = 40,
551      duration = 0;
552
553  var color = d3.scale.linear()
554      .domain([-1, 0, 1])
555      .range(["#d62728", "#fff", "#1f77b4"]);
556
557  // For each small multiple

558  function horizon(g) {
559    g.each(function(d, i) {
560      var g = d3.select(this),
561          n = 2 * bands + 1,
562          xMin = Infinity,
563          xMax = -Infinity,
564          yMax = -Infinity,
565          x0, // old x-scale
566          y0, // old y-scale
567          id; // unique id for paths
568
569      // Compute x- and y-values along with extents.
570      var data = d.map(function(d, i) {
571        var xv = x.call(this, d, i),
572            yv = y.call(this, d, i);
573        if (xv < xMin) xMin = xv;
574        if (xv > xMax) xMax = xv;
575        if (-yv > yMax) yMax = -yv;
576        if (yv > yMax) yMax = yv;
577        return [xv, yv];
578      });
579
580      // Compute the new x- and y-scales.
581      var x1 = d3.scale.linear().domain([xMin, xMax]).range([0, w]),
582          y1 = d3.scale.linear().domain([0, yMax]).range([0, h * bands]);
583
584      // Retrieve the old scales, if this is an update.
585      if (this.__chart__) {
586        x0 = this.__chart__.x;
587        y0 = this.__chart__.y;
588        id = this.__chart__.id;
589      } else {
590        x0 = d3.scale.linear().domain([0, Infinity]).range(x1.range());
591        y0 = d3.scale.linear().domain([0, Infinity]).range(y1.range());
592        id = ++d3_chart_horizonId;
593      }
594
595      // We'll use a defs to store the area path and the clip path.
596      var defs = g.selectAll("defs")
597          .data([data]);
598
599      var defsEnter = defs.enter().append("svg:defs");
600
601      // The clip path is a simple rect.
602      defsEnter.append("svg:clipPath")
603          .attr("id", "d3_chart_horizon_clip" + id)
604        .append("svg:rect")
605          .attr("width", w)
606          .attr("height", h);
607
608      defs.select("rect").transition()
609          .duration(duration)
610          .attr("width", w)
611          .attr("height", h);
612
613      // The area path is rendered with our resuable d3.svg.area.
614      defsEnter.append("svg:path")
615          .attr("id", "d3_chart_horizon_path" + id)
616          .attr("d", d3_chart_horizonArea
617          .interpolate(interpolate)
618          .x(function(d) { return x0(d[0]); })
619          .y0(h * bands)
620          .y1(function(d) { return h * bands - y0(d[1]); }))
621        .transition()
622          .duration(duration)
623          .attr("d", d3_chart_horizonArea
624          .x(function(d) { return x1(d[0]); })
625          .y1(function(d) { return h * bands - y1(d[1]); }));
626
627      defs.select("path").transition()
628          .duration(duration)
629          .attr("d", d3_chart_horizonArea);
630
631      // We'll use a container to clip all horizon layers at once.
632      g.selectAll("g")
633          .data([null])
634        .enter().append("svg:g")
635          .attr("clip-path", "url(#d3_chart_horizon_clip" + id + ")");
636
637      // Define the transform function based on the mode.
638      var transform = mode == "offset"
639          ? function(d) { return "translate(0," + (d + (d < 0) - bands) * h + ")"; }
640          : function(d) { return (d < 0 ? "scale(1,-1)" : "") + "translate(0," + (d - bands) * h + ")"; };
641
642      // Instantiate each copy of the path with different transforms.
643      var u = g.select("g").selectAll("use")
644          .data(d3.range(-1, -bands - 1, -1).concat(d3.range(1, bands + 1)), Number);
645
646      // TODO don't fudge the enter transition
647      u.enter().append("svg:use")
648          .attr("xlink:href", "#d3_chart_horizon_path" + id)
649          .attr("transform", function(d) { return transform(d + (d > 0 ? 1 : -1)); })
650          .style("fill", color)
651        .transition()
652          .duration(duration)
653          .attr("transform", transform);
654
655      u.transition()
656          .duration(duration)
657          .attr("transform", transform)
658          .style("fill", color);
659
660      u.exit().transition()
661          .duration(duration)
662          .attr("transform", transform)
663          .remove();
664
665      // Stash the new scales.
666      this.__chart__ = {x: x1, y: y1, id: id};
667    });
668    d3.timer.flush();
669  }
670
671  horizon.duration = function(x) {
672    if (!arguments.length) return duration;
673    duration = +x;
674    return horizon;
675  };
676
677  horizon.bands = function(x) {
678    if (!arguments.length) return bands;
679    bands = +x;
680    color.domain([-bands, 0, bands]);
681    return horizon;
682  };
683
684  horizon.mode = function(x) {
685    if (!arguments.length) return mode;
686    mode = x + "";
687    return horizon;
688  };
689
690  horizon.colors = function(x) {
691    if (!arguments.length) return color.range();
692    color.range(x);
693    return horizon;
694  };
695
696  horizon.interpolate = function(x) {
697    if (!arguments.length) return interpolate;
698    interpolate = x + "";
699    return horizon;
700  };
701
702  horizon.x = function(z) {
703    if (!arguments.length) return x;
704    x = z;
705    return horizon;
706  };
707
708  horizon.y = function(z) {
709    if (!arguments.length) return y;
710    y = z;
711    return horizon;
712  };
713
714  horizon.width = function(x) {
715    if (!arguments.length) return w;
716    w = +x;
717    return horizon;
718  };
719
720  horizon.height = function(x) {
721    if (!arguments.length) return h;
722    h = +x;
723    return horizon;
724  };
725
726  return horizon;
727};
728
729var d3_chart_horizonArea = d3.svg.area(),
730    d3_chart_horizonId = 0;
731
732function d3_chart_horizonX(d) {
733  return d[0];
734}
735
736function d3_chart_horizonY(d) {
737  return d[1];
738}
739// Based on http://vis.stanford.edu/protovis/ex/qqplot.html
740d3.chart.qq = function() {
741  var width = 1,
742      height = 1,
743      duration = 0,
744      domain = null,
745      tickFormat = null,
746      n = 100,
747      x = d3_chart_qqX,
748      y = d3_chart_qqY;
749
750  // For each small multiple

751  function qq(g) {
752    g.each(function(d, i) {
753      var g = d3.select(this),
754          qx = d3_chart_qqQuantiles(n, x.call(this, d, i)),
755          qy = d3_chart_qqQuantiles(n, y.call(this, d, i)),
756          xd = domain && domain.call(this, d, i) || [d3.min(qx), d3.max(qx)], // new x-domain
757          yd = domain && domain.call(this, d, i) || [d3.min(qy), d3.max(qy)], // new y-domain
758          x0, // old x-scale
759          y0; // old y-scale
760
761      // Compute the new x-scale.
762      var x1 = d3.scale.linear()
763          .domain(xd)
764          .range([0, width]);
765
766      // Compute the new y-scale.
767      var y1 = d3.scale.linear()
768          .domain(yd)
769          .range([height, 0]);
770
771      // Retrieve the old scales, if this is an update.
772      if (this.__chart__) {
773        x0 = this.__chart__.x;
774        y0 = this.__chart__.y;
775      } else {
776        x0 = d3.scale.linear().domain([0, Infinity]).range(x1.range());
777        y0 = d3.scale.linear().domain([0, Infinity]).range(y1.range());
778      }
779
780      // Stash the new scales.
781      this.__chart__ = {x: x1, y: y1};
782
783      // Update diagonal line.
784      var diagonal = g.selectAll("line.diagonal")
785          .data([null]);
786
787      diagonal.enter().append("svg:line")
788          .attr("class", "diagonal")
789          .attr("x1", x1(yd[0]))
790          .attr("y1", y1(xd[0]))
791          .attr("x2", x1(yd[1]))
792          .attr("y2", y1(xd[1]));
793
794      diagonal.transition()
795          .duration(duration)
796          .attr("x1", x1(yd[0]))
797          .attr("y1", y1(xd[0]))
798          .attr("x2", x1(yd[1]))
799          .attr("y2", y1(xd[1]));
800
801      // Update quantile plots.
802      var circle = g.selectAll("circle")
803          .data(d3.range(n).map(function(i) {
804            return {x: qx[i], y: qy[i]};
805          }));
806
807      circle.enter().append("svg:circle")
808          .attr("class", "quantile")
809          .attr("r", 4.5)
810          .attr("cx", function(d) { return x0(d.x); })
811          .attr("cy", function(d) { return y0(d.y); })
812          .style("opacity", 1e-6)
813        .transition()
814          .duration(duration)
815          .attr("cx", function(d) { return x1(d.x); })
816          .attr("cy", function(d) { return y1(d.y); })
817          .style("opacity", 1);
818
819      circle.transition()
820          .duration(duration)
821          .attr("cx", function(d) { return x1(d.x); })
822          .attr("cy", function(d) { return y1(d.y); })
823          .style("opacity", 1);
824
825      circle.exit().transition()
826          .duration(duration)
827          .attr("cx", function(d) { return x1(d.x); })
828          .attr("cy", function(d) { return y1(d.y); })
829          .style("opacity", 1e-6)
830          .remove();
831
832      var xformat = tickFormat || x1.tickFormat(4),
833          yformat = tickFormat || y1.tickFormat(4),
834          tx = function(d) { return "translate(" + x1(d) + "," + height + ")"; },
835          ty = function(d) { return "translate(0," + y1(d) + ")"; };
836
837      // Update x-ticks.
838      var xtick = g.selectAll("g.x.tick")
839          .data(x1.ticks(4), function(d) {
840            return this.textContent || xformat(d);
841          });
842
843      var xtickEnter = xtick.enter().append("svg:g")
844          .attr("class", "x tick")
845          .attr("transform", function(d) { return "translate(" + x0(d) + "," + height + ")"; })
846          .style("opacity", 1e-6);
847
848      xtickEnter.append("svg:line")
849          .attr("y1", 0)
850          .attr("y2", -6);
851
852      xtickEnter.append("svg:text")
853          .attr("text-anchor", "middle")
854          .attr("dy", "1em")
855          .text(xformat);
856
857      // Transition the entering ticks to the new scale, x1.
858      xtickEnter.transition()
859          .duration(duration)
860          .attr("transform", tx)
861          .style("opacity", 1);
862
863      // Transition the updating ticks to the new scale, x1.
864      xtick.transition()
865          .duration(duration)
866          .attr("transform", tx)
867          .style("opacity", 1);
868
869      // Transition the exiting ticks to the new scale, x1.
870      xtick.exit().transition()
871          .duration(duration)
872          .attr("transform", tx)
873          .style("opacity", 1e-6)
874          .remove();
875
876      // Update ticks.
877      var ytick = g.selectAll("g.y.tick")
878          .data(y1.ticks(4), function(d) {
879            return this.textContent || yformat(d);
880          });
881
882      var ytickEnter = ytick.enter().append("svg:g")
883          .attr("class", "y tick")
884          .attr("transform", function(d) { return "translate(0," + y0(d) + ")"; })
885          .style("opacity", 1e-6);
886
887      ytickEnter.append("svg:line")
888          .attr("x1", 0)
889          .attr("x2", 6);
890
891      ytickEnter.append("svg:text")
892          .attr("text-anchor", "end")
893          .attr("dx", "-.5em")
894          .attr("dy", ".3em")
895          .text(yformat);
896
897      // Transition the entering ticks to the new scale, y1.
898      ytickEnter.transition()
899          .duration(duration)
900          .attr("transform", ty)
901          .style("opacity", 1);
902
903      // Transition the updating ticks to the new scale, y1.
904      ytick.transition()
905          .duration(duration)
906          .attr("transform", ty)
907          .style("opacity", 1);
908
909      // Transition the exiting ticks to the new scale, y1.
910      ytick.exit().transition()
911          .duration(duration)
912          .attr("transform", ty)
913          .style("opacity", 1e-6)
914          .remove();
915    });
916  }
917
918  qq.width = function(x) {
919    if (!arguments.length) return width;
920    width = x;
921    return qq;
922  };
923
924  qq.height = function(x) {
925    if (!arguments.length) return height;
926    height = x;
927    return qq;
928  };
929
930  qq.duration = function(x) {
931    if (!arguments.length) return duration;
932    duration = x;
933    return qq;
934  };
935
936  qq.domain = function(x) {
937    if (!arguments.length) return domain;
938    domain = x == null ? x : d3.functor(x);
939    return qq;
940  };
941
942  qq.count = function(z) {
943    if (!arguments.length) return n;
944    n = z;
945    return qq;
946  };
947
948  qq.x = function(z) {
949    if (!arguments.length) return x;
950    x = z;
951    return qq;
952  };
953
954  qq.y = function(z) {
955    if (!arguments.length) return y;
956    y = z;
957    return qq;
958  };
959
960  qq.tickFormat = function(x) {
961    if (!arguments.length) return tickFormat;
962    tickFormat = x;
963    return qq;
964  };
965
966  return qq;
967};
968
969function d3_chart_qqQuantiles(n, values) {
970  var m = values.length - 1;
971  values = values.slice().sort(d3.ascending);
972  return d3.range(n).map(function(i) {
973    return values[~~(i * m / n)];
974  });
975}
976
977function d3_chart_qqX(d) {
978  return d.x;
979}
980
981function d3_chart_qqY(d) {
982  return d.y;
983}
984})();
Note: See TracBrowser for help on using the repository browser.