javascript - 无法使用 d3 仅在 "rect "内显示一次 X 轴值

标签 javascript d3.js

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-type" content="text/html; charset=utf-8">
<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 = 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.category20();

    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.selectAll('.mouse-per-line') // Rectangle
      .data([cities])
      .enter()
      .append("rect")
      .attr("width", width)
      .attr("height", 90)
      .style("padding", "5px")
      .style("stroke", "#272525")
      .style("fill", "#272525")
      .style("stroke-width", "1px")
      .style("opacity", "0")
      .attr('x', 10)
      .attr('y', -45);

    mousePerLine.selectAll('.mouse-per-line') // Circle
      .data(cities)
      .enter()
      .append("circle")
      .attr("r", 5)
      .style("stroke", function(d) {
        return color(d.name);
      })
      .style("fill", function(d) {
        return color(d.name);
      })
      .style("stroke-width", "1px")
      .style("opacity", "0");

    mousePerLine.selectAll('.mouse-per-line') // Text
      .data(cities)
      .enter()
      .append("text")
      .attr("transform", "translate(15,13)")
      .style("fill", function(d) {
        return color(d.name);
      })
      .style("font-weight", "bold")
      .style("font-size", "10pt");

    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 rect")
          .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 rect")
          .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("foo", function(d, i) {
          var xDate = x.invert(mouse[0]);
          var bisect;
          var heights = [];

          var xDateValue = /\w*.\s.\d.\d*.\d*.:\d*.:\d*/.exec(xDate);
          // console.log(xDateValue);

          d3.selectAll('circle')
            .attr("transform", function(d, j) {
              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[j].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
              }
              heights[j] = pos.y;
              return "translate(" + mouse[0] + "," + pos.y + ")";
            });
          var avgheight = 0;
          for (var z = 0; z < heights.length; z++) {
            avgheight = avgheight + heights[z];
          }
          avgheight = avgheight / d.length;

          d3.select(this).selectAll('rect')
            .attr("transform", function(d, i) {
              return "translate(" + mouse[0] + "," + avgheight + ")";
            });
          var rectangleText = "";
          for (var t = 1; t < heights.length; t++) {
            rectangleText = rectangleText + "<br/>" + y.invert(heights[t]).toFixed(2);
          }

          d3.select(this)
            .selectAll('text').text(function(d, i) {
              return xDateValue + "  " + d.name + "  " + y.invert(heights[i]).toFixed(2)
            }).attr("transform", function(d, i) {
              return "translate(" + mouse[0] + "," + (avgheight + 30 - (i * 25)) + ")";
            }).attr("dx", '20px');
          return "translate(" + mouse[0] + "," + pos.y + ")";
        });
    });
  </script>
</body>

</html>
您好,我是 D3 库的新手,正在尝试构建多折线图并在鼠标悬停时在“矩形”内显示 X-Y 轴的数据。我已经付出了很多努力,到目前为止已经实现了在鼠标悬停在“矩形”内时显示 X-Y 轴的数据。但是在这个多线图中,X 轴的数据在多个轴上显示了多次。我希望日期值/X 轴值应仅在其他数据之上显示一次,而不是显示多次。请帮助我提供任何提示/建议。感谢您提前提供的任何帮助。我知道我犯了一些愚蠢的错误,但我被困住了。

最佳答案

这是对现有代码的快速修改。它继续您的方法并为日期添加一个额外的 text 元素:

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-type" content="text/html; charset=utf-8">
<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 = 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.category20();

    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.selectAll('.mouse-per-line') // Rectangle
      .data([cities])
      .enter()
      .append("rect")
      .attr("width", width)
      .attr("height", 110)
      .style("padding", "5px")
      .style("stroke", "#272525")
      .style("fill", "#272525")
      .style("stroke-width", "1px")
      .style("opacity", "0")
      .attr('x', 10)
      .attr('y', -45);

    mousePerLine.selectAll('.mouse-per-line') // Circle
      .data(cities)
      .enter()
      .append("circle")
      .attr("r", 5)
      .style("stroke", function(d) {
        return color(d.name);
      })
      .style("fill", function(d) {
        return color(d.name);
      })
      .style("stroke-width", "1px")
      .style("opacity", "0");

    mousePerLine.append("text")
      .attr("class","DateText");
    mousePerLine.selectAll('.mouse-per-line') // Text
      .data(cities)
      .enter()
      .append("text")
      .attr("class", "ValueText")
      .attr("transform", "translate(15,13)")
      .style("fill", function(d) {
        return color(d.name);
      })
      .style("font-weight", "bold")
      .style("font-size", "10pt");

    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 rect")
          .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 rect")
          .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("foo", function(d, i) {
          var xDate = x.invert(mouse[0]);
          var bisect;
          var heights = [];

          var xDateValue = /\w*.\s.\d.\d*.\d*.:\d*.:\d*/.exec(xDate);
          // console.log(xDateValue);

          d3.selectAll('circle')
            .attr("transform", function(d, j) {
              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[j].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
              }
              heights[j] = pos.y;
              return "translate(" + mouse[0] + "," + pos.y + ")";
            });
          var avgheight = 0;
          for (var z = 0; z < heights.length; z++) {
            avgheight = avgheight + heights[z];
          }
          avgheight = avgheight / d.length;

          d3.select(this).selectAll('rect')
            .attr("transform", function(d, i) {
              return "translate(" + mouse[0] + "," + avgheight + ")";
            });
          var rectangleText = "";
          for (var t = 1; t < heights.length; t++) {
            rectangleText = rectangleText + "<br/>" + y.invert(heights[t]).toFixed(2);
          }
  
          d3.select(this)
            .select(".DateText")
            .attr("transform", function(d, i) {
              return "translate(" + mouse[0] + "," + (avgheight - 25) + ")";
            }).attr("dx", '20px')
            .text(xDateValue)
            .style("fill", "white");

          d3.select(this)
            .selectAll('.ValueText').text(function(d, i) {
              return d.name + "  " + y.invert(heights[i]).toFixed(2)
            }).attr("transform", function(d, i) {
              return "translate(" + mouse[0] + "," + (avgheight + 50 - (i * 25)) + ")";
            }).attr("dx", '20px');
          return "translate(" + mouse[0] + "," + pos.y + ")";
        });
    });
  </script>
</body>

</html>

关于javascript - 无法使用 d3 仅在 "rect "内显示一次 X 轴值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38437200/

相关文章:

javascript - 样式化 d3 的工具提示

json - D3 热图转换代码以使用 json 而不是 tsv

javascript - 交叉过滤器和D3 : Filter by group AND dimension

javascript - Backbone.js 和 three.js - 带 Canvas 的 MVC

javascript - pikaday 日期未从 php 表单发送

javascript - 使用 onload 函数创建 img 元素 - 纯 javascript

Javascript 闭包和 d3

javascript - WebRTC - 在通信过程中改变视频流

javascript - 如何根据行数设置 Google Chart 高度?

javascript - 复圆图