2017-02-07 6 views
2

D3の新機能で、以下のツールチップをアプリケーションに組み込んでいます。私は単一の折れ線グラフと複数折れ線グラフの両方を持っています。D3 - シングルおよびマルチラインチャートのツールチップ

シングルライン: https://bl.ocks.org/alandunning/cfb7dcd7951826b9eacd54f0647f48d3

マルチライン:Multiseries line chart with mouseover tooltip

あなたが見ることができるように、2つのツールチップの機能が異なっています。 [単線]ツールチップは各データポイントからジャンプしますが、[多線]はグラフのパスに連続して続きます。私は、Single Lineのツールチップがどのように動作するかを模倣するように、Multi Line機能を変更したいと思います。

ご協力いただければ幸いです。詳しい情報を提供する必要がある場合はお知らせください。

シングル折れ線グラフ:

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); 
} 

マルチラインチャート:

//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

また、私が働いているデータは、以下の配列

の配列が私のコードであることに注意あなたが提供したMultiseries line chart with mouseover tooltipから、私はMarkの回答を参考にしました。

基本的に、マウスの位置をマウス[0]で把握してツールチップを動かす代わりに、ツールチップをx軸データの各ティックに表示するように設定する必要があります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>

関連する問題