function tufteAxis() { var scale, orient, data, q; var medianGap = 5, // gap width where the median of data is lineGap = 3; // gap between lines marking 1st and 3rd quartiles var line = d3.svg.line(); function axis(g) { var lineData = createLineData(); var paths = g.selectAll("path").data(lineData); paths.enter().append("path"); paths .transition().duration(1000) .attr("d", line); paths.exit().remove(); } axis.scale = function(x) { if (!arguments.length) return scale; scale = x; return axis; }; axis.orient = function(x) { if (!arguments.length) return orient; orient = x; return axis; }; axis.data = function(x) { if (!arguments.length) return data; data = x; q = getQuartiles(data); return axis; }; axis.medianGap = function(x) { if (!arguments.length) return medianGap; medianGap = x; return axis; }; axis.lineGap = function(x) { if (!arguments.length) return lineGap; lineGap = x; return axis; }; function getQuartiles(values) { values.sort(function(a, b) { return a - b; }); var n = values.length - 1, q = {}; [0, .25, .5, .75, 1].forEach(function(p) { var i = Math.round(n*p); q[p] = values[i]; }) return q; } function createLineData() { if (orient === "bottom") { return [ [ [scale(q[0]), lineGap], [scale(q[.25]), lineGap] ], [ [scale(q[.25]), 0], [scale(q[.5]) - medianGap/2, 0] ], [ [scale(q[.5]) + medianGap/2, 0], [scale(q[.75]), 0] ], [ [scale(q[.75]), lineGap], [scale(q[1]), lineGap] ] ]; } else if (orient === "left") { return [ [ [0, scale(q[0])], [0, scale(q[.25])] ], [ [lineGap, scale(q[.25])], [lineGap, scale(q[.5]) + medianGap/2] ], [ [lineGap, scale(q[.5]) - medianGap/2], [lineGap, scale(q[.75])] ], [ [0, scale(q[.75])], [0, scale(q[1])] ] ]; } } return axis; }