Skip to content

Instantly share code, notes, and snippets.

@dankronstal
Last active February 5, 2016 22:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dankronstal/e8a97d98f7592f490c81 to your computer and use it in GitHub Desktop.
Save dankronstal/e8a97d98f7592f490c81 to your computer and use it in GitHub Desktop.
Timeline with markers

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

<!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