Scenario:
On mouse hover on a particular node, I am trying to highlight that node and all its interconnected nodes by changing the opacity and all the other nodes should be blurred.
Expectation: On mouse hover on a node, Opacity of that node and all its interconnected nodes should be 1 whereas the other nodes should be blurred using lesser opacity.
Existing functionality: It does not work as expected when i hover on any node for the first time. But from the second time onward It is working properly.
Please refer the jsfiddle: jsfiddle
var nodeElements = g.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", 60)
.attr("stroke", "#fff")
.attr('stroke-width', 21)
.attr("id", function(d) { return d.id })
//.attr("fill", function(d) {return color(d.id)})
.attr('fill', function(d, i) { return 'url(#grad' + i + ')'; })
.on('contextmenu', function(d){
d3.event.preventDefault();
menu(d3.mouse(svg.node())[0], d3.mouse(svg.node())[1]);
})
.on('mouseover', selectNode)
.on('mouseout', releaseNode)
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
function selectNode(selectedNode) {
var neighbors = getNeighbors(selectedNode)
nodeElements.transition().duration(500)
.attr('opacity', function(node) {
return setOpacity(node,neighbors, selectedNode);
})
.attr('r', function(node) {
return getNodeRadius(node,neighbors);
});
nodeElements.attr('fill', function(node) {
// send selectedNode to your getNodeColor
return getNodeColor(node,neighbors,selectedNode);
})
textElements.style('font-size', function(node) {
return getTextColor(node, neighbors)
})
textElements.attr('opacity', function(node) {
return setOpacity(node,neighbors, selectedNode);
})
linkElements.style('stroke', function(link) {
return getLinkColor(selectedNode, link)
})
}
function releaseNode() {
nodeElements
.attr('r', 60)
.attr('fill', function(d, i) { return 'url(#grad' + i + ')'; })
.attr('opacity', 1);
linkElements.style('stroke', 'grey');
textElements.attr('opacity','1');
}
function setOpacity(node, neighbors, selectedNode) {
if (Array.isArray(neighbors) && neighbors.indexOf(node.id) > -1) {
return 1;
} else {
return 0.3;
}
}
The solution is quite simple: don't use attr
to set the opacity, use style
instead:
nodeElements.transition()
.duration(500)
.style('opacity', function(node) {
return setOpacity(node,neighbors, selectedNode);
})
Here is the code with that change only: https://jsfiddle.net/uye24zjn/
The explanation is a bit more complicated. Firstly, have in mind that you never set any previous opacity, be it using attr
or style
.
When you transition the opacity D3 uses a getter to retrieve the original value, applying the respective method (attr
or style
). And here comes the problem: because you never set any opacity using the attr
method, the previous value using attr
as the getter is null
. However, using style
, the value is 1
:
const circle = d3.select("g")
.append("circle")
.attr("r", 50);
console.log("The opacity value using 'attr' as a getter is: " + circle.attr("opacity"))
console.log("The opacity value using 'style' as a getter is: " + circle.style("opacity"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg>
<g transform="translate(150,75)"></g>
</svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
The consequence is that D3 will try to transition from null
(zero) to 1
, and you'll see the circles disappearing at the begin of the transition. As a demo, hover over the circle:
const circle = d3.select("g")
.append("circle")
.attr("r", 50);
circle.on("mouseover", function() {
d3.select(this).transition()
.duration(1000)
.attr("opacity", function() {
return 1
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg>
<g transform="translate(150,75)"></g>
</svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
The issue happens only on the first mouseover (just like your code), because after hovering over the circle the opacity is set as an attribute, and there will be no more null
when attr
is used as a getter.
As a general rule, use attr
to set attributes, and use style
to set the styles. In other words: use style
to set anything that you would do using a CSS file.