m_p m_p - 6 months ago 24
Javascript Question

More efficient way to handle Mouseevent

I have implemented a sankey chart, with the ability to trace a nodes path through the chart. However as the functionality stands, a user has to

mouseenter
a node to both highlight and remove the highlight of a node's path. What is a good way to highlight the node's path on mouseenter and then remove that highlight (via opacity change)
mouseout
? Btw I have tried to just call the function for using a combination of
mouseenter
and
mouseout
and that didn't seem to fix the problem. Here is my code:

function highlightNodeLinks(node, i) {

var remainingNodes = [],
nextNodes = [],
strokeOpacity = 0,
traverse;

if ( d3.select(this).attr('data-hover') === '1' ) {
d3.select(this).attr('data-hover', '0');
strokeOpacity = 0.2;
} else {
d3.select(this).attr('data-hover', '1');
strokeOpacity = 0.5;
}

traverse = [{
linkType : 'sourceLinks',
nodeType : 'target'
}, {
linkType : 'targetLinks',
nodeType : 'source'
}];

traverse.forEach(function (step) {
node[step.linkType].forEach(function (link) {
remainingNodes.push(link[step.nodeType]);
highlightLink(link.id, strokeOpacity);
});

while (remainingNodes.length) {
nextNodes = [];
remainingNodes.forEach(function (node) {
node[step.linkType].forEach(function (link) {
nextNodes.push(link[step.nodeType]);
highlightLink(link.id, strokeOpacity);
});
});
remainingNodes = nextNodes;
}
});
}

function highlightLink(id, opacity) {
d3.select('#link-' + id).style('stroke-opacity', opacity);
}


and here is how it is called:

.on('mouseover', highlightNodeLinks)


As always thanks in advance for any consideration and advice.

Answer

In two steps: first a preprocessing, so that each node "knows" the set of links that must be highlighted on mouseover. This is heavy for the memory (it won't scale for very large graphs), but should be much more responsive

//call this once after loading the data and first drawing of the links
node.forEach(function(n) {

 linkIds= []; //add this field to each node

 traverse.forEach(function (step) {
   node[step.linkType].forEach(function (link) {
     remainingNodes.push(link[step.nodeType]);
     linkIds.push(link.id);
   });

   while (remainingNodes.length) {
    nextNodes = [];
     remainingNodes.forEach(function (node) {
      node[step.linkType].forEach(function (link) {
         nextNodes.push(link[step.nodeType]);
         linkIds(link.id);
       });
     });
     remainingNodes = nextNodes;
   }

   //add the list of links to a new  field in the node
   //& transform already ids into d3 selections
   n.linksToHighlight = linkIds.map(function(id) {return d3.select("link-"+id)})
 });

Then, then highlighting code:

function highlighter(ratio) {
  return function(node) {
     node.linksToHighlight.forEach(function (s) {s.style("stroke-opacity",ratio})
  }
}


[select nodes]
 .on('mouseenter', highlighter(0.5) )     
 .on('mouseout', highlighter(1) )
Comments