Skip to content

Instantly share code, notes, and snippets.

@guypursey
Last active August 19, 2016 12:49
Show Gist options
  • Save guypursey/620adb6185d7a6446f1ab34f9b579a1d to your computer and use it in GitHub Desktop.
Save guypursey/620adb6185d7a6446f1ab34f9b579a1d to your computer and use it in GitHub Desktop.
Tweaked timeline in D3.js
license: mit
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<title>Chronological Diagram of Asia</title>
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.v2.js"></script>
<style type="text/css">
.chart {
shape-rendering: crispEdges;
}
.mini text {
font: 9px sans-serif;
}
.main text {
font: 12px sans-serif;
}
.miniItem0 {
fill: darksalmon;
stroke-width: 6;
}
.miniItem1 {
fill: darkolivegreen;
fill-opacity: .7;
stroke-width: 6;
}
.miniItem2 {
fill: slategray;
fill-opacity: .7;
stroke-width: 6;
}
.brush .extent {
stroke: gray;
fill: dodgerblue;
fill-opacity: .365;
}
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
</style>
</head>
<body>
<script type="text/javascript">
//data
var lanes = ["Chinese","Japanese","Korean"],
laneLength = lanes.length,
items = [{"lane": 0, "id": "Qin", "start": 5, "end": 205},
{"lane": 0, "id": "Jin", "start": 265, "end": 420},
{"lane": 0, "id": "Sui", "start": 580, "end": 615},
{"lane": 0, "id": "Tang", "start": 620, "end": 900},
{"lane": 0, "id": "Song", "start": 960, "end": 1265},
{"lane": 0, "id": "Yuan", "start": 1270, "end": 1365},
{"lane": 0, "id": "Ming", "start": 1370, "end": 1640},
{"lane": 0, "id": "Qing", "start": 1645, "end": 1910},
{"lane": 1, "id": "Yamato", "start": 300, "end": 530},
{"lane": 1, "id": "Asuka", "start": 550, "end": 700},
{"lane": 1, "id": "Nara", "start": 710, "end": 790},
{"lane": 1, "id": "Heian", "start": 800, "end": 1180},
{"lane": 1, "id": "Kamakura", "start": 1190, "end": 1330},
{"lane": 1, "id": "Muromachi", "start": 1340, "end": 1560},
{"lane": 1, "id": "Edo", "start": 1610, "end": 1860},
{"lane": 1, "id": "Meiji", "start": 1870, "end": 1900},
{"lane": 1, "id": "Taisho", "start": 1910, "end": 1920},
{"lane": 1, "id": "Showa", "start": 1925, "end": 1985},
{"lane": 1, "id": "Heisei", "start": 1990, "end": 1995},
{"lane": 2, "id": "Three Kingdoms", "start": 10, "end": 670},
{"lane": 2, "id": "North and South States", "start": 690, "end": 900},
{"lane": 2, "id": "Goryeo", "start": 920, "end": 1380},
{"lane": 2, "id": "Joseon", "start": 1390, "end": 1890},
{"lane": 2, "id": "Korean Empire", "start": 1900, "end": 1945}]
items = items.map(v => {
v.start = new Date(`${v.start < 10 ? "0" : ""}${v.start < 100 ? "00" : ""}${v.start}-01-01`)
v.end = new Date(`${v.end}-12-31`)
return v
})
timeBegin = new Date("0000")
timeEnd = new Date("2000")
</script>
<script type="text/javascript">
var m = [20, 15, 15, 120] //top right bottom left
var w = 960 - m[1] - m[3]
var h = 500 - m[0] - m[2]
var f = 20
var miniHeight = laneLength * 12 + 50
var mainHeight = h - miniHeight - 50
//scales
var x = d3.time.scale()
.domain([timeBegin, timeEnd])
.range([0, w])
var x1 = d3.time.scale()
.domain([timeBegin, timeEnd])
.range([0, w])
var y1 = d3.scale.linear()
.domain([0, laneLength])
.range([0, mainHeight])
var y2 = d3.scale.linear()
.domain([0, laneLength])
.range([0, miniHeight])
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(d3.time.years, 100)
.tickFormat(d3.time.format("%Y"))
.tickSize(5)
.tickPadding(1)
var x1Axis = d3.svg.axis()
.scale(x1)
.orient("bottom")
.ticks(d3.time.years, 100)
.tickFormat(d3.time.format("%Y"))
.tickSize(5)
.tickPadding(1)
var chart = d3.select("body")
.append("svg")
.attr("width", w + m[1] + m[3])
.attr("height", h + m[0] + m[2])
.attr("class", "chart")
chart.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", w)
.attr("height", mainHeight)
var main = chart.append("g")
.attr("transform", `translate(${m[3]}, ${m[0]})`)
.attr("width", w)
.attr("height", mainHeight)
.attr("class", "main")
var mini = chart.append("g")
.attr("transform", `translate(${m[3]}, ${mainHeight + m[0]})`)
.attr("width", w)
.attr("height", miniHeight)
.attr("class", "mini")
var focus = main.append("g")
.attr("class", "focus")
focus.append("g")
.attr("class", "x1 axis")
.call(x1Axis)
//main lanes and texts
main.append("g").selectAll(".laneLines")
.data(items)
.enter().append("line")
.attr("x1", m[1])
.attr("y1", d => y1(d.lane) + f)
.attr("x2", w)
.attr("y2", d => y1(d.lane) + f)
.attr("stroke", "lightgray")
main.append("g").selectAll(".laneText")
.data(lanes)
.enter().append("text")
.text(d => d)
.attr("x", -m[1])
.attr("y", (d, i) => y1(i + .5) + f)
.attr("dy", ".5ex")
.attr("text-anchor", "end")
.attr("class", "laneText")
//mini lanes and texts
mini.append("g").selectAll(".laneLines")
.data(items)
.enter().append("line")
.attr("x1", m[1])
.attr("y1", d => y2(d.lane) + f)
.attr("x2", w)
.attr("y2", d => y2(d.lane) + f)
.attr("stroke", "lightgray")
mini.append("g").selectAll(".laneText")
.data(lanes)
.enter().append("text")
.text(d => d)
.attr("x", -m[1])
.attr("y", (d, i) => y2(i + .5) + f)
.attr("dy", ".5ex")
.attr("text-anchor", "end")
.attr("class", "laneText")
var itemRects = main.append("g")
.attr("clip-path", "url(#clip)")
//mini item rects
mini.append("g").selectAll("miniItems")
.data(items)
.enter().append("rect")
.attr("class", d => `miniItem${d.lane}`)
.attr("x", d => x(d.start))
.attr("y", d => y2(d.lane + .5) - 5 + f)
.attr("width", d => x(d.end) - x(d.start))
.attr("height", 10)
//mini labels
mini.append("g").selectAll(".miniLabels")
.data(items)
.enter().append("text")
.text(d => d.id)
.attr("x", d => x(d.start))
.attr("y", d => y2(d.lane + .5) + f)
.attr("dy", ".5ex")
//brush
var brush = d3.svg.brush()
.x(x)
.on("brush", display)
mini.append("g")
.attr("class", "x brush")
.call(brush)
.selectAll("rect")
.attr("y", 1 + f)
.attr("height", miniHeight - 1)
display()
function display() {
var rects
var labels
var minExtent = brush.empty() ? x1.domain()[0] : brush.extent()[0]
var maxExtent = brush.empty() ? x1.domain()[1] : brush.extent()[1]
var visItems = items.filter(d => d.start < maxExtent && d.end > minExtent)
if (!brush.empty()) {
mini.select(".brush")
.call(brush.extent([minExtent, maxExtent]))
}
x1.domain([minExtent, maxExtent])
focus.select(".x1.axis").call(x1Axis)
//update main item rects
rects = itemRects.selectAll("rect")
.data(visItems, d => d.id)
.attr("x", d => x1(d.start))
.attr("width", d => x1(d.end) - x1(d.start))
rects.enter().append("rect")
.attr("class", d => "miniItem" + d.lane)
.attr("x", d => x1(d.start))
.attr("y", d => y1(d.lane) + 10 + f)
.attr("width", d => x1(d.end) - x1(d.start))
.attr("height", d => .8 * y1(1))
rects.exit().remove()
//update the item labels
labels = itemRects.selectAll("text")
.data(visItems, d => d.id)
.attr("x", d => x1(Math.max(d.start, minExtent) + 2))
labels.enter().append("text")
.text(d => d.id)
.attr("x", d => x1(Math.max(d.start, minExtent)))
.attr("y", d => y1(d.lane + .5))
.attr("text-anchor", "start")
labels.exit().remove()
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment