Inspired by this time map post I wanted to build a block that shows a bit more of the intuition about how time maps work. This is a very simple timemap built with D3 that visualizes the speed and frequency of keystrokes.
Built with blockbuilder.org
Inspired by this time map post I wanted to build a block that shows a bit more of the intuition about how time maps work. This is a very simple timemap built with D3 that visualizes the speed and frequency of keystrokes.
Built with blockbuilder.org
| <!DOCTYPE html> | |
| <head> | |
| <meta charset="utf-8"> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script> | |
| <style> | |
| body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; } | |
| svg { width:100%; height: 100% } | |
| #inputField { | |
| border: none; | |
| border-bottom: 1px solid #333; | |
| margin: 20px 50px; | |
| font-size: 24px; | |
| width: 89%; | |
| } | |
| text { | |
| font-family: Helvetica; | |
| color: #777; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <input type="text" id="inputField" /> | |
| <svg width="960" height="450"> | |
| <g transform="translate(320, 10)" id="axis"> | |
| <g id="xAxis"></g> | |
| <g id="yAxis"></g> | |
| <g transform="translate(180, 370)"> | |
| <text text-anchor="middle">Time Since Previous Event (ms)</text> | |
| </g> | |
| <g transform="translate(-50, 180) rotate(-90)"> | |
| <text text-anchor="middle">Time Until Next Event (ms)</text> | |
| </g> | |
| </g> | |
| <g transform="translate(320, 10)" id="points"> | |
| </g> | |
| </svg> | |
| <script> | |
| var events = [] | |
| var width = 320 | |
| var timeScale = d3.scale.pow().exponent(0.5) | |
| .domain([0, 2000]) | |
| .range([0, width]) | |
| .clamp(true) | |
| var scatterPlot = d3.select('#points') | |
| var inputField = d3.select('#inputField') | |
| var addCharacter = function(char) { | |
| var now = new Date().getTime() | |
| var sincePrevious = Infinity | |
| if (events.length > 0) { | |
| sincePrevious = now - events[events.length - 1].time | |
| events[events.length - 1].tilNext = sincePrevious | |
| } | |
| events.push({ | |
| time: now, | |
| sincePrevious: sincePrevious, | |
| tilNext: Infinity, | |
| char: char | |
| }) | |
| render() | |
| } | |
| inputField.on('keydown', function() { | |
| var char = String.fromCharCode(d3.event.keyCode) | |
| addCharacter(char) | |
| }) | |
| var xAxis = d3.select("#xAxis").selectAll("g") | |
| .data(timeScale.ticks(12)) | |
| .enter().append('g') | |
| .each(function(d, i) { | |
| var g = d3.select(this) | |
| var x = timeScale(d) | |
| g.attr('transform', 'translate(' + x + ',' + width + ')') | |
| var tickLength = 5 | |
| if (i % 3 === 1) { | |
| var text = g.append('text') | |
| text.text(d) | |
| .attr('fill', '#888') | |
| .attr('font-size', 11) | |
| .attr('font-family', 'Helvetica') | |
| .attr('y', 20) | |
| tickLength = 10 | |
| } | |
| var rect = g.append('rect') | |
| rect | |
| .attr('width', 1) | |
| .attr('height', tickLength) | |
| .attr('fill', '#888') | |
| }) | |
| var yAxis = d3.select("#yAxis").selectAll("g") | |
| .data(timeScale.ticks(12)) | |
| .enter().append('g') | |
| .each(function(d, i) { | |
| var g = d3.select(this) | |
| var y = width - timeScale(d) | |
| g.attr('transform', 'translate(0,' + y + ')') | |
| var tickLength = 5 | |
| if (i % 3 === 1) { | |
| var text = g.append('text') | |
| text.text(d) | |
| .attr('font-size', 11) | |
| .attr('font-family', 'Helvetica') | |
| .attr('fill', '#888') | |
| .attr('x', -10) | |
| .attr('text-anchor', 'end') | |
| tickLength = 10 | |
| } | |
| var rect = g.append('rect') | |
| rect | |
| .attr('x', -tickLength) | |
| .attr('width', tickLength) | |
| .attr('height', 1) | |
| .attr('fill', '#888') | |
| }) | |
| var render = function() { | |
| var points = scatterPlot.selectAll('text') | |
| .data(events, function(d) { return d.time }) | |
| points.enter() | |
| .append('text') | |
| .attr('fill', '#f80') | |
| .attr('r', 2) | |
| .text(function(d) { | |
| return d.char | |
| }) | |
| points | |
| .attr('opacity', function(d) { | |
| if (d.tilNext === Infinity) { | |
| return 0.1 | |
| } else { | |
| return 0.3 | |
| } | |
| }) | |
| .transition() | |
| .attr('x', function(d) { | |
| return timeScale(d.sincePrevious) | |
| }) | |
| .attr('y', function(d) { | |
| return width - timeScale(d.tilNext) | |
| }) | |
| points.exit().remove() | |
| } | |
| var demoString = "This is a time map. Type here and see." | |
| var demoDelay = [111,132,121,132,500,80,90,500,100,500,100,131,178,132,500,133,112,131,122,121,500,89,78,80,67,200,67,87,58,59,200,98,78,88,200,45,65,76,89] | |
| var addNextChar = function(i) { | |
| inputField.attr('value', demoString.substr(0,i)) | |
| addCharacter(demoString[i].toUpperCase()) | |
| var nextFn = function(next) { | |
| return function() { | |
| addNextChar(next) | |
| } | |
| }(i + 1) | |
| if (i < demoString.length) { | |
| setTimeout(nextFn, demoDelay[i]) | |
| } | |
| } | |
| setTimeout(function() { | |
| addNextChar(0) | |
| }, 1200) | |
| //inputField.node().focus() | |
| </script> | |
| </body> |