首页 > 解决方案 > 分层边缘集群组名称

问题描述

我有以下集群图,它工作得很好。猜测这与我如何将东西打包在一起有关?

var diameter = 900,
  radius = diameter / 2,
  innerRadius = radius - 180;

var cluster = d3.cluster()
  .size([360, innerRadius]);


var line = d3.radialLine()
  .curve(d3.curveBundle.beta(0.2))
  .radius(function(d) {
    return d.y;
  })
  .angle(function(d) {
    return d.x / 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");

let samlDeps = [
  'dsi.backend.SAML-ASSERTIONS',
  'dsi.frontend.OIDC'
];

var arc = d3.arc()
  .innerRadius(10)
  .outerRadius(15);

var classes = [{
    'name': 'dsi.frontend.HELP',
    'size': 1000,
    'imports': [
      'dsi.db.AUDIT',
      'dsi.frontend.PROFILE',
      'dsi.frontend.INTERACTIONS',
      'dsi.frontend.SERVICES',
      'dsi.frontend.SUPPORT',
      'dsi.backend.APPLICATIONS',
      'dsi.cache.REDIS',
      'dsi.frontend.CDN',
    ]
  },
  {
    'name': 'dsi.frontend.INTERACTIONS',
    'size': 1000,
    'imports': [
      'dsi.db.AUDIT',
      'dsi.frontend.PROFILE',
      'dsi.frontend.HELP',
      'dsi.frontend.SERVICES',
      'dsi.frontend.SUPPORT',
      'dsi.db.DIRECTORIES',
      'dsi.frontend.OIDC',
      'dsi.backend.DEVICES',
      'dsi.backend.ORGANISATIONS',
      'dsi.backend.APPLICATIONS',
      'dsi.backend.ACCESS',
      'dsi.cache.REDIS',
      'dsi.frontend.CDN',
    ]
  },
  {
    'name': 'dsi.frontend.MANAGE',
    'size': 1000,
    'imports': [
      'dsi.db.AUDIT',
      'dsi.frontend.INTERACTIONS',
      'dsi.frontend.HELP',
      'dsi.frontend.SERVICES',
      'dsi.frontend.PROFILE',
      'dsi.backend.ACCESS',
      'dsi.backend.APPLICATIONS',
      'dsi.backend.DIRECTORIES',
      'dsi.db.ORGANISATION',
      'dsi.cache.SEARCH',
      'dsi.cache.REDIS',
      'dsi.frontend.CDN',
      'dsi.frontend.OIDC',
    ]
  },
  {
    'name': 'dsi.frontend.OIDC',
    'size': 1000,
    'imports': [
      'dsi.db.AUDIT',
      'dsi.backend.DIRECTORIES',
      'dsi.cache.REDIS',
      'dsi.frontend.HELP',
      'dsi.db.DIRECTORIES',
      'dsi.db.ORGANISATION',
      'dsi.backend.APPLICATIONS',
      'dsi.frontend.CDN',
      'dsi.frontend.INTERACTIONS',
    ]
  },
  {
    'name': 'dsi.frontend.PUBLIC-API',
    'size': 1000,
    'imports': [
      'dsi.cache.REDIS',
      'dsi.backend.DIRECTORIES',
      'dsi.backend.APPLICATIONS',
      'dsi.backend.ACCESS',
      'dsi.backend.ORGANISATIONS',
    ]
  },
  {
    'name': 'dsi.frontend.PROFILE',
    'size': 1000,
    'imports': [
      'dsi.db.AUDIT',
      'dsi.frontend.INTERACTIONS',
      'dsi.frontend.HELP',
      'dsi.frontend.SERVICES',
      'dsi.frontend.SUPPORT',
      'dsi.backend.DIRECTORIES',
      'dsi.backend.ORGANISATIONS',
      'dsi.backend.APPLICATIONS',
      'dsi.backend.ACCESS',
      'dsi.cache.SEARCH',
      'dsi.frontend.CDN',
      'dsi.frontend.OIDC',
    ]
  },
  {
    'name': 'dsi.frontend.SUPPORT',
    'size': 1000,
    'imports': [
      'dsi.db.AUDIT',
      'dsi.frontend.PROFILE',
      'dsi.frontend.INTERACTIONS',
      'dsi.frontend.HELP',
      'dsi.frontend.SERVICES',
      'dsi.frontend.OIDC',
      'dsi.cache.SEARCH',
      'dsi.cache.REDIS',
      'dsi.backend.DIRECTORIES',
      'dsi.backend.ORGANISATIONS',
      'dsi.backend.DEVICES',
      'dsi.backend.APPLICATIONS',
      'dsi.backend.ACCESS',
      'dsi.frontend.CDN',
    ]
  },
  {
    'name': 'dsi.frontend.SERVICES',
    'size': 1000,
    'imports': [
      'dsi.db.AUDIT',
      'dsi.frontend.PROFILE',
      'dsi.frontend.INTERACTIONS',
      'dsi.frontend.HELP',
      'dsi.frontend.SUPPORT',
      'dsi.backend.DIRECTORIES',
      'dsi.backend.ORGANISATIONS',
      'dsi.backend.ACCESS',
      'dsi.backend.APPLICATIONS',
      'dsi.cache.SEARCH',
      'dsi.db.ORGANISATION',
      'dsi.frontend.OIDC',
      'dsi.cache.REDIS',
      'dsi.db.DIRECTORIES',
      'dsi.db.ORGANISATION',
      'dsi.frontend.CDN',
    ]
  },
  {
    'name': 'dsi.frontend.CDN',
    'size': 1000,
    'imports': []
  },

  {
    'name': 'dsi.backend.ACCESS',
    'size': 1000,
    'imports': [
      'dsi.db.AUDIT',
      'dsi.db.ORGANISATION',
      'dsi.cache.REDIS',
    ]
  },
  {
    'name': 'dsi.backend.APPLICATIONS',
    'size': 1000,
    'imports': [
      'dsi.db.AUDIT',
      'dsi.db.ORGANISATION',
      'dsi.db.ORGANISATION',
    ]
  },
  {
    'name': 'dsi.backend.SAML-ASSERTIONS',
    'size': 1000,
    'imports': [
      'dsi.db.AUDIT',
      'dsi.backend.DIRECTORIES',
      'dsi.backend.ACCESS',
      'dsi.backend.APPLICATIONS',
    ]
  },
  {
    'name': 'dsi.backend.DEVICES',
    'size': 1000,
    'imports': [
      'dsi.db.AUDIT',
      'dsi.frontend.CDN',
      'dsi.db.DEVICES',
    ]
  },
  {
    'name': 'dsi.backend.DIRECTORIES',
    'size': 1000,
    'imports': [
      'dsi.db.AUDIT',
      'dsi.db.DIRECTORIES',
      'dsi.backend.APPLICATIONS',
      'dsi.cache.REDIS',
    ]
  },
  {
    'name': 'dsi.backend.ORGANISATIONS',
    'size': 1000,
    'imports': [
      'dsi.db.AUDIT',
      'dsi.db.ORGANISATION',
      'dsi.backend.DIRECTORIES',
      'dsi.cache.REDIS',
    ]
  },
  {
    'name': 'dsi.backend.sa',
    'size': 1000,
    'imports': [
      'dsi.backend.APPLICATIONS',
    ]
  },

  {
    'name': 'dsi.saml.A-FORMS',
    'size': 1000,
    'imports': samlDeps
  },
  {
    'name': 'dsi.saml.ASP',
    'size': 1000,
    'imports': samlDeps
  },
  {
    'name': 'dsi.saml.COLLECT',
    'size': 1000,
    'imports': samlDeps
  },
  {
    'name': 'dsi.saml.DQT',
    'size': 1000,
    'imports': samlDeps
  },
  {
    'name': 'dsi.saml.GIAS',
    'size': 1000,
    'imports': samlDeps
  },
  {
    'name': 'dsi.saml.INFORMATION-EXCHANGE',
    'size': 1000,
    'imports': samlDeps
  },
  {
    'name': 'dsi.saml.KEY-TO-SUCCESS',
    'size': 1000,
    'imports': samlDeps
  },
  {
    'name': 'dsi.saml.KEY-TO-SUCCESS_SECURE-ACCESS',
    'size': 1000,
    'imports': samlDeps
  },
  {
    'name': 'dsi.saml.psp',
    'size': 1000,
    'imports': samlDeps
  },
  {
    'name': 'dsi.saml.SCHOOL-2-SCHOOL',
    'size': 1000,
    'imports': samlDeps
  },

  {
    'name': 'dsi.db.AUDIT',
    'size': 1000,
    'imports': []
  },
  {
    'name': 'dsi.db.DIRECTORIES',
    'size': 1000,
    'imports': []
  },
  {
    'name': 'dsi.db.ORGANISATION',
    'size': 1000,
    'imports': []
  },
  {
    'name': 'dsi.db.DEVICES',
    'size': 1000,
    'imports': []
  },

  {
    'name': 'dsi.cache.REDIS',
    'size': 1000,
    'imports': []
  },
  {
    'name': 'dsi.cache.SEARCH',
    'size': 1000,
    'imports': []
  },
];

var root = packageHierarchy(classes)
  .sum(function(d) {
    return d.size;
  });

cluster(root);

link = link
  .data(packageImports(root.leaves()))
  .enter().append("path")
  .each(function(d) {
    d.source = d[0], d.target = d[d.length - 1];
  })
  .attr("class", "link")
  .attr("d", line);

node = node
  .data(root.leaves())
  .enter().append("text")
  .attr("class", "node")
  .attr("dy", "0.31em")
  .attr("transform", function(d) {
    return "rotate(" + (d.x - 90) + ")translate(" + (d.y + 8) + ",0)" + (d.x < 180 ? "" : "rotate(180)");
  })
  .attr("text-anchor", function(d) {
    return d.x < 180 ? "start" : "end";
  })
  .text(function(d) {
    return d.data.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;
    })
    .raise();

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

// 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 d3.hierarchy(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.data.name] = d;
  });

  // For each import, construct a link from the source to target node.
  nodes.forEach(function(d) {
    if (d.data.imports) d.data.imports.forEach(function(i) {
      imports.push(map[d.data.name].path(map[i]));
    });
  });

  return imports;
}
.node {
  font: 300 11px "Helvetica Neue", Helvetica, Arial, sans-serif;
  fill: #444;
}

.node:hover {
  fill: #000;
}

.link {
  stroke: steelblue;
  stroke-opacity: 0.5;
  fill: none;
  pointer-events: none;
  stroke-width: 1px;
  stroke-linecap: round;
}

.node:hover,
.node--source,
.node--target {
  font-weight: 700;
}


/* text color */

.node--source {
  fill: red;
}


/* text color */

.node--target {
  fill: red;
}

.link--source,
.link--target {
  stroke-opacity: 1;
  stroke-width: 2px;
}


/* line color */

.link--source {
  stroke: red;
}


/* line color */

.link--target {
  stroke: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>

我追求的是两件事

  1. 我需要每个组显示一个标题。EG 数据库说它们是数据库
  2. 3 条颜色线(我目前有 2 条颜色相同),1 条用于依赖,1 条用于依赖,1 条用于两者

D3 新手,不确定如何实现这些?

TIA

标签: javascriptd3.js

解决方案


我认为您遇到的问题是度数和弧度之间的转换。rotate - translate - rotate这就是为什么你必须在例程中使用聪明的解决方案。

我已将集群更改为以弧度输出,然后将groups其作为文本添加为​​节点的父节点。我还更改了链接类,使传入和传出链接的颜色不同。如果一个链接有两个类,那么它是绿色的。

你颠倒了link--sourceandlink--target类,我把它颠倒了。

var diameter = 900,
  radius = diameter / 2,
  innerRadius = radius - 180;

var cluster = d3.cluster()
  .size([2 * Math.PI, innerRadius]);

var line = d3.radialLine()
  .curve(d3.curveBundle.beta(0.2))
  .radius(function(d) {
    return d.y;
  })
  .angle(function(d) {
    return d.x;
  });

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"),
  group = svg.append("g").selectAll(".group");

let samlDeps = [
  'dsi.backend.SAML-ASSERTIONS',
  'dsi.frontend.OIDC'
];

var arc = d3.arc()
  .innerRadius(10)
  .outerRadius(15);

var classes = [{
    'name': 'dsi.frontend.HELP',
    'size': 1000,
    'imports': [
      'dsi.db.AUDIT',
      'dsi.frontend.PROFILE',
      'dsi.frontend.INTERACTIONS',
      'dsi.frontend.SERVICES',
      'dsi.frontend.SUPPORT',
      'dsi.backend.APPLICATIONS',
      'dsi.cache.REDIS',
      'dsi.frontend.CDN',
    ]
  },
  {
    'name': 'dsi.frontend.INTERACTIONS',
    'size': 1000,
    'imports': [
      'dsi.db.AUDIT',
      'dsi.frontend.PROFILE',
      'dsi.frontend.HELP',
      'dsi.frontend.SERVICES',
      'dsi.frontend.SUPPORT',
      'dsi.db.DIRECTORIES',
      'dsi.frontend.OIDC',
      'dsi.backend.DEVICES',
      'dsi.backend.ORGANISATIONS',
      'dsi.backend.APPLICATIONS',
      'dsi.backend.ACCESS',
      'dsi.cache.REDIS',
      'dsi.frontend.CDN',
    ]
  },
  {
    'name': 'dsi.frontend.MANAGE',
    'size': 1000,
    'imports': [
      'dsi.db.AUDIT',
      'dsi.frontend.INTERACTIONS',
      'dsi.frontend.HELP',
      'dsi.frontend.SERVICES',
      'dsi.frontend.PROFILE',
      'dsi.backend.ACCESS',
      'dsi.backend.APPLICATIONS',
      'dsi.backend.DIRECTORIES',
      'dsi.db.ORGANISATION',
      'dsi.cache.SEARCH',
      'dsi.cache.REDIS',
      'dsi.frontend.CDN',
      'dsi.frontend.OIDC',
    ]
  },
  {
    'name': 'dsi.frontend.OIDC',
    'size': 1000,
    'imports': [
      'dsi.db.AUDIT',
      'dsi.backend.DIRECTORIES',
      'dsi.cache.REDIS',
      'dsi.frontend.HELP',
      'dsi.db.DIRECTORIES',
      'dsi.db.ORGANISATION',
      'dsi.backend.APPLICATIONS',
      'dsi.frontend.CDN',
      'dsi.frontend.INTERACTIONS',
    ]
  },
  {
    'name': 'dsi.frontend.PUBLIC-API',
    'size': 1000,
    'imports': [
      'dsi.cache.REDIS',
      'dsi.backend.DIRECTORIES',
      'dsi.backend.APPLICATIONS',
      'dsi.backend.ACCESS',
      'dsi.backend.ORGANISATIONS',
    ]
  },
  {
    'name': 'dsi.frontend.PROFILE',
    'size': 1000,
    'imports': [
      'dsi.db.AUDIT',
      'dsi.frontend.INTERACTIONS',
      'dsi.frontend.HELP',
      'dsi.frontend.SERVICES',
      'dsi.frontend.SUPPORT',
      'dsi.backend.DIRECTORIES',
      'dsi.backend.ORGANISATIONS',
      'dsi.backend.APPLICATIONS',
      'dsi.backend.ACCESS',
      'dsi.cache.SEARCH',
      'dsi.frontend.CDN',
      'dsi.frontend.OIDC',
    ]
  },
  {
    'name': 'dsi.frontend.SUPPORT',
    'size': 1000,
    'imports': [
      'dsi.db.AUDIT',
      'dsi.frontend.PROFILE',
      'dsi.frontend.INTERACTIONS',
      'dsi.frontend.HELP',
      'dsi.frontend.SERVICES',
      'dsi.frontend.OIDC',
      'dsi.cache.SEARCH',
      'dsi.cache.REDIS',
      'dsi.backend.DIRECTORIES',
      'dsi.backend.ORGANISATIONS',
      'dsi.backend.DEVICES',
      'dsi.backend.APPLICATIONS',
      'dsi.backend.ACCESS',
      'dsi.frontend.CDN',
    ]
  },
  {
    'name': 'dsi.frontend.SERVICES',
    'size': 1000,
    'imports': [
      'dsi.db.AUDIT',
      'dsi.frontend.PROFILE',
      'dsi.frontend.INTERACTIONS',
      'dsi.frontend.HELP',
      'dsi.frontend.SUPPORT',
      'dsi.backend.DIRECTORIES',
      'dsi.backend.ORGANISATIONS',
      'dsi.backend.ACCESS',
      'dsi.backend.APPLICATIONS',
      'dsi.cache.SEARCH',
      'dsi.db.ORGANISATION',
      'dsi.frontend.OIDC',
      'dsi.cache.REDIS',
      'dsi.db.DIRECTORIES',
      'dsi.db.ORGANISATION',
      'dsi.frontend.CDN',
    ]
  },
  {
    'name': 'dsi.frontend.CDN',
    'size': 1000,
    'imports': []
  },

  {
    'name': 'dsi.backend.ACCESS',
    'size': 1000,
    'imports': [
      'dsi.db.AUDIT',
      'dsi.db.ORGANISATION',
      'dsi.cache.REDIS',
    ]
  },
  {
    'name': 'dsi.backend.APPLICATIONS',
    'size': 1000,
    'imports': [
      'dsi.db.AUDIT',
      'dsi.db.ORGANISATION',
      'dsi.db.ORGANISATION',
    ]
  },
  {
    'name': 'dsi.backend.SAML-ASSERTIONS',
    'size': 1000,
    'imports': [
      'dsi.db.AUDIT',
      'dsi.backend.DIRECTORIES',
      'dsi.backend.ACCESS',
      'dsi.backend.APPLICATIONS',
    ]
  },
  {
    'name': 'dsi.backend.DEVICES',
    'size': 1000,
    'imports': [
      'dsi.db.AUDIT',
      'dsi.frontend.CDN',
      'dsi.db.DEVICES',
    ]
  },
  {
    'name': 'dsi.backend.DIRECTORIES',
    'size': 1000,
    'imports': [
      'dsi.db.AUDIT',
      'dsi.db.DIRECTORIES',
      'dsi.backend.APPLICATIONS',
      'dsi.cache.REDIS',
    ]
  },
  {
    'name': 'dsi.backend.ORGANISATIONS',
    'size': 1000,
    'imports': [
      'dsi.db.AUDIT',
      'dsi.db.ORGANISATION',
      'dsi.backend.DIRECTORIES',
      'dsi.cache.REDIS',
    ]
  },
  {
    'name': 'dsi.backend.sa',
    'size': 1000,
    'imports': [
      'dsi.backend.APPLICATIONS',
    ]
  },

  {
    'name': 'dsi.saml.A-FORMS',
    'size': 1000,
    'imports': samlDeps
  },
  {
    'name': 'dsi.saml.ASP',
    'size': 1000,
    'imports': samlDeps
  },
  {
    'name': 'dsi.saml.COLLECT',
    'size': 1000,
    'imports': samlDeps
  },
  {
    'name': 'dsi.saml.DQT',
    'size': 1000,
    'imports': samlDeps
  },
  {
    'name': 'dsi.saml.GIAS',
    'size': 1000,
    'imports': samlDeps
  },
  {
    'name': 'dsi.saml.INFORMATION-EXCHANGE',
    'size': 1000,
    'imports': samlDeps
  },
  {
    'name': 'dsi.saml.KEY-TO-SUCCESS',
    'size': 1000,
    'imports': samlDeps
  },
  {
    'name': 'dsi.saml.KEY-TO-SUCCESS_SECURE-ACCESS',
    'size': 1000,
    'imports': samlDeps
  },
  {
    'name': 'dsi.saml.psp',
    'size': 1000,
    'imports': samlDeps
  },
  {
    'name': 'dsi.saml.SCHOOL-2-SCHOOL',
    'size': 1000,
    'imports': samlDeps
  },

  {
    'name': 'dsi.db.AUDIT',
    'size': 1000,
    'imports': []
  },
  {
    'name': 'dsi.db.DIRECTORIES',
    'size': 1000,
    'imports': []
  },
  {
    'name': 'dsi.db.ORGANISATION',
    'size': 1000,
    'imports': []
  },
  {
    'name': 'dsi.db.DEVICES',
    'size': 1000,
    'imports': []
  },

  {
    'name': 'dsi.cache.REDIS',
    'size': 1000,
    'imports': []
  },
  {
    'name': 'dsi.cache.SEARCH',
    'size': 1000,
    'imports': []
  },
];

var root = packageHierarchy(classes)
  .sum(function(d) {
    return d.size;
  });

cluster(root);

group = group
  .data(root.descendants().filter(function(d) {
    return d.height === 1;
  }))
  .enter()
  .append('text')
  .each(function(d) {
    d.angle = (d3.sum(d.children, function(v) {
      return v.x;
    }) / d.children.length);
    d.radius = d3.max(d.children, function(v) {
      return v.y;
    }) + 140;
  })
  .classed("group", true)
  .attr('x', function(d) {
    return Math.sin(d.angle) * d.radius;
  })
  .attr('y', function(d) {
    return -Math.cos(d.angle) * d.radius;
  })
  .attr("text-anchor", function(d) {
    return d.angle < Math.PI ? "start" : "end";
  })
  .text(function(d) {
    return d.data.key.toUpperCase();
  });

link = link
  .data(packageImports(root.leaves()))
  .enter().append("path")
  .each(function(d) {
    d.source = d[0], d.target = d[d.length - 1];
  })
  .attr("class", "link")
  .attr("d", line);

node = node
  .data(root.leaves())
  .enter().append("text")
  .attr("class", "node")
  .attr("dy", "0.31em")
  .attr('x', function(d) {
    return Math.sin(d.x) * d.y;
  })
  .attr('y', function(d) {
    return -Math.cos(d.x) * d.y;
  })
  .attr("transform", function(d) {
    const angle = (d.x * 180 / Math.PI) - 90 + (d.x < Math.PI ? 0 : 180);
    const x = Math.sin(d.x) * d.y;
    const y = -Math.cos(d.x) * d.y
    return "rotate(" + angle + "," + x + "," + y + ")";
  })
  .attr("text-anchor", function(d) {
    return d.x < Math.PI ? "start" : "end";
  })
  .text(function(d) {
    return d.data.key;
  })
  .on("mouseover", mouseovered)
  .on("mouseout", mouseouted);

function mouseovered(d) {
  node
    .each(function(n) {
      n.target = n.source = false;
    });

  link
    .classed("link--source", function(l) {
      if (l.target === d) return l.source.source = true;
    })
    .classed("link--target", function(l) {
      if (l.source === d) return l.target.target = true;
    })
    .filter(function(l) {
      return l.target === d || l.source === d;
    })
    .raise();

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

// 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 d3.hierarchy(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.data.name] = d;
  });

  // For each import, construct a link from the source to target node.
  nodes.forEach(function(d) {
    if (d.data.imports) d.data.imports.forEach(function(i) {
      imports.push(map[d.data.name].path(map[i]));
    });
  });

  return imports;
}
.node,
.group {
  font: 300 11px "Helvetica Neue", Helvetica, Arial, sans-serif;
  fill: #444;
}

.group {
  font-size: 16px;
}

.node:hover {
  fill: #000;
}

.link {
  stroke: steelblue;
  stroke-opacity: 0.5;
  fill: none;
  pointer-events: none;
  stroke-width: 1px;
  stroke-linecap: round;
}

.node:hover,
.node--source,
.node--target {
  font-weight: 700;
}


/* text color */

.node--source {
  fill: red;
}

.node--target {
  fill: blue;
}

.node--target.node-source {
  fill: green;
}

.link--source,
.link--target {
  stroke-opacity: 1;
  stroke-width: 2px;
}


/* line color */

.link--source {
  stroke: red;
}

.link--target {
  stroke: blue;
}

.link--source.link--target {
  stroke: green;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>


推荐阅读