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>