This is a visualization that displays events on a timeline. The radius of the circles corresponds to the number of events that happened.
A Pen by Geoffrey Bell on CodePen.
This is a visualization that displays events on a timeline. The radius of the circles corresponds to the number of events that happened.
A Pen by Geoffrey Bell on CodePen.
<div id="chart" class="chart"></div> |
var data = [ | |
{ | |
count: 12, | |
date: "Wed Sep 03 2014 20:43:05 GMT-0400 (EDT)" | |
}, | |
{ | |
count: 1, | |
date: "Thu Sep 04 2014 01:50:51 GMT-0400 (EDT)" | |
}, | |
{ | |
count: 5, | |
date: "Sat Sep 13 2014 05:45:04 GMT-0400 (EDT)" | |
}, | |
{ | |
count: 17, | |
date: "Mon Sep 15 2014 11:26:33 GMT-0400 (EDT)" | |
}, | |
{ | |
count: 8, | |
date: "Tue Sep 16 2014 05:40:35 GMT-0400 (EDT)" | |
}, | |
{ | |
count: 3, | |
date: "Wed Sep 17 2014 23:38:53 GMT-0400 (EDT)" | |
}, | |
{ | |
count: 2, | |
date: "Sun Sep 21 2014 16:42:47 GMT-0400 (EDT)" | |
}, | |
{ | |
count: 19, | |
date: "Thu Sep 25 2014 16:57:42 GMT-0400 (EDT)" | |
}, | |
{ | |
count: 2, | |
date: "Fri Sep 26 2014 21:56:27 GMT-0400 (EDT)" | |
}, | |
{ | |
count: 1, | |
date: "Sat Oct 04 2014 01:52:43 GMT-0400 (EDT)" | |
}, | |
{ | |
count: 1, | |
date: "Sat Oct 04 2014 15:57:51 GMT-0400 (EDT)" | |
}, | |
{ | |
count: 5, | |
date: "Sat Oct 04 2014 22:13:53 GMT-0400 (EDT)" | |
}, | |
{ | |
count: 1, | |
date: "Sat Oct 25 2014 17:25:35 GMT-0400 (EDT)" | |
}, | |
{ | |
count: 15, | |
date: "Wed Nov 05 2014 00:16:09 GMT-0500 (EST)" | |
}, | |
{ | |
count: 1, | |
date: "Wed Nov 05 2014 22:57:16 GMT-0500 (EST)" | |
}, | |
{ | |
count: 2, | |
date: "Fri Nov 07 2014 21:48:50 GMT-0500 (EST)" | |
} | |
]; | |
// Perform some data type conversion | |
data.forEach(function ( d ) { | |
d.date = new Date(d.date); | |
}); | |
var el = d3.select('#chart'), | |
svg = null, | |
chart = null, | |
width = document.getElementById('chart').offsetWidth, | |
height = document.getElementById('chart').offsetHeight, | |
minObjHeight = 10, | |
maxObjHeight = height*0.25, | |
margin = { | |
top: maxObjHeight*2, | |
right: maxObjHeight*4, | |
bottom: maxObjHeight*2, | |
left: maxObjHeight*4 | |
}, | |
mostRecent = d3.max(data, function ( d ) { return d.date; }), | |
oldest = d3.min(data, function ( d ) { return d.date; }), | |
self = this; | |
// Setup our x scale. This will convert a date | |
// to an x coordinate | |
var x = d3.time.scale() | |
.domain([ | |
d3.min(data, function ( d ) { return d.date; }), | |
d3.max(data, function ( d ) { return d.date; }) | |
]) | |
.range([0, width - margin.right]); | |
// Reorder the data by count. This allows for larger | |
// counts (larger circles) to be rendered first. | |
// i.e. below smaller circles. | |
data.sort(function ( a, b ) { | |
return d3.descending(a.count, b.count); | |
}); | |
// Setup radius scale. | |
var r = d3.scale.linear() | |
.clamp(true) | |
.domain([1, d3.max(data, function ( d, i ) { | |
// We're going to assume that the first count | |
// represents the initial load, which could be | |
// a large number and would skew the subsequent | |
// radius scaling. | |
return (i === 0) ? 1 : d.count; | |
})]) | |
.range([minObjHeight, maxObjHeight]); | |
var xAxis = d3.svg.axis() | |
.scale(x) | |
.ticks(4) | |
.tickSize(0) | |
.tickPadding(10); | |
var zoom = d3.behavior.zoom() | |
.x(x) | |
.scaleExtent([1,Infinity]) | |
.on('zoom', function() { | |
draw(); | |
}); | |
var svg = el.append('svg') | |
.attr('id', 'svg') | |
.attr('height', height) | |
.attr('width', width); | |
var chart = svg.append('g') | |
.attr('transform', 'translate(' + (margin.left * 0.5) + ', 0)'); | |
var tooltip = d3.select('body').append('div') | |
.classed('tooltip', true) | |
.style('z-index', 9000) | |
.style('opacity', 0); | |
tooltip.append('div').classed('down-arrow', true); | |
tooltip.append('div').classed('content', true); | |
var dateAxis = chart.append('g') | |
.attr('class', 'x axis') | |
.attr('transform', 'translate(0, ' + (height-24) + ')'); | |
var midline = chart.append('line') | |
.attr('class', 'midline') | |
.attr('x1', 0 - margin.left * 0.5) | |
.attr('y1', height * 0.5) | |
.attr('x2', width) | |
.attr('y2', height * 0.5); | |
var timeframe = chart.append('line') | |
.attr('class', 'timeframe') | |
.attr('y1', height * 0.5) | |
.attr('y2', height * 0.5); | |
var zoomTarget = chart.append('rect') | |
.attr('class', 'pane') | |
.attr('width', width) | |
.attr('height', height) | |
.style('cursor', 'move') | |
.style('fill', 'none') | |
.style('pointer-events', 'all') | |
.call(zoom); | |
var circles = chart.selectAll('circle') | |
.data(data); | |
circles.enter().append('circle') | |
.attr('cy', height*0.5) | |
.attr('r', function ( d ) { | |
return r(d.count); | |
}) | |
.on('mouseover', function ( d ) { | |
var format = d3.time.format('%m/%d/%y'), | |
message = format(new Date(d.date)) + '<br/>' + d.count + ((d.count === 1) ? ' change' : ' changes'), | |
svgTop = document.getElementById('svg').offsetTop,//33 | |
svgLeft = document.getElementById('svg').offsetLeft,//33 | |
circle = d3.select(this), | |
circleX = parseInt(circle.attr('cx'), 10), | |
circleY = parseInt(circle.attr('cy'), 10), | |
circleR = parseInt(circle.attr('r'), 10), | |
left, | |
top; | |
circle.classed('hover', true); | |
// Calculate positioning | |
// 56 = tooltip height | |
// 5 = spacer | |
console.log(svgTop, circleY, circleR); | |
top = svgTop + circleY - circleR - 56 - 5; | |
// 110 = tooltip width | |
left = svgLeft - 110*0.5 + margin.left*0.5 + circleX; | |
tooltip.transition() | |
.duration(200) | |
.style('opacity', 1); | |
tooltip | |
.style('left', left + 'px') | |
.style('top', top + 'px') | |
.select('.content').html(message); | |
}) | |
.on('mouseout', function () { | |
d3.select(this).classed('hover', false); | |
tooltip.transition() | |
.duration(500) | |
.style('opacity', 0); | |
}); | |
circles.exit().remove(); | |
var draw = function () { | |
dateAxis.call(xAxis); | |
circles.attr('cx', function ( d ) { | |
return x(d.date); | |
}); | |
timeframe.attr('x1', function () { | |
return x(oldest); | |
}); | |
timeframe.attr('x2', function () { | |
return x(mostRecent); | |
}); | |
}; | |
var resize = function () { | |
var width = document.getElementById('chart').offsetWidth; | |
//Update width related values | |
x.range([0, width - margin.right]); | |
svg.attr('width', width); | |
midline.attr('x2', width); | |
zoomTarget.attr('width', width); | |
draw(); | |
}; | |
d3.select(window).on('resize', resize); | |
draw(); |
body { | |
padding: 25px; | |
font-family: Verdana, Arial, Helvetica, sans-serif; | |
font-size: 13px; | |
font-style: normal; | |
font-weight: normal; | |
} | |
.chart { | |
background: lightgrey; | |
width: 100%; | |
height: 80px; | |
circle { | |
fill: steelblue; | |
stroke: white; | |
stroke-width: 2px; | |
cursor: pointer; | |
&.hover { | |
stroke: lightgrey; | |
stroke-width: 4px; | |
stroke-opacity: 0.8; | |
} | |
} | |
line.midline { | |
stroke: darkgrey; | |
stroke-width: 2px; | |
} | |
line.timeframe { | |
stroke: black; | |
stroke-width: 2px; | |
} | |
} | |
.tooltip { | |
font-size: 14px; | |
position: absolute; | |
text-align: center; | |
padding: 10px; | |
background: white; | |
border: 1px solid steelblue; | |
border-radius: 8px; | |
pointer-events: none; | |
width: 110px; | |
.down-arrow { | |
position: absolute; | |
width: 0; | |
height: 0; | |
border-left: 5px solid transparent; | |
border-right: 5px solid transparent; | |
border-top: 5px solid steelblue; | |
left: 49px; | |
bottom: -6px; | |
&:before { | |
content: ''; | |
position: absolute; | |
width: 0; | |
height: 0; | |
border-left: 4px solid transparent; | |
border-right: 4px solid transparent; | |
border-top: 4px solid white; | |
left: -4px; | |
bottom: 2px; | |
} | |
} | |
} |