2017-02-07 2 views
2

Я довольно новичок в D3 и просто включил приведенные ниже подсказки в свое приложение. У меня есть как однострочный график, так и многострочный график.D3 - всплывающие подсказки с одной и несколькими линиями

Однолинейные: https://bl.ocks.org/alandunning/cfb7dcd7951826b9eacd54f0647f48d3

многоканальная линия: Multiseries line chart with mouseover tooltip

Как вы можете видеть, функциональность два подсказок различна. Всплывающая подсказка Single Line перескакивает с каждой точки данных, в то время как Multi Line постоянно следует за траекторией диаграммы. Я хочу изменить функциональность Multi Line, чтобы имитировать работу всплывающей подсказки Single Line.

Любая помощь была бы принята с благодарностью. Пожалуйста, дайте мне знать, если мне нужно предоставить дополнительную информацию. Также обратите внимание, что данные я работаю является массивом массивов

Ниже мой код:

Single Line Chart:

let g = svg.append('g'); 
    g.append("path") 
    .datum(this.dataObj) 
    .attr("class",`line-${this.yAxisData} line`) 
    .attr('d', line) 
    .attr("stroke",`${this.color(this.dataObj.label)}`) 
    .attr("fill",'none') 
    .attr("transform",  `translate(${this.margin.left},${this.margin.top})`); 

var focus = g.append("g") 
    .attr("class", "focus") 
    .style("display", "none"); 

    focus.append("line") 
    .datum(this.dataObj) 
    .attr("class", "x-hover-line hover-line") 
    .attr("transform",`translate(${this.margin.left},${this.margin.top})`) 
    .attr("stroke",`${this.color(this.dataObj.label)}`) 
    .attr("y1", 0) 
    .attr("y2", height); 

    focus.append("circle") 
    .datum(this.dataObj) 
    .attr("transform",`translate(${this.margin.left},${this.margin.top})`) 
    .attr("stroke",`${this.color(this.dataObj.label)}`) 
    .attr("r", 7.5); 

    focus.append("text") 
    .attr("class","linetip") 
    .attr("x", 40) 
    .attr("dy", "0.5em"); 

    svg.append("rect") 
    .attr("transform", `translate(${this.margin.left},${this.margin.top})`) 
    .attr("class", "overlay") 
    .attr("width", width) 
    .attr("height", height) 
    .on("mouseover", function() { focus.style("display", null); }) 
    .on("mouseout", function() { focus.style("display", "none"); }) 
    .on("mousemove", this.mousemove); 

mousemove() { 
    var bisectDate = d3.bisector(function(d) { return d.date; }).left; 
    let mouse = d3.mouse(d3.event.currentTarget); 
    let svg = d3.select(this.container); 
    var x0 = this.x.invert(mouse[0]); 
    var i = bisectDate(this.dataObj, x0); 
    var d0 = this.dataObj[i - 1]; 
    var d1 = this.dataObj[i]; 
    var d = x0 - d0.date > d1.date - x0 ? d1 : d0; 
    var focus = svg.select(".focus"); 
    focus.attr("transform", "translate(" + this.x(d[this.xAxisData]) + "," + this.y(d[this.yAxisData]) + ")"); 
    focus.select("text").text(`[${d[this.yAxisData]}]`); 
    focus.select(".x-hover-line").attr("y2", this.height - this.y(d[this.yAxisData])); 
    focus.select(".y-hover-line").attr("x2", this.width + this.width); 
} 

Мульти Line Chart:

//append paths 
    let g = svg.append('g'); 
    let chartLines = g.selectAll('.lines') 
    .data(this.dataObj) 
    .enter() 
    .append('g') 
    .attr('class', 'lines'); 

    chartLines.append('path') 
    .attr('class','line') 
    .attr('d', d => { 
     return line(d); 
    }) 
    .attr('stroke', (d) => color(d[0].label)) 
    .attr('fill','none') 
    .attr("transform", `translate(${this.margin.left},0)`); 

    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", "2px") 
    .style("stroke-dasharray", "3,3") 
    .style("opacity", "0"); 

    var mousePerLine = mouseG.selectAll('.mouse-per-line') 
    .data(this.dataObj) 
    .enter() 
    .append("g") 
    .attr("class", "mouse-per-line"); 

    mousePerLine.append("circle") 
    .datum(d=>{return d}) 
    .attr("r", 7) 
    .attr("stroke", (d,i) => { 
     console.log(d) 
     return `${this.color(d[i].label)}` 
    }) 
    .style("fill", "none") 
    .style("opacity", "0"); 

    mousePerLine.append("text") 
    .datum(d=>{return d}) 
    .attr("transform", "translate(10,3)"); 

    mouseG.append('svg:rect') // append a rect to catch mouse movements on canvas 
    .attr("transform", `translate(${this.margin.left},0)`) 
    .attr('width', width) // can't catch mouse events on a g element 
    .attr('height', height) 
    .attr('fill', 'none') 
    .attr('pointer-events', 'all') 
    .on('mouseout',() => { // 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"); 
    }) 
    .on('mouseover',() => { // 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"); 
    }) 
    .on('mousemove',() => { 
     let mouse = d3.mouse(d3.event.currentTarget); 
     d3.select(".mouse-line") 
     .attr("d",() => { 
      var d = "M" + mouse[0] + "," + height; 
      d += " " + mouse[0] + "," + 0; 
      return d; 
     }); 
     d3.selectAll(".mouse-per-line") 
     .attr("transform", (d, i) => { 
      var lines = document.getElementsByClassName('line') 
      var xDate = this.x.invert(mouse[0]) 
      var bisect = d3.bisector(function(d) { return d.date; }).right; 
      var idx = bisect(this.dataObj, xDate); 
      var beginning = 0, 
      end = lines[i].getTotalLength() 
      var target = null; 

      while (true){ 
       var target = Math.floor((beginning + end)/2); 
       var 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('text') 
      .text(this.y.invert(pos.y)); 

      return "translate(" + mouse[0] + "," + pos.y +")"; 
     }); 
    }); 

ответ

3

Я принял ответ Mark в качестве ссылки от Multiseries line chart with mouseover tooltip, который вы указали.

В принципе, вам нужно установить всплывающие подсказки, чтобы отображать на каждом тике данных по оси x, поэтому вместо того, чтобы захватывать положение мыши с помощью мыши [0] и перемещать всплывающие подсказки, вы должны переместить ее в положение, где данные по оси x.

Вот деталь изменений, которые я сделал:

mouseG.append('svg:rect') 
    .attr('width', width) 
    .attr('height', height) 
    .attr('fill', 'none') 
    .attr('pointer-events', 'all') 
    .on('mouseout',() => { 
     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',() => { 
     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',() => { 
     let mouse = d3.mouse(d3.event.currentTarget); 
     // MOVE THIS BEFORE THE RETURN 
     // d3.select(".mouse-line") 
     // .attr("d",() => { 
     //  var d = "M" + mouse[0] + "," + height; 
     //  d += " " + mouse[0] + "," + 0; 
     //  return d; 
     // }); 
     d3.selectAll(".mouse-per-line") 
     .attr("transform", (d, i) => { 
      var lines = document.getElementsByClassName('line') 
      var xDate = this.x.invert(mouse[0]) 
      var bisect = d3.bisector(function(d) { return d.date; }).right; 
      var idx = bisect(this.dataObj, xDate); 

      // GET RID OF THIS 
      // var beginning = 0, 
      // end = lines[i].getTotalLength() 
      // var target = null; 

      // while (true){ 
      //  var target = Math.floor((beginning + end)/2); 
      //  var 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 
      // } 

      // REPLACE pos.y WITH y(d.values[idx].temperature) 
      // AND mouse[0] WITH x(d.values[idx].date) 
      d3.select('text') 
      .text(this.y.invert(pos.y)); 

      return "translate(" + mouse[0] + "," + pos.y +")"; 
     }); 
    }); 

Ниже полностью рабочий код с изменениями применяются. Для этого фрагмента я использовал interpolate('linear') для правильного отображения значений; если вы используете interpolate('basis'), то всплывающие подсказки и строки не будут соответствовать правильно:

<!DOCTYPE html> 
 
<html> 
 

 
<head> 
 
    <script data-require="[email protected]" data-semver="3.5.3" src="http://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 \t New York \t San Francisco \t Austin\n\ 
 
20111001 \t 63.4 \t 62.7 \t 72.2\n\ 
 
20111002 \t 58.0 \t 59.9 \t 67.7\n\ 
 
20111003 \t 53.3 \t 59.1 \t 69.4\n\ 
 
20111004 \t 55.7 \t 58.8 \t 68.0\n\ 
 
20111005 \t 64.2 \t 58.7 \t 72.4\n\ 
 
20111006 \t 58.8 \t 57.0 \t 77.0\n\ 
 
20111007 \t 57.9 \t 56.7 \t 82.3\n\ 
 
20111008 \t 61.8 \t 56.8 \t 78.9\n\ 
 
20111009 \t 69.3 \t 56.7 \t 68.8\n\ 
 
20111010 \t 71.2 \t 60.1 \t 68.7\n\ 
 
20111011 \t 68.7 \t 61.1 \t 70.3\n\ 
 
20111012 \t 61.8 \t 61.5 \t 75.3\n\ 
 
20111013 \t 63.0 \t 64.3 \t 76.6\n\ 
 
20111014 \t 66.9 \t 67.1 \t 66.6\n\ 
 
20111015 \t 61.7 \t 64.6 \t 68.0\n\ 
 
20111016 \t 61.8 \t 61.6 \t 70.6\n\ 
 
20111017 \t 62.8 \t 61.1 \t 71.1\n\ 
 
20111018 \t 60.8 \t 59.2 \t 70.0\n\ 
 
20111019 \t 62.1 \t 58.9 \t 61.6\n\ 
 
20111020 \t 65.1 \t 57.2 \t 57.4\n\ 
 
20111021 \t 55.6 \t 56.4 \t 64.3\n\ 
 
20111022 \t 54.4 \t 60.7 \t 72.4\n"; 
 

 
    var margin = { 
 
     top: 20, 
 
     right: 80, 
 
     bottom: 30, 
 
     left: 50 
 
    }, 
 
     width = 400 - margin.left - margin.right, 
 
     height = 250 - 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("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 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"); 
 
     }) 
 
     .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"); 
 
     }) 
 
     .on('mousemove', function() { // mouse moving over canvas 
 
     var mouse = d3.mouse(this); 
 

 
     d3.selectAll(".mouse-per-line") 
 
      .attr("transform", function (d, i) { 
 

 
      var xDate = x.invert(mouse[0]), 
 
       bisect = d3.bisector(function (d) { return d.date; }).left; 
 
      idx = bisect(d.values, xDate); 
 

 
      d3.select(this).select('text') 
 
       .text(y.invert(y(d.values[idx].temperature)).toFixed(2)); 
 

 
      d3.select(".mouse-line") 
 
       .attr("d", function() { 
 
       var data = "M" + x(d.values[idx].date) + "," + height; 
 
       data += " " + x(d.values[idx].date) + "," + 0; 
 
       return data; 
 
       }); 
 
      return "translate(" + x(d.values[idx].date) + "," + y(d.values[idx].temperature) + ")"; 
 
      }); 
 
     }); 
 

 
    </script> 
 
</body> 
 

 
</html>

Смежные вопросы