【问题标题】:d3.js multiple line chart highlighting datapoints onlyd3.js 多折线图仅突出显示数据点
【发布时间】:2018-01-22 08:57:06
【问题描述】:

我不太了解 d3.js 中的 bisect 函数,以便通过垂直线突出显示值。

我已经让它适用于一条线/路径,但性能很差,至少在谷歌浏览器中。可能是因为我的函数计算路径上的每个点而不是仅计算数据点,这是我真正需要的。

代码如下:

/*create svg element*/
var svg = d3.select('.linechart')
.append('svg')
.attr('width', w)
.attr('height', h)
.attr('id', 'chart');

/*x scale*/
var xScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) {
  return d[0];
})])
.range([padding, w - padding]);

/*y scale*/
var yScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) {
  return d[1];
})])
.range([h - padding, padding]);

/*x axis*/
var xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom')
.ticks(20)
.tickSize(0, 0)
//.tickPadding(padding);

/*append x axis*/
svg.append('g')
.attr({
  'class': 'xaxis',
  //'transform': 'translate(0,' + (h - padding) + ')'
  'transform': 'translate(0,' + 0 + ')'
})
.call(xAxis);

/*y axis*/
var yAxis = d3.svg.axis()
.scale(yScale)
.orient('left')
.tickSize(0, 0)
.tickValues([0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]);

/*append y axis*/
svg.append('g')
.attr({
  'class': 'yaxis',
  'transform': 'translate(' + padding + ',0)'
})
.call(yAxis);


/*define line*/
var lines = d3.svg.line()
.x(function(d) {
  return xScale(d[0])
})
.y(function(d) {
  return yScale(d[1])
})
.interpolate('monotone');


/*append line*/
var path = svg.append('path')
.attr({
  'd': lines(dataset),
  'fill': 'none',
  'class': 'lineChart'
});

/*get length*/
var length = svg.select('.lineChart').node().getTotalLength();

/*animate line chart*/
svg.select('.lineChart')
.attr("stroke-dasharray", length + " " + length)
.attr("stroke-dashoffset", length)
.transition()
.ease('linear')
.delay(function(d) {
  return dataset.length * 100;
})
.duration(3000)
.attr("stroke-dashoffset", 0);

/*add points*/
var points = svg.selectAll('circle')
.data(dataset)
.enter()
.append('circle');

/*point attributes*/
points.attr('cy', function(d) {
  return yScale(d[1])
})
.style('opacity', 0)
.transition()
.duration(1000)
.ease('elastic')
.delay(function(d, i) {
  return i * 100;
})
.attr({
  'cx': function(d) {
    return xScale(d[0]);
  },
  'cy': function(d) {
    return yScale(d[1]);
  },
  'r': 5,
  'class': 'datapoint',
  'id': function(d, i) {
    return i;
  }
})


.style('opacity', 1);

//  LINES INDIVIDUAL
function drawIndividualLines (){

  /*define line*/
  var linesIndividual = d3.svg.line()
  .x(function(d) {
    return xScale(d[0])
  })
  .y(function(d) {
    return yScale(d[1])
  })
  .interpolate('monotone');

  /*append line*/
  var pathIndividual = svg.append('path')
  .attr({
    //'d': linesIndividual(datasetIndividual),
    'd': linesIndividual(datasetIndividual),
    'fill': 'none',
    'class': 'lineChartIndividual'
  });

  /*get length*/
  var lengthIndividual = svg.select('.lineChartIndividual').node().getTotalLength();

  /*animate line chart*/
  svg.select('.lineChartIndividual')
  .attr("stroke-dasharray", lengthIndividual + " " + lengthIndividual)
  .attr("stroke-dashoffset", lengthIndividual)
  .transition()
  .ease('linear')
  .delay(function(d) {
    return datasetIndividual.length * 100;
  })
  .duration(3000)
  .attr("stroke-dashoffset", 0);

  /*add points*/
  var pointsIndividual = svg.selectAll('circleIndividual')
  .data(datasetIndividual)
  .enter()
  .append('circle');

  /*point attributes*/
  pointsIndividual.attr('cy', function(d) {
    return yScale(d[1])
  })
  .style('opacity', 0)
  .transition()
  .duration(1000)
  .ease('elastic')
  .delay(function(d, i) {
    return i * 100;
  })
  .attr({
    'cx': function(d) {
      return xScale(d[0]);
    },
    'cy': function(d) {
      return yScale(d[1]);
    },
    'r': 5,
    'class': 'datapointIndividual',
    'id': function(d, i) {
      return i;
    }
  })


  .style('opacity', 1);
};


$(".individual").click(function() {
  drawIndividualLines();
  drawIndividualLegend();
  swapShifts();
});

var mouseG = svg.append("g")
.attr("class", "mouse-over-effects");

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

var linesForMouse = document.getElementsByClassName('lineChart');
var linesIndividualForMouse = document.getElementsByClassName('lineChartIndividual');

var mousePerLine = mouseG.selectAll('.mouse-per-line')
.data(dataset)
.enter()
.append("g")
.attr("class", "mouse-per-line");

mousePerLine.append("circle")
.attr("r", 7)
.style("stroke", "#A0B1AB")
.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', w) // can't catch mouse events on a g element
.attr('height', h)
.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(w/mouse[0])
    //var xDate = xScale.invert(mouse[0]),
    //bisect = d3.bisector(function(d) { return d.date; }).right;
    //      idx = bisect(d.values, xDate);

    var beginning = 0,
    end = length,
    target = null
    console.log(end);

    while (true) {
      target = Math.floor((beginning + end) / 2);
      //pos = linesForMouse[i].getPointAtLength(target);
      pos = svg.select('.lineChart').node().getPointAtLength(target);
      //console.log(pos);
      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(yScale.invert(pos.y).toFixed(2))
    .attr("fill", "#fff");

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

这是一个小提琴: https://jsfiddle.net/mindcraft/vk2w7k2f/2/

所以我的问题是:

如何仅突出显示数据点? (通过 bisect 函数我还不明白,我猜……)

如何将相同的功能应用到第二行(以更有效的方式单击“显示个人”按钮后可见?

提前谢谢你!

【问题讨论】:

    标签: javascript d3.js charts highlighting bisection


    【解决方案1】:

    让我看看能不能给你解释一下d3.bisector()。与搜索两个数据点之间的每个值相比,它会给您带来巨大的性能改进。平分线是一种用于排序数据的数组搜索形式。但是,您不是搜索特定值,而是搜索插入任意值x 的正确索引以保持排序顺序(将数组平分线 bisects 分成两半,一个一个值小于或等于x,一个值大于x)。该索引还为您提供数据集中与新值最接近的值(即indexindex-1。这就是bisector() 在您的示例中真正有用的方式。它基本上会返回数据集中最接近的两个值到鼠标的位置。您所要做的就是算术找出两者中的哪一个是最接近的

    这是来自https://bl.ocks.org/micahstubbs/e4f5c830c264d26621b80b754219ae1b 的过程示例,我的 cmets 映射到我的上述解释:

    function mousemove() {
      //convert absolute coordinates to the proper scale
      const x0 = x.invert(d3.mouse(this)[0]);
      //bisect the data
      const i = bisectDate(data, x0, 1);
      //get the two closest elements to the mouse
      const d0 = data[i - 1];
      const d1 = data[i];
      //check which one is actually the closest
      const d = x0 - d0.date > d1.date - x0 ? d1 : d0;
      //draw your lines
      focus.attr('transform', `translate(${x(d.date)}, ${y(d.close)})`);
      focus.select('line.x')
        .attr('x1', 0)
        .attr('x2', -x(d.date))
        .attr('y1', 0)
        .attr('y2', 0);
    
      focus.select('line.y')
        .attr('x1', 0)
        .attr('x2', 0)
        .attr('y1', 0)
        .attr('y2', height - y(d.close));
    
      focus.select('text').text(formatCurrency(d.close));
    }
    

    【讨论】:

    • 非常感谢您的解释。一般概念对我来说已经很清楚了,但是您的 cmets 仍然可以帮助我更好地理解它。在此处提出问题之前,我在寻找解决方案时已经找到了该示例,但我无法使其适用于我当前的设置。
    • 但是我又试了一次,还是没有成功。我正在使用 d3.js v3,但也有一个示例:bl.ocks.org/micahstubbs/d66a1662fd64a08051dc473f0d1f956e 我个人的问题是数据绑定——或者更准确地说——让 mousemove() 函数与我当前的数据设置一起工作。我没有使用带有命名表头的 tsv 文件,而是一个数组(二维?),我的整个结构不同,这让我很难应用你建议的概念。
    • 假设我的数据集如下所示:var dataset = [ [1, (Math.round(Math.random() * 50) + 50)], [2, (Math.round(Math.random() * 50) + 50)], [3, (Math.round(Math.random() * 50) + 50)], [4, (Math.round(Math.random() * 50) + 50)], [5, (Math.round(Math.random() * 50) + 50)] ]; 我如何定义 const bisectDate? (在示例中为: const bisectDate = d3.bisector(d => d.date).left;)
    • 看起来您将数据存储为一个长度为 2 的数组。在 d3.bisector 变量声明的情况下,d 将引用每个长度为 2 的数组。假设数据的“有趣”部分位于索引 1,您将声明 const bisectDate = d3.bisector(d =&gt; d[1]).left; 如果您将数据存储为对象数组({ key: value }),您可以声明 d3.bisector(d =&gt; d.key) 等。
    猜你喜欢
    • 2017-09-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-09-23
    • 1970-01-01
    • 2019-03-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多