Frame91 Frame91 - 8 months ago 25
CSS Question

D3 mouse-over of links in node-link diagram, increase 'accepted' range

Sometimes I need to display edges in node-link diagrams with a very small stroke-width (< 3px). This makes it very hard for the user to hover over them.

I'm using the

.on('mouseover', () => //do stuff)
function.

Is there an easy way to increase the radius that would trigger the mouseover event? Let's say it should always assume the edge has a stroke-width of at least 5px.

I'm coloring the edges dynamically, but is there maybe a way to set the color of the edge to something like (see the gray panel as the edge, layed out horizontally):

transparent (2px)
color (1px)
transparent (2px)


So that it actually has a size of 5px, but only 1px is visible?

Or do I really have to calculate whether or not my edge is overlapping with my mouse manually? (It's definitely possible, but given the fact that some edges are curved, others not, ... it really is a hassle).

Answer Source

Is there an easy way to increase the radius that would trigger the mouseover event?

No, the event handler is added to the element, and if a narrow element has a stroke width of 3px the function will only run when the mouse is over those pixels.

Is there maybe a way to set the color of the edge to something like [...] it actually has a size of 5px, but only 1px is visible?

That's possible using a path and combining a coloured fill with a transparent stroke. However, a way easier approach is just duplicating the selection, with exactly the same attributes, and making the top paths or lines (by "top" I mean the selection that comes later in the code) transparent and with a larger stroke-width.

In this demo, for instance, there are 20px-wide transparent lines, which capture the mousemove event, over the visible narrow lines:

//these lines are painted first
var links = svg.selectAll("foo")
    .data(edges)
    .enter()
    .append("line")
    .style("stroke", "#ccc")
    .style("stroke-width", 1);

//these transparent lines are painted on top, and they capture the mousemove
var linksTransparent = svg.selectAll("foo")
    .data(edges)
    .enter()
    .append("line")
    .style("stroke", "none")
    .attr("pointer-events", "all")
    .style("stroke-width", 20)
    .on("mousemove", d => {
        console.log("source: " + d.source.id + ", target: " + d.target.id)
    });

var width = 400;
var height = 200;

var svg = d3.select("body")
  .append("svg")
  .attr("width", width)
  .attr("height", height);

var nodes = [{
  "id": "One"
}, {
  "id": "Two"
}, {
  "id": "Three"
}, {
  "id": "Four"
}];

var edges = [{
  "source": 0,
  "target": 1
}, {
  "source": 0,
  "target": 2
}, {
  "source": 0,
  "target": 3
}];

var simulation = d3.forceSimulation()
  .force("link", d3.forceLink().distance(60))
  .force("charge", d3.forceManyBody().strength(-200))
  .force("center", d3.forceCenter(width / 2, height / 2));

var links = svg.selectAll("foo")
  .data(edges)
  .enter()
  .append("line")
  .style("stroke", "#ccc")
  .style("stroke-width", 1);

var links2 = svg.selectAll("foo")
  .data(edges)
  .enter()
  .append("line")
  .style("stroke", "none")
  .attr("pointer-events", "all")
  .style("stroke-width", 20)
  .on("mousemove", d => {
    console.log("source: " + d.source.id + ", target: " + d.target.id)
  });

var color = d3.scaleOrdinal(d3.schemeCategory20);

var node = svg.selectAll("foo")
  .data(nodes)
  .enter()
  .append("g")
  .call(d3.drag()
    .on("start", dragstarted)
    .on("drag", dragged)
    .on("end", dragended));

var nodeCircle = node.append("circle")
  .attr("r", 10)
  .attr("stroke", "gray")
  .attr("fill", (d, i) => color(i));

var texts = node.append("text")
  .style("fill", "black")
  .attr("dx", 20)
  .attr("dy", 8)
  .text(function(d) {
    return d.id;
  });

simulation.nodes(nodes);
simulation.force("link")
  .links(edges);

simulation.on("tick", function() {
  links.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;
    });

  links2.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;
    });

  node.attr("transform", (d) => "translate(" + d.x + "," + d.y + ")")

});

function dragstarted(d) {
  if (!d3.event.active) simulation.alphaTarget(0.3).restart();
  d.fx = d.x;
  d.fy = d.y;
}

function dragged(d) {
  d.fx = d3.event.x;
  d.fy = d3.event.y;
}

function dragended(d) {
  if (!d3.event.active) simulation.alphaTarget(0);
  d.fx = null;
  d.fy = null;
}
<script src="https://d3js.org/d3.v4.min.js"></script>