javascript - d3: how to snap mouse over cursor to existing data points -
in following snippet (based on answer of @gerardo furtado d3 multi line mouse on cursor both y , x value) mouse on cursor interpolating values between data points.
how possible cursor shows values real data points?
<!doctype html> <html> <head> <script data-require="d3@3.5.3" data-semver="3.5.3" src="//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\ 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\ 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\ 20111022 54.4 60.7 72.4\n"; var margin = { top: 20, right: 80, bottom: 30, left: 50 }, width = 500 - margin.left - margin.right, height = 500 - margin.top - margin.bottom; var parsedate = d3.time.format("%y%m%d").parse; var parsedate2 = d3.time.format("%y/%m/%d"); 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("linear") .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 * 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") // black vertical line 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 rect catch mouse movements on canvas .attr('width', width) // can't catch mouse events on g element .attr('height', height) .attr('fill', 'none') .attr('pointer-events', 'all') .on('mouseout', function() { // on mouse out hide line, circles , 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"); }) .on('mouseover', function() { // on mouse in show line, circles , 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"); }) .on('mousemove', function() { // mouse moving on 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) { 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) + " - " + parsedate2(xdate)); return "translate(" + mouse[0] + "," + pos.y +")"; }); }); </script> </body> </html>
Comments
Post a Comment