D3折线图鼠标悬停坐标效果

D3 Line Chart Mouseover Coordinates Effect

本文关键字:坐标 悬停 折线图 鼠标 D3      更新时间:2023-09-26

我正在尝试创建一个折线图,用户可以将鼠标悬停在点上以显示一个圆圈(也可以在页面上进行其他操作,但我将首先让一个圆圈出现)。

我试过几个教程,结果都是一样的:圆圈要么出现在直线上方,要么出现在侧面,几乎不在图表上。

如何使圆出现在其对应的坐标位置。下面是一个问题和d3代码的Fiddle。JS Fiddle中还包括我的其他失败尝试。

基本图表:

var data=[{
    "date":"January 1, 2008","total":'73'},
    {"date":"February 1, 2008","total":'40'},
    {"date":"March 1, 2008","total":'43'},
    {"date":"April 1, 2008","total":'28'},
    {"date":"May 1, 2008","total":'35'},
    {"date":"June 1, 2008","total":'20'},
    {"date":"July 1, 2008","total":'48'}
          ];

var chartWidth =650;
    var chartHeight=375;
    var chartMargins ={
            top:20,
            right: 20,
            bottom: 20,
            left: 50};
    var yScaleDomain=[1, 100];

    var chart = d3.select('#chartCanvas'),
        WIDTH=chartWidth,
        HEIGHT=chartHeight,
        MARGINS= chartMargins;

    //SET AXIS SCALES

    xScale = d3.time.scale().range([MARGINS.left, WIDTH - MARGINS.right]).domain(d3.extent(data, function(d) { return new Date(d.date); }));
   var x = d3.time.scale()
    .range([0, WIDTH]);
var y = d3.scale.linear()
    .range([HEIGHT, 0]);
    bisectDate = d3.bisector(function(d) { return d.date; }).left,
    yScale = d3.scale.linear().range([HEIGHT - MARGINS.top, MARGINS.bottom]).domain(yScaleDomain);

    //DRAW AXES
    xAxis = d3.svg.axis()
        .scale(xScale),
    yAxis = d3.svg.axis()
        .scale(yScale)
    .orient("left");
    chart.append("svg:g")
        .attr("transform", "translate(0," + (HEIGHT - MARGINS.bottom) + ")")
        .call(xAxis);
    chart.append("svg:g")
        .attr("transform", "translate(" + (MARGINS.left) + ",0)")
        .call(yAxis);
    //DRAW LINES/ PLOT DATA

   var lineGen = d3.svg.line()
        .x(function(d) {
            //Date() turns string date to numerical value
            return xScale(new Date(d.date));
        })
        .y(function(d) {
            return yScale(d.total);
        })
        //tell d3 what type of lines to draw (straight/linear ones)
        .interpolate('linear'); 
 chart.append("path")
      .datum(data)
      .attr("class", "line")
      .attr("d", lineGen);

鼠标悬停尝试1(Fiddle中评论的其他尝试):

var Y_value; 
    var curve1 = chart.select("path.line").data([data]);
    x = d3.time.scale().range([MARGINS.left, WIDTH - MARGINS.right]);
    y = d3.scale.linear().range([HEIGHT - MARGINS.top, MARGINS.bottom]);

    var circle = chart.append("circle")
            .attr("r", 8)
            .attr("cx", 0)
            .attr("cy", 0)
            .style({fill: '#fff', 'fill-opacity': .2, stroke: '#000', "stroke-width": '1px'})
            .attr("opacity", 0);
    var tooltip = circle.append("chart:title");

    chart.on("mouseover", function() {
        var X_pixel = d3.mouse(this)[0],
            X_date = x.invert(X_pixel);
        var Y_pixel = y(Y_value);
        var pathData = curve1.data()[0]; // recupere donnée de la courbe
        pathData.forEach(function(element, index, array) {
                if ((index+1 < array.length) && (array[index].date <= X_date) && (array[index+1].date >= X_date)) {
                    if (X_date-array[index].date < array[index+1].date-X_date)  Y_value = array[index].val;
                    else Y_value = array[index+1].val;
                }
            });
            circle.attr("opacity", 1)
                .attr("cx", X_pixel)
                .attr("cy", Math.round(y(Y_value)));
            tooltip.text("X = " + (X_date) + "'nY = " + (Y_value));
        });
     chart.append('svg:path')
            .attr('d', lineGen(data))
            .attr('stroke', 'green')
            .attr('stroke-width', 2)
            .attr('fill', 'none');
        stickyChart.append('svg:path')
            .attr('d', lineGen2(data))
            .attr('stroke', 'green')
            .attr('stroke-width', 2)
            .attr('fill', 'none');

这位先生有一个很好的实现:

https://bl.ocks.org/mbostock/3902569

如果它消失了,我在这里创建了一个独立的版本:

var margin = {
    top: 20,
    right: 50,
    bottom: 30,
    left: 50
  },
  width = 400,
  height = 150;
var parseDate = d3.time.format("%d-%b-%y").parse,
  bisectDate = d3.bisector(function(d) {
    return d.date;
  }).left,
  formatValue = d3.format(",.2f"),
  formatCurrency = function(d) {
    return "$" + formatValue(d);
  };
var x = d3.time.scale()
  .range([0, width]);
var y = d3.scale.linear()
  .range([height, 0]);
var xAxis = d3.svg.axis()
  .scale(x)
  .orient("bottom");
var yAxis = d3.svg.axis()
  .scale(y)
  .orient("left");
var line = d3.svg.line()
  .x(function(d) {
    return x(d.date);
  })
  .y(function(d) {
    return y(d.close);
  });
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 + ")");
function process(data) {
  data.sort(function(a, b) {
    return a.date - b.date;
  });
  x.domain([data[0].date, data[data.length - 1].date]);
  y.domain(d3.extent(data, function(d) {
    return d.close;
  }));
  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("Price ($)");
  svg.append("path")
    .datum(data)
    .attr("class", "line")
    .attr("d", line);
  var focus = svg.append("g")
    .attr("class", "focus")
    .style("display", "none");
  focus.append("circle")
    .attr("r", 4.5);
  focus.append("text")
    .attr("x", 9)
    .attr("dy", ".35em");
  svg.append("rect")
    .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", mousemove);
  function mousemove() {
    var x0 = x.invert(d3.mouse(this)[0]),
      i = bisectDate(data, x0, 1),
      d0 = data[i - 1],
      d1 = data[i],
      d = x0 - d0.date > d1.date - x0 ? d1 : d0;
    focus.attr("transform", "translate(" + x(d.date) + "," + y(d.close) + ")");
    focus.select("text").text(formatCurrency(d.close));
  }
}
var data = [];
var close = 95;
for (var i = 0; i < 100; i++) {
  var date = new Date();
  var dateValue = date.getDate() + i;
  date.setDate(dateValue);
  close = close - 1 + 2 * Math.random();
  data[i] = {
    date: date,
    close: close
  };
}
process(data);
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;
}
.overlay {
  fill: none;
  pointer-events: all;
}
.focus circle {
  fill: none;
  stroke: steelblue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>