Created
August 29, 2019 21:22
-
-
Save aitee/b2dd03869985b0dce61e59c0b378692e to your computer and use it in GitHub Desktop.
Revisions
-
aitee created this gist
Aug 29, 2019 .There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1 @@ license: mit 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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,9 @@ An interactive multi-line chart. Note, I borrowed a bit of code from Duopixel's excellent code sample [here](http://bl.ocks.org/duopixel/3824661). Built with [blockbuilder.org](http://blockbuilder.org) forked from <a href='http://bl.ocks.org/larsenmtl/'>larsenmtl</a>'s block: <a href='http://bl.ocks.org/larsenmtl/e3b8b7c2ca4787f77d78f58d41c3da91'>d3 mouseover multi-line chart</a> forked from <a href='http://bl.ocks.org/aitee/'>aitee</a>'s block: <a href='http://bl.ocks.org/aitee/a9dabad60c459911d062d49a9f0333b8'>d3 mouseover multi-line chart</a> 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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,390 @@ <!DOCTYPE html> <html> <head> <script data-require="d3@3.5.3" data-semver="3.5.3" src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script> <style> body { font: 10px sans-serif; } .axis path, .axis line { fill: none; stroke: #000; shape-rendering: crispEdges; } .x.axis path { display: none; } .line { fill: none; stroke: steelblue; stroke-width: 1.5px; } </style> </head> <body> <script> var myData = "date New York San Francisco Austin\n\ 20111001 63.4 62.7 72.2\n\ 20111002 58.0 59.9 67.7\n\ 20111003 53.3 59.1 69.4\n\ 20111004 55.7 58.8 68.0\n\ 20111005 64.2 58.7 72.4\n\ 20111006 58.8 57.0 77.0\n\ 20111007 57.9 56.7 82.3\n\ 20111008 61.8 56.8 78.9\n\ 20111009 69.3 56.7 68.8\n\ 20111010 71.2 60.1 68.7\n\ 20111011 68.7 61.1 70.3\n\ 20111012 61.8 61.5 75.3\n\ 20111013 63.0 64.3 76.6\n\ 20111014 66.9 67.1 66.6\n\ 20111015 61.7 64.6 68.0\n\ 20111016 61.8 61.6 70.6\n\ 20111017 62.8 61.1 71.1\n\ 20111018 60.8 59.2 70.0\n\ 20111019 62.1 58.9 61.6\n\ 20111020 65.1 57.2 57.4\n\ 20111021 55.6 56.4 64.3\n\ 20111022 54.4 60.7 72.4\n"; var margin = { top: 20, right: 80, bottom: 30, left: 50 }, width = 900 - margin.left - margin.right, height = 500 - margin.top - margin.bottom; var parseDate = d3.time.format("%Y%m%d").parse; var x = d3.time.scale() .range([0, width]); var y = d3.scale.linear() .range([height, 0]); var color = d3.scale.category10(); var xAxis = d3.svg.axis() .scale(x) .orient("bottom"); var yAxis = d3.svg.axis() .scale(y) .orient("left"); var line = d3.svg.line() .interpolate("basis") .x(function(d) { return x(d.date); }) .y(function(d) { return y(d.temperature); }); var svg = d3.select("body").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 + ")"); var data = d3.tsv.parse(myData); color.domain(d3.keys(data[0]).filter(function(key) { return key !== "date"; })); data.forEach(function(d) { d.date = parseDate(d.date); }); var cities = color.domain().map(function(name) { return { name: name, values: data.map(function(d) { return { date: d.date, temperature: +d[name] }; }) }; }); x.domain(d3.extent(data, function(d) { return d.date; })); y.domain([ d3.min(cities, function(c) { return d3.min(c.values, function(v) { return v.temperature; }); }), d3.max(cities, function(c) { return d3.max(c.values, function(v) { return v.temperature; }); }) ]); var legend = svg.selectAll('g') .data(cities) .enter() .append('g') .attr('class', 'legend'); legend.append('rect') .attr('x', width - 20) .attr('y', function(d, i) { return i * 20; }) .attr('width', 10) .attr('height', 10) .style('fill', function(d) { return color(d.name); }); legend.append('text') .attr('x', width - 8) .attr('y', function(d, i) { return (i * 20) + 9; }) .text(function(d) { return d.name; }); svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(xAxis); svg.append("g") .attr("class", "y axis") .call(yAxis) .append("text") .attr("transform", "rotate(-90)") .attr("y", 6) .attr("dy", ".71em") .style("text-anchor", "end") .text("Temperature (ºF)"); var city = svg.selectAll(".city") .data(cities) .enter().append("g") .attr("class", "city"); city.append("path") .attr("class", "line") .attr("d", function(d) { return line(d.values); }) .style("stroke", function(d) { return color(d.name); }); city.append("text") .datum(function(d) { return { name: d.name, value: d.values[d.values.length - 1] }; }) .attr("transform", function(d) { return "translate(" + x(d.value.date) + "," + y(d.value.temperature) + ")"; }) .attr("x", 3) .attr("dy", ".35em") .text(function(d) { return d.name; }); var mouseG = svg.append("g") .attr("class", "mouse-over-effects"); mouseG.append("path") // this is the black vertical line to follow mouse .attr("class", "mouse-line") .style("stroke", "black") .style("stroke-width", "1px") .style("opacity", "0"); var lines = document.getElementsByClassName('line'); var mousePerLine = mouseG.selectAll('.mouse-per-line') .data(cities) .enter() .append("g") .attr("class", "mouse-per-line"); mousePerLine.append("circle") .attr("r", 7) .style("stroke", function(d) { return color(d.name); }) .style("fill", "none") .style("stroke-width", "1px") .style("opacity", "0"); mousePerLine.append("text") .attr("transform", "translate(10,3)"); mouseG.append('svg:rect') // append a rect to catch mouse movements on canvas .attr('width', width) // can't catch mouse events on a g element .attr('height', height) .attr('fill', 'none') .attr('pointer-events', 'all') .on('mouseout', function() { // on mouse out hide line, circles and text d3.select(".mouse-line") .style("opacity", "0"); d3.selectAll(".mouse-per-line circle") .style("opacity", "0"); d3.selectAll(".mouse-per-line text") .style("opacity", "0"); d3.selectAll(".tooltip-component") .style("visibility", "hidden"); }) .on('mouseover', function() { // on mouse in show line, circles and text d3.select(".mouse-line") .style("opacity", "1"); d3.selectAll(".mouse-per-line circle") .style("opacity", "1"); d3.selectAll(".mouse-per-line text") .style("opacity", "1"); d3.selectAll(".tooltip-component") .style("visibility", "visible"); }) .on('mousemove', function() { // mouse moving over canvas var mouse = d3.mouse(this); d3.select(".mouse-line") .attr("d", function() { var d = "M" + mouse[0] + "," + height; d += " " + mouse[0] + "," + 0; return d; }); d3.selectAll(".mouse-per-line") .attr("transform", function(d, i) { //console.log(width/mouse[0]) var xDate = x.invert(mouse[0]), bisect = d3.bisector(function(d) { return d.date; }).right; idx = bisect(d.values, xDate); var beginning = 0, end = lines[i].getTotalLength(), target = null; while (true){ target = Math.floor((beginning + end) / 2); pos = lines[i].getPointAtLength(target); if ((target === end || target === beginning) && pos.x !== mouse[0]) { break; } if (pos.x > mouse[0]) end = target; else if (pos.x < mouse[0]) beginning = target; else break; //position found } d3.select(this).select('text') .text(y.invert(pos.y).toFixed(2)); return "translate(" + mouse[0] + "," + pos.y +")"; }); }); var cursorToTooltipOffset = 5; var updateTooltipContents = function(tooltip, tooltipValues) { if (!tooltipValues) return tooltip.style("display", "none"); var rows = tooltip.selectAll(".tooltip-row") .data(tooltipValues, d => d.value+d.color); var row = rows.enter().append("g") .attr("class", "tooltip-row") .attr("transform", (d, i) => `translate(5, ${(i * 20 + 5)})`); rows.exit().remove(); let colorSquareSize = 10; row.append("rect") .attr("height", colorSquareSize) .attr("width", colorSquareSize) .attr("fill", d => d.color); var textMargin = 8; var rowText = row.select("text"); row.append("text") .attr("transform", `translate(${colorSquareSize+textMargin}, 0)`) .attr("dominant-baseline", "hanging") .attr("text-rendering", "optimizeLegibility") .attr("stroke", "none") .attr("fill", "white") .attr("font-size", "16").append("tspan") .attr("fill", "white") .text(d => d.value) ; }; var plotArea = d3.select(".mouse-over-effects"); var tooltip = svg.append("g").attr("class", "tooltip tooltip-component"); tooltip.append("rect").attr("class", "tooltip-component") .attr("pointer-events", "none") .attr("height", 100) .attr("width", 150) .attr("visibility", "hidden") .attr("opacity", 0.6) .attr("stroke", "black"); var bisectDate = d3.bisector(function(d) { return d.date; }).left; svg.on("mousemove", function() { var mouse = d3.mouse(this); var verticalPositionPercent = 0.0; var y = plotArea.node().getBBox().height*verticalPositionPercent; var x0 = x.invert(mouse[0]); var i = bisectDate(data, x0, 1); var d0 = data[i - 1]; var d1 = data[i]; var d = undefined; var row = undefined; if (x0 - d0.date > d1.date - x0){ d = d1; row = data[i]; } else{ d = d0; row = data[i-1]; } var keys = []; for (var prop in row){ keys.push(prop); } keys = keys.filter(x => x != "date"); var result = keys.map(k => { return {value: row[k], color: color(k)}; }); tooltip .attr("transform", `translate(${mouse[0]+cursorToTooltipOffset}, ${y})`) .call(updateTooltipContents, result); }); </script> </body> </html>