突出显示最近点d3js线和点图

时间:2017-11-22 17:40:54

标签: javascript d3.js linechart mousehover

我目前有一个d3js线和点图。截至目前,当我将鼠标悬停在圆点上时,它会突出显示点并显示显示的值。我想看到的是当我在线上移动时,它会将最近的点悬停在灯光上的鼠标光标上与悬停在点上时相同。我确实检查过平分线,但不确定它是否仅对带日期的数据有用。下面是图表小提琴的链接。

https://jsfiddle.net/snt1/56qvqaz9/2/

var margin = {top: 30,right: 20,bottom: 100,left: 80 },
             width = 400 - margin.left - margin.right,
             height = 300 - margin.top - margin.bottom;



            var svg = d3.select("#charts").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 + ")")
                        .attr("preserveAspectRatio", "xMinYMin meet")
                        .attr("viewBox", "0 0 600 400")
                       //class to make it responsive
                        .classed("svg-content-responsive", true); ;


         // Set the ranges

            var x =  d3.scaleBand().rangeRound([0, width]).padding(0.1);
            var y = d3.scaleLinear().range([height, 0]);
            var xAxis ;   

               function tickLabels(dataLength, d) {
                   if (dataLength > 9) return "";
                   return d.replace(/^.+_/, '')
                 }

             /*  var xAxis = d3.axisBottom().scale(x)
                                .ticks()
                                .tickFormat(function(d,i) {  return tickLabels(toCSV.length, d) })*/
            /*   
               var ticks = d3.selectAll(".tick text");
               ticks.attr("class", function(d,i){
                   if(i%3 != 0) d3.select(this).remove();
               });


               */


               if (all.length < 16 ){
                xAxis = d3.axisBottom().scale(x).ticks(10);
               }
               else 
                   {
                   xAxis = d3.axisBottom().scale(x)
                   .tickFormat(function(d, i) {
                     return i % 3 === 0 ? d : null;
                   });
                   }
          //     var xAxis = d3.axisBottom().scale(x).ticks(10);

               var yAxis = d3.axisLeft().scale(y).ticks(5).tickSizeInner(-width).tickSizeOuter(0).tickPadding(10);


            // Define the line

               var valueline = d3.line().curve(d3.curveCatmullRom)
                                .x(function (d) {
                                    return x(d.label) + x.bandwidth() / 2;
                                                })
                                .y(function (d) {
                                    return y(d.value);
                                                });
               var data = all;


            // Get the data
               data.forEach(function (d) {
                       d.value = +d.value;
               });

           // Scale the range of the data
               x.domain(data.map(function (d) {return d.label;}));
               y.domain([0, d3.max(data, function (d) {return d.value;})]);

           // Add the valueline path.
               svg.append("path") // Add the valueline path.
               .attr("d", valueline(data))
                .attr("class", "line");

           // Add the scatterplot
               svg.selectAll("dot")
                   .data(data)
                 .enter().append("circle")
                   .attr("r", 4)
                   .attr("cx", function (d) {return x(d.label) + x.bandwidth() / 2;})
                   .attr("cy", function(d) { return y(d.value); }).on("mouseover", function() {
                     tooltip.style("display", null);
                   })
                  /* .on("mouseout", function() {
                       tooltip.transition()     
                         .duration(500)     
                         .style("opacity", 0);  
                       d3.select(this)
                        .attr("fill", "black")
                         .attr("r", 4);
                   })

* /

                    .on("mouseover", function() { tooltip.style("display", null); })
                    .on("mouseout", function() {  tooltip.style("display", "none");
                    d3.select(this)
                    .attr("fill", "black")
                     .attr("r", 4);})


                     .on("mousemove", function(d) {
                       d3.select(this)
                    .attr("fill", "red")
                     .attr("r", 8);

                   tooltip.transition().duration(200)
                     .style("opacity", 0.9);
                      tooltip.select("div").html( d.name +":" +" <br><strong>"  + d.value + "</strong>")
                     .style("position", "fixed")
                     .style("text-align", "center")
                     .style("width", "120px")
                     .style("height", "45px")
                     .style("padding", "2px")
                     .style("font", "12px sans-serif")
                     .style("background", "lightsteelblue")
                     .style("border", "0px")
                     .style("border-radius", "8px")
                     .style("left", (d3.event.pageX + 15) + "px")
                     .style("top", (d3.event.pageY - 28) + "px");

                 });
               var tooltip = d3.select("body").append("div")
               .attr("class", "tooltip")
               .style("opacity", 0.5);

             tooltip.append("rect")
               .attr("width", 30)
               .attr("height", 20)
               .attr("fill", "#ffffff")
               .style("opacity", 0.5);

             tooltip.append("div")
               .attr("x", 15)
               .attr("dy", "1.2em")
               .style("text-anchor", "middle")
               .attr("font-size", "1.5em")
               .attr("font-weight", "bold");


        // Add the X Axis
             svg.append("g")
             .attr("class", "x axis")
             .attr("transform", "translate(0," + height + ")")
              .call(xAxis)
               .selectAll("text")
               .style("text-anchor", "end")
               .attr("dx", "0.1em")
               .attr("dy", "-1em")
               .attr("y", 30)
               .attr("transform", "rotate(-30)");


        // Add the Y Axis
               svg.append("g") 
               .attr("class", "y axis")
                   .call(yAxis);

               svg.append("text")
                .attr("transform", "rotate(-90)")
                .attr("y",-30)
                .attr("x", 0 - (height / 2))
                .attr("dy", "-2em")
                .attr("text-anchor", "middle")
                .style("font-size", "13px")
                .text("Count");

1 个答案:

答案 0 :(得分:1)

有很多方法可以做你想做的事。此解决方案强力搜索线上鼠标悬停位置的最近点:

  .attr("class", "line")
  .on("mouseover", function(d) {
    var mx = d3.mouse(svg.node())[0], // x position of cursor
      c = 1e10,
      idx = -1;
    xs.forEach(function(x, i) { //xs is an array of points x location
      var dis = Math.abs(x - mx); //distance
      if (dis < c) {
        c = dis;
        idx = i; // find closest
      }
    });
    var dot = dots.nodes()[idx], //get dot
      d3Dot = d3.select(dot);
    d3Dot.on("mouseover").call(dot, d3Dot.datum()); //call it's mouseover event
  })
  .on("mouseout", function(d){
    tooltip.style("display", "none");
    dots
      .attr("fill", "black")
      .attr("r", 4);
  });

运行代码:

&#13;
&#13;
<!DOCTYPE html>
<html>

<head>
  <script data-require="d3@4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
  <style>
      .chart {
      background: #eee;
      padding: 3px;
    }
    
    .chart div {
      width: 0;
      transition: all 1s ease-out;
      -moz-transition: all 1s ease-out;
      -webkit-transition: all 1s ease-out;
    }
    
    .chart div {
      font: 10px sans-serif;
      background-color: steelblue;
      text-align: right;
      padding: 3px;
      margin: 5px;
      color: white;
      box-shadow: 2px 2px 2px #666;
    }
    
    .bar {
      fill: orange;
    }
    
    .bar:hover {
      fill: red;
    }
    
    rect.background {
      fill: white;
    }
    
    .axis {
      shape-rendering: crispEdges;
    }
    
    .axis path,
    .axis line {
      fill: none;
      stroke: #000;
    }
    
    tooltip {
      position: absolute;
      text-align: center;
      width: 120px;
      height: 45px;
      padding: 2px;
      font: 12px sans-serif;
      background: lightsteelblue;
      border: 0px;
      border-radius: 8px;
      pointer-events: none;
    }
    
    bar {
      fill: #8CD3DD;
    }
    
    bar:hover {
      fill: #F56C4E;
    }
    /* .tick:nth-child(3n) text {
    visibility: hidden;
} */
    
    .d3-tip {
      line-height: 1;
      font-weight: bold;
      padding: 12px;
      background: rgba(0, 0, 0, 0.8);
      color: #fff;
      border-radius: 2px;
    }
    /* Creates a small triangle extender for the tooltip */
    
    .d3-tip:after {
      box-sizing: border-box;
      display: inline;
      font-size: 10px;
      width: 100%;
      line-height: 1;
      color: rgba(0, 0, 0, 0.8);
      content: "\25BC";
      position: absolute;
      text-align: center;
    }
    /* Style northward tooltips differently */
    
    .d3-tip.n:after {
      margin: -1px 0 0 0;
      top: 100%;
      left: 0;
    }
    
    Creates a small triangle extender for the tooltip .d3-tip:after {
      box-sizing: border-box;
      display: inline;
      font-size: 10px;
      width: 100%;
      line-height: 1;
      color: rgba(0, 0, 0, 0.8);
      content: "\25BC";
      position: absolute;
      text-align: center;
    }
    
    Style northward tooltips differently .d3-tip.n:after {
      margin: -1px 0 0 0;
      top: 100%;
      left: 0;
    }
    
    svg text.label {
      fill: #ff0000;
      font: 25px;
      text-anchor: middle;
      font-weight: 400;
      text-anchor: middle;
      font-size: 125%;
      text-anchor: start;
    }
    
    path {
      stroke: steelblue;
      stroke-width: 2;
      /*   fill: none; */
      /* commenting out to show multiple ring donut chart. */
    }
    
    pathline {
      fill: none;
      stroke-width: 2;
      stroke: #000;
    }
    
    .axis path,
    .axis line {
      fill: none;
      stroke: grey;
      stroke-width: 1;
      shape-rendering: crispEdges;
    }
    
    .graph {
      width: auto;
    }
    
    .tooltip {
      color: black;
    }
    
    .axis {
      font: 12px sans-serif, Georgia, Arial;
    }
    
    .axis path,
    .axis line {
      fill: none;
      stroke: #dadada;
      shape-rendering: crispEdges;
    }
    /* ANGULAR MODAL */
    
    .dialogdemoBasicUsage #popupContainer {
      position: relative;
    }
    
    .dialogdemoBasicUsage .footer {
      width: 100%;
      text-align: center;
      margin-left: 20px;
    }
    
    .dialogdemoBasicUsage .footer,
    .dialogdemoBasicUsage .footer > code {
      font-size: 0.8em;
      margin-top: 50px;
    }
    
    .dialogdemoBasicUsage button {
      width: 200px;
    }
    
    .dialogdemoBasicUsage div#status {
      color: #c60008;
    }
    
    .dialogdemoBasicUsage .dialog-demo-prerendered md-checkbox {
      margin-bottom: 0;
    }
    /* Angular grids */
    
    .gridListdemoBasicUsage md-grid-list {
      margin: 8px;
    }
    
    gridListdemoBasicUsage .gray {
      background: #f5f5f5;
    }
    
    .gridListdemoBasicUsage .green {
      background: #b9f6ca;
    }
    
    .gridListdemoBasicUsage .yellow {
      background: #ffff8d;
    }
    
    .gridListdemoBasicUsage .blue {
      background: #84ffff;
    }
    
    .gridListdemoBasicUsage .purple {
      background: #b388ff;
    }
    
    .gridListdemoBasicUsage .red {
      background: #ff8a80;
    }
    
    .gridListdemoBasicUsage md-grid-tile {
      transition: all 400ms ease-out 50ms;
    }
    
    md-grid-list md-grid-tile md-grid-tile-footer,
    md-grid-list md-grid-tile md-grid-tile-header {
      height: 25px;
    }
    
    .svg-container {
      display: inline-block;
      position: relative;
      width: 100%;
      padding-bottom: 12%;
      vertical-align: top;
      overflow: hidden;
    }
    
    .line {
      fill: none;
      stroke: steelblue;
      stroke-width: 3px;
    }
    
    .svg-content-responsive {
      display: inline-block;
      position: absolute;
      top: 10px;
      left: 0;
    }
    
    .svg-content {
      display: inline-block;
      position: absolute;
      top: 0;
      left: 0;
    }
    
    #linechart {
      width: 100%;
      height: 100%;
      position: absolute;
    }
    /* TESTING GRIDS */
    
    .chart-tile {
      display: block;
      height: 100%;
      width: 100%;
    }
    
    .gridListdemoDynamicTiles .s64 {
      font-size: 64px;
    }
    
    .gridListdemoDynamicTiles .s32 {
      font-size: 48px;
    }
    
    .gridListdemoDynamicTiles md-grid-list {
      margin: 8px;
    }
    
    .gridListdemoDynamicTiles md-grid-tile {
      box-shadow: 2px 2px 3px 3px #888888;
      transition: all 300ms ease-out 50ms;
    }
    
    .gridListdemoDynamicTiles md-grid-tile md-grid-tile-footer {
      background: rgba(0, 0, 0, 0.68);
      height: 36px;
    }
    
    .gridListdemoDynamicTiles md-grid-tile-footer figcaption {
      width: 100%;
    }
    
    .gridListdemoDynamicTiles md-grid-tile-footer figcaption h3 {
      margin: 0;
      font-weight: 700;
      width: 100%;
      text-align: center;
    }
    /*
Copyright 2016 Google Inc. All Rights Reserved. 
Use of this source code is governed by an MIT-style license that can be in foundin the LICENSE file at https://material.angularjs.org/license.
*/
    
    @media (min-width: 1200px) {
      .container {
        width: 1400px;
        margin-right: -50px;
      }
    }
    
    @media only screen and (max-width: 260px) and (min-width: 1600px) {
      .container {
        width: 1700px;
        margin-right: -50px;
      }
    }
    
    #wrapper {
      border-style: solid;
      height: 100px;
      width: 200px;
    }
    
    #dropdown {
      vertical-align: middle;
      width: 80px;
      margin-left: 300px;
      margin-top: -30px;
    }
    /* Styles go here */
    
    .gridster-item {
      background-color: darkgrey;
    }
    
    .my-class {
      border: 1px solid red;
      height: 50px;
    }
    
    body {
      background-color: #fcfcfc;
    }
    
    .container {
      text-align: center;
      padding: 15px;
    }
    
    .left-div {
      display: inline-block;
      max-width: 300px;
      text-align: left;
      padding: 30px;
      background-color: #ddd;
      border-radius: 3px;
      margin: 15px;
      vertical-align: top;
    }
    
    .right-div {
      display: inline-block;
      max-width: 150px;
      text-align: left;
      padding: 30px;
      background-color: #ddd;
      border-radius: 3px;
      margin: 15px;
    }
    
    .left-text,
    .right-text {
      font: 300 16px/1.6 'Helvetica Neue' sans-serif;
      color: #333;
    }
    
    @media screen and (max-width: 600px) {
      .left-div, .right-div {
        max-width: 100%;
      }
  </style>
</head>

<body>
  <div id="charts">

  </div>
  <script>
    var all = [{
      "name": "SEASONAL_LYQ1",
      "code": "SEASONAL_LYQ1",
      "parent": "SEASONAL_POP",
      "value": "0",
      "label": "LYQ1",
      "children": []
    }, {
      "name": "SEASONAL_LYQ2",
      "code": "SEASONAL_LYQ2",
      "parent": "SEASONAL_POP",
      "value": "10",
      "label": "LYQ2",
      "children": []
    }, {
      "name": "SEASONAL_LYQ3",
      "code": "SEASONAL_LYQ3",
      "parent": "SEASONAL_POP",
      "value": "16",
      "label": "LYQ3",
      "children": []
    }, {
      "name": "SEASONAL_LYQ4",
      "code": "SEASONAL_LYQ4",
      "parent": "SEASONAL_POP",
      "value": "10",
      "label": "LYQ4",
      "children": []
    }, {
      "name": "SEASONAL_CYQ1",
      "code": "SEASONAL_CYQ1",
      "parent": "SEASONAL_POP",
      "value": "0",
      "label": "CYQ1",
      "children": []
    }, {
      "name": "SEASONAL_CYQ2",
      "code": "SEASONAL_CYQ2",
      "parent": "SEASONAL_POP",
      "value": "10",
      "label": "CYQ2",
      "children": []
    }, {
      "name": "SEASONAL_CYQ3",
      "code": "SEASONAL_CYQ3",
      "parent": "SEASONAL_POP",
      "value": "16",
      "label": "CYQ3",
      "children": []
    }, {
      "name": "SEASONAL_CYQ4",
      "code": "SEASONAL_CYQ4",
      "parent": "SEASONAL_POP",
      "value": "10",
      "label": "CYQ4",
      "children": []
    }];
    var margin = {
        top: 30,
        right: 20,
        bottom: 100,
        left: 80
      },
      width = 400 - margin.left - margin.right,
      height = 300 - margin.top - margin.bottom;



    var svg = d3.select("#charts").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 + ")")
      .attr("preserveAspectRatio", "xMinYMin meet")
      .attr("viewBox", "0 0 600 400")
      //class to make it responsive
      .classed("svg-content-responsive", true);;


    // Set the ranges

    var x = d3.scaleBand().rangeRound([0, width]).padding(0.1);
    var y = d3.scaleLinear().range([height, 0]);
    var xAxis;

    function tickLabels(dataLength, d) {
      if (dataLength > 9) return "";
      return d.replace(/^.+_/, '')
    }

    /*  var xAxis = d3.axisBottom().scale(x)
	            					.ticks()
				            		.tickFormat(function(d,i) {  return tickLabels(toCSV.length, d) })*/
    /*   
		           var ticks = d3.selectAll(".tick text");
		           ticks.attr("class", function(d,i){
		               if(i%3 != 0) d3.select(this).remove();
		           });
		           
		           
		           */


    if (all.length < 16) {
      xAxis = d3.axisBottom().scale(x).ticks(10);
    } else {
      xAxis = d3.axisBottom().scale(x)
        .tickFormat(function(d, i) {
          return i % 3 === 0 ? d : null;
        });
    }
    //     var xAxis = d3.axisBottom().scale(x).ticks(10);

    var yAxis = d3.axisLeft().scale(y).ticks(5).tickSizeInner(-width).tickSizeOuter(0).tickPadding(10);


    // Define the line

    var valueline = d3.line().curve(d3.curveCatmullRom)
      .x(function(d) {
        return x(d.label) + x.bandwidth() / 2;
      })
      .y(function(d) {
        return y(d.value);
      });
    var data = all;


    // Get the data
    data.forEach(function(d) {
      d.value = +d.value;
    });

    // Scale the range of the data
    x.domain(data.map(function(d) {
      return d.label;
    }));
    y.domain([0, d3.max(data, function(d) {
      return d.value;
    })]);

    // Add the valueline path.
    svg.append("path") // Add the valueline path.
      .attr("d", valueline(data))
      .attr("class", "line")
      .on("mouseover", function(d) {
        var mx = d3.mouse(svg.node())[0],
          c = 1e10,
          idx = -1;
        xs.forEach(function(x, i) {
          var dis = Math.abs(x - mx);
          if (dis < c) {
            c = dis;
            idx = i;
          }
        });
        var dot = dots.nodes()[idx],
          d3Dot = d3.select(dot);
        d3Dot.on("mouseover").call(dot, d3Dot.datum());
      })
      .on("mouseout", function(d){
        tooltip.style("display", "none");
        dots
          .attr("fill", "black")
          .attr("r", 4);
      })


    var xs = [];
    // Add the scatterplot
    var dots = svg.selectAll(".dot")
      .data(data)
      .enter().append("circle")
      .attr("class", "dot")
      .attr("r", 4)
      .attr("cx", function(d) {
        var xp = x(d.label) + x.bandwidth() / 2;
        xs.push(xp);
        return xp;
      })
      .attr("cy", function(d) {
        return y(d.value);
      })
      .on("mouseout", function() {
        tooltip.style("display", "none");
        d3.select(this)
          .attr("fill", "black")
          .attr("r", 4);
      })
      .on("mouseover", function(d) {
        
        tooltip.style("display", "block");

        var self = d3.select(this);

        self
          .attr("fill", "red")
          .attr("r", 8);

        tooltip.transition().duration(200)
          .style("opacity", 0.9);

        tooltip.select("div").html(d.name + ":" + " <br><strong>" + d.value + "</strong>")
          .style("position", "fixed")
          .style("text-align", "center")
          .style("width", "120px")
          .style("height", "45px")
          .style("padding", "2px")
          .style("font", "12px sans-serif")
          .style("background", "lightsteelblue")
          .style("border", "0px")
          .style("border-radius", "8px")
          .style("left", (+self.attr('cx') + 100) + "px")
          .style("top", (+self.attr('cy')) + "px");
      });

    var tooltip = d3.select("body").append("div")
      .attr("class", "tooltip")
      .style("opacity", 0.5);

    tooltip.append("rect")
      .attr("width", 30)
      .attr("height", 20)
      .attr("fill", "#ffffff")
      .style("opacity", 0.5);

    tooltip.append("div")
      .attr("x", 15)
      .attr("dy", "1.2em")
      .style("text-anchor", "middle")
      .attr("font-size", "1.5em")
      .attr("font-weight", "bold");


    // Add the X Axis
    svg.append("g")
      .attr("class", "x axis")
      .attr("transform", "translate(0," + height + ")")
      .call(xAxis)
      .selectAll("text")
      .style("text-anchor", "end")
      .attr("dx", "0.1em")
      .attr("dy", "-1em")
      .attr("y", 30)
      .attr("transform", "rotate(-30)");


    // Add the Y Axis
    svg.append("g")
      .attr("class", "y axis")
      .call(yAxis);

    svg.append("text")
      .attr("transform", "rotate(-90)")
      .attr("y", -30)
      .attr("x", 0 - (height / 2))
      .attr("dy", "-2em")
      .attr("text-anchor", "middle")
      .style("font-size", "13px")
      .text("Count");
  </script>
</body>

</html>
&#13;
&#13;
&#13;