[76] | 1 | // Based on http://vis.stanford.edu/protovis/ex/qqplot.html |
---|
| 2 | d3.chart.qq = function() { |
---|
| 3 | var width = 1, |
---|
| 4 | height = 1, |
---|
| 5 | duration = 0, |
---|
| 6 | domain = null, |
---|
| 7 | tickFormat = null, |
---|
| 8 | n = 100, |
---|
| 9 | x = d3_chart_qqX, |
---|
| 10 | y = d3_chart_qqY; |
---|
| 11 | |
---|
| 12 | // For each small multiple⊠|
---|
| 13 | function qq(g) { |
---|
| 14 | g.each(function(d, i) { |
---|
| 15 | var g = d3.select(this), |
---|
| 16 | qx = d3_chart_qqQuantiles(n, x.call(this, d, i)), |
---|
| 17 | qy = d3_chart_qqQuantiles(n, y.call(this, d, i)), |
---|
| 18 | xd = domain && domain.call(this, d, i) || [d3.min(qx), d3.max(qx)], // new x-domain |
---|
| 19 | yd = domain && domain.call(this, d, i) || [d3.min(qy), d3.max(qy)], // new y-domain |
---|
| 20 | x0, // old x-scale |
---|
| 21 | y0; // old y-scale |
---|
| 22 | |
---|
| 23 | // Compute the new x-scale. |
---|
| 24 | var x1 = d3.scale.linear() |
---|
| 25 | .domain(xd) |
---|
| 26 | .range([0, width]); |
---|
| 27 | |
---|
| 28 | // Compute the new y-scale. |
---|
| 29 | var y1 = d3.scale.linear() |
---|
| 30 | .domain(yd) |
---|
| 31 | .range([height, 0]); |
---|
| 32 | |
---|
| 33 | // Retrieve the old scales, if this is an update. |
---|
| 34 | if (this.__chart__) { |
---|
| 35 | x0 = this.__chart__.x; |
---|
| 36 | y0 = this.__chart__.y; |
---|
| 37 | } else { |
---|
| 38 | x0 = d3.scale.linear().domain([0, Infinity]).range(x1.range()); |
---|
| 39 | y0 = d3.scale.linear().domain([0, Infinity]).range(y1.range()); |
---|
| 40 | } |
---|
| 41 | |
---|
| 42 | // Stash the new scales. |
---|
| 43 | this.__chart__ = {x: x1, y: y1}; |
---|
| 44 | |
---|
| 45 | // Update diagonal line. |
---|
| 46 | var diagonal = g.selectAll("line.diagonal") |
---|
| 47 | .data([null]); |
---|
| 48 | |
---|
| 49 | diagonal.enter().append("svg:line") |
---|
| 50 | .attr("class", "diagonal") |
---|
| 51 | .attr("x1", x1(yd[0])) |
---|
| 52 | .attr("y1", y1(xd[0])) |
---|
| 53 | .attr("x2", x1(yd[1])) |
---|
| 54 | .attr("y2", y1(xd[1])); |
---|
| 55 | |
---|
| 56 | diagonal.transition() |
---|
| 57 | .duration(duration) |
---|
| 58 | .attr("x1", x1(yd[0])) |
---|
| 59 | .attr("y1", y1(xd[0])) |
---|
| 60 | .attr("x2", x1(yd[1])) |
---|
| 61 | .attr("y2", y1(xd[1])); |
---|
| 62 | |
---|
| 63 | // Update quantile plots. |
---|
| 64 | var circle = g.selectAll("circle") |
---|
| 65 | .data(d3.range(n).map(function(i) { |
---|
| 66 | return {x: qx[i], y: qy[i]}; |
---|
| 67 | })); |
---|
| 68 | |
---|
| 69 | circle.enter().append("svg:circle") |
---|
| 70 | .attr("class", "quantile") |
---|
| 71 | .attr("r", 4.5) |
---|
| 72 | .attr("cx", function(d) { return x0(d.x); }) |
---|
| 73 | .attr("cy", function(d) { return y0(d.y); }) |
---|
| 74 | .style("opacity", 1e-6) |
---|
| 75 | .transition() |
---|
| 76 | .duration(duration) |
---|
| 77 | .attr("cx", function(d) { return x1(d.x); }) |
---|
| 78 | .attr("cy", function(d) { return y1(d.y); }) |
---|
| 79 | .style("opacity", 1); |
---|
| 80 | |
---|
| 81 | circle.transition() |
---|
| 82 | .duration(duration) |
---|
| 83 | .attr("cx", function(d) { return x1(d.x); }) |
---|
| 84 | .attr("cy", function(d) { return y1(d.y); }) |
---|
| 85 | .style("opacity", 1); |
---|
| 86 | |
---|
| 87 | circle.exit().transition() |
---|
| 88 | .duration(duration) |
---|
| 89 | .attr("cx", function(d) { return x1(d.x); }) |
---|
| 90 | .attr("cy", function(d) { return y1(d.y); }) |
---|
| 91 | .style("opacity", 1e-6) |
---|
| 92 | .remove(); |
---|
| 93 | |
---|
| 94 | var xformat = tickFormat || x1.tickFormat(4), |
---|
| 95 | yformat = tickFormat || y1.tickFormat(4), |
---|
| 96 | tx = function(d) { return "translate(" + x1(d) + "," + height + ")"; }, |
---|
| 97 | ty = function(d) { return "translate(0," + y1(d) + ")"; }; |
---|
| 98 | |
---|
| 99 | // Update x-ticks. |
---|
| 100 | var xtick = g.selectAll("g.x.tick") |
---|
| 101 | .data(x1.ticks(4), function(d) { |
---|
| 102 | return this.textContent || xformat(d); |
---|
| 103 | }); |
---|
| 104 | |
---|
| 105 | var xtickEnter = xtick.enter().append("svg:g") |
---|
| 106 | .attr("class", "x tick") |
---|
| 107 | .attr("transform", function(d) { return "translate(" + x0(d) + "," + height + ")"; }) |
---|
| 108 | .style("opacity", 1e-6); |
---|
| 109 | |
---|
| 110 | xtickEnter.append("svg:line") |
---|
| 111 | .attr("y1", 0) |
---|
| 112 | .attr("y2", -6); |
---|
| 113 | |
---|
| 114 | xtickEnter.append("svg:text") |
---|
| 115 | .attr("text-anchor", "middle") |
---|
| 116 | .attr("dy", "1em") |
---|
| 117 | .text(xformat); |
---|
| 118 | |
---|
| 119 | // Transition the entering ticks to the new scale, x1. |
---|
| 120 | xtickEnter.transition() |
---|
| 121 | .duration(duration) |
---|
| 122 | .attr("transform", tx) |
---|
| 123 | .style("opacity", 1); |
---|
| 124 | |
---|
| 125 | // Transition the updating ticks to the new scale, x1. |
---|
| 126 | xtick.transition() |
---|
| 127 | .duration(duration) |
---|
| 128 | .attr("transform", tx) |
---|
| 129 | .style("opacity", 1); |
---|
| 130 | |
---|
| 131 | // Transition the exiting ticks to the new scale, x1. |
---|
| 132 | xtick.exit().transition() |
---|
| 133 | .duration(duration) |
---|
| 134 | .attr("transform", tx) |
---|
| 135 | .style("opacity", 1e-6) |
---|
| 136 | .remove(); |
---|
| 137 | |
---|
| 138 | // Update ticks. |
---|
| 139 | var ytick = g.selectAll("g.y.tick") |
---|
| 140 | .data(y1.ticks(4), function(d) { |
---|
| 141 | return this.textContent || yformat(d); |
---|
| 142 | }); |
---|
| 143 | |
---|
| 144 | var ytickEnter = ytick.enter().append("svg:g") |
---|
| 145 | .attr("class", "y tick") |
---|
| 146 | .attr("transform", function(d) { return "translate(0," + y0(d) + ")"; }) |
---|
| 147 | .style("opacity", 1e-6); |
---|
| 148 | |
---|
| 149 | ytickEnter.append("svg:line") |
---|
| 150 | .attr("x1", 0) |
---|
| 151 | .attr("x2", 6); |
---|
| 152 | |
---|
| 153 | ytickEnter.append("svg:text") |
---|
| 154 | .attr("text-anchor", "end") |
---|
| 155 | .attr("dx", "-.5em") |
---|
| 156 | .attr("dy", ".3em") |
---|
| 157 | .text(yformat); |
---|
| 158 | |
---|
| 159 | // Transition the entering ticks to the new scale, y1. |
---|
| 160 | ytickEnter.transition() |
---|
| 161 | .duration(duration) |
---|
| 162 | .attr("transform", ty) |
---|
| 163 | .style("opacity", 1); |
---|
| 164 | |
---|
| 165 | // Transition the updating ticks to the new scale, y1. |
---|
| 166 | ytick.transition() |
---|
| 167 | .duration(duration) |
---|
| 168 | .attr("transform", ty) |
---|
| 169 | .style("opacity", 1); |
---|
| 170 | |
---|
| 171 | // Transition the exiting ticks to the new scale, y1. |
---|
| 172 | ytick.exit().transition() |
---|
| 173 | .duration(duration) |
---|
| 174 | .attr("transform", ty) |
---|
| 175 | .style("opacity", 1e-6) |
---|
| 176 | .remove(); |
---|
| 177 | }); |
---|
| 178 | } |
---|
| 179 | |
---|
| 180 | qq.width = function(x) { |
---|
| 181 | if (!arguments.length) return width; |
---|
| 182 | width = x; |
---|
| 183 | return qq; |
---|
| 184 | }; |
---|
| 185 | |
---|
| 186 | qq.height = function(x) { |
---|
| 187 | if (!arguments.length) return height; |
---|
| 188 | height = x; |
---|
| 189 | return qq; |
---|
| 190 | }; |
---|
| 191 | |
---|
| 192 | qq.duration = function(x) { |
---|
| 193 | if (!arguments.length) return duration; |
---|
| 194 | duration = x; |
---|
| 195 | return qq; |
---|
| 196 | }; |
---|
| 197 | |
---|
| 198 | qq.domain = function(x) { |
---|
| 199 | if (!arguments.length) return domain; |
---|
| 200 | domain = x == null ? x : d3.functor(x); |
---|
| 201 | return qq; |
---|
| 202 | }; |
---|
| 203 | |
---|
| 204 | qq.count = function(z) { |
---|
| 205 | if (!arguments.length) return n; |
---|
| 206 | n = z; |
---|
| 207 | return qq; |
---|
| 208 | }; |
---|
| 209 | |
---|
| 210 | qq.x = function(z) { |
---|
| 211 | if (!arguments.length) return x; |
---|
| 212 | x = z; |
---|
| 213 | return qq; |
---|
| 214 | }; |
---|
| 215 | |
---|
| 216 | qq.y = function(z) { |
---|
| 217 | if (!arguments.length) return y; |
---|
| 218 | y = z; |
---|
| 219 | return qq; |
---|
| 220 | }; |
---|
| 221 | |
---|
| 222 | qq.tickFormat = function(x) { |
---|
| 223 | if (!arguments.length) return tickFormat; |
---|
| 224 | tickFormat = x; |
---|
| 225 | return qq; |
---|
| 226 | }; |
---|
| 227 | |
---|
| 228 | return qq; |
---|
| 229 | }; |
---|
| 230 | |
---|
| 231 | function d3_chart_qqQuantiles(n, values) { |
---|
| 232 | var m = values.length - 1; |
---|
| 233 | values = values.slice().sort(d3.ascending); |
---|
| 234 | return d3.range(n).map(function(i) { |
---|
| 235 | return values[~~(i * m / n)]; |
---|
| 236 | }); |
---|
| 237 | } |
---|
| 238 | |
---|
| 239 | function d3_chart_qqX(d) { |
---|
| 240 | return d.x; |
---|
| 241 | } |
---|
| 242 | |
---|
| 243 | function d3_chart_qqY(d) { |
---|
| 244 | return d.y; |
---|
| 245 | } |
---|