It's a timeline with a line graph presenting some sort of measure. Click along the timeline to place a marker representing a milestone, which adds an entry to the form below. Some scripting elements based on : http://bl.ocks.org/mikehadlow/93b471e569e31af07cd3
Last active
February 5, 2016 22:03
-
-
Save dankronstal/e8a97d98f7592f490c81 to your computer and use it in GitHub Desktop.
Timeline with markers
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<style> | |
.mark{ | |
background-image:url('https://bl.ocks.org/dankronstal/raw/e8a97d98f7592f490c81/marker.png'); | |
height:32px; | |
width:32px; | |
position:absolute; | |
opacity: .7; | |
} | |
svg { | |
pointer-events: all; | |
} | |
.x.axis path { | |
fill: none; | |
stroke: black; | |
stroke-width: 1px; | |
opacity: 0.8; | |
} | |
text { | |
color: black; | |
stroke: black; | |
fill: black; | |
} | |
.hover-line { | |
stroke: black; | |
fill: none; | |
stroke-width: 1px; | |
} | |
.inlet { | |
stroke: red; | |
color: red; | |
stroke-width: 2px; | |
opacity: 0.8; | |
fill: none; | |
} | |
.timeline { | |
stroke: steelblue; | |
color: steelblue; | |
stroke-width: 2px; | |
opacity: 0.8; | |
fill: none; | |
} | |
.axis path { | |
fill: none; | |
stroke: #000; | |
stroke-width: 2.5px; | |
shape-rendering: crispEdges; | |
} | |
.axis line { | |
fill: none; | |
stroke: #000; | |
stroke-width: 1.6px; | |
} | |
.axis .minor { | |
stroke: #000; | |
stroke-width: 0.4px; | |
} | |
</style> | |
<body> | |
<p>Click the timeline to add an event</p> | |
<div class="lifespan"> | |
</div> | |
<div class="milestones"> | |
</div> | |
<script src="https://d3js.org/d3.v3.min.js"></script> | |
<script> | |
var data = {"timeline":[]}; | |
for(i=0; i<50; i++){ | |
var n = Math.floor(Math.random() * 10); | |
data["timeline"][i] = {"value": n, "date": new Date(i+ (new Date()).getFullYear(),1,1)}; | |
} | |
console.log(data); | |
// Widths and margins. | |
var div_width = parseInt(d3.select('body').style('width').replace('px','')); | |
var graph_width = (div_width > 695) ? div_width: 695; | |
var margin = {top: 20, right: 20, bottom: 30, left: 0}, | |
width = graph_width - margin.left - margin.right, | |
height = 200 - margin.top - margin.bottom; | |
// X and Y scales. | |
var x = d3.time.scale(); | |
var y = d3.scale.linear().range([height, 0]); | |
// Construct our SVG object. | |
var svg = d3.select(".lifespan").append("svg") | |
.attr("width", width + margin.left + margin.right) | |
.attr("height", height + margin.top + margin.bottom) | |
.append("g") | |
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); | |
// Line functions. | |
var line = d3.svg.line().interpolate("monotone"); | |
// X-axis. | |
var xAxis = d3.svg.axis() | |
.orient("bottom"); | |
var xAxisG = svg.append("g") | |
.attr("class", "x axis") | |
.attr("transform", "translate(0," + height + ")"); | |
// Date parsing. | |
var parseDate = d3.time.format("%Y-%m-%d"); | |
// Hover line. | |
var hoverLineGroup = svg.append("g") | |
.attr("class", "hover-line"); | |
var hoverLine = hoverLineGroup | |
.append("line") | |
.attr("x1", 10).attr("x2", 10) | |
.attr("y1", 0).attr("y2", height); | |
// Hide hover line by default. | |
hoverLine.style("opacity", 1e-6); | |
x.domain(d3.extent(data.timeline, function(d) { return d.date; })); | |
var timelineMax = d3.max(data.timeline, function(d) { return d.value; }) | |
var max = timelineMax; | |
console.log('new max y', max) | |
y.domain([0, max]); | |
// Width of bars, without padding. | |
var barRawWidth = width / data['timeline'].length; | |
var barPadding = 0; | |
var xStart = barPadding + (barRawWidth/2); | |
// Update x scale function. | |
x.range([xStart, width-xStart]); | |
// Update line functions. | |
line.x(function(d) { return x(d.date); }) | |
.y(function(d) { return y(d.value); }); | |
// Update x-axis. | |
xAxis.scale(x); | |
d3.select(".x.axis") | |
.transition().duration(1000) | |
.call(xAxis); | |
xAxisG.selectAll("line").data(x.ticks(64), function(d) { return d; }) | |
.enter() | |
.append("line") | |
.attr("class", "minor") | |
.attr("y1", 0) | |
.attr("y2", -5) | |
.attr("x1", x) | |
.attr("x2", x); | |
// Draw line | |
var timeline = svg.selectAll("path.timeline").data([data.timeline], function(d) { return d.date; }); | |
timeline.transition().duration(100).style("opacity", 1e-6).transition().duration(1000).attr('d', line).style("opacity", 1.0); | |
timeline.enter().append("path") | |
.attr("class", "timeline") | |
.attr("d", line) | |
.style("opacity", 1e-6) | |
.transition().duration(1000) | |
.style("opacity", 1.0); | |
var milestones = d3.select(".milestones"); | |
// Add mouseover events. | |
var bisectDate = d3.bisector(function(d) { return d.date; }).left; | |
d3.select(".lifespan") | |
.on("click", function() { | |
//console.log('mouseover'); | |
var mouse = d3.mouse(this); | |
var mouseDate = x.invert(mouse[0]); | |
var v = bisectDate(data["timeline"],mouseDate); | |
var d0 = data["timeline"][v - 1] | |
var d1 = data["timeline"][v]; | |
// work out which date value is closest to the mouse | |
var m = mouseDate - d0.date > d1.date - mouseDate ? d1 : d0; | |
var x1 = x(m.date)-10; | |
var y1 = y(m.value)+50; | |
d3.select(".lifespan") | |
.append("div") | |
.attr("class",function(d){ return "mark mark"+v;}) | |
//for placement on interpolated line | |
.style("top",function(d){ return y1 + "px"; }) | |
.style("left",function(d){ return x1 + "px"; }); | |
var milestone = milestones | |
.append("div") | |
.attr("class", "milestone") | |
.attr("class",function(d){ return "mark"+v; }); | |
milestone.append("span") | |
.style("padding-left","2em") | |
.style("padding-right","2em") | |
.text(function(d){ return parseDate(m.date); }); | |
milestone.append("input") | |
.attr("type","text") | |
.attr("placeholder","Milestone Name"); | |
var btn = milestone.append("button") | |
.text("delete") | |
.attr("class",function(d){ return "mark"+v; }) | |
.on("click", function(){ | |
d3.selectAll(".mark"+v).remove(); | |
}); | |
}) | |
.on("mousemove", function() { | |
var x = d3.mouse(this)[0]; | |
hoverLine.attr("x1", x).attr("x2", x).style("opacity", 1); | |
}) | |
.on("mouseout", function() { | |
hoverLine.style("opacity", 1e-6); | |
}); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment