d3.js 多重关系视觉/linkHorizo​​ntal()/纠结树

我试图模仿一个按时间段描述多种关系的视觉效果,就像这样(时间段=世代):

http://img2.mukewang.com/646761d10001eae406540587.jpg

但是,到目前为止,我的努力还没有成功。我仍然在浏览器中得到空白输出。

据我所知,所有的部分都已到位。我有我的数据levels,然后使用以下方法整理了必要的层次结构坐标:


  var links = []

  nodes.forEach(d => {

    d.parents.forEach(p => links.push({source: d, bundle: d.bundle, target: p}))

  })


  const cluster = d3.cluster()

    .size([width, height]);


  const root = d3.hierarchy(links);

  cluster(root);


  var nodeG = svg.selectAll('.node')

      .data(root.links())

      .attr('class','node')

      .enter()

      .append('g');

从这里开始,我使用了d3.linkHorizontal()我的链接功能:


      nodeG.append("path")

        .attr("class", "link")

        .attr("d", d3.linkHorizontal()

          .x(function(d) { return d.y; })

          .y(function(d) { return d.x; }))

        .style('stroke-width', '3px');

从概念上讲,我看不出每个节点包含多个关系会如何改变。而且,控制台日志中没有任何错误,我不确定如何进一步排除故障。


问题

是什么阻止了我的视觉效果按预期呈现(在上图中)?如果可能的话,想要精确的复制品。


编辑

如果有帮助,这里是 observable 的视觉效果,但不能将其视为独立的视觉效果。


https://observablehq.com/@nitaku/tangled-tree-visualization-ii?collection=@nitaku/tangled-trees


慕无忌1623718
浏览 241回答 1
1回答

茅侃侃

我认为你所做的很多事情,特别是围绕数据争论,都是没有必要的,特别是因为你打了电话,后来d3.hierarchy()又打了d3.cluster()电话。我已将其替换为d3.stratify(处理尚未采用正确格式的分层数据)。我也替换d3.cluster为d3.tree()因为我不清楚你为什么要d3.cluster在这里使用。您的数据有多个父节点、多个根甚至浮动节点,而 d3 并不打算处理这些问题。我的解决方法是将伪节点附加到每一层,以确保只有一个节点并且所有节点始终处于正确的层级。为了确保正确绘制链接,我编写了一个自定义getLinks函数,可以处理多个父母。我还编写了一个自定义链接生成器,它以您希望的方式绘制链接。d3 在这里没有提供太多的灵活性,但您可以使用源代码来获得灵感。编辑我改变了逻辑,更关注哪些“伙伴”有孩子,所以指向同一个孩子的两个链接都处于同一级别 - 就像你的照片一样。我还根据他们有多少合作伙伴绘制了节点,并为每个链接提供了一个偏移量,以便线条更加清晰。我对节点进行了排序,以便真正的专业创作者位于顶部(Zeus),这样可以提供更平衡且不那么拥挤的视图。const margins = {&nbsp; top: 20,&nbsp; bottom: 300,&nbsp; left: 30,&nbsp; right: 100,};const height = 900;const width = 900;const totalWidth = width + margins.left + margins.right;const totalHeight = height + margins.top + margins.bottom;const svg = d3.select('body')&nbsp; .append('svg')&nbsp; .attr('width', totalWidth)&nbsp; .attr('height', totalHeight);const graphGroup = svg.append('g')&nbsp; .attr('transform', "translate(" + margins.left + "," + margins.top + ")");const levels = [&nbsp; [{&nbsp; &nbsp; id: 'Chaos',&nbsp; }],&nbsp; [{&nbsp; &nbsp; id: 'Gaea',&nbsp; &nbsp; parents: ['Chaos'],&nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Uranus',&nbsp; &nbsp; },&nbsp; ],&nbsp; [{&nbsp; &nbsp; id: 'Oceanus',&nbsp; &nbsp; parents: ['Gaea', 'Uranus'],&nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Thethys',&nbsp; &nbsp; &nbsp; parents: ['Gaea', 'Uranus'],&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Pontus',&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Rhea',&nbsp; &nbsp; &nbsp; parents: ['Gaea', 'Uranus'],&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Cronus',&nbsp; &nbsp; &nbsp; parents: ['Gaea', 'Uranus'],&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Coeus',&nbsp; &nbsp; &nbsp; parents: ['Gaea', 'Uranus'],&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Phoebe',&nbsp; &nbsp; &nbsp; parents: ['Gaea', 'Uranus'],&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Crius',&nbsp; &nbsp; &nbsp; parents: ['Gaea', 'Uranus'],&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Hyperion',&nbsp; &nbsp; &nbsp; parents: ['Gaea', 'Uranus'],&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Iapetus',&nbsp; &nbsp; &nbsp; parents: ['Gaea', 'Uranus'],&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Thea',&nbsp; &nbsp; &nbsp; parents: ['Gaea', 'Uranus'],&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Themis',&nbsp; &nbsp; &nbsp; parents: ['Gaea', 'Uranus'],&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Mnemosyne',&nbsp; &nbsp; &nbsp; parents: ['Gaea', 'Uranus'],&nbsp; &nbsp; },&nbsp; ],&nbsp; [{&nbsp; &nbsp; id: 'Doris',&nbsp; &nbsp; parents: ['Oceanus', 'Thethys'],&nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Neures',&nbsp; &nbsp; &nbsp; parents: ['Pontus', 'Gaea'],&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Dionne',&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Demeter',&nbsp; &nbsp; &nbsp; parents: ['Rhea', 'Cronus'],&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Hades',&nbsp; &nbsp; &nbsp; parents: ['Rhea', 'Cronus'],&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Hera',&nbsp; &nbsp; &nbsp; parents: ['Rhea', 'Cronus'],&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Alcmene',&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Zeus',&nbsp; &nbsp; &nbsp; parents: ['Rhea', 'Cronus'],&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Eris',&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Leto',&nbsp; &nbsp; &nbsp; parents: ['Coeus', 'Phoebe'],&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Amphitrite',&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Medusa',&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Poseidon',&nbsp; &nbsp; &nbsp; parents: ['Rhea', 'Cronus'],&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Hestia',&nbsp; &nbsp; &nbsp; parents: ['Rhea', 'Cronus'],&nbsp; &nbsp; },&nbsp; ],&nbsp; [{&nbsp; &nbsp; id: 'Thetis',&nbsp; &nbsp; parents: ['Doris', 'Neures'],&nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Peleus',&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Anchises',&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Adonis',&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Aphrodite',&nbsp; &nbsp; &nbsp; parents: ['Zeus', 'Dionne'],&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Persephone',&nbsp; &nbsp; &nbsp; parents: ['Zeus', 'Demeter'],&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Ares',&nbsp; &nbsp; &nbsp; parents: ['Zeus', 'Hera'],&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Hephaestus',&nbsp; &nbsp; &nbsp; parents: ['Zeus', 'Hera'],&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Hebe',&nbsp; &nbsp; &nbsp; parents: ['Zeus', 'Hera'],&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Hercules',&nbsp; &nbsp; &nbsp; parents: ['Zeus', 'Alcmene'],&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Megara',&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Deianira',&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Eileithya',&nbsp; &nbsp; &nbsp; parents: ['Zeus', 'Hera'],&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Ate',&nbsp; &nbsp; &nbsp; parents: ['Zeus', 'Eris'],&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Leda',&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Athena',&nbsp; &nbsp; &nbsp; parents: ['Zeus'],&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Apollo',&nbsp; &nbsp; &nbsp; parents: ['Zeus', 'Leto'],&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Artemis',&nbsp; &nbsp; &nbsp; parents: ['Zeus', 'Leto'],&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Triton',&nbsp; &nbsp; &nbsp; parents: ['Poseidon', 'Amphitrite'],&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Pegasus',&nbsp; &nbsp; &nbsp; parents: ['Poseidon', 'Medusa'],&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Orion',&nbsp; &nbsp; &nbsp; parents: ['Poseidon'],&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Polyphemus',&nbsp; &nbsp; &nbsp; parents: ['Poseidon'],&nbsp; &nbsp; },&nbsp; ],&nbsp; [{&nbsp; &nbsp; id: 'Deidamia',&nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Achilles',&nbsp; &nbsp; &nbsp; parents: ['Peleus', 'Thetis'],&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Creusa',&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Aeneas',&nbsp; &nbsp; &nbsp; parents: ['Anchises', 'Aphrodite'],&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Lavinia',&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Eros',&nbsp; &nbsp; &nbsp; parents: ['Hephaestus', 'Aphrodite'],&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Helen',&nbsp; &nbsp; &nbsp; parents: ['Leda', 'Zeus'],&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Menelaus',&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Polydueces',&nbsp; &nbsp; &nbsp; parents: ['Leda', 'Zeus'],&nbsp; &nbsp; },&nbsp; ],&nbsp; [{&nbsp; &nbsp; id: 'Andromache',&nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Neoptolemus',&nbsp; &nbsp; &nbsp; parents: ['Deidamia', 'Achilles'],&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Aeneas(2)',&nbsp; &nbsp; &nbsp; parents: ['Creusa', 'Aeneas'],&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Pompilius',&nbsp; &nbsp; &nbsp; parents: ['Creusa', 'Aeneas'],&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Iulus',&nbsp; &nbsp; &nbsp; parents: ['Lavinia', 'Aeneas'],&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Hermione',&nbsp; &nbsp; &nbsp; parents: ['Helen', 'Menelaus'],&nbsp; &nbsp; },&nbsp; ],];levels.unshift([]);// We add one pseudo node to every level to deal with parentless nodeslevels.forEach((l, i) => {&nbsp; l.forEach((n, j) => {&nbsp; &nbsp; n.level = i;&nbsp; &nbsp; if (n.parents !== undefined) {&nbsp; &nbsp; &nbsp; n.parent = n.parents[0];&nbsp; &nbsp; } else {&nbsp; &nbsp; &nbsp; n.parent = `pseudo-${i - 1}`;&nbsp; &nbsp; }&nbsp; });&nbsp; l.unshift({&nbsp; &nbsp; id: `pseudo-${i}`,&nbsp; &nbsp; parent: i > 0 ? `pseudo-${i - 1}` : "",&nbsp; &nbsp; level: i&nbsp; });});const nodes = levels.flat();const colours = d3.scaleOrdinal()&nbsp; .domain(nodes.filter(n => n.parents)&nbsp; &nbsp; .map(n => n.parents.sort()&nbsp; &nbsp; &nbsp; .join("-")))&nbsp; .range(d3.schemePaired);function getLinks(nodes) {&nbsp; return nodes&nbsp; &nbsp; .filter(n => n.data.parents !== undefined)&nbsp; &nbsp; .map(n => n.data.parents.map(p => ({&nbsp; &nbsp; &nbsp; source: nodes.find(n => n.id === p),&nbsp; &nbsp; &nbsp; target: n&nbsp; &nbsp; })))&nbsp; &nbsp; .flat();}const offsetPerPartner = 3;const drawNodePath = d => {&nbsp; const radius = 5;&nbsp; // The number of partners determines the node height&nbsp; // But when a node has only one partner,&nbsp; // treat it the same as when it has zero&nbsp; const nPartners = (d.data.partners && d.data.partners.length > 1)&nbsp; &nbsp; ? d.data.partners.length&nbsp; &nbsp; : 0;&nbsp; // We want to centre each node&nbsp; const straightLineOffset = (nPartners * offsetPerPartner) / 2;&nbsp; const context = d3.path();&nbsp; context.moveTo(-radius, 0);&nbsp; context.lineTo(-radius, -straightLineOffset);&nbsp; context.arc(0, -straightLineOffset, radius, -Math.PI, 0);&nbsp; context.lineTo(radius, straightLineOffset);&nbsp; context.arc(0, straightLineOffset, radius, 0, Math.PI);&nbsp; context.closePath();&nbsp; return context + "";};const drawLinkCurve = (x0, y0, x1, y1, offset, radius) => {&nbsp; const context = d3.path();&nbsp; context.moveTo(x0, y0);&nbsp; context.lineTo(x1 - 2 * radius - offset, y0);&nbsp; // If there is not enough space to draw two corners, reduce the corner radius&nbsp; if (Math.abs(y0 - y1) < 2 * radius) {&nbsp; &nbsp; radius = Math.abs(y0 - y1) / 2;&nbsp; }&nbsp; if (y0 < y1) {&nbsp; &nbsp; context.arcTo(x1 - offset - radius, y0, x1 - offset - radius, y0 + radius, radius);&nbsp; &nbsp; context.lineTo(x1 - offset - radius, y1 - radius);&nbsp; &nbsp; context.arcTo(x1 - offset - radius, y1, x1 - offset, y1, radius);&nbsp; } else if (y0 > y1) {&nbsp; &nbsp; context.arcTo(x1 - offset - radius, y0, x1 - offset - radius, y0 - radius, radius);&nbsp; &nbsp; context.lineTo(x1 - offset - radius, y1 + radius);&nbsp; &nbsp; context.arcTo(x1 - offset - radius, y1, x1 - offset, y1, radius);&nbsp; }&nbsp; context.lineTo(x1, y1);&nbsp; return context + "";};const partnershipsPerLevel = {};const getPartnershipOffset = (parent, partner) => {&nbsp; let partnershipId, level;&nbsp; if(partner !== undefined) {&nbsp; &nbsp; // On every level, every relationship gets its own offset. If a relationship&nbsp; &nbsp; // spans multiple levels, the furthest level is chosen&nbsp; &nbsp; level = Math.max(parent.depth, partner.level);&nbsp; &nbsp; if(!partnershipsPerLevel[level]) { partnershipsPerLevel[level] = []; }&nbsp; &nbsp; partnershipId = [parent.id, partner.id].sort().join("-");&nbsp; } else {&nbsp; &nbsp; level = parent.depth;&nbsp; &nbsp; if(!partnershipsPerLevel[level]) { partnershipsPerLevel[level] = []; }&nbsp; &nbsp; partnershipId = parent.id;&nbsp; }&nbsp; // Assume that the partnership already has a slot assigned&nbsp; const partnershipOffset = partnershipsPerLevel[level].indexOf(partnershipId);&nbsp; if(partnershipOffset === -1) {&nbsp; &nbsp; // Apparently not&nbsp; &nbsp; return partnershipsPerLevel[level].push(partnershipId) - 1;&nbsp; }&nbsp; return partnershipOffset;}const lineRadius = 10;const offsetStep = 5;const linkFn = link => {&nbsp; const thisParent = link.source;&nbsp; const partnerId = link.target.data.parents.find(p => p !== thisParent.id);&nbsp; const partners = thisParent.data.partners || [];&nbsp; // Let the first link start with this negative offset&nbsp; // But when a node has only one partner,&nbsp; // treat it the same as when it has zero&nbsp; const startOffset = (partners.length > 1)&nbsp; &nbsp; ? -(partners.length * offsetPerPartner) / 2&nbsp; &nbsp; : 0;&nbsp; const partner = partners.find(p => p.id === partnerId);&nbsp; // Chaos has no partner, nor Zeus with Athena&nbsp; const nthPartner = partner !== undefined&nbsp; &nbsp; ? partners.indexOf(partner)&nbsp; &nbsp; : (partners || []).length;&nbsp; const partnershipOffset = getPartnershipOffset(thisParent, partner);&nbsp; return drawLinkCurve(&nbsp; &nbsp; thisParent.y,&nbsp; &nbsp; thisParent.x + startOffset + offsetPerPartner * nthPartner,&nbsp; &nbsp; link.target.y,&nbsp; &nbsp; link.target.x,&nbsp; &nbsp; offsetStep * partnershipOffset,&nbsp; &nbsp; lineRadius&nbsp; );};function draw(root) {&nbsp; // Now every node has had it's position set, we can draw them now&nbsp; const nodes = root.descendants()&nbsp; &nbsp; .filter(n => !n.id.startsWith("pseudo-"));&nbsp; const links = getLinks(nodes)&nbsp; &nbsp; .filter(l => !l.source.id.startsWith("pseudo-"));&nbsp; const link = graphGroup.selectAll(".link")&nbsp; &nbsp; .data(links);&nbsp; link.exit().remove();&nbsp; link.enter()&nbsp; &nbsp; .append("path")&nbsp; &nbsp; .attr("class", "link")&nbsp; &nbsp; .merge(link)&nbsp; &nbsp; .attr("stroke", d => colours(d.target.data.parents.sort().join("-")))&nbsp; &nbsp; .attr("d", linkFn);&nbsp; const node = graphGroup.selectAll(".node")&nbsp; &nbsp; .data(nodes);&nbsp; node.exit().remove();&nbsp; const newNode = node.enter()&nbsp; &nbsp; .append("g")&nbsp; &nbsp; .attr("class", "node");&nbsp; newNode.append("path")&nbsp; &nbsp; .attr("d", drawNodePath);&nbsp; newNode.append("text")&nbsp; &nbsp; .attr("dy", -3)&nbsp; &nbsp; .attr("x", 6);&nbsp; newNode.merge(node)&nbsp; &nbsp; .attr("transform", d => `translate(${d.y},${d.x})`)&nbsp; &nbsp; .selectAll("text")&nbsp; &nbsp; .text(d => d.id);}const root = d3.stratify()&nbsp; .parentId(d => d.parent)&nbsp; (nodes);// Map the different sets of parents,// assigning each parent an array of partnersgetLinks(root.descendants())&nbsp; .filter(l => l.target.data.parents)&nbsp; .forEach(l => {&nbsp; &nbsp; const parentNames = l.target.data.parents;&nbsp; &nbsp; if (parentNames.length > 1) {&nbsp; &nbsp; &nbsp; const parentNodes = parentNames.map(p => nodes.find(n => n.id === p));&nbsp; &nbsp; &nbsp; parentNodes.forEach(p => {&nbsp; &nbsp; &nbsp; &nbsp; if (!p.partners) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; p.partners = [];&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; parentNodes&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .filter(n => n !== p && !p.partners.includes(n))&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .forEach(n => {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; p.partners.push(n);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; });&nbsp; &nbsp; &nbsp; });&nbsp; &nbsp; }&nbsp; });// Take nodes with more partners first,// also counting the partners of the childrenroot&nbsp; .sum(d => (d.value || 0) + (d.partners || []).length)&nbsp; .sort((a, b) => b.value - a.value);const tree = d3.tree()&nbsp; .size([height, width])&nbsp; .separation((a, b) => {&nbsp; &nbsp; // More separation between nodes with many children&nbsp; &nbsp; const totalPartners = (a.data.partners || []).length + (b.data.partners || []).length;&nbsp; &nbsp; return 1 + (totalPartners / 5);&nbsp; });draw(tree(root));.link {&nbsp; fill: none;&nbsp; stroke-width: 2;}.node path {&nbsp; stroke: black;&nbsp; stroke-width: 3;&nbsp; fill: white;}<script src="https://d3js.org/d3.v5.js"></script>

开满天机

svg除 外,元素中没有附加任何内容graphGroup。显然root.links()返回一个空数组,并且svg.&nbsp;这也是您没有收到任何错误的原因。通过创建此数组并在其上迭代,如果您还更改,则可以实现您希望在树中实现的基本形状:.attr("d",&nbsp;d3.linkHorizontal() &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.x(function(d)&nbsp;{&nbsp;return&nbsp;d.y;&nbsp;}) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.y(function(d)&nbsp;{&nbsp;return&nbsp;d.x;&nbsp;}))和:&nbsp;.attr("d",&nbsp;d3.linkHorizontal() &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.source(d&nbsp;=>&nbsp;[d.xs,d.ys]&nbsp;) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.target(d&nbsp;=>&nbsp;[d.xt,d.yt]))您要实现的树的基本形状可以在下面的代码片段中看到。尝试查看此示例是否有助于根据需要设置树的样式。var margins = {&nbsp; top: 20,&nbsp; bottom: 300,&nbsp; left: 30,&nbsp; right: 100};var height = 600;var width = 900;var totalWidth = width + margins.left + margins.right;var totalHeight = height + margins.top + margins.bottom;var svg = d3.select('body')&nbsp; .append('svg')&nbsp; .attr('width', totalWidth)&nbsp; .attr('height', totalHeight);var graphGroup = svg.append('g')&nbsp; .attr('transform', "translate(" + margins.left + "," + margins.top + ")");var levels = [&nbsp; [{&nbsp; &nbsp; id: 'Chaos'&nbsp; }],&nbsp; [{&nbsp; &nbsp; &nbsp; id: 'Gaea',&nbsp; &nbsp; &nbsp; parents: ['Chaos']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Uranus'&nbsp; &nbsp; }&nbsp; ],&nbsp; [{&nbsp; &nbsp; &nbsp; id: 'Oceanus',&nbsp; &nbsp; &nbsp; parents: ['Gaea', 'Uranus']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Thethys',&nbsp; &nbsp; &nbsp; parents: ['Gaea', 'Uranus']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Pontus'&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Rhea',&nbsp; &nbsp; &nbsp; parents: ['Gaea', 'Uranus']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Cronus',&nbsp; &nbsp; &nbsp; parents: ['Gaea', 'Uranus']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Coeus',&nbsp; &nbsp; &nbsp; parents: ['Gaea', 'Uranus']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Phoebe',&nbsp; &nbsp; &nbsp; parents: ['Gaea', 'Uranus']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Crius',&nbsp; &nbsp; &nbsp; parents: ['Gaea', 'Uranus']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Hyperion',&nbsp; &nbsp; &nbsp; parents: ['Gaea', 'Uranus']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Iapetus',&nbsp; &nbsp; &nbsp; parents: ['Gaea', 'Uranus']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Thea',&nbsp; &nbsp; &nbsp; parents: ['Gaea', 'Uranus']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Themis',&nbsp; &nbsp; &nbsp; parents: ['Gaea', 'Uranus']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Mnemosyne',&nbsp; &nbsp; &nbsp; parents: ['Gaea', 'Uranus']&nbsp; &nbsp; }&nbsp; ],&nbsp; [{&nbsp; &nbsp; &nbsp; id: 'Doris',&nbsp; &nbsp; &nbsp; parents: ['Oceanus', 'Thethys']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Neures',&nbsp; &nbsp; &nbsp; parents: ['Pontus', 'Gaea']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Dionne'&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Demeter',&nbsp; &nbsp; &nbsp; parents: ['Rhea', 'Cronus']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Hades',&nbsp; &nbsp; &nbsp; parents: ['Rhea', 'Cronus']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Hera',&nbsp; &nbsp; &nbsp; parents: ['Rhea', 'Cronus']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Alcmene'&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Zeus',&nbsp; &nbsp; &nbsp; parents: ['Rhea', 'Cronus']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Eris'&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Leto',&nbsp; &nbsp; &nbsp; parents: ['Coeus', 'Phoebe']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Amphitrite'&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Medusa'&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Poseidon',&nbsp; &nbsp; &nbsp; parents: ['Rhea', 'Cronus']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Hestia',&nbsp; &nbsp; &nbsp; parents: ['Rhea', 'Cronus']&nbsp; &nbsp; }&nbsp; ],&nbsp; [{&nbsp; &nbsp; &nbsp; id: 'Thetis',&nbsp; &nbsp; &nbsp; parents: ['Doris', 'Neures']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Peleus'&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Anchises'&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Adonis'&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Aphrodite',&nbsp; &nbsp; &nbsp; parents: ['Zeus', 'Dionne']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Persephone',&nbsp; &nbsp; &nbsp; parents: ['Zeus', 'Demeter']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Ares',&nbsp; &nbsp; &nbsp; parents: ['Zeus', 'Hera']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Hephaestus',&nbsp; &nbsp; &nbsp; parents: ['Zeus', 'Hera']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Hebe',&nbsp; &nbsp; &nbsp; parents: ['Zeus', 'Hera']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Hercules',&nbsp; &nbsp; &nbsp; parents: ['Zeus', 'Alcmene']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Megara'&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Deianira'&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Eileithya',&nbsp; &nbsp; &nbsp; parents: ['Zeus', 'Hera']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Ate',&nbsp; &nbsp; &nbsp; parents: ['Zeus', 'Eris']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Leda'&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Athena',&nbsp; &nbsp; &nbsp; parents: ['Zeus']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Apollo',&nbsp; &nbsp; &nbsp; parents: ['Zeus', 'Leto']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Artemis',&nbsp; &nbsp; &nbsp; parents: ['Zeus', 'Leto']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Triton',&nbsp; &nbsp; &nbsp; parents: ['Poseidon', 'Amphitrite']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Pegasus',&nbsp; &nbsp; &nbsp; parents: ['Poseidon', 'Medusa']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Orion',&nbsp; &nbsp; &nbsp; parents: ['Poseidon']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Polyphemus',&nbsp; &nbsp; &nbsp; parents: ['Poseidon']&nbsp; &nbsp; }&nbsp; ],&nbsp; [{&nbsp; &nbsp; &nbsp; id: 'Deidamia'&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Achilles',&nbsp; &nbsp; &nbsp; parents: ['Peleus', 'Thetis']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Creusa'&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Aeneas',&nbsp; &nbsp; &nbsp; parents: ['Anchises', 'Aphrodite']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Lavinia'&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Eros',&nbsp; &nbsp; &nbsp; parents: ['Hephaestus', 'Aphrodite']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Helen',&nbsp; &nbsp; &nbsp; parents: ['Leda', 'Zeus']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Menelaus'&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Polydueces',&nbsp; &nbsp; &nbsp; parents: ['Leda', 'Zeus']&nbsp; &nbsp; }&nbsp; ],&nbsp; [{&nbsp; &nbsp; &nbsp; id: 'Andromache'&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Neoptolemus',&nbsp; &nbsp; &nbsp; parents: ['Deidamia', 'Achilles']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Aeneas(2)',&nbsp; &nbsp; &nbsp; parents: ['Creusa', 'Aeneas']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Pompilius',&nbsp; &nbsp; &nbsp; parents: ['Creusa', 'Aeneas']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Iulus',&nbsp; &nbsp; &nbsp; parents: ['Lavinia', 'Aeneas']&nbsp; &nbsp; },&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; id: 'Hermione',&nbsp; &nbsp; &nbsp; parents: ['Helen', 'Menelaus']&nbsp; &nbsp; }&nbsp; ]]// precompute level depthlevels.forEach((l, i) => l.forEach(n => n.level = i));var nodes = levels.reduce(((a, x) => a.concat(x)), []);var nodes_index = {};nodes.forEach(d => nodes_index[d.id] = d);// objectificationnodes.forEach(d => {&nbsp; d.parents = (d.parents === undefined ? [] : d.parents).map(p => nodes_index[p])})// precompute bundleslevels.forEach((l, i) => {&nbsp; var index = {}&nbsp; l.forEach(n => {&nbsp; &nbsp; if (n.parents.length == 0) {&nbsp; &nbsp; &nbsp; return&nbsp; &nbsp; }&nbsp; &nbsp; var id = n.parents.map(d => d.id).sort().join('--')&nbsp; &nbsp; if (id in index) {&nbsp; &nbsp; &nbsp; index[id].parents = index[id].parents.concat(n.parents)&nbsp; &nbsp; } else {&nbsp; &nbsp; &nbsp; index[id] = {&nbsp; &nbsp; &nbsp; &nbsp; id: id,&nbsp; &nbsp; &nbsp; &nbsp; parents: n.parents.slice(),&nbsp; &nbsp; &nbsp; &nbsp; level: i&nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; &nbsp; n.bundle = index[id]&nbsp; })&nbsp; l.bundles = Object.keys(index).map(k => index[k])&nbsp; l.bundles.forEach((b, i) => b.i = i)})var links = []nodes.forEach(d => {&nbsp; d.parents.forEach(p => links.push({&nbsp; &nbsp; source: d,&nbsp; &nbsp; bundle: d.bundle,&nbsp; &nbsp; target: p&nbsp; }))})var bundles = levels.reduce(((a, x) => a.concat(x.bundles)), [])// reverse pointer from parent to bundlesbundles.forEach(b => b.parents.forEach(p => {&nbsp; if (p.bundles_index === undefined) {&nbsp; &nbsp; p.bundles_index = {}&nbsp; }&nbsp; if (!(b.id in p.bundles_index)) {&nbsp; &nbsp; p.bundles_index[b.id] = []&nbsp; }&nbsp; p.bundles_index[b.id].push(b)}))nodes.forEach(n => {&nbsp; if (n.bundles_index !== undefined) {&nbsp; &nbsp; n.bundles = Object.keys(n.bundles_index).map(k => n.bundles_index[k])&nbsp; } else {&nbsp; &nbsp; n.bundles_index = {}&nbsp; &nbsp; n.bundles = []&nbsp; }&nbsp; n.bundles.forEach((b, i) => b.i = i)})links.forEach(l => {&nbsp; if (l.bundle.links === undefined) {&nbsp; &nbsp; l.bundle.links = []&nbsp; }&nbsp; l.bundle.links.push(l)})// layoutconst padding = 8const node_height = 22const node_width = 70const bundle_width = 14const level_y_padding = 16const metro_d = 4const c = 16const min_family_height = 16nodes.forEach(n => n.height = (Math.max(1, n.bundles.length) - 1) * metro_d)var x_offset = paddingvar y_offset = paddinglevels.forEach(l => {&nbsp; x_offset += l.bundles.length * bundle_width&nbsp; y_offset += level_y_padding&nbsp; l.forEach((n, i) => {&nbsp; &nbsp; n.x = n.level * node_width + x_offset&nbsp; &nbsp; n.y = node_height + y_offset + n.height / 2&nbsp; &nbsp; y_offset += node_height + n.height&nbsp; })})var i = 0levels.forEach(l => {&nbsp; l.bundles.forEach(b => {&nbsp; &nbsp; b.x = b.parents[0].x + node_width + (l.bundles.length - 1 - b.i) * bundle_width&nbsp; &nbsp; b.y = i * node_height&nbsp; })&nbsp; i += l.length})links.forEach(l => {&nbsp; l.xt = l.target.x&nbsp; l.yt = l.target.y + l.target.bundles_index[l.bundle.id].i * metro_d - l.target.bundles.length * metro_d / 2 + metro_d / 2&nbsp; l.xb = l.bundle.x&nbsp; l.xs = l.source.x&nbsp; l.ys = l.source.y})// compress vertical spacevar y_negative_offset = 0levels.forEach(l => {&nbsp; y_negative_offset += -min_family_height + d3.min(l.bundles, b => d3.min(b.links, link => (link.ys - c) - (link.yt + c))) || 0&nbsp; l.forEach(n => n.y -= y_negative_offset)})// very ugly, I knowlinks.forEach(l => {&nbsp; l.yt = l.target.y + l.target.bundles_index[l.bundle.id].i * metro_d - l.target.bundles.length * metro_d / 2 + metro_d / 2&nbsp; l.ys = l.source.y&nbsp; l.c1 = l.source.level - l.target.level > 1 ? node_width + c : c&nbsp; l.c2 = c})const cluster = d3.cluster()&nbsp; .size([width, height]);const root = d3.hierarchy(links);cluster(root);let oValues = Object.values(root)[0];let linkks = oValues.map(x => x.bundle.links);linkks.forEach((linkk) => {&nbsp;let nodeG1 = svg.append("g")&nbsp; &nbsp; .selectAll("circle")&nbsp; &nbsp; .data(linkk)&nbsp; &nbsp; .join("circle")&nbsp; &nbsp; .attr("cx", d => d.target.x)&nbsp; &nbsp; .attr("cy", d => d.target.y)&nbsp; &nbsp; .attr("fill", "none")&nbsp; &nbsp; .attr("stroke", (d) => {&nbsp; &nbsp; &nbsp; return '#' + Math.floor(16777215 * Math.sin(3 * Math.PI / (5 * (parseInt(d.target.level) + 1)))).toString(16);&nbsp; &nbsp; })&nbsp; &nbsp; .attr("r", 6);&nbsp; let nodeG11 = svg.append("g")&nbsp; &nbsp; .selectAll("circle")&nbsp; &nbsp; .data(linkk)&nbsp; &nbsp; .join("circle")&nbsp; &nbsp; .attr("cx", d => d.source.x)&nbsp; &nbsp; .attr("cy", d => d.source.y)&nbsp; &nbsp; .attr("fill", "none")&nbsp; &nbsp; .attr("stroke", (d) => {&nbsp; &nbsp; &nbsp; return '#' + Math.floor(16777215 * Math.sin(3 * Math.PI / (5 * (parseInt(d.source.level) + 1)))).toString(16);&nbsp; &nbsp; })&nbsp; &nbsp; .attr("r", 6);&nbsp; let nodeG2 = svg.append("g")&nbsp; &nbsp; .attr("font-family", "sans-serif")&nbsp; &nbsp; .attr("font-size", 14)&nbsp; &nbsp; .selectAll("text")&nbsp; &nbsp; .data(linkk)&nbsp; &nbsp; .join("text")&nbsp; &nbsp; .attr("class", "text")&nbsp; &nbsp; .attr("x", d => d.target.x + padding)&nbsp; &nbsp; .attr("y", d => d.target.y)&nbsp; &nbsp; .text(d => d.target.id )&nbsp; &nbsp; .attr("fill", (d) => {&nbsp; &nbsp; &nbsp; return '#' + Math.floor(16777215 * Math.sin(3 * Math.PI / (5 * (parseInt(d.target.level) + 2)))).toString(16);&nbsp; &nbsp; });&nbsp;&nbsp;let nodeG22 = svg.append("g")&nbsp; &nbsp; .attr("font-family", "sans-serif")&nbsp; &nbsp; .attr("font-size", 14)&nbsp; &nbsp; .selectAll("text")&nbsp; &nbsp; .data(linkk)&nbsp; &nbsp; .join("text")&nbsp; &nbsp; .attr("class", "text")&nbsp; &nbsp; .attr("x", d => d.source.x + padding)&nbsp; &nbsp; .attr("y", d => d.source.y)&nbsp; &nbsp; .text(d => d.source.id )&nbsp; &nbsp; .attr("fill", (d) => {&nbsp; &nbsp; &nbsp; return '#' + Math.floor(16777215 * Math.sin(3 * Math.PI / (5 * (parseInt(d.source.level) + 1)))).toString(16);&nbsp; &nbsp; });&nbsp;&nbsp; let nodeG = svg.append('g')&nbsp; &nbsp; .attr('class', 'node')&nbsp; &nbsp; .selectAll("path")&nbsp; &nbsp; .data(linkk)&nbsp; &nbsp; .join('path')&nbsp; &nbsp; .attr("class", "link")&nbsp; &nbsp; .attr("d", d3.linkHorizontal()&nbsp; &nbsp; &nbsp; .source(d => [d.xs, d.ys])&nbsp; &nbsp; &nbsp; .target(d => [d.xt, d.yt]))&nbsp; &nbsp; .attr("fill", "none")&nbsp; &nbsp; .attr("stroke-opacity", 0.325)&nbsp; &nbsp; .attr("stroke-width", 0.75)&nbsp; &nbsp; .attr("stroke", (d) => {&nbsp; &nbsp; &nbsp; return '#' + Math.floor(16777215 * Math.sin(3 * Math.PI / (4 * parseInt(d.source.level)))).toString(16);&nbsp; &nbsp; });});path {&nbsp; display: block;&nbsp; z-index: 0;}text,circle {&nbsp; display: block;&nbsp; z-index: 1000;}<script src="https://d3js.org/d3.v5.min.js"></script>
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

JavaScript