This commit is contained in:
Jason Davies 2011-04-24 10:18:03 +01:00
Родитель 43e2ccca41
Коммит 82153e60b5
8 изменённых файлов: 529 добавлений и 1 удалений

Просмотреть файл

@ -96,6 +96,7 @@ d3.chart.js: \
src/chart/chart.js \
src/chart/box.js \
src/chart/bullet.js \
src/chart/qq.js \
src/end.js
d3.layout.js: \

Просмотреть файл

@ -535,4 +535,180 @@ function d3_chart_bulletWidth(x) {
return Math.abs(x(d) - x0);
};
}
// Based on http://vis.stanford.edu/protovis/ex/qqplot.html
d3.chart.qq = function() {
var width = 1,
height = 1,
duration = 0,
domain = null,
intervals = 100,
distribution = d3_chart_qqUniform;
values = d3_chart_qqValues;
qi = d3_chart_qqQi;
// For each small multiple…
function qq(g) {
g.each(function(d, i) {
var g = d3.select(this),
q1 = d3_chart_qqQuantile(intervals, distribution.call(this, d, i)),
q2 = d3_chart_qqQuantile(intervals, values.call(this, d, i));
// Compute the new x-scale.
var x1 = d3.scale.linear()
.domain(domain && domain.call(this, d, i) || [d3.min(q1), d3.max(q1)])
.range([0, width]);
var y1 = d3.scale.linear()
.domain(domain && domain.call(this, d, i) || [d3.min(q1), d3.max(q2)])
.range([height, 0]);
// Retrieve the old scales, if this is an update.
var x0, y0;
if (this.__chart__) {
x0 = this.__chart__.x;
y0 = this.__chart__.y;
} else {
x0 = d3.scale.linear()
.domain([0, Infinity])
.range(x1.range());
y0 = d3.scale.linear()
.domain([0, Infinity])
.range(y1.range());
}
// Stash the new scales.
this.__chart__ = {x: x1, y: y1};
// Update diagonal line.
var xd = x1.domain(), yd = y1.domain();
var diagonal = g.selectAll("line.diagonal")
.data([{x1: x1(yd[0]), y1: y1(xd[0]), x2: x1(yd[1]), y2: y1(xd[1])}]);
diagonal.enter().append("svg:line")
.attr("class", "diagonal")
.attr("x1", function(d) { return d.x1; })
.attr("y1", function(d) { return d.y1; })
.attr("x2", function(d) { return d.x2; })
.attr("y2", function(d) { return d.y2; })
.style("opacity", 1e-6)
.transition()
.duration(duration)
.style("opacity", 1);
diagonal.transition()
.duration(duration)
.attr("x1", function(d) { return d.x1; })
.attr("y1", function(d) { return d.y1; })
.attr("x2", function(d) { return d.x2; })
.attr("y2", function(d) { return d.y2; });
// Update quantile plots.
var datum = g.selectAll("circle")
.data(d3.range(.01, 1, .01));
datum.enter().append("svg:circle")
.attr("cx", function(d) { return x0(qi(d, q1)); })
.attr("cy", function(d) { return y0(qi(d, q2)); })
.attr("r", 5)
.transition()
.duration(duration)
.attr("cx", function(d) { return x1(qi(d, q1)); })
.attr("cy", function(d) { return y1(qi(d, q2)); });
datum.transition()
.duration(duration)
.attr("cx", function(d) { return x1(qi(d, q1)); })
.attr("cy", function(d) { return y1(qi(d, q2)); });
datum.exit().transition()
.duration(duration)
.style("opacity", 1e-6)
.attr("cx", function(d) { return x1(qi(d, q1)); })
.attr("cy", function(d) { return y1(qi(d, q2)); })
.remove();
// Update border box.
var box = g.selectAll("rect")
.data([[xd[1], yd[1]]]);
box.enter().append("svg:rect")
.attr("class", "box")
.attr("x", 0)
.attr("y", 0)
.attr("width", function(d) { return x0(d[0]); })
.attr("height", function(d) { return x0(d[1]); })
.transition()
.duration(duration)
.attr("width", function(d) { return x1(d[0]); })
.attr("height", function(d) { return x1(d[1]); });
box.transition()
.duration(duration)
.attr("width", function(d) { return x1(d[0]); })
.attr("height", function(d) { return x1(d[1]); });
});
}
qq.width = function(x) {
if (!arguments.length) return width;
width = x;
return qq;
};
qq.height = function(x) {
if (!arguments.length) return height;
height = x;
return qq;
};
qq.duration = function(x) {
if (!arguments.length) return duration;
duration = x;
return qq;
};
qq.domain = function(x) {
if (!arguments.length) return domain;
domain = x == null ? x : d3.functor(x);
return qq;
};
qq.intervals = function(x) {
if (!arguments.length) return intervals;
intervals = x;
return qq;
};
qq.distribution = function(x) {
if (!arguments.length) return distribution;
distribution = x;
return qq;
};
qq.values = function(x) {
if (!arguments.length) return values;
values = x;
return qq;
};
return qq;
};
// Compute quantiles of a distribution.
function d3_chart_qqQuantile(n, values) {
values = values.slice().sort(function(a, b) { return a - b; });
return d3.range(n).map(function(i) { return values[Math.floor(i * (values.length - 1) / n)]; });
}
// Lookup the value for an input quantile.
function d3_chart_qqQi(f, quantiles) {
return quantiles[Math.round(f * (quantiles.length - 1))];
}
function d3_chart_qqUniform() {
return d3.range(10000).map(Math.random);
}
function d3_chart_qqValues(d) {
return d.values;
}
})()

2
d3.chart.min.js поставляемый

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

3
examples/qq/qq.css Normal file
Просмотреть файл

@ -0,0 +1,3 @@
.qq circle, .qq .diagonal, .qq .box { stroke: #000; stroke-width: .5px; fill: none; }
.qq .box { stroke: #666; }
.qq { font: 12px sans-serif; }

20
examples/qq/qq.html Normal file
Просмотреть файл

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<title>Q-Q Plots</title>
<script type="text/javascript" src="../../d3.js"></script>
<script type="text/javascript" src="../../d3.chart.js"></script>
<script type="text/javascript" src="../../d3.csv.js"></script>
<link type="text/css" rel="stylesheet" href="../button.css"/>
<link type="text/css" rel="stylesheet" href="qq.css"/>
</head>
<body>
<div id="chart">
<button class="first last" onclick="transition()">
Update
</button><p>
</div>
<script type="text/javascript" src="turkers.js"></script>
<script type="text/javascript" src="qq.js"></script>
</body>
</html>

101
examples/qq/qq.js Normal file
Просмотреть файл

@ -0,0 +1,101 @@
/** Sample from a normal distribution with mean 0, stddev 1. */
function normal_sample() {
var x = 0, y = 0, rds, c;
do {
x = Math.random() * 2 - 1;
y = Math.random() * 2 - 1;
rds = x * x + y * y;
} while (rds == 0 || rds > 1);
c = Math.sqrt(-2 * Math.log(rds) / rds); // Box-Muller transform
return x * c; // throw away extra sample y * c
}
// Uniform random distribution
var uniform = function() { return Math.random(); };
uniform.label = "Uniform Distribution";
// Simple 1D Gaussian (normal) distribution
var avg = mean(turkers.percent.values);
var dev = deviation(turkers.percent.values);
var normal1 = function() { return avg + dev * normal_sample(); }
normal1.label = "Gaussian (Normal) Distribution";
// Gaussian Mixture Model (k=3) fit using E-M algorithm
var normal3 = function() {
var dd = [
[0.10306430789206111, 0.0036139086950272735, 0.30498647327844536],
[0.5924252668569606, 0.0462763685758622, 0.4340870312025223],
[0.9847627827855167, 2.352350767874714E-4, 0.2609264955190324]],
r = Math.random(),
i = r < dd[0][2] ? 0 : r < dd[0][2] + dd[1][2] ? 1 : 2,
d = dd[i];
return d[0] + Math.sqrt(d[1]) * normal_sample();
}
normal3.label = "Mixture of 3 Gaussians";
var w = 270,
h = 270,
m = [10, 50, 20, 30], // top right bottom left
min = Infinity,
max = -Infinity;
var chart = d3.chart.qq()
.width(w).height(h)
.domain([-.5, 1.5])
.distribution(function(d) { return d.dist; });
/* Distributions for comparison. */
var dists = [uniform, normal1, normal3];
var wrapper = d3.select("#chart").append("svg:svg")
.attr("class", "qq");
var vis = wrapper.selectAll("g")
.data(dists.map(function(d) { return {dist: d3.range(10000).map(d), values: turkers.percent.values, label: d.label}; }))
.enter().append("svg:g")
.attr("transform", function(d, i) { return "translate(" + ((w + m[3]) * i + m[3]) + "," + m[0] + ")"; })
.call(chart);
wrapper.append("svg:text")
.attr("transform", "rotate(-90)translate(" + -(h / 2 + m[0]) + ", 20)")
.attr("text-anchor", "middle")
.text("Turker Task Group Completion %");
vis.append("svg:text")
.attr("dy", "1em")
.attr("dx", ".3em")
.text(function(d, i) { return d.label; });
function sum(array) {
var s = 0, i = -1, n = array.length;
while (++i < n) s += array[i];
return s;
}
function mean(array) {
return sum(array) / array.length;
}
function variance(array) {
if (array.length < 1) return NaN;
if (array.length === 1) return 0;
var m = mean(array), sum = 0;
for (var i = 0; i < array.length; i++) {
var d = array[i] - m;
sum += d * d;
}
return sum;
}
function deviation(array) {
return Math.sqrt(variance(array) / (array.length - 1));
}
chart.duration(1000);
window.transition = function() {
vis.map(randomize).call(chart);
};
function randomize(d) {
return {dist: d.dist, values: d.values.map(Math.random)};
}

51
examples/qq/turkers.js Normal file
Просмотреть файл

@ -0,0 +1,51 @@
var turkers = {
percent: {
minValue: 0.009259259,
maxValue: 1,
values: [
0.009259259, 0.014285714, 0.014285714, 0.016666667,
0.016666667, 0.017857143, 0.018518519, 0.027777778,
0.028571429, 0.028571429, 0.028571429, 0.033333333,
0.033333333, 0.035714286, 0.0375, 0.041666667,
0.041666667, 0.041666667, 0.041666667, 0.042857143,
0.042857143, 0.042857143, 0.05, 0.055555556,
0.069444444, 0.083333333, 0.083333333, 0.083333333,
0.083333333, 0.083333333, 0.083333333, 0.085714286,
0.1, 0.1, 0.101851852, 0.104166667,
0.111111111, 0.111111111, 0.114285714, 0.114285714,
0.116666667, 0.12037037, 0.125, 0.125,
0.128571429, 0.133333333, 0.138888889, 0.141666667,
0.142857143, 0.142857143, 0.15, 0.152777778, 0.158333333,
0.166666667, 0.171428571, 0.183333333, 0.185714286,
0.185714286, 0.1875, 0.190140845, 0.194444444,
0.2, 0.204545455, 0.208333333, 0.214285714,
0.214285714, 0.253521127, 0.271428571, 0.277777778,
0.291666667, 0.3, 0.3, 0.307017544,
0.324074074, 0.328571429, 0.333333333, 0.333333333,
0.342857143, 0.357142857, 0.358333333, 0.378787879,
0.381355932, 0.395833333, 0.4, 0.414285714,
0.414285714, 0.414285714, 0.414285714, 0.43,
0.433333333, 0.4375, 0.445833333, 0.450704225,
0.453333333, 0.458333333, 0.466666667, 0.476666667,
0.494736842, 0.5, 0.516666667, 0.533333333,
0.55, 0.557142857, 0.56884058, 0.569444444,
0.571428571, 0.585714286, 0.61, 0.622222222,
0.657407407, 0.666666667, 0.678947368, 0.685714286,
0.685714286, 0.69047619, 0.7, 0.7,
0.7, 0.711538462, 0.763888889, 0.771428571,
0.788888889, 0.8, 0.8, 0.808333333,
0.824712644, 0.828571429, 0.836842105, 0.839285714,
0.839285714, 0.84, 0.842857143, 0.842857143,
0.842857143, 0.85, 0.859649123, 0.869791667,
0.871428571, 0.871428571, 0.892344498, 0.914285714,
0.928571429, 0.933908046, 0.953703704, 0.973684211,
0.975, 0.981481481, 0.983333333, 0.985714286,
0.985714286, 0.985714286, 0.985714286, 0.985714286,
0.985714286, 0.985714286, 0.985714286, 0.985714286,
0.985714286, 0.985714286, 0.985714286, 0.985714286,
0.985714286, 0.987096774, 0.990740741, 0.991666667,
0.992, 0.994047619, 0.996666667, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
]
}
};

176
src/chart/qq.js Normal file
Просмотреть файл

@ -0,0 +1,176 @@
// Based on http://vis.stanford.edu/protovis/ex/qqplot.html
d3.chart.qq = function() {
var width = 1,
height = 1,
duration = 0,
domain = null,
intervals = 100,
distribution = d3_chart_qqUniform;
values = d3_chart_qqValues;
qi = d3_chart_qqQi;
// For each small multiple…
function qq(g) {
g.each(function(d, i) {
var g = d3.select(this),
q1 = d3_chart_qqQuantile(intervals, distribution.call(this, d, i)),
q2 = d3_chart_qqQuantile(intervals, values.call(this, d, i));
// Compute the new x-scale.
var x1 = d3.scale.linear()
.domain(domain && domain.call(this, d, i) || [d3.min(q1), d3.max(q1)])
.range([0, width]);
var y1 = d3.scale.linear()
.domain(domain && domain.call(this, d, i) || [d3.min(q1), d3.max(q2)])
.range([height, 0]);
// Retrieve the old scales, if this is an update.
var x0, y0;
if (this.__chart__) {
x0 = this.__chart__.x;
y0 = this.__chart__.y;
} else {
x0 = d3.scale.linear()
.domain([0, Infinity])
.range(x1.range());
y0 = d3.scale.linear()
.domain([0, Infinity])
.range(y1.range());
}
// Stash the new scales.
this.__chart__ = {x: x1, y: y1};
// Update diagonal line.
var xd = x1.domain(), yd = y1.domain();
var diagonal = g.selectAll("line.diagonal")
.data([{x1: x1(yd[0]), y1: y1(xd[0]), x2: x1(yd[1]), y2: y1(xd[1])}]);
diagonal.enter().append("svg:line")
.attr("class", "diagonal")
.attr("x1", function(d) { return d.x1; })
.attr("y1", function(d) { return d.y1; })
.attr("x2", function(d) { return d.x2; })
.attr("y2", function(d) { return d.y2; })
.style("opacity", 1e-6)
.transition()
.duration(duration)
.style("opacity", 1);
diagonal.transition()
.duration(duration)
.attr("x1", function(d) { return d.x1; })
.attr("y1", function(d) { return d.y1; })
.attr("x2", function(d) { return d.x2; })
.attr("y2", function(d) { return d.y2; });
// Update quantile plots.
var datum = g.selectAll("circle")
.data(d3.range(.01, 1, .01));
datum.enter().append("svg:circle")
.attr("cx", function(d) { return x0(qi(d, q1)); })
.attr("cy", function(d) { return y0(qi(d, q2)); })
.attr("r", 5)
.transition()
.duration(duration)
.attr("cx", function(d) { return x1(qi(d, q1)); })
.attr("cy", function(d) { return y1(qi(d, q2)); });
datum.transition()
.duration(duration)
.attr("cx", function(d) { return x1(qi(d, q1)); })
.attr("cy", function(d) { return y1(qi(d, q2)); });
datum.exit().transition()
.duration(duration)
.style("opacity", 1e-6)
.attr("cx", function(d) { return x1(qi(d, q1)); })
.attr("cy", function(d) { return y1(qi(d, q2)); })
.remove();
// Update border box.
var box = g.selectAll("rect")
.data([[xd[1], yd[1]]]);
box.enter().append("svg:rect")
.attr("class", "box")
.attr("x", 0)
.attr("y", 0)
.attr("width", function(d) { return x0(d[0]); })
.attr("height", function(d) { return x0(d[1]); })
.transition()
.duration(duration)
.attr("width", function(d) { return x1(d[0]); })
.attr("height", function(d) { return x1(d[1]); });
box.transition()
.duration(duration)
.attr("width", function(d) { return x1(d[0]); })
.attr("height", function(d) { return x1(d[1]); });
});
}
qq.width = function(x) {
if (!arguments.length) return width;
width = x;
return qq;
};
qq.height = function(x) {
if (!arguments.length) return height;
height = x;
return qq;
};
qq.duration = function(x) {
if (!arguments.length) return duration;
duration = x;
return qq;
};
qq.domain = function(x) {
if (!arguments.length) return domain;
domain = x == null ? x : d3.functor(x);
return qq;
};
qq.intervals = function(x) {
if (!arguments.length) return intervals;
intervals = x;
return qq;
};
qq.distribution = function(x) {
if (!arguments.length) return distribution;
distribution = x;
return qq;
};
qq.values = function(x) {
if (!arguments.length) return values;
values = x;
return qq;
};
return qq;
};
// Compute quantiles of a distribution.
function d3_chart_qqQuantile(n, values) {
values = values.slice().sort(function(a, b) { return a - b; });
return d3.range(n).map(function(i) { return values[Math.floor(i * (values.length - 1) / n)]; });
}
// Lookup the value for an input quantile.
function d3_chart_qqQi(f, quantiles) {
return quantiles[Math.round(f * (quantiles.length - 1))];
}
function d3_chart_qqUniform() {
return d3.range(10000).map(Math.random);
}
function d3_chart_qqValues(d) {
return d.values;
}