-
-
Save natemiller/f2faa97c0bd4af49b5bb to your computer and use it in GitHub Desktop.
Playing with Errorbars
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[ | |
{ | |
"name":"site1", | |
"temp": 30, | |
"pH": 7.2, | |
"x": 10, | |
"y": 50, | |
"s": 6 | |
}, | |
{ | |
"name":"site1", | |
"temp": 25, | |
"pH": 8.0, | |
"x": 10, | |
"y": 35, | |
"s": 4 | |
}, | |
{ | |
"name":"site1", | |
"temp": 25, | |
"pH": 7.2, | |
"x": 15, | |
"y": 70, | |
"s": 10 | |
}, | |
{ | |
"name":"site2", | |
"temp": 30, | |
"pH": 7.2, | |
"x": 20, | |
"y": 20, | |
"s": 8 | |
}, | |
{ | |
"name":"site2", | |
"temp": 30, | |
"pH": 8.0, | |
"x": 25, | |
"y": 55, | |
"s": 10 | |
}, | |
{ | |
"name":"site2", | |
"temp": 25, | |
"pH": 8.0, | |
"x": 30, | |
"y": 30, | |
"s": 2 | |
}, | |
{ | |
"name":"site3", | |
"temp": 25, | |
"pH": 7.6, | |
"x": 50, | |
"y": 100, | |
"s": 14 | |
} | |
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function errorBar(){ | |
//TODO | |
//- Think of an additional way to represent error bars, maybe just the orthognal case (i.e. use width not height) | |
//- Does ordinal scale even make sense here? May need to remove. | |
var size = 5, | |
xError = function(d) {return exists(d[0].s);}, | |
yError = function(d) {return exists(d[1].s);}, | |
errormarker = null, //points will inherit from g.dataset | |
xValue = function(d) {return (d[0].x === undefined)?d[0]:d[0].x;}, | |
yValue = function(d) {return (d[1].x === undefined)?d[1]:d[1].x;}, | |
oldXScale, //probably can give these smarter defaults | |
oldYScale, | |
xScale, | |
yScale; | |
function marks(datapoints){ | |
datapoints.each(function(d,i) { | |
var g = d3.select(this), | |
//Use color/errormarker defined. | |
errormarker = errormarker || exists(g.datum().errormarker); | |
//Error | |
var err = g.selectAll(".err") | |
.data(function(d){return (!(xError(d) === null) || !(yError(d) === null))?[d]:[]},xValue), | |
errEnter, | |
errExit, | |
errUpdate; | |
switch (errormarker) { | |
case null: { | |
errEnter = err.enter().append("path").attr("class","err") | |
errExit = d3.transition(err.exit()).remove(), | |
errUpdate = d3.transition(err), | |
errTransform = function(selection,a,b){ | |
selection.attr("d", function(d){ | |
var h = (yError(d) === null)?[-size,size]:[(b(yValue(d) - yError(d)) - b(yValue(d))),(b(yValue(d) + yError(d)) - b(yValue(d)))], | |
w = (xError(d) === null)?[-size,size]:[(a(xValue(d) - xError(d)) - a(xValue(d))),(a(xValue(d) + xError(d)) - a(xValue(d)))]; | |
return "M 0," + h[0] + | |
" L 0," + h[1] + | |
" M " + w[0] + "," + h[1] + | |
" L " + w[1] + "," + h[1] + | |
" M " + w[0] + "," + h[0] + | |
" L " + w[1] + "," + h[0] | |
})};; | |
break; | |
} | |
case errormarker: { //Normalize custom errors to 1px in their definition. Will be scaled back up here by size. | |
err.append("use").attr("class",marker + " err"); | |
err.selectAll("use").attr("xlink:href","#" + marker) | |
.attr("transform",function(d){return "scale(" + xError(d) + "," + yError(d) + ")"}); | |
break; | |
} | |
}; | |
// For quantitative scales: | |
// - enter new ticks from the old scale | |
// - exit old ticks to the new scale | |
if (xScale.ticks) { | |
errEnter.call(errTransform, oldXScale, oldYScale); | |
errUpdate.call(errTransform, xScale, yScale); | |
errExit.call(errTransform, xScale, yScale); | |
} | |
// For ordinal scales: | |
// - any entering ticks are undefined in the old scale | |
// - any exiting ticks are undefined in the new scale | |
// Therefore, we only need to transition updating ticks. | |
else { | |
var dx = xScale.rangeBand() / 2, x = function(d) { return xScale(d) + dx; }; | |
var dy = yScale.rangeBand() / 2, y = function(d) { return yScale(d) + dy; }; | |
errEnter.call(errTransform, x, y); | |
errUpdate.call(errTransform, x, y); | |
} | |
}); | |
} | |
marks.size = function(_) { | |
if (!arguments.length) return size; | |
size = _; | |
return marks; | |
}; | |
marks.xError = function(_) { | |
if (!arguments.length) return xError; | |
xError = _; | |
return marks; | |
}; | |
marks.yError = function(_) { | |
if (!arguments.length) return yError; | |
yError = _; | |
return marks; | |
}; | |
marks.oldXScale = function(_) { | |
if (!arguments.length) return oldXScale; | |
oldXScale = _; | |
return marks; | |
}; | |
marks.oldYScale = function(_) { | |
if (!arguments.length) return oldYScale; | |
oldYScale = _; | |
return marks; | |
}; | |
marks.xScale = function(_) { | |
if (!arguments.length) return xScale; | |
xScale = _; | |
return marks; | |
}; | |
marks.yScale = function(_) { | |
if (!arguments.length) return yScale; | |
yScale = _; | |
return marks; | |
}; | |
marks.xValue = function(_) { | |
if (!arguments.length) return xValue; | |
xValue = _; | |
return marks; | |
}; | |
marks.yValue = function(_) { | |
if (!arguments.length) return yValue; | |
yValue = _; | |
return marks; | |
}; | |
marks.errormarker = function(_) { | |
if (!arguments.length) return errormarker; | |
errormarker = _; | |
return marks; | |
}; | |
return marks; | |
}; | |
function exists(a){ | |
return (a === undefined)?null:a; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<style> | |
svg { | |
font: 10px sans-serif; | |
} | |
.axis path, | |
.axis line { | |
fill: none; | |
stroke: #000; | |
shape-rendering: crispEdges; | |
} | |
path { | |
stroke-width: 1.5px; | |
stroke: darkgrey; | |
stroke-dasharray:"3, 3"; | |
} | |
.legend{ | |
stroke: "grey"; | |
} | |
</style> | |
<body> | |
<script src="http://d3js.org/d3.v3.min.js"></script> | |
<script src="errorbar.js"></script> | |
<div id="option"> | |
<input name="updateButton" type="button" value="X-axis: Temp"/> | |
</div> | |
<script> | |
var margin = {top: 10, right: 10, bottom: 100, left: 40}, | |
size = 5, | |
width = 960 - margin.left - margin.right, | |
height = 500 - margin.top - margin.bottom, | |
x = d3.scale.linear().range([0, width]), | |
y = d3.scale.linear().range([height, 0]), | |
xAxis = d3.svg.axis().scale(x).orient("bottom"), | |
yAxis = d3.svg.axis().scale(y).orient("left"); | |
var eb = errorBar() | |
.oldXScale(x) | |
.xScale(x) | |
.oldYScale(y) | |
.yScale(y) | |
.yValue(function(d){return d.y}) | |
.xValue(function(d){return d.pH}) | |
.xError(function(d){return null}) | |
.yError(function(d){return d.s}); | |
var color = d3.scale.category10(); | |
var svg = d3.select("body").append("svg") | |
.attr("width", width + margin.left + margin.right) | |
.attr("height", height + margin.top + margin.bottom); | |
var plot = svg.append("g") | |
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); | |
d3.json("error.data.json", function(error,data) { | |
color.domain(d3.keys(data[0]).filter(function(key) { return key == "name"; })); | |
x.domain([d3.min(data.map(function(d) {return d.pH}))-0.2,d3.max(data.map(function(d) { return d.pH; }))+0.2]); | |
y.domain([d3.min(data.map(function(d) {return (d.y - d.s)})),d3.max(data.map(function(d) { return (d.y + d.s); }))]); | |
//Initial Plot | |
var circles = plot.selectAll("g") | |
.data(data) | |
.enter().append("g"); | |
var plotErrorbar = circles.append("g") | |
.attr("transform", function(d) {return "translate("+ x(d.pH) +","+ y(d.y) +")"}) | |
.style("stroke-dasharray", ("3, 3")) | |
.call(eb); | |
var plotCircles = circles.append("circle") | |
.attr("class","circle") | |
.attr("cx", function(d) {return x(d.pH);}) | |
.attr("cy", function(d){return y(d.y);}) | |
.attr("r",8) | |
.attr("fill",function(d) {return color(d.name);}) | |
.style("stroke", "darkgrey"); | |
var plotText = circles.append("text") | |
.attr("class","legend") | |
.attr('x', width-40) | |
.attr('y', function(d,i) {return i*21;}) | |
.text(function(d) { return d.name; }); | |
var plotRect = circles.append("rect") | |
.attr('x', width-50) | |
.attr('y', function(d,i) {return i*20;}) | |
.attr('width', 10) | |
.attr('height', 10) | |
.style('fill', function(d) {return color(d.name);}); | |
svg.append("g") | |
.attr("class","x axis") | |
.attr("transform", "translate("+ margin.left +"," + (height + margin.top) + ")") | |
.call(xAxis); | |
svg.append("g") | |
.attr("class","y axis") | |
.attr("transform","translate("+ margin.left +","+ margin.top +" )") | |
.call(yAxis); | |
//Add the transition | |
d3.select("div") | |
.on("click", function() { | |
x.domain([d3.min(data.map(function(d) {return d.temp}))-1,d3.max(data.map(function(d) { return d.temp; }))+1]); | |
y.domain([d3.min(data.map(function(d) {return (d.y - d.s)})),d3.max(data.map(function(d) { return (d.y + d.s); }))]); | |
//Select and update the circles | |
plotCircles.selectAll("circle") | |
.data(data) | |
.enter().append("circle") | |
.attr("class","circle") | |
.attr("cx", function(d) {return x(d.temp);}) | |
.attr("cy", function(d){return y(d.y);}) | |
.attr("r",8) | |
.attr("fill",function(d) {return color(d.pH);}) | |
.style("stroke", "darkgrey"); | |
plotCircles.transition() | |
.duration(1000) | |
.attr("class","circle") | |
.attr("cx", function(d) {return x(d.temp);}) | |
.attr("cy", function(d){return y(d.y);}) | |
.attr("r",8) | |
.attr("fill",function(d) {return color(d.pH);}) | |
.style("stroke", "darkgrey"); | |
//Select and update the rectangles in legend | |
plotRect.selectAll("rect") | |
.data(data) | |
.enter().append("rect") | |
.attr("class","rect") | |
.attr('x', width-50) | |
.attr('y', function(d,i) {return i*20;}) | |
.attr('width', 10) | |
.attr('height', 10) | |
.style('fill', function(d) {return color(d.pH);}); | |
plotRect.transition() | |
.duration(1000) | |
.attr("class","rect") | |
.attr('x', width-50) | |
.attr('y', function(d,i) {return i*20;}) | |
.attr('width', 10) | |
.attr('height', 10) | |
.style('fill', function(d) {return color(d.pH);}); | |
//Select and update the text in legend | |
plotText.selectAll("text") | |
.data(data) | |
.enter().append("text") | |
.attr("class","legend") | |
.attr('x', width-40) | |
.attr('y', function(d,i) {return i*21;}) | |
.text(function(d) { return d.pH; }); | |
plotText.transition() | |
.duration(1000) | |
.attr("class","legend") | |
.attr('x', width-40) | |
.attr('y', function(d,i) {return i*21;}) | |
.text(function(d) { return d.pH; }); | |
//Update the axes | |
svg.select(".x.axis") | |
.transition() | |
.duration(1000) | |
.call(xAxis); | |
svg.select(".y.axis") | |
.transition() | |
.duration(1000) | |
.call(yAxis); | |
//Error bars | |
plotErrorbar.selectAll("g") | |
.data(data) | |
.enter() | |
.append("g") | |
.attr("transform", function(d) {return "translate("+ x(d.temp) +","+ y(d.y) +")"}) | |
.style("stroke-dasharray", ("3, 3")) | |
.call(eb); | |
plotErrorbar.transition() | |
.duration(1000) | |
.attr("transform", function(d) {return "translate("+ x(d.temp) +","+ y(d.y) +")"}) | |
.style("stroke-dasharray", ("3, 3")) | |
.call(eb); | |
//Remove Text, Rectangles and Circles | |
plotText.exit() | |
.transition() | |
.duration(1000) | |
.remove(); | |
plotRect.exit() | |
.transition() | |
.duration(1000) | |
.remove(); | |
plotCircles.exit() | |
.transition() | |
.duration(1000) | |
.remove(); | |
plotErrorbar.exit() | |
.transition() | |
.duration(1000) | |
.remove(); | |
}); | |
}); | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment