Skip to content

Instantly share code, notes, and snippets.

@chris-creditdesign
Last active August 29, 2015 14:09
Show Gist options
  • Save chris-creditdesign/ef625dbb4249ade8efa2 to your computer and use it in GitHub Desktop.
Save chris-creditdesign/ef625dbb4249ade8efa2 to your computer and use it in GitHub Desktop.
A Pen by chris-creditdesign.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>D3js Timeline</title>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<div class="infochart">
<div class="pop-up" style="opacity: 0;"><a class="closer" href="#">X</a><h3></h3><p></p></div>
</div>
<script src="./script.js" charset="utf-8"></script>
//data
var lanesObjects = [ {"lane": 0, 'location':'Jordan'},
{"lane": 1, 'location':'Saudi Arabia'},
{"lane": 2, 'location':'Qatar'},
{"lane": 3, 'location':'The Netherlands'},
{"lane": 4, 'location':'Germany'},
{"lane": 5, 'location':'Switzerland'},
{"lane": 6, 'location':'United Kingdom'}
],
laneLength = lanesObjects.length,
items = [ {'lane': 0, 'headline':'Two fatal cases caused by coronavirus', 'location':'Jordan', 'id': 'A cluster of 11 cases of respiratory disease at a hospital in Amman, Jordan, tests negative for coronaviruses. But <a href="http://www.who.int/csr/don/2012_11_30/en/index.html" target="_blank">retrospective testing in November</a> reveals that the two fatal cases were caused by a new coronavirus.', "start": "24 April 2012", "end": "24 April 2012"},
{'lane': 1, 'headline':'The first identified infection with the new coronavirus', 'location':'Saudi Arabia', 'id': 'The <a href="http://www.nature.com/news/sars-veterans-tackle-coronavirus-1.11513" target="_blank">first identified infection with the new coronavirus</a> involves a 60-year-old man, who is admitted to the Dr Soliman Fakeeh Hospital in Jeddah, Saudi Arabia, with severe pneumonia and acute renal failure on 13 June. He dies on 24 June.', 'start': '13 June 2012', 'end': '13 June 2012'},
{'lane': 1, 'headline':'ProMED announcement', 'location':'Saudi Arabia', 'id': 'Ali Mohamed Zaki, a microbiologist at Dr Soliman Fakeeh Hospital in Jeddah, Saudi Arabia, announces on online disease-reporting system <a href="http://www.promedmail.org/direct.php?id=20120920.1302733" target="_blank">ProMED</a> that the infection was caused by a novel coronavirus.', 'start': '20 September 2012', 'end': '20 September 2012'},
{'lane': 2, 'headline':'Second case confirmed', 'location':'Qatar', 'id': 'The <a href="http://www.hpa.org.uk/webw/HPAweb&amp;Page&amp;HPAwebAutoListName/Page/1317136202637" target="_blank">UK Health Protection Agency reports</a> that it has confirmed a second case: a 49-year-old man from Qatar who fell ill on 3 September with similar symptoms.', 'start': '22 September 2012', 'end': '22 September 2012'},
{'lane': 3, 'headline':'Genome sequence in GenBank', 'location':'The Netherlands', 'id': 'Researchers at the Erasmus Medical Centre in Rotterdam, the Netherlands, deposit the coronavirus genome sequence in <a href="http://www.ncbi.nlm.nih.gov/nuccore/JX869059" target="_blank">GenBank</a>, and give it the provisional name human betacoronavirus 2c EMC/2012.', 'start': '26 September 2012', 'end': '26 September 2012'},
{'lane': 4, 'headline':'First diagnostic tests', 'location':'Germany', 'id': 'German <a href="http://www.eurosurveillance.org/ViewArticle.aspx?ArticleId=20285" target="_blank">researchers publish the first diagnostic tests</a> for the new coronavirus.', 'start': '27 September 2012', 'end': '27 September 2012'},
{'lane': 1, 'headline':'New case confirmed', 'location':'Saudi Arabia', 'id': 'A <a href="http://www.promedmail.org/direct.php?id=20121104.1391285" target="_blank">new case</a> of coronavirus infection is confirmed in Saudi Arabia. ', 'start': '4 November 2012', 'end': '4 November 2012'},
{'lane': 1, 'headline':'Four new cases confirmed', 'location':'Saudi Arabia', 'id': 'Saudi Arabia <a href="http://www.who.int/csr/don/2012_11_23/en/index.html" target="_blank">reports a cluster of four cases</a> of respiratory disease. Three are subsequently confirmed as having the coronavirus, and two of the patients die.', 'start': '23 November 2012', 'end': '23 November 2012'},
{'lane': 2, 'headline':'New case confirmed', 'location':'Quatar', 'id': 'A new coronavirus case is <a href="http://www.ecdc.europa.eu/en/publications/Publications/20121207-Novel-coronavirus-rapid-risk-assessment.pdf" target="_blank">reported in Qatar</a>.', 'start': '23 November 2012', 'end': '23 November 2012'},
{'lane': 5, 'headline':'Global surveillance effort', 'location':'Switzerland', 'id': 'The World Health Organization in Geneva, Switzerland, <a href="http://www.who.int/csr/disease/coronavirus_infections/InterimRevisedSurveillanceRecommendations_nCoVinfection_03Dec12.pdf" target="_blank">urges a global surveillance effort</a> for the coronavirus, and advises health authorities to thoroughly investigate any clusters of severe respiratory disease, including testing suspect cases for the virus.', 'start': '3 December 2012', 'end': '3 December 2012'},
{'lane': 1, 'headline':'Patient dies', 'location':'Saudi Arabia', 'id': 'Patient <a href="http://www.who.int/csr/don/2013_02_21/en/index.html" target="_blank">dies in Saudi Arabia,</a> no other details given.', 'start': '10 February 2013', 'end': '10 February 2013'},
{'lane': 6, 'headline':'Patient dies', 'location':'United Kingdom', 'id': '<a href="http://www.hpa.org.uk/webw/HPAweb&amp;HPAwebStandard/HPAweb_C/1317138119464" target="_blank">Third case in a UK cluster confirmed.</a> The cluster of cases, one of whom died, raises concerns as strong evidence that human-to-human transmission likely occurred. One case also showed only mild illness suggesting mild or asymptomatic cases may be going undetected.', 'start': '15 February 2013', 'end': '15 February 2013'},
{'lane': 1, 'headline':'Patient dies', 'location':'Saudi Arabia', 'id': '69-year-old male <a href="http://www.who.int/csr/don/2013_03_06/en/index.html" target="_blank">dies in unnamed location</a> in Saudi Arabia.', 'start': '19 February 2013', 'end': '19 February 2013'},
{'lane': 5, 'headline':'14 cases confirmed so far', 'location':'Switzerland', 'id': 'The World Health Organization has so far reported a total of 14 cases, including 8 deaths, in Saudi Arabia, Qatar, Jordan, and the United Kingdom.', 'start': '11 March 2013', 'end': '11 March 2013'}
],
timeBegin = getDate("2012"),
timeEnd = getDate("01 July 2013"),
origin,
duration = 250;
/* Helper function to format and parse date from data */
function getDate(d) {
return new Date(d);
}
var marginTop = 20,
marginRight = 25,
marginBottom = 15,
marginLeft = 120,
padding = 2,
width = 630 - marginRight - marginLeft,
height = 500 - marginTop - marginBottom,
miniHeight = laneLength * 12 + 50,
mainHeight = height - miniHeight - 50;
// Scales
var x = d3.time.scale()
.domain([timeBegin, timeEnd])
.range([0, width]);
var x1 = d3.time.scale()
.range([0, width]);
var y1 = d3.scale.linear()
.domain([0, laneLength])
.range([padding, mainHeight]);
var y2 = d3.scale.linear()
.domain([0, laneLength])
.range([0, miniHeight]);
var radius = y1(0.1);
var miniRadius = y2(0.25);
var chart = d3.select(".infochart")
.append("svg")
.attr("width", width + marginRight + marginLeft )
.attr("height", height + marginTop + marginBottom )
.attr("class", "chart");
chart.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height" , mainHeight + 2*marginBottom);
var main = chart.append("g")
.attr("transform", "translate(" + marginLeft + "," + marginTop + ")" )
.attr("width", width)
.attr("height", mainHeight)
.attr("class", "main");
var mini = chart.append("g")
.attr("transform", "translate(" + marginLeft + "," + (mainHeight + marginTop*3) + ")" )
.attr("width", width)
.attr("height", miniHeight)
.attr("class", "mini");
// main lanes and text
main.append("g").selectAll("line")
.data(lanesObjects)
.enter().append("line")
.attr("x1", 0)
.attr("y1", function(d) { return y1(d.lane); })
.attr("x2", width)
.attr("y2", function(d) { return y1(d.lane); } )
.attr("class", "mainLines");
main.append("g").selectAll(".laneText")
.data(lanesObjects)
.enter()
.append("text")
.text(function(d) { return d.location;} )
.attr("x", -marginRight)
.attr("y", function(d, i) { return y1(i + .5); } )
.attr("dy", ".5ex")
.attr("text-anchor", "end")
.attr("class", "laneText");
//mini lanes and text
mini.append("g").selectAll("line")
.data(lanesObjects)
.enter()
.append("line")
.attr("x1", 0)
.attr("y1", function(d) { return y2(d.lane); } )
.attr("x2", width)
.attr("y2", function(d) { return y2(d.lane); } )
.attr("class", "miniLines");
// mini item rects
mini.append("g").selectAll("circle")
.data(items)
.enter()
.append("circle")
.attr("class", function(d) { return "location" + d.lane; } )
.attr("cx", function(d) { return x(getDate(d.start)); } )
.attr("cy", function(d) { return y2(d.lane) + 2*miniRadius; } )
.attr("r", function(d) { return miniRadius } );
/* no mini labels for now */
/*mini.append("g").selectAll("text")
.data(items)
.enter()
.append("text")
.text(function(d) { return d.id; } )
.attr("x", function(d) { return x(getDate(d.start)) } )
.attr("y", function(d) { return y2(d.lane); } )
.attr("text-anchor", "middle");*/
mini.append("g").selectAll(".laneText")
.data(lanesObjects)
.enter()
.append("text")
.text(function(d) { return d.location; } )
.attr("x", -marginRight)
.attr("y", function(d, i) { return y2(i + .5); } )
.attr("dy", ".5ex")
.attr("text-anchor", "end")
.attr("class", "laneText");
// draw the x axis
var xDateAxis = d3.svg.axis()
.scale(x)
.tickSize(-miniHeight, 0)
.orient('bottom');
mini.append('g')
.attr('transform', 'translate(0,' + miniHeight + ')')
.attr('class', 'axis')
.call(xDateAxis);
var x1DateAxis = d3.svg.axis()
.scale(x1)
.ticks(5)
.tickSize(-mainHeight+padding, 0)
.orient('bottom');
main.append('g')
.attr('transform', 'translate(0,' + mainHeight + ')')
.attr('class', 'axis');
// brush
var brush = d3.svg.brush()
.x(x)
.extent([timeBegin,timeEnd])
.on("brush", display);
mini.append("g")
.attr("class", "x brush")
.call(brush)
.selectAll("rect")
.attr("height", miniHeight);
var circleHolder = main.append("g")
.attr("clip-path", "url(#clip)");
function display() {
var rects,
circles,
labels,
minExtent = brush.extent()[0],
maxExtent = brush.extent()[1];
if (minExtent.getTime() === maxExtent.getTime()) {
minExtent = timeBegin;
maxExtent = timeEnd;
}
var visItems = items.filter( function(d) {return getDate(d.start) < 1.25*maxExtent && getDate(d.end) > 0.75*minExtent; } );
d3.select(".pop-up")
.classed("hidden", true);
mini.select(".brush")
.call(brush.extent([minExtent, maxExtent]));
x1.domain([minExtent, maxExtent]);
main.select('.axis')
.call(x1DateAxis);
/* Build the circles for the main box */
circles = circleHolder.selectAll("circle")
.data(visItems, function(d) { return d.id; } )
.attr("class", function(d) { return "location" + d.lane; } )
.attr("cx", function(d) { return x1(getDate(d.start)); } )
.attr("r", radius );
circles.enter().append("circle")
.attr("class", function(d) { return "location" + d.lane; } )
.attr("cx", function(d) { return x1(getDate(d.start)); } )
.attr("cy", function(d) { return y1(d.lane) + 2*radius; } )
.attr("r", radius );
circles.exit().remove();
circles.on("click", function(d) {
d3.select(".pop-up > p").html(d.id);
d3.select(".pop-up > h3").html(d.start);
d3.select(".pop-up")
.classed("hidden", false);
d3.select(".pop-up")
.transition()
.duration(duration)
.style("opacity", 1);
/*d3.select(this)
.transition()
.duration(duration)
.attr("r", 1.5*width )
.each("end", function(d){
});*/
});
// update the item labels
labels = circleHolder.selectAll("text")
.data(visItems, function(d) { return d.id } )
.attr("x", function(d) { return x1(getDate(d.start)) + 1.5*radius; } );
labels.enter().append("text")
.text(function(d) { return d.headline; } )
.attr("x", function(d) { return x1(getDate(d.start)) + 1.5*radius; } )
.attr("y", function(d) { return y1(d.lane) + 2.5*radius; } )
.attr("text-anchor", "start");
labels.exit().remove();
}
d3.select(".closer").on("click", function(e){
d3.select(".pop-up")
.transition()
.duration(duration)
.style("opacity", 0);
d3.select(".pop-up")
.classed("hidden", true);
display();
d3.event.preventDefault();
return false;
} );
display();
body {
color: #000000;
font-family: Verdana,arial,Helvetica,sans-serif;
font-size: 90%;
margin: 50px;
}
/*body svg {
background-color: #FFFFFF;
margin: 50px;
}*/
.infochart {
position: relative;
width: 630px;
}
.pop-up {
background-color: #fff;
margin: 50px 50px 50px 145px;
padding: 10px;
position: absolute;
width: 415px;
-webkit-box-shadow: 4px 4px 2px #999;
-moz-box-shadow: 4px 4px 2px #999;
box-shadow: 4px 4px 2px #999;
opacity: 0;
}
.pop-up h3 {
margin: 35px 0 0;
}
.pop-up.hidden {
display: none;
z-index: -1;
}
/*.pop-up a {
color: cornflowerblue;
}*/
.closer {
cursor: pointer;
z-index:1000;
color: #000;
font: bold 20px verdana, sans-serif;
text-decoration:none;
float: right;
margin-top: -5px;
}
.location0 {
fill: #B01117;
}
.location1 {
fill: #DD322D;
}
.location2 {
fill: #F37920;
}
.location3 {
fill: #B4892D;
}
.location4 {
fill: #FFC10E;
}
.location5 {
fill: #859A3B;
}
.location6 {
fill: #00652E;
}
svg .main text {
font: 11px sans-serif;
}
svg .mini text, svg .axis text, .laneText {
font: 9px sans-serif;
}
svg .miniItem0 {
fill: #EE9996;
stroke-width: 6;
}
svg .miniItem1 {
fill: #97C5E5;
stroke-width: 6;
}
svg .miniItem2 {
fill: #C2CD9D;
stroke-width: 6;
}
svg .brush .extent {
stroke: gray;
fill: #D5E8F4;
fill-opacity: .2;
shape-rendering: crispEdges;
}
svg .axis path,
svg .axis line {
fill: none;
stroke: #aaa;
shape-rendering: crispEdges;
}
svg .main circle {
cursor: pointer;
-webkit-transition: all 0.1s;
-moz-transition: all 0.1s;
-ms-transition: all 0.1s;
-o-transition: all 0.1s;
transition: all 0.1s;
}
svg .mainLines, svg .miniLines {
stroke-width: 1px;
stroke: #aaa;
shape-rendering: crispEdges;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment