This is a custom axis for values over time. In order to see different views, click anywhere on the box.
Last active
August 29, 2015 13:57
-
-
Save milroc/9353922 to your computer and use it in GitHub Desktop.
Contextually aware axis ticks
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"> | |
<html> | |
<head> | |
<link href='http://fonts.googleapis.com/css?family=Inconsolata' rel='stylesheet' type='text/css'> | |
<style> | |
.axis text { | |
font: 14px 'Inconsolata'; | |
} | |
.axis line, | |
.axis path { | |
fill: none; | |
stroke: #000; | |
shape-rendering: crispEdges; | |
} | |
.axis path { | |
stroke: none; | |
} | |
body { | |
min-height: 500px; | |
} | |
.end { | |
fill: steelblue; | |
} | |
</style> | |
</head> | |
<body> | |
<h1 id="title"></h1> | |
<div id="axis"></div> | |
<script src="http://d3js.org/d3.v3.min.js"></script> | |
<script src="src.js"></script> | |
<script> | |
var customTimeFormat = d3.time.format; | |
var data = [ | |
{ key: 'several years', values: [new Date(2008, 0, 1), new Date(2013, 0, 1)] }, | |
{ key: 'one year', values: [new Date(2012, 0, 1), new Date(2013, 0, 1)] }, | |
{ key: 'several months', values: [new Date(2012, 0, 1), new Date(2012, 5, 1)] }, | |
{ key: 'one month', values: [new Date(2012, 0, 1), new Date(2012, 1, 1)] }, | |
{ key: 'several weeks', values: [new Date(2012, 0, 1), new Date(2012, 0, 21)] }, | |
{ key: 'one week', values: [new Date(2012, 0, 1), new Date(2012, 0, 7)] }, | |
{ key: 'several days', values: [new Date(2012, 0, 1), new Date(2012, 0, 4)] }, | |
{ key: 'one day', values: [new Date(2012, 0, 1), new Date(2012, 0, 2)] }, | |
{ key: 'several hours', values: [new Date(2012, 0, 1), new Date(1325433600000)] }, | |
{ key: 'one hour', values: [new Date(2012, 0, 1), new Date(1325408400000)] }, | |
{ key: 'several minutes', values: [new Date(2012, 0, 1), new Date(1325406600000)] }, | |
{ key: 'one minute', values: [new Date(2012, 0, 1), new Date(1325404860000)] }, | |
{ key: 'several seconds', values: [new Date(2012, 0, 1), new Date(1325404830000)] }, | |
{ key: 'one second', values: [new Date(2012, 0, 1), new Date(1325404801000)] }, | |
{ key: 'several milliseconds', values: [new Date(2012, 0, 1), new Date(1325404800400)] }, | |
{ key: 'one millisecond', values: [new Date(2012, 0, 1), new Date(1325404800001)] }, | |
]; | |
var i = -1, | |
interval = 2000; | |
var update = function() { | |
++i; | |
if (i >= data.length) i = 0; | |
var w = Math.random()*600 + 500; | |
var margin = {top: 250, right: 40, bottom: 250, left: 40}, | |
width = w - margin.left - margin.right, | |
height = 500 - margin.top - margin.bottom; | |
var x = d3.time.scale() | |
.domain(data[i].values) | |
.range([0, width]); | |
var xAxis = d3.svg.haxis() | |
.scale(x) | |
.tickMultiFormat([ | |
["%-Lms", function(d) { return d.getMilliseconds(); }], // milliseconds | |
["%-Ss", function(d) { return d.getSeconds(); }], // seconds | |
["%-I:%M", function(d) { return d.getMinutes(); }], // minute | |
["%-I %p", function(d) { return d.getHours(); }], // hour | |
["%-d", function(d) { return d.getDay() && d.getDate() != 1; }], // day | |
["%b %-d", function(d) { return d.getDate() != 1; }], // monday of the week | |
["%b", function(d) { return d.getMonth(); }], // month | |
["%Y", function() { return true; }] // year | |
]) | |
.endTickMultiFormat([ | |
[":%M:%S.%Lms", function(d) { return d.getMilliseconds(); }], // milliseconds | |
[":%M:%Ss", function(d) { return d.getSeconds(); }], // seconds | |
["%-I:%M %p", function(d) { return d.getMinutes(); }], // minute | |
["%-I %p", function(d) { return d.getHours(); }], // hour | |
["%b %-d", function(d) { return d.getDay() && d.getDate() != 1; }], // day | |
["%b %-d", function(d) { return d.getDate() != 1; }], // monday of the week | |
["%Y %b", function(d) { return d.getMonth(); }], // month | |
["%Y", function() { return true; }] // year | |
]); | |
d3.select("#title").text(data[i].key); | |
var svg = d3.select('#axis') | |
.selectAll('svg') | |
.data([i]); | |
svg.enter() | |
.append("svg") | |
.attr("height", height + margin.top + margin.bottom) | |
.append("g") | |
.attr("transform", "translate(" + margin.left + "," + margin.top + ")") | |
.append("g") | |
.attr("class", "x axis") | |
.attr("transform", "translate(0," + height + ")") | |
svg.attr("width", width + margin.left + margin.right); | |
svg.selectAll('.x.axis') | |
.transition() | |
.duration(interval/2) | |
.call(xAxis); | |
}; | |
update(); | |
d3.select("body").on('click', update); | |
setInterval(update, 5000); | |
</script> | |
</body> | |
</html> |
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
// custom axis (hacked axis) | |
d3.svg.haxis = function() { | |
var scale = d3.scale.linear(), | |
orient = d3_svg_axisDefaultOrient, | |
innerTickSize = 6, | |
outerTickSize = 6, | |
tickPadding = 3, | |
tickArguments_ = [10], | |
tickValues = null, | |
tickFormat_, | |
endTickFormat_, | |
tickMultiFormat_, | |
endTickMultiFormat_; | |
function axis(g) { | |
g.each(function() { | |
var g = d3.select(this); | |
// Stash a snapshot of the new scale, and retrieve the old snapshot. | |
var scale0 = this.__chart__ || scale, | |
scale1 = this.__chart__ = scale.copy(); | |
// Ticks, or domain values for ordinal scales. | |
if (endTickMultiFormat_ != null) endTickMultiFormat_ = (endTickMultiFormat_ == null && tickMultiFormat_ != null) ? tickMultiFormat_ : null; | |
var ticks = tickValues == null ? (scale1.ticks ? scale1.ticks.apply(scale1, tickArguments_) : scale1.domain()) : tickValues, | |
tickLength = ticks.length, | |
tickFormat = (tickMultiFormat_ === null)?(tickFormat_ == null ? (scale1.tickFormat ? scale1.tickFormat.apply(scale1, tickArguments_) : d3_identity) : tickFormat_):(d3_time_formatMulti(tickMultiFormat_)), | |
endTickFormat = (endTickMultiFormat_ === null)?(tickFormat):(d3_time_formatMulti(endTickMultiFormat_)), | |
tick = g.selectAll(".tick").data(ticks, scale1), | |
tickEnter = tick.enter().insert("g", ".domain").attr("class", "tick").style("opacity", ε), | |
tickExit = d3.transition(tick.exit()).style("opacity", ε).remove(), | |
tickUpdate = d3.transition(tick).style("opacity", 1).attr("class", function(d, i) { return ((!i || i === tickLength - 1)?"end":"") + " tick"; }), | |
tickTransform; | |
function d3_time_formatMulti(formats) { | |
var n = formats.length, i = -1; | |
// convert to formats | |
while (++i < n) formats[i][0] = d3.time.format(formats[i][0]); | |
return function(date, i) { | |
var j = 0, | |
k = 0, | |
f = formats[j], | |
n = formats[k], | |
neighbor = ticks[(j > 0)?(j - 1):(j + 1)]; | |
while (!f[1](date)) f = formats[++j]; | |
while (!n[1](neighbor)) n = formats[++k]; | |
if (j - k > 1) { | |
f = formats[++k]; | |
} | |
return f[0](date); | |
}; | |
} | |
// Domain. | |
var range = d3_scaleRange(scale1), | |
path = g.selectAll(".domain").data([0]), | |
pathUpdate = (path.enter().append("path").attr("class", "domain"), d3.transition(path)); | |
tickEnter.append("line"); | |
tickEnter.append("text"); | |
var lineEnter = tickEnter.select("line"), | |
lineUpdate = tickUpdate.select("line"), | |
text = tick.select("text").text(function(d, i) { | |
var render = (endTickFormat && (!i || i === tickLength - 1))?endTickFormat(d, i):tickFormat(d, i); | |
if (!endTickFormat || render.indexOf('s') === -1) return render.toUpperCase(); | |
return render; | |
}), | |
textEnter = tickEnter.select("text"), | |
textUpdate = tickUpdate.select("text"); | |
switch (orient) { | |
case "bottom": { | |
tickTransform = d3_svg_axisX; | |
lineEnter.attr("y2", innerTickSize); | |
textEnter.attr("y", Math.max(innerTickSize, 0) + tickPadding); | |
lineUpdate.attr("x2", 0).attr("y2", innerTickSize); | |
textUpdate.attr("x", 0).attr("y", Math.max(innerTickSize, 0) + tickPadding); | |
text.attr("dy", ".71em").style("text-anchor", "middle"); | |
pathUpdate.attr("d", "M" + range[0] + "," + outerTickSize + "V0H" + range[1] + "V" + outerTickSize); | |
break; | |
} | |
case "top": { | |
tickTransform = d3_svg_axisX; | |
lineEnter.attr("y2", -innerTickSize); | |
textEnter.attr("y", -(Math.max(innerTickSize, 0) + tickPadding)); | |
lineUpdate.attr("x2", 0).attr("y2", -innerTickSize); | |
textUpdate.attr("x", 0).attr("y", -(Math.max(innerTickSize, 0) + tickPadding)); | |
text.attr("dy", "0em").style("text-anchor", "middle"); | |
pathUpdate.attr("d", "M" + range[0] + "," + -outerTickSize + "V0H" + range[1] + "V" + -outerTickSize); | |
break; | |
} | |
case "left": { | |
tickTransform = d3_svg_axisY; | |
lineEnter.attr("x2", -innerTickSize); | |
textEnter.attr("x", -(Math.max(innerTickSize, 0) + tickPadding)); | |
lineUpdate.attr("x2", -innerTickSize).attr("y2", 0); | |
textUpdate.attr("x", -(Math.max(innerTickSize, 0) + tickPadding)).attr("y", 0); | |
text.attr("dy", ".32em").style("text-anchor", "end"); | |
pathUpdate.attr("d", "M" + -outerTickSize + "," + range[0] + "H0V" + range[1] + "H" + -outerTickSize); | |
break; | |
} | |
case "right": { | |
tickTransform = d3_svg_axisY; | |
lineEnter.attr("x2", innerTickSize); | |
textEnter.attr("x", Math.max(innerTickSize, 0) + tickPadding); | |
lineUpdate.attr("x2", innerTickSize).attr("y2", 0); | |
textUpdate.attr("x", Math.max(innerTickSize, 0) + tickPadding).attr("y", 0); | |
text.attr("dy", ".32em").style("text-anchor", "start"); | |
pathUpdate.attr("d", "M" + outerTickSize + "," + range[0] + "H0V" + range[1] + "H" + outerTickSize); | |
break; | |
} | |
} | |
// If either the new or old scale is ordinal, | |
// entering ticks are undefined in the old scale, | |
// and so can fade-in in the new scale’s position. | |
// Exiting ticks are likewise undefined in the new scale, | |
// and so can fade-out in the old scale’s position. | |
if (scale1.rangeBand) { | |
var x = scale1, dx = x.rangeBand() / 2; | |
scale0 = scale1 = function(d) { return x(d) + dx; }; | |
} else if (scale0.rangeBand) { | |
scale0 = scale1; | |
} else { | |
tickExit.call(tickTransform, scale1); | |
} | |
tickEnter.call(tickTransform, scale0); | |
tickUpdate.call(tickTransform, scale1); | |
}); | |
} | |
axis.scale = function(x) { | |
if (!arguments.length) return scale; | |
scale = x; | |
return axis; | |
}; | |
axis.orient = function(x) { | |
if (!arguments.length) return orient; | |
orient = x in d3_svg_axisOrients ? x + "" : d3_svg_axisDefaultOrient; | |
return axis; | |
}; | |
axis.ticks = function() { | |
if (!arguments.length) return tickArguments_; | |
tickArguments_ = arguments; | |
return axis; | |
}; | |
axis.tickValues = function(x) { | |
if (!arguments.length) return tickValues; | |
tickValues = x; | |
return axis; | |
}; | |
axis.tickFormat = function(x) { | |
if (!arguments.length) return tickFormat_; | |
tickFormat_ = x; | |
return axis; | |
}; | |
axis.tickMultiFormat = function(x) { | |
if (!arguments.length) return tickMultiFormat_; | |
tickMultiFormat_ = x; | |
return axis; | |
}; | |
axis.endTickMultiFormat = function(x) { | |
if (!arguments.length) return endTickMultiFormat_; | |
endTickMultiFormat_ = x; | |
return axis; | |
}; | |
axis.tickSize = function(x) { | |
var n = arguments.length; | |
if (!n) return innerTickSize; | |
innerTickSize = +x; | |
outerTickSize = +arguments[n - 1]; | |
return axis; | |
}; | |
axis.innerTickSize = function(x) { | |
if (!arguments.length) return innerTickSize; | |
innerTickSize = +x; | |
return axis; | |
}; | |
axis.outerTickSize = function(x) { | |
if (!arguments.length) return outerTickSize; | |
outerTickSize = +x; | |
return axis; | |
}; | |
axis.tickPadding = function(x) { | |
if (!arguments.length) return tickPadding; | |
tickPadding = +x; | |
return axis; | |
}; | |
axis.tickSubdivide = function() { | |
return arguments.length && axis; | |
}; | |
return axis; | |
}; | |
// necessary variables from the d3 namespace | |
var ε = 1e-6; | |
var d3_identity = function(d) { return d; }; | |
var d3_svg_axisDefaultOrient = "bottom", | |
d3_svg_axisOrients = {top: 1, right: 1, bottom: 1, left: 1}; | |
function d3_svg_axisX(selection, x) { | |
selection.attr("transform", function(d) { return "translate(" + x(d) + ",0)"; }); | |
} | |
function d3_svg_axisY(selection, y) { | |
selection.attr("transform", function(d) { return "translate(0," + y(d) + ")"; }); | |
} | |
function d3_scaleExtent(domain) { | |
var start = domain[0], stop = domain[domain.length - 1]; | |
return start < stop ? [start, stop] : [stop, start]; | |
} | |
function d3_scaleRange(scale) { | |
return scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range()); | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment