d3.js多重折线图仅突出显示数据点

时间:2017-08-14 13:40:05

标签: javascript d3.js charts highlighting bisection

我没有完全摆脱d3.js中的bisect函数,以便通过垂直线突出显示值。

我已经让它适用于一条线路/路径,但性能很差,至少在google chrome中。可能是因为我的函数计算路径上的每个点而不是数据点,这是我实际需要的。

以下是代码:

/*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/

所以我的问题是:

如何设置仅突出显示数据点? (通过我不理解的平分功能,我想......)

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

提前谢谢!

1 个答案:

答案 0 :(得分:0)

让我看看能否为您解释d3.bisector()。与搜索两个数据点之间的每个值相比,它将为您带来巨大的性能提升。平分线是一种用于排序数据的阵列搜索形式。但是,您不是要搜索特定值,而是搜索插入任意值x的正确索引,以便维护排序顺序(平分线阵列分成两半) ,一个值小于或等于x,一个值大于x)。此索引还为您提供数据集中与新值最接近的值(即indexindex-1。这就是bisector()在您的示例中非常有用的方式。它基本上会返回数据集中两个与鼠标位置最接近的值。您只需要算法查找两者中哪一个最接近的

以下是https://bl.ocks.org/micahstubbs/e4f5c830c264d26621b80b754219ae1b的过程示例,我的评论映射到上面的解释:

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));
}