Fading network connections in directed network - d3js

戏子无情 提交于 2020-04-28 22:53:29

问题


I have a directed network using the d3.layout.force . Adapting this answer, I have managed to get nodes and links to fade if connected (direction of connection doesn't matter).

What I am having trouble with is to be able to change the opacity of markers when the path they are on is having its opacity altered with a mouseover event.

This is the script including the isConnected function for determining what nodes are connected:

A live example is here.

<script>


  function bar() {
  console.log("click");
  force.stop();
  force.start();
}



  var links = [
  {source: "A", target: "D", type: "high"},
  {source: "A", target: "K", type: "high"},
  {source: "B", target: "G", type: "high"},
  {source: "C", target: "A", type: "low"},
  {source: "D", target: "K", type: "low"},
  {source: "E", target: "A", type: "low"},
  {source: "F", target: "B", type: "low"},
  {source: "K", target: "J", type: "low"},
  {source: "F", target: "A", type: "low"},
  {source: "F", target: "I", type: "low"},
  {source: "G", target: "H", type: "low"},
  {source: "E", target: "K", type: "high"},
  {source: "E", target: "G", type: "low"},
  {source: "E", target: "F", type: "high"},
  {source: "D", target: "E", type: "high"}  
];

var nodes = {};

// Compute the distinct nodes from the links.
links.forEach(function(link) {
  link.source = nodes[link.source] || (nodes[link.source] = {name: link.source});
  link.target = nodes[link.target] || (nodes[link.target] = {name: link.target});
});

var width = 960,
    height = 700;

var force = d3.layout.force()
    .nodes(d3.values(nodes))
    .links(links)
    .size([width, height])
    .linkDistance(105)
    .charge(-775)
    .on("tick", tick)
    .start();



force.on("start", function () {
    console.log("start");
});
force.on("end", function () {
    console.log("end");
});

R=18



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

// add defs-marker
svg.append('svg:defs')
    .append('svg:marker')
    .attr('id', 'end-arrow')
    .attr('viewBox', '0 0 10 10')
    .attr('refX', 2+R)
    .attr('refY', 5)
     .attr('markerWidth', 4)
     .attr('markerHeight', 4)
    .attr('orient', 'auto')
    .append('svg:path')
    .attr('d', 'M0,0 L0,10 L10,5 z');


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

var node = svg.selectAll(".node")
    .data(force.nodes())
    .enter().append("g")
    .attr("class", "node")
    .call(force.drag);

node.append("circle")
    .attr("r", R)
    .on("mouseover", fade(.1))
    .on("mouseout", fade(1))
;

node.append("text")
    .attr("x", 0)
    .attr("dy", ".35em")
    .text(function(d) { return d.name; });




function tick() {
  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 + ")"; });
}



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

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

 function fade(opacity) {
        return function(d) {
            node.style("stroke-opacity", function(o) {
                thisOpacity = isConnected(d, o) ? 1 : opacity;
                this.setAttribute('fill-opacity', thisOpacity);
                return thisOpacity;
            });

            link.style("stroke-opacity", function(o) {
                return o.source === d || o.target === d ? 1 : opacity;
            });

           marker.style("opacity", function(o) {
                return o.source === d || o.target === d ? 1 : opacity;
            });
        };
 }


</script>

A tangentially related question would be how to shorten the path so that when the opacity of nodes and links fade, that the line going to the middle of each node is not noticeable.


回答1:


Your approach is not feasible because of the way marker instances are rendered. Cannibalizing one of my own answers and quoting the SVG spec:

11.6.4 Details on how markers are rendered

[...]

The rendering effect of a marker is as if the contents of the referenced ‘marker’ element were deeply cloned into a separate non-exposed DOM tree for each instance of the marker. Because the cloned DOM tree is non-exposed, the SVG DOM does not show the cloned instance of the marker.

Only the original marker elements, i.e. the declaring <marker> elements, are stylable using CSS, whereas the cloned instances referenced via properties marker-start, marker-mid, or marker-end are not accessible and therefore not individually stylable.

CSS2 selectors can be applied to the original (i.e., referenced) elements because they are part of the formal document structure. CSS2 selectors cannot be applied to the (conceptually) cloned DOM tree because its contents are not part of the formal document structure.


To circumvent these constraints you could use two defining marker elements, the second being a cloned version of the first with reduced opacity.

// add defs-markers
svg.append('svg:defs').selectAll("marker")
    .data([{id:"end-arrow", opacity:1}, {id:"end-arrow-fade", opacity:0.1}])
  .enter().append('marker')
    .attr('id', function(d) { return d.id; })
    .attr('viewBox', '0 0 10 10')
    .attr('refX', 2+R)
    .attr('refY', 5)
    .attr('markerWidth', 4)
    .attr('markerHeight', 4)
    .attr('orient', 'auto')
  .append('svg:path')
    .attr('d', 'M0,0 L0,10 L10,5 z')
    .style("opacity", function(d) { return d.opacity; });

Within your fade() function you are then able to switch the lines' marker-end properties to refer to the appropriate marker's id:

link.attr("marker-end", function(o) {
  return opacity === 1 || o.source === d || o.target === d
    ? 'url(#end-arrow)' : 'url(#end-arrow-fade)';
});          

Have a look at the following snippet for a working demo:

function bar() {
  console.log("click");
  force.stop();
  force.start();
}
  
var links = [
  {source: "A", target: "D", type: "high"},
  {source: "A", target: "K", type: "high"},
  {source: "B", target: "G", type: "high"},
  {source: "C", target: "A", type: "low"},
  {source: "D", target: "K", type: "low"},
  {source: "E", target: "A", type: "low"},
  {source: "F", target: "B", type: "low"},
  {source: "K", target: "J", type: "low"},
  {source: "F", target: "A", type: "low"},
  {source: "F", target: "I", type: "low"},
  {source: "G", target: "H", type: "low"},
  {source: "E", target: "K", type: "high"},
  {source: "E", target: "G", type: "low"},
  {source: "E", target: "F", type: "high"},
  {source: "D", target: "E", type: "high"}  
];

var nodes = {};

// Compute the distinct nodes from the links.
links.forEach(function(link) {
  link.source = nodes[link.source] || (nodes[link.source] = {name: link.source});
  link.target = nodes[link.target] || (nodes[link.target] = {name: link.target});
});

var width = 600,
    height = 600;

var force = d3.layout.force()
    .nodes(d3.values(nodes))
    .links(links)
    .size([width, height])
    .linkDistance(105)
    .charge(-775)
    .on("tick", tick)
    .start();

force.on("start", function () {
    console.log("start");
});
force.on("end", function () {
    console.log("end");
});

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

// add defs-markers
svg.append('svg:defs').selectAll("marker")
    .data([{id:"end-arrow", opacity:1}, {id:"end-arrow-fade", opacity:0.1}])
  .enter().append('marker')
    .attr('id', function(d) { return d.id; })
    .attr('viewBox', '0 0 10 10')
    .attr('refX', 2+R)
    .attr('refY', 5)
    .attr('markerWidth', 4)
    .attr('markerHeight', 4)
    .attr('orient', 'auto')
  .append('svg:path')
    .attr('d', 'M0,0 L0,10 L10,5 z')
    .style("opacity", function(d) { return d.opacity; });

var link = svg.selectAll(".link")
    .data(force.links())
    .enter()
    .append("line")
    .attr("class", "link")
    .attr('marker-end', 'url(#end-arrow)');  
 
var node = svg.selectAll(".node")
    .data(force.nodes())
    .enter().append("g")
    .attr("class", "node")
    .call(force.drag);

node.append("circle")
    .attr("r", R)
    .on("mouseover", fade(.1))
    .on("mouseout", fade(1))
;

node.append("text")
    .attr("x", 0)
    .attr("dy", ".35em")
    .text(function(d) { return d.name; });
  
function tick() {
  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 + ")";
  });
}
  
var linkedByIndex = {};
links.forEach(function(d) {
  linkedByIndex[d.source.index + "," + d.target.index] = 1;
});

function isConnected(a, b) {
  return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index +   "," + a.index] || a.index == b.index;
}
  
function fade(opacity) {
  return function(d) {
    node.style("stroke-opacity", function(o) {
      thisOpacity = isConnected(d, o) ? 1 : opacity;
      this.setAttribute('fill-opacity', thisOpacity);
      return thisOpacity;
    });

    link.style("stroke-opacity", function(o) {
      return o.source === d || o.target === d ? 1 : opacity;
    });

    link.attr("marker-end", function(o) {
      return opacity === 1 || o.source === d || o.target === d
        ? 'url(#end-arrow)' : 'url(#end-arrow-fade)';
    });          
  };
 }
.node circle {
  fill: #DDD;
  stroke: #777;
  stroke-width: 2px;
}
.node text {
  font-family: sans-serif;
  text-anchor: middle;
  pointer-events: none;
  user-select: none;
  -webkit-user-select: none;
}
.link {
  stroke: #88A;
  stroke-width: 4px;
}
text {
  font: 18px sans-serif;
  pointer-events: none;
}
#end-arrow {
  fill: #88A;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>


来源:https://stackoverflow.com/questions/35655779/fading-network-connections-in-directed-network-d3js

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!