TheDimLebowski
TheDimLebowski

Reputation: 474

D3 force layout hover on arrow

I am building a directed graph where on hover event I want to highlight the concerned node, links and neighbors. Everything goes fine until I add arrows to the links.

I cannot workout how to highlight the arrows as well as the links.

Any help is welcome.

jsfiddle: https://jsfiddle.net/dkroaefw/3/

code:

graph = {
  "nodes": [{
    "name": "a"
  }, {
    "name": "b"
  }, {
    "name": "c"
  }, {
    "name": "d"
  }, {
    "name": "e"
  }, {
    "name": "f"
  }, {
    "name": "g"
  }],
  "links": [{
    "source": 0,
    "target": 1,
    "value" :1
  }, {
    "source": 1,
    "target": 2,
    "value" :1
  }, {
    "source": 1,
    "target": 3,
    "value" :1
  }, {
    "source": 0,
    "target": 4,
    "value" :1
  }, {
    "source": 4,
    "target": 5,
    "value" :1
  }, {
    "source": 4,
    "target": 6,
    "value" :1
  }]
}

var linkedByIndex = {};
graph.links.forEach(function(d) {
  linkedByIndex[d.source + "," + d.target] = true;
});

function isConnected(a, b) {
  return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index] || a.index == b.index;
}

function hasConnections(a) {
  for (var property in linkedByIndex) {
    s = property.split(",");
    if ((s[0] == a.index || s[1] == a.index) && linkedByIndex[property])
      return true;
  }
  return false;
}

var width = 500
  height = 400

var force = d3.layout.force()
  .nodes(graph.nodes)
  .links(graph.links)
  .charge(-50)
  .linkDistance(50)
  .size([width, height])
  .start();

// Zoom definition
var zoom = d3.behavior.zoom()
  .scaleExtent([1, 10])
  .on("zoom", zoomed);

function zoomed() {
  container.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}

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


var rect = svg.append("rect")
  .attr("width", width)
  .attr("height", height)
  .style("fill", "none")
  .style("pointer-events", "all")
  .call(zoom);

var container = svg.append("g");

// build the arrow.
var arrow = container.append("svg:defs")
  .selectAll("marker")
  .data(["end"]) // Different link/path types can be defined here
  .enter().append("svg:marker") // This section adds in the arrows
  .attr("class", "arrow")
  .attr("id", String)
  .attr("viewBox", "0 -5 10 10")
  .attr("refX", 20)
  .attr("markerWidth", 6)
  .attr("markerHeight", 6)
  .attr("orient", "auto")
  .append("svg:path")
  .attr("d", "M0,-5L10,0L0,5");

var link = container.append("g")
  .attr("class", "links")
  .selectAll(".links")
  .data(force.links())
  .enter().append("line")
  .attr("class", "link")
  .attr("marker-end", "url(#end)");

var node = container.append("g")
  .attr("class", "nodes")
  .selectAll(".node")
  .data(force.nodes())
  .enter().append("g")
  .attr("class", "node")
  .attr("cx", function(d) {
    return d.x;
  })
  .attr("cy", function(d) {
    return d.y;
  })
  .on("mouseover", mouseover)
  .on("mouseout", mouseout)
  .call(force.drag);

node.append("circle")
  .attr("r", 5);

node.append("text")
  .attr("class", "text")
  .attr("x", 12)
  .attr("dy", ".35em")
  .text('')

force.on("tick", function(f) {

  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;
    });
  node.attr("transform", function(d) {
    return "translate(" + d.x + "," + d.y + ")";
  });
});

function mouseover(d) {
  d3.select(this).select("circle").transition()
    .duration(750)
    .attr("r", 8);
  set_highlight(d);
}

function mouseout(d) {
  d3.select(this).select("circle").transition()
    .duration(750)
    .attr("r", 5);
  exit_highlight(d)
}

function set_highlight(d) {
  node.select("text").text(function(o) {
    return isConnected(d, o) ? o.name : "";
  })
  node.attr("class", function(o) {
    return isConnected(d, o) ? "node-active" : "node";
  });
  link.attr("class", function(o) {
    return o.source.index == d.index || o.target.index == d.index ? "link-active" : "link";
  });
}

function exit_highlight(d) {
  node.attr("class", "node");
  link.attr("class", "link");
  node.select("text").text("")
}

Upvotes: 0

Views: 1157

Answers (1)

Rick
Rick

Reputation: 415

So there are 2 things that you need to take into account imho:

  1. Since you want to highlight only certain arrows, you will need to define two markers and switch the markers for the links.
  2. You will also want to switch the link's style itself.

So basically - check out this fiddle to see the full changes:

  var arrows = defs
     .selectAll("marker")
     // let's add two markers: one for unhovered links and one for hovered links.
     .data(["end", "end-active"]) // Different link/path types can be defined here
     .enter().append("svg:marker") // This section adds in the arrows
     .attr("id", String)
     .attr("viewBox", "0 -5 10 10")
     .attr("refX", 20)
     .attr("markerWidth", 6)
     .attr("markerHeight", 6)
     .attr("orient", "auto")
     .append("svg:path")
     .attr("d", "M0,-5L10,0L0,5");

     //define the classes for each of the markers.
     defs.select("#end").attr("class", "arrow");
     defs.select("#end-active").attr("class", "arrow-active");
//...
// then further down in function set_highlight and exit highlight:
function set_highlight(d) {
  node.select("text").text(function(o) {
    return isConnected(d, o) ? o.name : "";
  })
  node.attr("class", function(o) {
    return isConnected(d, o) ? "node-active" : "node";
  });
  link.attr("marker-end", function(o) {
    return o.source.index == d.index || o.target.index == d.index ? "url(#end-active)" : "url(#end)";
  });
  link.attr("class", function(o) {
    return o.source.index == d.index || o.target.index == d.index ? "link-active" : "link";
  });
}



function exit_highlight(d) {
  node.attr("class", "node");
  link.attr("class", "link");
  link.attr("marker-end", "url(#end)");
  node.select("text").text("")
}

EDIT: if you want to reuse some of the code you could add this:

// inside the set_highlight function
    link.attr("marker-end", function(o) {
        return isLinkForNode(d, o) ? "url(#end-active)" : "url(#end)";
    });
    link.attr("class", function(o) {
        return isLinkForNode(d, o) ? "link-active" : "link";
    });


function isLinkForNode(node, link) {
    return link.source.index == node.index || link.target.index == node.index;
}

Upvotes: 3

Related Questions