带有鼠标悬停工具提示的多系列折线图 [英] Multiseries line chart with mouseover tooltip

查看:24
本文介绍了带有鼠标悬停工具提示的多系列折线图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用

现在,我正在尝试添加一个 x 值鼠标悬停工具提示,当您将鼠标悬停在其垂直位置时,它会在每行显示一个工具提示.类似于this,但适用于多行.

我找到了这个 StackOverflow 答案(它包括一个 JSFiddle),但我似乎无法让它与我的多系列折线图一起使用.

svg.append("path")//这是鼠标跟随的黑色垂直线.attr("class","mouseLine").style("中风","黑色").style("笔画宽度", "1px").style("不透明度", "0");var mouseCircle = causation.append("g")//对于每一行,添加组来保存文本和圆圈.attr("class","mouseCircle");mouseCircle.append("circle")//添加一个沿着路径跟随的圆.attr("r", 7).style("stroke", function(d) { console.log(d); return color(d.key); }).style("填充","无").style("笔画宽度", "1px");mouseCircle.append("文本").attr("transform", "translate(10,3)");//保存坐标的文本var bisect = d3.bisector(function(d) { return d.YEAR; }).right;//可重用的二等分来查找线之前/之后的点svg.append('svg:rect')//追加一个矩形来捕捉画布上的鼠标移动.attr('width', width)//不能在 g 元素上捕捉鼠标事件.attr('身高', 身高).attr('填充', '无').attr('指针事件', '所有').on('mouseout', function(){//在鼠标移开时隐藏线条、圆圈和文本d3.select(".mouseLine").style("不透明度", "0");d3.selectAll(".mouseCircle圆").style("不透明度", "0");d3.selectAll(".mouseCircle 文本").style("不透明度", "0");}).on('mouseover', function(){//在显示行、圆圈和文本中的鼠标上d3.select(".mouseLine").style("不透明度", "1");d3.selectAll(".mouseCircle圆").style("不透明度", "1");d3.selectAll(".mouseCircle 文本").style("不透明度", "1");}).on('mousemove', function() {//鼠标在画布上移动d3.select(".mouseLine").attr("d", function(){yRange = y.range();//y轴范围var xCoor = d3.mouse(this)[0];//鼠标在 x 中的位置var xDate = x.invert(xCoor);//鼠标x对应的日期d3.selectAll('.mouseCircle')//对于每个圈子组.each(函数(d,i){var rightIdx = bisect(data[1].values, xDate);//在鼠标右键的数据中查找日期var interSect = get_line_intersection(xCoor,//得到我们的垂直线和数据线的交点y范围[0],坐标,y范围[1],x(data[i].values[rightIdx-1].YEAR),y(data[i].values[rightIdx-1].VALUE),x(data[i].values[rightIdx].YEAR),y(data[i].values[rightIdx].VALUE));d3.select(this)//将圆移动到交点.attr('transform', 'translate(' + interSect.x + ',' + interSect.y + ')');d3.select(this.children[1])//写出坐标.text(xDate.toLocaleDateString() + "," + y.invert(interSect.y).toFixed(0));});返回 "M"+ xCoor +"," + yRange[0] + "L" + xCoor + "," + yRange[1];//定位垂直线});});//从这里:https://stackoverflow.com/a/1968345/16363函数 get_line_intersection(p0_x, p0_y, p1_x, p1_y,p2_x, p2_y, p3_x, p3_y){var rV = {};var s1_x, s1_y, s2_x, s2_y;s1_x = p1_x - p0_x;s1_y = p1_y - p0_y;s2_x = p3_x - p2_x;s2_y = p3_y - p2_y;无功 s, t;s = (-s1_y * (p0_x - p2_x) + s1_x * (p0_y - p2_y))/(-s2_x * s1_y + s1_x * s2_y);t = (s2_x * (p0_y - p2_y) - s2_y * (p0_x - p2_x))/(-s2_x * s1_y + s1_x * s2_y);如果 (s >= 0 && s <= 1 && t >= 0 && t <= 1){//检测到碰撞rV.x = p0_x + (t * s1_x);rV.y = p0_y + (t * s1_y);}返回 RV;}

所以,简单地说,我想将我的折线图 JSFiddle 与这个 工具提示 JSFiddle.有人知道怎么做这个吗?或者有没有更简单的方法来创建这样的工具提示?任何帮助表示赞赏!

解决方案

你提到的问题我在 4 月份回答过.从那时起,我对 SVGd3 有了更多了解,所以我将把它作为该答案的更新.

注意,我从@Duopixel 的优秀代码示例这里借用了一些代码.

以下是评论详情:

//为所有无意义的鼠标附加一个 gvar mouseG = svg.append("g").attr("class", "鼠标悬停效果");//这是垂直线mouseG.append("路径").attr("class", "鼠标线").style("中风", "黑色").style("笔画宽度", "1px").style("不透明度", "0");//保留对我们所有行的引用var lines = document.getElementsByClassName('line');//这里是每个圆圈的 g 和行上的文本var mousePerLine = mouseG.selectAll('.mouse-per-line').data(城市).进入().append("g").attr("class", "每行鼠标");//圆圈mousePerLine.append("圆圈").attr("r", 7).style("stroke", function(d) {返回颜色(d.name);}).style("填充", "无").style("笔画宽度", "1px").style("不透明度", "0");//文本mousePerLine.append("文本").attr("transform", "translate(10,3)");//rect 捕捉鼠标移动mouseG.append('svg:rect').attr('宽度', 宽度).attr('身高', 身高).attr('填充', '无').attr('指针事件', '所有').on('mouseout', function() {//在鼠标移开时隐藏线、圆和文本d3.select(".鼠标线").style("不透明度", "0");d3.selectAll(".mouse-per-line circle").style("不透明度", "0");d3.selectAll(".mouse-per-line text").style("不透明度", "0");}).on('mouseover', function() {//在显示行、圆圈和文本中的鼠标上d3.select(".鼠标线").style("不透明度", "1");d3.selectAll(".mouse-per-line circle").style("不透明度", "1");d3.selectAll(".mouse-per-line text").style("不透明度", "1");}).on('mousemove', function() {//鼠标在画布上移动var 鼠标 = d3.mouse(this);//移动垂直线d3.select(".鼠标线").attr("d", function() {var d = "M" + 鼠标[0] + "," + 高度;d += " " + 鼠标[0] + "," + 0;返回d;});//定位圆和文本d3.selectAll(".mouse-per-line").attr(转换",函数(d,i){控制台日志(宽度/鼠标[0])var xDate = x.invert(鼠标[0]),bisect = d3.bisector(function(d) { return d.date; }).right;idx = bisect(d.values, xDate);//因为我们使用曲线拟合,所以我们不能像我在上一个答案中所做的那样继续寻找点//这将使用一些 SVG 路径函数进行搜索//在直线上找到正确的位置//来自 http://bl.ocks.org/duopixel/3824661变量开始 = 0,end = lines[i].getTotalLength(),目标 = 空;而(真){target = Math.floor((beginning + end)/2);pos = lines[i].getPointAtLength(target);if ((target === end || target === begin) && pos.x !== mouse[0]) {休息;}if (pos.x > mouse[0]) end = target;else if (pos.x < mouse[0]) begin = target;否则休息;//找到位置}//用 y 值更新文本d3.select(this).select('text').text(y.invert(pos.y).toFixed(2));//返回位置return "translate(" + mouse[0] + "," + pos.y +")";});});

<小时>

完整的工作代码:

<头><script data-require="d3@3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"><风格>身体 {字体:10px 无衬线;}.轴路径,.轴线{填充:无;中风:#000;形状渲染:crispEdges;}.x.轴路径{显示:无;}.线 {填充:无;笔画:钢蓝;笔画宽度:1.5px;}</风格><身体><脚本>var myData = "日期纽约旧金山奥斯汀
20111001 63.4 62.7 72.2
20111002 58.0 59.9 67.7
20111003 53.3 59.1 69.4
20111004 55.7 58.8 68.0
20111005 64.2 58.7 72.4
20111006 58.8 57.0 77.0
20111007 57.9 56.7 82.3
20111008 61.8 56.8 78.9
20111009 69.3 56.7 68.8
20111010 71.2 60.1 68.7
20111011 68.7 61.1 70.3
20111012 61.8 61.5 75.3
20111013 63.0 64.3 76.6
20111014 66.9 67.1 66.6
20111015 61.7 64.6 68.0
20111016 61.8 61.6 70.6
20111017 62.8 61.1 71.1
20111018 60.8 59.2 70.0
20111019 62.1 58.9 61.6
20111020 65.1 57.2 57.4
20111021 55.6 56.4 64.3
20111022 54.4 60.7 72.4
";变量边距 = {前:20,右:80,底部:30,左:50},宽度 = 500 - margin.left - margin.right,高度 = 500 - margin.top - margin.bottom;var parseDate = d3.time.format("%Y%m%d").parse;var x = d3.time.scale().range([0, 宽度]);var y = d3.scale.linear().range([高度, 0]);var 颜色 = d3.scale.category10();var xAxis = d3.svg.axis().scale(x).orient("底部");var yAxis = d3.svg.axis().scale(y).orient("左");var line = d3.svg.line().interpolate("基础").x(函数(d){返回 x(d.date);}).y(函数(d){返回 y(d.温度);});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 数据 = d3.tsv.parse(myData);color.domain(d3.keys(data[0]).filter(function(key) {返回键 !== "日期";}));数据.forEach(函数(d){d.date = parseDate(d.date);});var city = color.domain().map(function(name) {返回 {姓名:姓名,值: data.map(function(d) {返回 {日期:d.date,温度:+d[名称]};})};});x.domain(d3.extent(数据,函数(d){返回 d.date;}));y.domain([d3.min(城市,功能(c){返回 d3.min(c.values, function(v) {返回 v. 温度;});}),d3.max(城市,功能(c){返回 d3.max(c.values, function(v) {返回 v. 温度;});})]);var Legend = svg.selectAll('g').data(城市).进入().append('g').attr('class', '传奇');Legend.append('rect').attr('x', 宽度 - 20).attr('y', function(d, i) {返回 i * 20;}).attr('宽度', 10).attr('身高', 10).style('填充', 函数(d) {返回颜色(d.name);});Legend.append('文本').attr('x', 宽度 - 8).attr('y', function(d, i) {返回 (i * 20) + 9;}).text(function(d) {返回 d.name;});svg.append("g").attr("class", "x 轴").attr("transform", "translate(0," + height + ")").call(xAxis);svg.append("g").attr("class", "y 轴").call(yAxis).append("文本").attr(变换",旋转(-90)").attr("y", 6).attr("dy", ".71em").style("文本锚", "结束").text("温度 (ºF)");var city = svg.selectAll(".city").data(城市).enter().append("g").attr("class", "city");city.append("路径").attr("class", "line").attr(d",函数(d){返回行(d.values);}).style("stroke", function(d) {返回颜色(d.name);});city.append("文本").数据(功能(d){返回 {名称:d.name,值:d.values[d.values.length - 1]};}).attr(转换",函数(d){返回 "translate(" + x(d.value.date) + "," + y(d.value.temperature) + ")";}).attr("x", 3).attr("dy", ".35em").text(function(d) {返回 d.name;});var mouseG = svg.append("g").attr("class", "鼠标悬停效果");mouseG.append("path")//这是跟随鼠标的黑色垂直线.attr("class", "鼠标线").style("中风", "黑色").style("笔画宽度", "1px").style("不透明度", "0");var lines = document.getElementsByClassName('line');var mousePerLine = mouseG.selectAll('.mouse-per-line').data(城市).进入().append("g").attr("class", "每行鼠标");mousePerLine.append("圆圈").attr("r", 7).style("stroke", function(d) {返回颜色(d.name);}).style("填充", "无").style("笔画宽度", "1px").style("不透明度", "0");mousePerLine.append("文本").attr("transform", "translate(10,3)");mouseG.append('svg:rect')//追加一个矩形来捕捉画布上的鼠标移动.attr('width', width)//不能在 g 元素上捕捉鼠标事件.attr('身高', 身高).attr('填充', '无').attr('指针事件', '所有').on('mouseout', function() {//在鼠标移开时隐藏线、圆和文本d3.select(".鼠标线").style("不透明度", "0");d3.selectAll(".mouse-per-line circle").style("不透明度", "0");d3.selectAll(".mouse-per-line text").style("不透明度", "0");}).on('mouseover', function() {//在显示行、圆圈和文本中的鼠标上d3.select(".鼠标线").style("不透明度", "1");d3.selectAll(".mouse-per-line circle").style("不透明度", "1");d3.selectAll(".mouse-per-line text").style("不透明度", "1");}).on('mousemove', function() {//鼠标在画布上移动var 鼠标 = d3.mouse(this);d3.select(".鼠标线").attr("d", function() {var d = "M" + 鼠标[0] + "," + 高度;d += " " + 鼠标[0] + "," + 0;返回d;});d3.selectAll(".mouse-per-line").attr(转换",函数(d,i){控制台日志(宽度/鼠标[0])var xDate = x.invert(鼠标[0]),bisect = d3.bisector(function(d) { return d.date; }).right;idx = bisect(d.values, xDate);变量开始 = 0,end = lines[i].getTotalLength(),目标 = 空;而(真){target = Math.floor((beginning + end)/2);pos = lines[i].getPointAtLength(target);if ((target === end || target === begin) && pos.x !== mouse[0]) {休息;}if (pos.x > mouse[0]) end = target;else if (pos.x < mouse[0]) begin = target;否则休息;//找到位置}d3.select(this).select('text').text(y.invert(pos.y).toFixed(2));return "translate(" + mouse[0] + "," + pos.y +")";});});</html>

I've created a multi-series line chart using this bl.ocks.org code example. I've managed to recreate it on JSFiddle.

Now, I'm trying to add an x-value mouseover tooltip, which displays the a tooltip at each line when you hover its vertical position. Something like this, but for multiple lines.

I found this StackOverflow answer (it includes a JSFiddle), but I can't seem to make it work with my multiseries line chart.

svg.append("path") // this is the black vertical line to follow mouse
  .attr("class","mouseLine")  
  .style("stroke","black")
  .style("stroke-width", "1px")
  .style("opacity", "0");

var mouseCircle = causation.append("g") // for each line, add group to hold text and circle
      .attr("class","mouseCircle"); 

mouseCircle.append("circle") // add a circle to follow along path
  .attr("r", 7)
  .style("stroke", function(d) { console.log(d); return color(d.key); })
  .style("fill","none")
  .style("stroke-width", "1px"); 

mouseCircle.append("text")
  .attr("transform", "translate(10,3)"); // text to hold coordinates

var bisect = d3.bisector(function(d) { return d.YEAR; }).right; // reusable bisect to find points before/after line

svg.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(".mouseLine")
            .style("opacity", "0");
        d3.selectAll(".mouseCircle circle")
            .style("opacity", "0");
      d3.selectAll(".mouseCircle text")
            .style("opacity", "0");
  })
  .on('mouseover', function(){ // on mouse in show line, circles and text
        d3.select(".mouseLine")
            .style("opacity", "1");
         d3.selectAll(".mouseCircle circle")
            .style("opacity", "1");
        d3.selectAll(".mouseCircle text")
            .style("opacity", "1");
  })
  .on('mousemove', function() { // mouse moving over canvas
      d3.select(".mouseLine")
      .attr("d", function(){
          yRange = y.range(); // range of y axis
          var xCoor = d3.mouse(this)[0]; // mouse position in x
          var xDate = x.invert(xCoor); // date corresponding to mouse x 
          d3.selectAll('.mouseCircle') // for each circle group
              .each(function(d,i){
                 var rightIdx = bisect(data[1].values, xDate); // find date in data that right off mouse
                 var interSect = get_line_intersection(xCoor,  // get the intersection of our vertical line and the data line
                      yRange[0], 
                      xCoor, 
                      yRange[1],
                      x(data[i].values[rightIdx-1].YEAR),
                      y(data[i].values[rightIdx-1].VALUE),
                      x(data[i].values[rightIdx].YEAR),
                      y(data[i].values[rightIdx].VALUE));

              d3.select(this) // move the circle to intersection
                  .attr('transform', 'translate(' + interSect.x + ',' + interSect.y + ')');

              d3.select(this.children[1]) // write coordinates out
                  .text(xDate.toLocaleDateString() + "," + y.invert(interSect.y).toFixed(0));

              });

          return "M"+ xCoor +"," + yRange[0] + "L" + xCoor + "," + yRange[1]; // position vertical line
      });
  });

// from here: https://stackoverflow.com/a/1968345/16363
function get_line_intersection(p0_x, p0_y, p1_x, p1_y, 
    p2_x, p2_y, p3_x, p3_y)
{
    var rV = {};
    var s1_x, s1_y, s2_x, s2_y;
    s1_x = p1_x - p0_x;     s1_y = p1_y - p0_y;
    s2_x = p3_x - p2_x;     s2_y = p3_y - p2_y;

    var s, t;
    s = (-s1_y * (p0_x - p2_x) + s1_x * (p0_y - p2_y)) / (-s2_x * s1_y + s1_x * s2_y);
    t = ( s2_x * (p0_y - p2_y) - s2_y * (p0_x - p2_x)) / (-s2_x * s1_y + s1_x * s2_y);

    if (s >= 0 && s <= 1 && t >= 0 && t <= 1)
    {
        // Collision detected
        rV.x = p0_x + (t * s1_x);
        rV.y = p0_y + (t * s1_y);
    }

    return rV;
}

So, to put it simply, I want to combine my line chart JSFiddle with this tooltip JSFiddle. Does anybody know how to do this? Or is there an easier way to create a tooltip like this? Any help is appreciated!

解决方案

The question you referenced I answered back in April. Since then, I've learned a bit more about SVG and d3, so I'll let this serve as an update to that answer.

Note, I borrowed a bit of code from @Duopixel's excellent code sample here.

Here's the commented particulars:

// append a g for all the mouse over nonsense
var mouseG = svg.append("g")
  .attr("class", "mouse-over-effects");

// this is the vertical line
mouseG.append("path")
  .attr("class", "mouse-line")
  .style("stroke", "black")
  .style("stroke-width", "1px")
  .style("opacity", "0");

// keep a reference to all our lines
var lines = document.getElementsByClassName('line');

// here's a g for each circle and text on the line
var mousePerLine = mouseG.selectAll('.mouse-per-line')
  .data(cities)
  .enter()
  .append("g")
  .attr("class", "mouse-per-line");

// the circle
mousePerLine.append("circle")
  .attr("r", 7)
  .style("stroke", function(d) {
    return color(d.name);
  })
  .style("fill", "none")
  .style("stroke-width", "1px")
  .style("opacity", "0");

// the text
mousePerLine.append("text")
  .attr("transform", "translate(10,3)");

// rect to capture mouse movements
mouseG.append('svg:rect')
  .attr('width', width)
  .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);

    // move the vertical line
    d3.select(".mouse-line")
      .attr("d", function() {
        var d = "M" + mouse[0] + "," + height;
        d += " " + mouse[0] + "," + 0;
        return d;
      });

    // position the circle and text
    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);

        // since we are use curve fitting we can't relay on finding the points like I had done in my last answer
        // this conducts a search using some SVG path functions
        // to find the correct position on the line
        // from http://bl.ocks.org/duopixel/3824661
        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
        }

        // update the text with y value
        d3.select(this).select('text')
          .text(y.invert(pos.y).toFixed(2));

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


Full working code:

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

20111001	63.4	62.7	72.2

20111002	58.0	59.9	67.7

20111003	53.3	59.1	69.4

20111004	55.7	58.8	68.0

20111005	64.2	58.7	72.4

20111006	58.8	57.0	77.0

20111007	57.9	56.7	82.3

20111008	61.8	56.8	78.9

20111009	69.3	56.7	68.8

20111010	71.2	60.1	68.7

20111011	68.7	61.1	70.3

20111012	61.8	61.5	75.3

20111013	63.0	64.3	76.6

20111014	66.9	67.1	66.6

20111015	61.7	64.6	68.0

20111016	61.8	61.6	70.6

20111017	62.8	61.1	71.1

20111018	60.8	59.2	70.0

20111019	62.1	58.9	61.6

20111020	65.1	57.2	57.4

20111021	55.6	56.4	64.3

20111022	54.4	60.7	72.4
";

    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 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");
      })
      .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.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 +")";
          });
      });
      
  </script>
</body>

</html>

这篇关于带有鼠标悬停工具提示的多系列折线图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆