Skip to content

Instantly share code, notes, and snippets.

@gtb104
Created December 2, 2014 18:56
Show Gist options
  • Save gtb104/07e7f5059b27ddc22568 to your computer and use it in GitHub Desktop.
Save gtb104/07e7f5059b27ddc22568 to your computer and use it in GitHub Desktop.

Event Timeline

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.

License.

<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;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment