toolkit/javascript/d3/d3.chart.js
changeset 47 c0b4a8b5a012
equal deleted inserted replaced
46:efd9c589177a 47:c0b4a8b5a012
       
     1 (function(){d3.chart = {};
       
     2 // Inspired by http://informationandvisualization.de/blog/box-plot
       
     3 d3.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 
       
   288 function d3_chart_boxWhiskers(d) {
       
   289   return [0, d.length - 1];
       
   290 }
       
   291 
       
   292 function 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/
       
   302 d3.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 
       
   512 function d3_chart_bulletRanges(d) {
       
   513   return d.ranges;
       
   514 }
       
   515 
       
   516 function d3_chart_bulletMarkers(d) {
       
   517   return d.markers;
       
   518 }
       
   519 
       
   520 function d3_chart_bulletMeasures(d) {
       
   521   return d.measures;
       
   522 }
       
   523 
       
   524 function d3_chart_bulletTranslate(x) {
       
   525   return function(d) {
       
   526     return "translate(" + x(d) + ",0)";
       
   527   };
       
   528 }
       
   529 
       
   530 function 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
       
   543 d3.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 
       
   729 var d3_chart_horizonArea = d3.svg.area(),
       
   730     d3_chart_horizonId = 0;
       
   731 
       
   732 function d3_chart_horizonX(d) {
       
   733   return d[0];
       
   734 }
       
   735 
       
   736 function d3_chart_horizonY(d) {
       
   737   return d[1];
       
   738 }
       
   739 // Based on http://vis.stanford.edu/protovis/ex/qqplot.html
       
   740 d3.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 
       
   969 function 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 
       
   977 function d3_chart_qqX(d) {
       
   978   return d.x;
       
   979 }
       
   980 
       
   981 function d3_chart_qqY(d) {
       
   982   return d.y;
       
   983 }
       
   984 })();