I noticed the Control Flow section in the D3 API and tried to apply those on my code. My final goal is to avoid any “mouseenter”, “mouseleave”, “click”.. etc. interaction as long as the transition() is still ongoing.
The snippet displays 3 nodes which are swapping the color after an “mouseenter” event. The problem is, if I interrupt a ongoing transition with in “mouseenter” event the color changes back to white. How can I avoid such behaviour?
After my applying attempts I receive await is only valid in async functions and async generators
as an error.
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>D3v6 Transition Example</title> <!-- call external d3.js framework --> <script src="https://d3js.org/d3.v6.js"></script> </head> <style> body { background-color: rgb(220, 220, 220); overflow: hidden; margin: 0px; } .node { stroke: white; stroke-width: 2px; cursor: pointer; } .node:hover { stroke: red } .link { fill: none; cursor: default; stroke: rgb(0, 0, 0); stroke-width: 3px; } </style> <body> <svg id="svg"> </svg> <script> var graph = { "nodes": [ { "id": 0, }, { "id": 1, }, { "id": 2, } ], "links": [ { "source": 0, "target": 1, }, { "source": 1, "target": 2, }, { "source": 2, "target": 0, } ] } var width = window.innerWidth var height = window.innerHeight var svg = d3.select("svg") .attr("class", "canvas") .attr("width", width) .attr("height", height) .call(d3.zoom().on("zoom", function (event) { svg.attr("transform", event.transform) })) .append("g") // remove zoom on dblclick listener d3.select("svg").on("dblclick.zoom", null) var linkContainer = svg.append("g").attr("class", "linkContainer") var nodeContainer = svg.append("g").attr("class", "nodeContainer") var isRed = false; var simulation = d3.forceSimulation() .force("link", d3.forceLink().id(function (d) { return d.id; }).distance(100)) .force("charge", d3.forceManyBody().strength(-500)) .force("center", d3.forceCenter(width / 2, height / 2)) .force("collision", d3.forceCollide().radius(50)) initialize() function initialize() { link = linkContainer.selectAll(".link") .data(graph.links) .join("line") .attr("class", "link") node = nodeContainer.selectAll(".node") .data(graph.nodes, d => d.id) .join("g") .attr("class", "node") node.selectAll("circle") .data(d => [d]) .join("circle") .attr("r", 30) .style("fill", "whitesmoke") .on("mouseenter", mouseEnter) node.selectAll("text") .data(d=> [d]) .join("text") .style("class", "icon") .attr("font-family", "FontAwesome") .attr("dominant-baseline", "central") .attr("text-anchor", "middle") .attr("font-size", 30) .attr("fill", "black") .attr("stroke-width", "0px") .attr("pointer-events", "none") .text((d) => { return d.id }) simulation .nodes(graph.nodes) .on("tick", ticked); simulation .force("link") .links(graph.links) } function mouseEnter(d) { if (!isRed) { d3.select(this) .transition() .duration(1500) .style("fill", "red") isRed = true } else { d3.select(this) .transition() .duration(1500) .style("fill", "whitesmoke") isRed = false } } function ticked() { // update link positions link .attr("x1", function (d) { return d.source.x; }) .attr("y1", function (d) { return d.source.y; }) .attr("x2", function (d) { return d.target.x; }) .attr("y2", function (d) { return d.target.y; }); // update node positions node .attr("transform", function (d) { return "translate(" + d.x + ", " + d.y + ")"; }); } </script> </body> </html>
Answer
There are several ways to do this, like using a flag. However, the most idiomatic D3 is just check if the element has an active transition, which is can be as simple as:
if(d3.active(this)) return;
Here is your code with that change:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>D3v6 Transition Example</title> <!-- call external d3.js framework --> <script src="https://d3js.org/d3.v6.js"></script> </head> <style> body { background-color: rgb(220, 220, 220); overflow: hidden; margin: 0px; } .node { stroke: white; stroke-width: 2px; cursor: pointer; } .node:hover { stroke: red } .link { fill: none; cursor: default; stroke: rgb(0, 0, 0); stroke-width: 3px; } </style> <body> <svg id="svg"> </svg> <script> var graph = { "nodes": [{ "id": 0, }, { "id": 1, }, { "id": 2, } ], "links": [{ "source": 0, "target": 1, }, { "source": 1, "target": 2, }, { "source": 2, "target": 0, } ] } var width = window.innerWidth var height = window.innerHeight var svg = d3.select("svg") .attr("class", "canvas") .attr("width", width) .attr("height", height) .call(d3.zoom().on("zoom", function(event) { svg.attr("transform", event.transform) })) .append("g") // remove zoom on dblclick listener d3.select("svg").on("dblclick.zoom", null) var linkContainer = svg.append("g").attr("class", "linkContainer") var nodeContainer = svg.append("g").attr("class", "nodeContainer") var isRed = false; var simulation = d3.forceSimulation() .force("link", d3.forceLink().id(function(d) { return d.id; }).distance(100)) .force("charge", d3.forceManyBody().strength(-500)) .force("center", d3.forceCenter(width / 2, height / 2)) .force("collision", d3.forceCollide().radius(50)) initialize() function initialize() { link = linkContainer.selectAll(".link") .data(graph.links) .join("line") .attr("class", "link") node = nodeContainer.selectAll(".node") .data(graph.nodes, d => d.id) .join("g") .attr("class", "node") node.selectAll("circle") .data(d => [d]) .join("circle") .attr("r", 30) .style("fill", "whitesmoke") .on("mouseenter", mouseEnter) node.selectAll("text") .data(d => [d]) .join("text") .style("class", "icon") .attr("font-family", "FontAwesome") .attr("dominant-baseline", "central") .attr("text-anchor", "middle") .attr("font-size", 30) .attr("fill", "black") .attr("stroke-width", "0px") .attr("pointer-events", "none") .text((d) => { return d.id }) simulation .nodes(graph.nodes) .on("tick", ticked); simulation .force("link") .links(graph.links) } function mouseEnter(d) { if (d3.active(this)) return; if (!isRed) { d3.select(this) .transition() .duration(1500) .style("fill", "red") isRed = true } else { d3.select(this) .transition() .duration(1500) .style("fill", "whitesmoke") isRed = false } } function ticked() { // update link positions link .attr("x1", function(d) { return d.source.x; }) .attr("y1", function(d) { return d.source.y; }) .attr("x2", function(d) { return d.target.x; }) .attr("y2", function(d) { return d.target.y; }); // update node positions node .attr("transform", function(d) { return "translate(" + d.x + ", " + d.y + ")"; }); } </script> </body> </html>