简体   繁体   中英

d3js: Add quantitative dimension to Hierarchical Edge Bundling

Given Mike Bostock's example on Hierarchical Edge Bundling ( https://bl.ocks.org/mbostock/7607999 ), how would one add a quantitative dimension to the plot?

What I want to achieve is, for example, the link between "Visualization" (at 12 o'clock) and "SpanningTree" (at 1 o'clock) to have a transparency of 50% and the link between "Visualization" and "AspectRatioBanker" (also 1 o'clock) to have 0% transparency. Let's assume "SpanningTree" is a less important import of "Visualization" than "AspectRatiobanker" and I want to visualize this aspect using this gradient colour scale.

Considering the data, every element in the "imports" array must have a numeric value, indicating the "importance" of that element.

Considering d3, every link has to be coloured based on this numeric value.

Is there a possibility to add such a feature without changing too much of the example code?

If I understand your question correctly you want to start with data like:

[
  {
    "name": "flare.analytics.cluster.AgglomerativeCluster",
    "size": 3938,
    "imports": [
      {
        "link": "flare.animate.Transitioner",
        "value": 47.194114234125514
      },
      {
        "link": "flare.vis.data.DataList",
        "value": 66.57002100495298
      },
      {
        "link": "flare.util.math.IMatrix",
        "value": 5.987508739765435
      },
      {
        "link": "flare.analytics.cluster.MergeEdge",
        "value": 31.750046370493678
      },
      {
        "link": "flare.analytics.cluster.HierarchicalCluster",
        "value": 10.186873728884827
      },
      {
        "link": "flare.vis.data.Data",
        "value": 28.60757703865271
      }
    ]
  },
  ...

And then color the links based on the value property in the imports.

To do that given the linked example, modify the packageImports function to retain the value property in the links collection:

// Return a list of imports for the given array of nodes.
function packageImports(nodes) {
  var map = {},
      imports = [];

  // Compute a map from name to node.
  nodes.forEach(function(d) {
    map[d.name] = d;
  });

  // For each import, construct a link from the source to target node.
  nodes.forEach(function(d) {
    if (d.imports) d.imports.forEach(function(i) {
      var target = map[i.link]; // find the target
      target.value = i.value; // retain the value
      imports.push({source: map[d.name], target: target});
    });
  });
  return imports;
}

Then add the color when you append the nodes:

var colorScale = d3.scale.quantize()
  .range(["#2c7bb6", "#00a6ca","#00ccbc","#90eb9d","#ffff8c","#f9d057","#f29e2e","#e76818","#d7191c"])
  .domain([0,100]);

...

link = link
  .data(bundle(links))
  .enter().append("path")
  .each(function(d) { d.source = d[0], d.target = d[d.length - 1]; })
  .attr("class", "link")
  .attr("d", line)
  .style("stroke", function(d){
    return colorScale(d.target.value); //<-- add color
  });

Full running code:

 <!DOCTYPE html> <meta charset="utf-8"> <style> .node { font: 300 11px "Helvetica Neue", Helvetica, Arial, sans-serif; fill: #bbb; } .node:hover { fill: #000; } .link { fill: none; pointer-events: none; } .node:hover, .node--source, .node--target { font-weight: 700; } .link--source, .link--target { stroke-opacity: 1; stroke-width: 2px; } .link--source { stroke: #d62728; } .link--target { stroke: #2ca02c; } </style> <body> <script src="//d3js.org/d3.v3.min.js"></script> <script> var diameter = 960, radius = diameter / 2, innerRadius = radius - 120; var cluster = d3.layout.cluster() .size([360, innerRadius]) .sort(null) .value(function(d) { return d.size; }); var bundle = d3.layout.bundle(); var line = d3.svg.line.radial() .interpolate("bundle") .tension(.85) .radius(function(d) { return dy; }) .angle(function(d) { return dx / 180 * Math.PI; }); var svg = d3.select("body").append("svg") .attr("width", diameter) .attr("height", diameter) .append("g") .attr("transform", "translate(" + radius + "," + radius + ")"); var link = svg.append("g").selectAll(".link"), node = svg.append("g").selectAll(".node"); d3.json("https://jsonblob.com/api/851bd2f2-d85d-11e6-b16a-ad927fd57221", function(error, classes) { if (error) throw error; var nodes = cluster.nodes(packageHierarchy(classes)), links = packageImports(nodes); var colorScale = d3.scale.quantize() .range(["#2c7bb6", "#00a6ca","#00ccbc","#90eb9d","#ffff8c","#f9d057","#f29e2e","#e76818","#d7191c"]) .domain([0,100]); link = link .data(bundle(links)) .enter().append("path") .each(function(d) { d.source = d[0], d.target = d[d.length - 1]; }) .attr("class", "link") .attr("d", line) .style("stroke", function(d){ return colorScale(d.target.value); }) node = node .data(nodes.filter(function(n) { return !n.children; })) .enter().append("text") .attr("class", "node") .attr("dy", ".31em") .attr("transform", function(d) { return "rotate(" + (dx - 90) + ")translate(" + (dy + 8) + ",0)" + (dx < 180 ? "" : "rotate(180)"); }) .style("text-anchor", function(d) { return dx < 180 ? "start" : "end"; }) .text(function(d) { return d.key; }) //.on("mouseover", mouseovered) //.on("mouseout", mouseouted); }); /* function mouseovered(d) { node .each(function(n) { n.target = n.source = false; }); link .classed("link--target", function(l) { if (l.target === d) return l.source.source = true; }) .classed("link--source", function(l) { if (l.source === d) return l.target.target = true; }) .filter(function(l) { return l.target === d || l.source === d; }) .each(function() { this.parentNode.appendChild(this); }); node .classed("node--target", function(n) { return n.target; }) .classed("node--source", function(n) { return n.source; }); } function mouseouted(d) { link .classed("link--target", false) .classed("link--source", false); node .classed("node--target", false) .classed("node--source", false); } */ //d3.select(self.frameElement).style("height", diameter + "px"); // Lazily construct the package hierarchy from class names. function packageHierarchy(classes) { var map = {}; function find(name, data) { var node = map[name], i; if (!node) { node = map[name] = data || {name: name, children: []}; if (name.length) { node.parent = find(name.substring(0, i = name.lastIndexOf("."))); node.parent.children.push(node); node.key = name.substring(i + 1); } } return node; } classes.forEach(function(d) { find(d.name, d); }); return map[""]; } // Return a list of imports for the given array of nodes. function packageImports(nodes) { var map = {}, imports = []; // Compute a map from name to node. nodes.forEach(function(d) { map[d.name] = d; }); // For each import, construct a link from the source to target node. nodes.forEach(function(d) { if (d.imports) d.imports.forEach(function(i) { var target = map[i.link]; target.value = i.value; imports.push({source: map[d.name], target: target}); }); }); return imports; } </script> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM