Skip to content

Instantly share code, notes, and snippets.

@tsenga
Created November 11, 2012 18:44
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save tsenga/4055833 to your computer and use it in GitHub Desktop.
Save tsenga/4055833 to your computer and use it in GitHub Desktop.
Visualisation Exploration: D3: Timeline Experiment

An experiment within the Visualisation Exploration series.

Exploring, techniques and practices in the field of data visualisation.

See the output: http://bl.ocks.org/tsenga/4055833.

Key history of this experiment:

  • D3, the data driven visualisation enabler.
  • Initial prototype written in handcrafted SVG with handcrafted data
  • Creation of sample .json dataset
  • Migration to systematic data treatment using D3
  • Conversion to reusable charts convention

Possible future directions:

  • Pulling out style treatment into configuration and .css
  • Enabling configuration of left and right gutter spacing
  • Distance between events proportional to time between events
  • Auto-sizing of event boxes, depending on data to be presented
  • Auto-packing of event boxes, to maximise space
d3.json("./data/history.json", function(json) {
if (json === null) return; // parse problem, nothing to do here
// setup data for chart
json.events.forEach(function(p, i) {
p.date = +p.date; // coerce into right type
});
json.events.sort(function(a,b) { return a.date < b.date ? -1 : a.date > b.date ? 1 : 0; });
// instantiate the chart
var chart = timelineChart();
chart.title(function(d) { return d.name; }) // accessor for event title
.date(function(d) { return d.date; }) // accessor for event date
.details(function(d) { return d.party; })
.width(600); // width of chart
// join and render
d3.select("#chart").datum(json.events).call(chart);
});
{
"events" : [
{ "date" : "2010", "name" : "David Cameron", "party" : "Conservative" },
{ "date" : "2007", "name" : "Gordon Brown", "party" : "Labour" },
{ "date" : "1997", "name" : "Tony Blair", "party" : "Labour" },
{ "date" : "1990", "name" : "John Major", "party" : "Conservative" },
{ "date" : "1979", "name" : "Margaret Thatcher", "party" : "Conservative" },
{ "date" : "1976", "name" : "James Callaghan", "party" : "Labour" },
{ "date" : "1974", "name" : "Harold Wilson", "party" : "Labour" },
{ "date" : "1970", "name" : "Edward Heath", "party" : "Conservative" }
]
}
<HTML>
<head>
<title>D3 Timeline demonstration</title>
<script src="http://d3js.org/d3.v2.min.js"></script>
<script src="timeline.js"></script>
</head>
<body>
<h1>Vis Exploration - D3 - Timeline</h1>
<div id="chart"></div>
</body>
<script src="example.js"></script>
</HTML>
function timelineChart() {
var _title = function(value) { return value; };
var _date = function(value) { return value; };
var _details = null;
var _entryHeight = 50; // spacing between each entry
var _entryGap = 10; // gap above the start of each entry
var _width = 0; // default is set later
// left & right margins of each column, including the middle line (midMargin)
// all derived from _width - see my.width() below
var _midMargin = 0;
var _leftColMarginL = 0, _leftColMarginR = 0;
var _rightColMarginL = 0, _rightColMarginR = 0;
function my(selection) {
selection.each(function(d, i) {
// generate chart here; 'd' is the data and 'this' is the element
// establish base SVG frame
var svgBase = d3.select(this).append("svg:svg")
.attr("width", _rightColMarginR + 5)
.attr("height", (d.length + 1.5) * _entryHeight);
// draw mid-line - use number of events to determine length of the line
svgBase.append("line")
.attr("x1", _midMargin)
.attr("y1", 0)
.attr("x2", _midMargin)
.attr("y2", (d.length + 1.5) * _entryHeight)
.attr("stroke", "#999999")
.attr("stroke-width", 5);
// now bind data and draw entries
var entryBase = svgBase.selectAll(".entry")
.data(d)
.enter()
.append("g");
entryBase.append("circle")
.attr("cx", _midMargin)
.attr("cy", function(d, i) { return i * _entryHeight + _entryGap + 25; })
.attr("r", "5")
.attr("style", "fill:#999999; stroke:#ffffff; stroke-width:3")
// draw box around each event, factoring in left or right column-ness
entryBase.append("polygon")
.attr("points", function(d, i) {
var yTop = i * _entryHeight + _entryGap;
// polygon has notch on right or left depending on odd/even of index
return (i%2 == 0) ?
_leftColMarginL + "," + yTop
+ " " + _leftColMarginR + "," + yTop
+ " " + _leftColMarginR + "," + (yTop + 20)
+ " " + (_leftColMarginR+5) + "," + (yTop + 25)
+ " " + _leftColMarginR + "," + (yTop + 30)
+ " " + _leftColMarginR + "," + (yTop + 85)
+ " " + _leftColMarginL + "," + (yTop + 85)
:
_rightColMarginR + "," + yTop
+ " " + _rightColMarginL + "," + yTop
+ " " + _rightColMarginL + "," + (yTop + 20)
+ " " + (_rightColMarginL-5) + "," + (yTop + 25)
+ " " + _rightColMarginL + "," + (yTop + 30)
+ " " + _rightColMarginL + "," + (yTop + 85)
+ " " + _rightColMarginR + "," + (yTop + 85);
})
.attr("style", "fill:#eeeeee; stroke:#999999; stroke-width:1")
.on("mouseover", function() {d3.select(this).style("fill", "aliceblue").style("stroke", "#0000ff");})
.on("mouseout", function() {d3.select(this).style("fill", "#eeeeee").style("stroke", "#999999"); });
var textBase = entryBase.append("text")
// text is written in to left or right column, depending on odd/even of index
.attr("x", function(d,i) { return ((i%2 == 0) ? _leftColMarginL : _rightColMarginL) + 4; })
// set height & style of text block
.attr("y", function(d, i) { return i * _entryHeight + _entryGap + 14; })
.attr("style", "font-size: 12px; font-family: Arial");
// lay out text block - date followed by event title
textBase.append("tspan")
.attr("style", "stroke-width:1; stroke:#000000; kerning:1.2")
.text(function(d) { return _date(d); })
textBase.append("tspan")
.attr("x", function(d, i) { return ((i%2 == 0) ? _leftColMarginL : _rightColMarginL) + 4; })
.attr("dy", 14)
.text(function(d) { return _title(d); } );
if (_details)
textBase.append("tspan")
.attr("x", function(d, i) { return ((i%2 == 0) ? _leftColMarginL : _rightColMarginL) + 4; })
.attr("dy", 14)
.text(function(d) { return _details(d); } );
});
}
my.width = function(value) {
if (!arguments.length) return _width;
_width = value;
_midMargin = _width/2;
_leftColMarginR = _midMargin - 13;
_rightColMarginL = _midMargin + 13;
_leftColMarginL = 5;
_rightColMarginR = _width - 5;
return my;
}
// configuration accessors and setters
my.entryHeight = function(value) {
if (!arguments.length) return _entryHeight;
_entryHeight = value;
return my;
}
my.entryGap = function(value) {
if (!arguments.length) return _entryGap;
_entryGap = value;
return my;
}
my.details = function(value) {
if (!arguments.length) return _details;
_details = value;
return my;
}
my.title = function(value) {
if (!arguments.length) return _title;
_title = value;
return my;
}
my.date = function(value) {
if (!arguments.length) return _date;
_date = value;
return my;
}
my.width(600);
return my;
}
@kylelk
Copy link

kylelk commented Jan 27, 2014

Thank you, This is very good work!

@tsenga
Copy link
Author

tsenga commented Feb 27, 2014

Thx. Much appreciated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment