Last active
September 7, 2016 11:37
-
-
Save codespaced/d9cacea29e547f13bf1cc401a958e321 to your computer and use it in GitHub Desktop.
D3 drag dateline example
This file contains hidden or 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
| 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; | |
| } | |
This file contains hidden or 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> | |
| <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> | |
This file contains hidden or 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
| .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