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> |