This is a custom axis for values over time. In order to see different views, click anywhere on the box.
| <!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> |
| // 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