Home > OS >  Go back to previous children order after calling .raise()
Go back to previous children order after calling .raise()

Time:03-17

I'm building a network with d3js (basically nodes and links)

When I mouseouver one node, I want to highlight the associated links and make them top of parentNode to make them really visible

On mouseover, I've made something like this

// get links to highlight given the selected node
var highlightLinks = nodeLinks(id);

lines.each(function(index){

    // if index is in selected links
    if (highlightLinks.include(index)){

         // highlight link with yellow color
         this.setAttribute('style',"stroke:#ffc900")

         // raise the link on top of parent node
         d3.select(this).raise()
    }
    else {
         // else grey
         this.setAttribute('style',"stroke:#1B1B1B")
    }
}

It's working fine with the first mouseouver. But when I get out, my lines aren't ordered the same way anymore, and the if clause is highlighting random links for other mouseovers

I think the solution should be to re-order back the links when exiting mouseover, but I can't find a way to do this. I've stored the movedIndex as an array

What I would like :

  • (state 1) My links before first mouseover : [0,1,2,3,4,5]
  • (action 1) Enter mouseover highlighting 1 and 2, raising them, stored the movedIndex = [1,2]
  • (state 2) My links after mouseover : [0,3,4,5,1,2]
  • (action 2) Exit mouseover highlighting 1 and 2, do some magic with movedIndex to "unraise" them
  • (state 3) My links should be again : [0,1,2,3,4,5]

CodePudding user response:

Calling d3.lower() in reverse order will restore the original order after d3.raise(). See reorderItems as an example:

const colors = ['red', 'green', 'blue', 'yellow', 'orange'];

const g = d3.select('g');
const angleUnit = Math.PI * 2 / colors.length;

const reorderItems = () => {
  for (let index = colors.length - 1; index >= 0; index--)
    g.select(`circle[item-index='${index}']`).lower();
}

const createItem = (color, index) => {
  const angle = angleUnit * index;
  const x = 50 * Math.sin(angle);
  const y = 50 * -Math.cos(angle);
  g.append('circle')
    .attr('cx', x)
    .attr('cy', y)
    .attr('r', 40)
    .attr('item-index', index)
    .style('fill', color)
    .on('mouseenter', function() {
        d3.select(this).raise();
    })
    .on('mouseleave', reorderItems);
}

colors.forEach(createItem);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="200" height="200">
  <g transform="translate(100, 100)" />
</svg>

CodePudding user response:

Provided you're not changing your data, the easiest alternative is selection.order(), which:

Re-inserts elements into the document such that the document order of each group matches the selection order.

Thus, all you need in your mouseout event is this:

lines.order();

Here's a simple demo (using v7):

const svg = d3.select("svg");
const circles = svg.selectAll(null)
  .data(d3.range(0, 1.2, 0.2))
  .enter()
  .append("circle")
  .attr("r", 40)
  .attr("cy", 50)
  .attr("cx", d => 80   240 * d)
  .style("fill", d3.interpolateTurbo);

circles.on("mouseover", event => d3.select(event.currentTarget).raise())
  .on("mouseout", () => circles.order());
<script src="https://d3js.org/d3.v7.min.js"></script>
<svg width="400" height="100"></svg>

  • Related