最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - d3 raise() not working on specific selection - Stack Overflow

programmeradmin0浏览0评论

I have a linechart made with d3, but due to the shape of the data, the lines and dots (I'm using dot's over the lines for each specific data point) usually end up being in top of each other.

To counter this problem, I ended giving opacity 0.4 to the lines and dots, and when you hover over a line, the lines and dots of this specific line of data pops out, and sets it's opacity to 1.

My problem is: I'm using the .raise() funcion to make them pop out and stand over the rest of the lines and dots, the function is working only with my lines selection and not with my dots selection, and I don't know why.

My code:

// draw the data lines
    const lines = svg.selectAll('.line')
      .data(this.data)
      .enter()
      .append('path')
      .attr('class', 'data.line')
      .attr("fill", "none")
      .attr("stroke", d => colors(d.key))
      .attr("stroke-linejoin", "round")
      .attr("stroke-linecap", "round")
      .attr("stroke-width", 2.5)
      .attr('stroke-opacity', 0.4)
      .attr('d', d => line(d.values))
      .on('mouseenter', d => {
        // Highlight them
        let myCircles = circles.selectAll('.circle');
        lines.attr('stroke-opacity', b => {
          return b.key === d.key ? 1 : 0.4;
        });
        myCircles.attr('fill-opacity', b => {
          return b[this.typeIdentifier] === d.key ? 1 : 0.4;
        });
        // Bring them to the front
        myCircles = circles.selectAll('.circle')
          .filter(b => b[this.typeIdentifier] === d.key);
        const myLines = lines.filter(b => b.key === d.key);
        myLines.raise();
        myCircles.raise();
      });

// draw the circles
    const circles = svg.selectAll('.circle')
      .data(this.data)
      .enter()
      .append('g');

    circles.selectAll('.circle')
      .data(d => d.values)
      .enter()
      .append('circle')
      .attr('class', 'circle')
      .attr('stroke', 'white')
      .attr('stroke-width', 1)
      .attr('r', 6)
      .attr('fill', d => colors(d[this.typeIdentifier]))
      .attr('fill-opacity', 0.4)
      .attr('cx', d => x(d[this.xAxisValue]) + x.bandwidth() / 2)
      .attr('cy', d => y(d[this.yAxisValue]))
      .on('mouseenter', (d, b, j) => {
        tooltip.raise();
        tooltip.style("display", null);
        tooltip.select("#text1").text(d[this.typeIdentifier])
          .attr('fill', colors(d[this.typeIdentifier]));
        tooltip.select('#text4').text(d[this.yAxisValue]);
        tooltip.select('#text5').text(d[this.xAxisValue]);
        const tWidth = tooltip.select('#text1').node().getComputedTextLength() > 60 ? tooltip.select('#text1').node().getComputedTextLength() + 20 : 80;
        tooltipRect.attr('width', tWidth);
        const xPosition = d3.mouse(j[b])[0];
        const yPosition = d3.mouse(j[b])[1];
        if (xPosition + tWidth + 35 < this.xWIDTH) {  // display on the right
          tooltip.attr("transform", `translate(${xPosition + 15}, ${yPosition - 25})`);
        } else {  // display on the left
          tooltip.attr("transform", `translate(${xPosition - tWidth - 15}, ${yPosition - 25})`);
        }
      })
      .on('mouseleave', d => {
        tooltip.style("display", "none");
      })

So, when you hover the mouse over a line, this should bring the line and dots associated to it to the front, with opacity 1, but for some reason, it's only working on the lines selection, and not on the myCircles selection. The selection is not empty, and I've been printing them all along to test it out. Also, I've tried to bring the circles one by one (with singular selections, and with raw elements) to the front using the .raise() method, and it's not working eiter.

Why is it not working? Could it have to do with the tooltip on hover over the circles? Am I doing something wrong and not seeing it?

I have a linechart made with d3, but due to the shape of the data, the lines and dots (I'm using dot's over the lines for each specific data point) usually end up being in top of each other.

To counter this problem, I ended giving opacity 0.4 to the lines and dots, and when you hover over a line, the lines and dots of this specific line of data pops out, and sets it's opacity to 1.

My problem is: I'm using the .raise() funcion to make them pop out and stand over the rest of the lines and dots, the function is working only with my lines selection and not with my dots selection, and I don't know why.

My code:

// draw the data lines
    const lines = svg.selectAll('.line')
      .data(this.data)
      .enter()
      .append('path')
      .attr('class', 'data.line')
      .attr("fill", "none")
      .attr("stroke", d => colors(d.key))
      .attr("stroke-linejoin", "round")
      .attr("stroke-linecap", "round")
      .attr("stroke-width", 2.5)
      .attr('stroke-opacity', 0.4)
      .attr('d', d => line(d.values))
      .on('mouseenter', d => {
        // Highlight them
        let myCircles = circles.selectAll('.circle');
        lines.attr('stroke-opacity', b => {
          return b.key === d.key ? 1 : 0.4;
        });
        myCircles.attr('fill-opacity', b => {
          return b[this.typeIdentifier] === d.key ? 1 : 0.4;
        });
        // Bring them to the front
        myCircles = circles.selectAll('.circle')
          .filter(b => b[this.typeIdentifier] === d.key);
        const myLines = lines.filter(b => b.key === d.key);
        myLines.raise();
        myCircles.raise();
      });

// draw the circles
    const circles = svg.selectAll('.circle')
      .data(this.data)
      .enter()
      .append('g');

    circles.selectAll('.circle')
      .data(d => d.values)
      .enter()
      .append('circle')
      .attr('class', 'circle')
      .attr('stroke', 'white')
      .attr('stroke-width', 1)
      .attr('r', 6)
      .attr('fill', d => colors(d[this.typeIdentifier]))
      .attr('fill-opacity', 0.4)
      .attr('cx', d => x(d[this.xAxisValue]) + x.bandwidth() / 2)
      .attr('cy', d => y(d[this.yAxisValue]))
      .on('mouseenter', (d, b, j) => {
        tooltip.raise();
        tooltip.style("display", null);
        tooltip.select("#text1").text(d[this.typeIdentifier])
          .attr('fill', colors(d[this.typeIdentifier]));
        tooltip.select('#text4').text(d[this.yAxisValue]);
        tooltip.select('#text5').text(d[this.xAxisValue]);
        const tWidth = tooltip.select('#text1').node().getComputedTextLength() > 60 ? tooltip.select('#text1').node().getComputedTextLength() + 20 : 80;
        tooltipRect.attr('width', tWidth);
        const xPosition = d3.mouse(j[b])[0];
        const yPosition = d3.mouse(j[b])[1];
        if (xPosition + tWidth + 35 < this.xWIDTH) {  // display on the right
          tooltip.attr("transform", `translate(${xPosition + 15}, ${yPosition - 25})`);
        } else {  // display on the left
          tooltip.attr("transform", `translate(${xPosition - tWidth - 15}, ${yPosition - 25})`);
        }
      })
      .on('mouseleave', d => {
        tooltip.style("display", "none");
      })

So, when you hover the mouse over a line, this should bring the line and dots associated to it to the front, with opacity 1, but for some reason, it's only working on the lines selection, and not on the myCircles selection. The selection is not empty, and I've been printing them all along to test it out. Also, I've tried to bring the circles one by one (with singular selections, and with raw elements) to the front using the .raise() method, and it's not working eiter.

Why is it not working? Could it have to do with the tooltip on hover over the circles? Am I doing something wrong and not seeing it?

Share Improve this question edited Dec 30, 2018 at 15:17 Gerardo Furtado 102k9 gold badges128 silver badges177 bronze badges asked Dec 25, 2018 at 16:37 Maximiliano Andres Friedl SchwMaximiliano Andres Friedl Schw 3016 silver badges21 bronze badges
Add a ment  | 

1 Answer 1

Reset to default 8

Actually, selection.raise() is working. The problem here is just the tree structure of your SVG: all the circles for a given line belong to a <g> element.

If you look at the docs, you'll see that selection.raise():

Re-inserts each selected element, in order, as the last child of its parent.

The emphasis above is mine: the key work here is parent. So, what you want is to raise the <g> element that contains the selected circles above the other <g> elements for the other circles, not the circles inside their <g> parent.

In your case, it's as simple as changing...

myCircles = circles.selectAll('.circle').filter(etc...)

...to:

myCircles = circles.filter(etc...)

Now, myCircles is the selection with the <g> element, which you can raise. Pay attention to the filter function: as you didn't share your data structure I don't know if the data array for the <g> elements (that is, this.data) contains the key property. Change it accordingly.

Here is a demo:

We have a set of circles for each line, each set inside their own <g> parent. Only the left circles are separated, all other circles are draw one over the other on purpose. When you hover over a circle (use the ones on the left) its <g> container is raised, in this case using...

d3.select(this.parentNode).raise()

..., so all circles are visible:

const svg = d3.select("svg");
const scale = d3.scaleOrdinal(d3.schemeSet1);
const lineGenerator = d3.line()
  .x(function(d) {
    return d.x
  })
  .y(function(d) {
    return d.y
  })
const data = d3.range(5).map(function(d) {
  return {
    key: d,
    values: d3.range(5).map(function(e) {
      return {
        x: 50 + 100 * e,
        y: e ? 150 : 50 + 50 * d
      }
    })
  }
});
const lines = svg.selectAll(null)
  .data(data)
  .enter()
  .append("path")
  .attr("d", function(d) {
    return lineGenerator(d.values);
  })
  .style("fill", "none")
  .style("stroke-width", "3px")
  .style("stroke", function(d) {
    return scale(d.key)
  });
const circleGroups = svg.selectAll(null)
  .data(data)
  .enter()
  .append("g");
const circles = circleGroups.selectAll(null)
  .data(function(d) {
    return d.values
  })
  .enter()
  .append("circle")
  .attr("r", 20)
  .attr("cx", function(d) {
    return d.x
  })
  .attr("cy", function(d) {
    return d.y
  })
  .style("fill", function(d) {
    return scale(d3.select(this.parentNode).datum().key)
  });
circles.on("mouseover", function(d) {
  const thisKey = d3.select(this.parentNode).datum().key;
  lines.filter(function(e) {
    return e.key === thisKey;
  }).raise();
  d3.select(this.parentNode).raise();
})
<script src="https://d3js/d3.v5.min.js"></script>
<svg width="500" height="300"></svg>

发布评论

评论列表(0)

  1. 暂无评论