Skip to content

Commit

Permalink
Merge branch 'jasondavies-qq'
Browse files Browse the repository at this point in the history
* jasondavies-qq:
  Improvements to qq chart.
  Add x- and y-scales to Q-Q plot.
  Add Q-Q plot.
  • Loading branch information
mbostock committed May 1, 2011
2 parents 1eaff45 + 71622b7 commit f76e515
Show file tree
Hide file tree
Showing 12 changed files with 652 additions and 4 deletions.
1 change: 1 addition & 0 deletions Makefile
Expand Up @@ -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: \
Expand Down
245 changes: 245 additions & 0 deletions d3.chart.js
Expand Up @@ -535,4 +535,249 @@ 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,
tickFormat = null,
n = 100,
x = d3_chart_qqX,
y = d3_chart_qqY;

// For each small multiple…
function qq(g) {
g.each(function(d, i) {
var g = d3.select(this),
qx = d3_chart_qqQuantiles(n, x.call(this, d, i)),
qy = d3_chart_qqQuantiles(n, y.call(this, d, i)),
xd = domain && domain.call(this, d, i) || [d3.min(qx), d3.max(qx)], // new x-domain
yd = domain && domain.call(this, d, i) || [d3.min(qy), d3.max(qy)], // new y-domain
x0, // old x-scale
y0; // old y-scale

// Compute the new x-scale.
var x1 = d3.scale.linear()
.domain(xd)
.range([0, width]);

// Compute the new y-scale.
var y1 = d3.scale.linear()
.domain(yd)
.range([height, 0]);

// Retrieve the old scales, if this is an update.
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 diagonal = g.selectAll("line.diagonal")
.data([null]);

diagonal.enter().append("svg:line")
.attr("class", "diagonal")
.attr("x1", x1(yd[0]))
.attr("y1", y1(xd[0]))
.attr("x2", x1(yd[1]))
.attr("y2", y1(xd[1]));

diagonal.transition()
.duration(duration)
.attr("x1", x1(yd[0]))
.attr("y1", y1(xd[0]))
.attr("x2", x1(yd[1]))
.attr("y2", y1(xd[1]));

// Update quantile plots.
var circle = g.selectAll("circle")
.data(d3.range(n).map(function(i) {
return {x: qx[i], y: qy[i]};
}));

circle.enter().append("svg:circle")
.attr("class", "quantile")
.attr("r", 4.5)
.attr("cx", function(d) { return x0(d.x); })
.attr("cy", function(d) { return y0(d.y); })
.style("opacity", 1e-6)
.transition()
.duration(duration)
.attr("cx", function(d) { return x1(d.x); })
.attr("cy", function(d) { return y1(d.y); })
.style("opacity", 1);

circle.transition()
.duration(duration)
.attr("cx", function(d) { return x1(d.x); })
.attr("cy", function(d) { return y1(d.y); })
.style("opacity", 1);

circle.exit().transition()
.duration(duration)
.attr("cx", function(d) { return x1(d.x); })
.attr("cy", function(d) { return y1(d.y); })
.style("opacity", 1e-6)
.remove();

var xformat = tickFormat || x1.tickFormat(4),
yformat = tickFormat || y1.tickFormat(4),
tx = function(d) { return "translate(" + x1(d) + "," + height + ")"; },
ty = function(d) { return "translate(0," + y1(d) + ")"; };

// Update x-ticks.
var xtick = g.selectAll("g.x.tick")
.data(x1.ticks(4), function(d) {
return this.textContent || xformat(d);
});

var xtickEnter = xtick.enter().append("svg:g")
.attr("class", "x tick")
.attr("transform", function(d) { return "translate(" + x0(d) + "," + height + ")"; })
.attr("opacity", 1e-6);

xtickEnter.append("svg:line")
.attr("y1", 0)
.attr("y2", -6);

xtickEnter.append("svg:text")
.attr("text-anchor", "middle")
.attr("dy", "1em")
.text(xformat);

// Transition the entering ticks to the new scale, x1.
xtickEnter.transition()
.duration(duration)
.attr("transform", tx)
.attr("opacity", 1);

// Transition the updating ticks to the new scale, x1.
xtick.transition()
.duration(duration)
.attr("transform", tx)
.attr("opacity", 1);

// Transition the exiting ticks to the new scale, x1.
xtick.exit().transition()
.duration(duration)
.attr("transform", tx)
.attr("opacity", 1e-6)
.remove();

// Update ticks.
var ytick = g.selectAll("g.y.tick")
.data(y1.ticks(4), function(d) {
return this.textContent || yformat(d);
});

var ytickEnter = ytick.enter().append("svg:g")
.attr("class", "y tick")
.attr("transform", function(d) { return "translate(0," + y0(d) + ")"; })
.attr("opacity", 1e-6);

ytickEnter.append("svg:line")
.attr("x1", 0)
.attr("x2", 6);

ytickEnter.append("svg:text")
.attr("text-anchor", "end")
.attr("dx", "-.5em")
.attr("dy", ".3em")
.text(yformat);

// Transition the entering ticks to the new scale, y1.
ytickEnter.transition()
.duration(duration)
.attr("transform", ty)
.attr("opacity", 1);

// Transition the updating ticks to the new scale, y1.
ytick.transition()
.duration(duration)
.attr("transform", ty)
.attr("opacity", 1);

// Transition the exiting ticks to the new scale, y1.
ytick.exit().transition()
.duration(duration)
.attr("transform", ty)
.attr("opacity", 1e-6)
.remove();
});
}

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.count = function(z) {
if (!arguments.length) return n;
n = z;
return qq;
};

qq.x = function(z) {
if (!arguments.length) return x;
x = z;
return qq;
};

qq.y = function(z) {
if (!arguments.length) return y;
y = z;
return qq;
};

qq.tickFormat = function(x) {
if (!arguments.length) return tickFormat;
tickFormat = x;
return qq;
};

return qq;
};

function d3_chart_qqQuantiles(n, values) {
var m = values.length - 1;
values = values.slice().sort(d3.ascending);
return d3.range(n).map(function(i) {
return values[~~(i * m / n)];
});
}

function d3_chart_qqX(d) {
return d.x;
}

function d3_chart_qqY(d) {
return d.y;
}
})()
2 changes: 1 addition & 1 deletion d3.chart.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion d3.js
@@ -1,4 +1,4 @@
(function(){d3 = {version: "1.13.4"}; // semver
(function(){d3 = {version: "1.14.0"}; // semver
if (!Date.now) Date.now = function() {
return +new Date();
};
Expand Down
2 changes: 1 addition & 1 deletion d3.min.js

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions examples/qq/qq.css
@@ -0,0 +1,18 @@
#chart {
font: 10px sans-serif;
width: 960px;
height: 310px;
}

.qq .box, .qq .tick line, .qq .quantile, .qq .diagonal {
stroke: #aaa;
fill: none;
}

.qq .quantile {
stroke: #000;
}

.qq g + g .y.tick {
display: none;
}
19 changes: 19 additions & 0 deletions examples/qq/qq.html
@@ -0,0 +1,19 @@
<!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>
<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>
</div>
<script type="text/javascript" src="stats.js"></script>
<script type="text/javascript" src="qq.js"></script>
</body>
</html>
66 changes: 66 additions & 0 deletions examples/qq/qq.js
@@ -0,0 +1,66 @@
var w = 280,
h = 280,
m = [10, 0, 20, 35], // top right bottom left
n = 10000; // number of samples to generate

var chart = d3.chart.qq()
.width(w)
.height(h)
.domain([-.1, 1.1])
.tickFormat(function(d) { return ~~(d * 100); });

var vis = d3.select("#chart")
.append("svg:svg")
.append("svg:g")
.attr("transform", "translate(" + m[3] + "," + m[0] + ")");

d3.json("turkers.json", function(turkers) {
var tm = mean(turkers),
td = deviation(turkers),
dd = [
[0.10306430789206111, 0.0036139086950272735, 0.30498647327844536],
[0.5924252668569606, 0.0462763685758622, 0.4340870312025223],
[0.9847627827855167, 2.352350767874714e-4, 0.2609264955190324]
];

var g = vis.selectAll("g")
.data([{
x: d3.range(n).map(Math.random),
y: turkers,
label: "Uniform Distribution"
}, {
x: d3.range(n).map(normal1(tm, td)),
y: turkers,
label: "Gaussian (Normal) Distribution"
}, {
x: d3.range(n).map(normal3(dd)),
y: turkers,
label: "Mixture of 3 Gaussians"
}])
.enter().append("svg:g")
.attr("class", "qq")
.attr("transform", function(d, i) { return "translate(" + (w + m[1] + m[3]) * i + ")"; });

g.append("svg:rect")
.attr("class", "box")
.attr("width", w)
.attr("height", h);

g.call(chart);

g.append("svg:text")
.attr("dy", "1.3em")
.attr("dx", ".6em")
.text(function(d) { return d.label; });

chart.duration(1000);

window.transition = function() {
g.map(randomize).call(chart);
};
});

function randomize(d) {
d.y = d3.range(n).map(Math.random);
return d;
}

0 comments on commit f76e515

Please sign in to comment.