Skip to content

Instantly share code, notes, and snippets.

@codespaced
Last active September 7, 2016 11:37
Show Gist options
  • Select an option

  • Save codespaced/d9cacea29e547f13bf1cc401a958e321 to your computer and use it in GitHub Desktop.

Select an option

Save codespaced/d9cacea29e547f13bf1cc401a958e321 to your computer and use it in GitHub Desktop.
D3 drag dateline example
var data = [{"Day":1,"Base":20.8,"Current":0,"Projected":16.37},{"Day":2,"Base":41.61,"Current":0,"Projected":32.74},{"Day":3,"Base":62.41,"Current":0,"Projected":49.11},{"Day":4,"Base":83.22,"Current":0,"Projected":65.47},{"Day":5,"Base":104.02,"Current":0,"Projected":81.84},{"Day":6,"Base":124.83,"Current":120,"Projected":98.21},{"Day":7,"Base":145.63,"Current":120,"Projected":114.58},{"Day":8,"Base":166.43,"Current":120,"Projected":130.95},{"Day":9,"Base":187.24,"Current":120,"Projected":147.32},{"Day":10,"Base":208.04,"Current":120,"Projected":163.68},{"Day":11,"Base":228.85,"Current":120,"Projected":180.05},{"Day":12,"Base":249.65,"Current":120,"Projected":196.42},{"Day":13,"Base":270.46,"Current":286,"Projected":212.79},{"Day":14,"Base":291.26,"Current":286,"Projected":229.16},{"Day":15,"Base":312.07,"Current":286,"Projected":245.53},{"Day":16,"Base":332.87,"Current":286,"Projected":261.89},{"Day":17,"Base":353.67,"Current":286,"Projected":278.26},{"Day":18,"Base":374.48,"Current":286,"Projected":294.63},{"Day":19,"Base":395.28,"Current":417,"Projected":311},{"Day":20,"Base":416.09,"Current":417,"Projected":327.37},{"Day":21,"Base":436.89,"Current":417,"Projected":343.74},{"Day":22,"Base":457.7,"Current":417,"Projected":360.11},{"Day":23,"Base":478.5,"Current":417,"Projected":376.47},{"Day":24,"Base":499.3,"Current":417,"Projected":392.84},{"Day":25,"Base":520.11,"Current":417,"Projected":409.21},{"Day":26,"Base":540.91,"Current":522,"Projected":425.58},{"Day":27,"Base":561.72,"Current":522,"Projected":441.95},{"Day":28,"Base":582.52,"Current":522,"Projected":458.32},{"Day":29,"Base":603.33,"Current":522,"Projected":474.68},{"Day":30,"Base":624.13,"Current":522,"Projected":491.05},{"Day":31,"Base":644.93,"Current":522,"Projected":507.42},{"Day":32,"Base":665.74,"Current":522,"Projected":523.79},{"Day":33,"Base":686.54,"Current":622,"Projected":540.16},{"Day":34,"Base":707.35,"Current":622,"Projected":556.53},{"Day":35,"Base":728.15,"Current":622,"Projected":572.89},{"Day":36,"Base":748.96,"Current":622,"Projected":589.26},{"Day":37,"Base":769.76,"Current":622,"Projected":605.63},{"Day":38,"Base":790.57,"Current":622,"Projected":622},{"Day":39,"Base":811.37,"Current":-1,"Projected":638.37},{"Day":40,"Base":832.17,"Current":-1,"Projected":654.74},{"Day":41,"Base":852.98,"Current":-1,"Projected":671.11},{"Day":42,"Base":873.78,"Current":-1,"Projected":687.47},{"Day":43,"Base":894.59,"Current":-1,"Projected":703.84},{"Day":44,"Base":915.39,"Current":-1,"Projected":720.21},{"Day":45,"Base":936.2,"Current":-1,"Projected":736.58},{"Day":46,"Base":957,"Current":-1,"Projected":752.95},{"Day":47,"Base":977.8,"Current":-1,"Projected":769.32},{"Day":48,"Base":998.61,"Current":-1,"Projected":785.68},{"Day":49,"Base":1019.41,"Current":-1,"Projected":802.05},{"Day":50,"Base":1040.22,"Current":-1,"Projected":818.42},{"Day":51,"Base":1061.02,"Current":-1,"Projected":834.79},{"Day":52,"Base":1081.83,"Current":-1,"Projected":851.16},{"Day":53,"Base":1102.63,"Current":-1,"Projected":867.53},{"Day":54,"Base":1123.43,"Current":-1,"Projected":883.89},{"Day":55,"Base":1144.24,"Current":-1,"Projected":900.26},{"Day":56,"Base":1165.04,"Current":-1,"Projected":916.63},{"Day":57,"Base":1185.85,"Current":-1,"Projected":933},{"Day":58,"Base":1206.65,"Current":-1,"Projected":949.37},{"Day":59,"Base":1227.46,"Current":-1,"Projected":965.74},{"Day":60,"Base":1248.26,"Current":-1,"Projected":982.11}];
var margin = { top: 70, right: 20, bottom: 50, left: 60 },
width = 900 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// setup the pixel scale for x & y and the color scale for z
var x = d3.scaleLinear().range([0, width]),
y = d3.scaleLinear().range([height, 0]),
z = d3.scaleOrdinal(d3.schemeCategory10);
// setup the range of days and values
var line = d3.line()
.x(function (d) { return x(d.day); })
.y(function (d) { if (d.value >= 0) return y(d.value); });
var entries = [];
var tmp = {};
var keys = [];
// get the data into a useable format
['Base','Current','Projected'].forEach( function(d){
keys.push(d);
entries.push({id:d,values:[]});
})
entries.forEach(function(d){
$.each(data, function () {
d.values.push(
{
day: this["Day"],
value: this[d.id]
});
})
});
// get some aggregate values
var indexes = getAggregateData(entries, keys);
// setup the x domain to go from 0 to the end of data
x.domain(d3.extent(data, function (d) { return parseInt(d.Day); }));
// setup the y domain to go from 0 to our max data value
y.domain([
0,
Math.max(d3.max(entries, function (c) { return d3.max(c.values, function (d) { return d.value; }); }), 0)
]);
// color
z.domain(keys);
// we first add an svg to our target container
var svg = d3.select("#chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom),
// then a group that we shift over and up to hold stuff
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// x axis
var xaxis = g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x)
.ticks(60));
// x axis title
g.append("text")
.attr("text-anchor", "middle") // this makes it easy to center the text as the transform is applied to the anchor
.attr("transform", "translate(" + (width / 2) + "," + (height + margin.bottom / 1.25) + ")") // centre below axis
.text("Day");
// y axis & y axis title
g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(y))
.append("text")
.attr("transform", "rotate(-90)")
.attr("x", 0 - (height / 2))
.attr("y", -50)
.attr("dy", "0.5em")
.attr("fill", "#000")
.text("Value");
// chart title
g.append("text")
.attr("x", (width / 2))
.attr("y", 0 - (margin.top / 1.7))
.attr("text-anchor", "middle")
.style("font", "18px Helvetica, Arial, Sans-serif")
.text("Drag Demo");
// the data lines themselves
var category = g.selectAll(".category")
.data(entries)
.enter().append("g")
.attr("class", "category");
var path = category.append("path")
.attr("class", "line")
.attr("id", function (d) { return d.id })
.attr("d", function (d) { return line(d.values); })
.style("stroke", function (d) { return z(d.id); })
.on("mouseover", handleMouseOver)
.on("mouseout", handleMouseOut);
// handles the jumping dateline on mouseclick
var clickbox = g.append("rect")
.attr("class", "empty")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.attr("transform", "translate(-" + margin.left + ",0)")
.on("click", clicked);
// the magic dateline
var dateline = g.append("line")
// using the max value of the actual to determine day
// zero based index, so add one for day
.data([indexes["Current"].index + 1])
.attr("class", "today")
.attr("x1", function (d) { return x(d)})
.attr("y1", 0)
.attr("x2", function (d) { return x(d)})
.attr("y2", height) // height starts the line on the axis
.attr("stroke", "#000");
// dateline is very skinny. this expands the grab area for dragging.
var grabbox = g.append("rect")
.data([indexes["Current"].index + 1])
.attr("class", "empty")
.attr("width", 40)
.attr("height", height)
.attr("x", function (d) { return x(d) - 20})
.attr("y", 0)
.on("mouseover", handleMouseOverGrab)
.on("mouseout", handleMouseOutGrab)
.call(d3.drag()
.on("start", dragstart)
.on("drag", dragging)
.on("end", dragend));
// jumping date bar
function clicked(d){
var coords = d3.mouse(this);
var target = Math.round(x.invert(coords[0] - margin.left));
var day;
// if past the end of the line, set it to the last legit date
if (target > indexes["Current"].index + 1) {
day = indexes["Current"].index + 1;
// if before the start of the line set it to day 1
} else if (target <= 0) {
day = 1;
// otherwise draw it in the middle
} else {
day = target;
}
// move the grabbox
grabbox
.attr("x", x(day) - 20);
// and the line itself
dateline
.attr("x1", x(day))
.attr("x2", x(day));
redraw(day);
}
// change the style of the bar to indicate dragging
// make it full height while dragging
function dragstart(d) {
d3.event.sourceEvent.stopPropagation();
dateline
.classed("dragging", true)
.attr("y1", 0);
}
// get the closest day to the drag and snap to it.
function dragging(d) {
var day = Math.round(x.invert(d3.event.x));
// stop dragging if we're at 0 or at the max day
if (day <= indexes["Current"].index + 1 && day > 0)
{
// reposition the grab box
d3.select(this)
.attr("x", d.x = x(day) - 20);
// reposition the grab line
dateline
.attr("x1", d.x1 = x(day))
.attr("x2", d.x2 = x(day))
}
}
function dragend(d) {
var day = Math.round(x.invert(dateline.attr("x1")));
redraw(day)
}
// end dragging the date bar
function redraw(day){
var indexes = getAggregateData(entries, day - 1);
xaxis.selectAll('text').each(function () {
if(this.__data__ == day){
this.classList.add("bold");
} else {
this.classList.remove("bold");
}
})
dateline
.classed("dragging", false)
.attr("y1", (y(indexes["Current"].max) - 100));
}
// date bar highlighting
function handleMouseOverGrab(d, i) { // Add interactivity
dateline
.style("stroke-width", "3.5px");
}
function handleMouseOutGrab(d, i) {
dateline
.style("stroke-width", "1px");
}
// line highlighting line
function handleMouseOver(d, i) { // Add interactivity
// Use D3 to select element, change color and size
d3.select(this)
.style("stroke-width", "3.5px");
}
function handleMouseOut(d, i) {
// Use D3 to select element, change color back to normal
d3.select(this)
.style("stroke-width", "1.5px");
}
// pull out indexes of max values / values for a specific day
function getAggregateData(entries, keys)
{
var indexes = [];
entries.forEach(function(d) {
var arr = Array.from(d.values, function(c){return c.value});
var max = d3.max(arr);
indexes[d.id] = {
index: arr.lastIndexOf(max),
max: max
};
});
return indexes;
}
<!doctype html>
<html lang="en">
<head>
<title>D3 Drag Demo</title>
<link href="style.css" rel="stylesheet">
</head>
<body>
<div id="chart"></div>
<script src="https://code.jquery.com/jquery-3.1.0.min.js" integrity="sha256-cCueBR6CsyA4/9szpPfrX3s49M9vUU5BgtiJj06wt/s=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.2/d3.min.js"></script>
<script src="d3.drag.js"></script>
</body>
</html>
.chart {
height: 500px;
width: 900px;
}
[id^="Projected"] {
stroke-dasharray: 10, 10;
}
.axis text.bold {
font-weight: bold;
font-size: 12px;
}
.dragging{
stroke: red;
}
.axis path,
.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
.line {
fill: none;
stroke-width: 1.5px;
}
.today {
stroke-dasharray: 7,1;
}
.empty{
fill: transparent;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment