A development on bunkat's timeline in d3.js.
Tweaked timeline in D3.js
<!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