如何将两个父节点连接到一个子节点,以及如何实用地为树中的每个节点制作工具提示?在 D3 js (SVG) 中

the*_*der 5 javascript css svg dom d3.js

我制作了一个树形图结构,它工作得非常好。但现在我想做一个小小的改变却无法做出改变。我想将两个父节点连接到一个子节点...我还尝试实用地向每个节点添加一个工具提示。

例如 - 如果您运行代码,您会更清楚地看到它。我想创建 Hanna 的子节点并标记它将连接到名为“Eric”的子节点。

知道如何实现这一目标吗?

var svg = d3
  .select("body")
  .append("svg")
  .attr("width", 600)
  .attr("height", 600)
  .append("g")
  .attr("transform", "translate(50,50)");

//tree data
var data = [
  { child: "John", parent: "" },
  { child: "Aron", parent: "Kevin" },
  { child: "Kevin", parent: "John" },
  { child: "Hannah", parent: "Anna" },
  { child: "Rose", parent: "Sarah" },
  { child: "Anna", parent: "John" },
  { child: "Sarah", parent: "Kevin" },
  { child: "Mark", parent: "Anna" },
  { child: "Angle", parent: "Sarah" },
];

//to construct
var dataStructure = d3
  .stratify()
  .id(function (d) {
    return d.child;
  })
  .parentId(function (d) {
    return d.parent;
  })(data);

//to define the size of the structure tree
var treeStructure = d3.tree().size([500, 300]);
var information = treeStructure(dataStructure);

//to make the connections curves
var connections = svg.append("g").selectAll("path").data(information.links());
connections
  .enter()
  .append("path")
  .attr("d", function (d) {
    return (
      "M" +
      d.source.x +
      "," +
      d.source.y +
      " C " +
      d.source.x +
      "," +
      (d.source.y + d.target.y) / 2 +
      " " +
      d.target.x +
      "," +
      (d.source.y + d.target.y) / 2 +
      " " +
      d.target.x +
      "," +
      d.target.y
    );
  });

//creating the circles with data info
var circles = svg
  .append("g")
  .selectAll("circle")
  .data(information.descendants());

//placing the circles
circles
  .enter()
  .append("circle")
  .attr("cx", function (d) {
    return d.x;
  })
  .attr("cy", function (d) {
    return d.y;
  })
  .attr("r", 7)
  .append("text");

//names
var names = svg.append("g").selectAll("text").data(information.descendants());
names
  .enter()
  .append("text")
  .text(function (d) {
    return d.data.child;
  })
  .attr("x", function (d) {
    return d.x + 7;
  })
  .attr("y", function (d) {
    return d.y + 4;
  });
Run Code Online (Sandbox Code Playgroud)
circle {
  fill: rgb(88, 147, 0);
}

path {
  fill: none;
  stroke: black;
}
Run Code Online (Sandbox Code Playgroud)
<script src="https://d3js.org/d3.v6.min.js"></script>
Run Code Online (Sandbox Code Playgroud)

Rub*_*oot 6

您可以做的最简单的事情就是随机分配一个父级,然后使用该父级来定位节点,并自己绘制链接。

var svg = d3
  .select("body")
  .append("svg")
  .attr("width", 600)
  .attr("height", 600)
  .append("g")
  .attr("transform", "translate(50,50)");

//tree data
var data = [
  { child: "Alice", parents: [] },
  { child: "Bob", parents: [] },
  { child: "Carol", parents: [] },
  { child: "Dave", parents: ["Alice", "Bob"] },
  { child: "Eve", parents: ["Alice", "Bob"] },
  { child: "Francis", parents: ["Bob", "Carol"] },
  { child: "Graham", parents: ["Carol"] },
  { child: "Hugh", parents: ["Eve", "Graham"] },
];

// Process the nodes, add a pseudo root node so we don't have
// multiple roots
data.forEach(function(d) {
  d.parentId = d.parents.length > 0 ? d.parents[0] : "root";
});
data.unshift({ child: "root", parentId: "" });

//to construct
var dataStructure = d3
  .stratify()
  .id(function (d) {
    return d.child;
  })
  .parentId(function (d) {
    return d.parentId;
  })(data);

//to define the size of the structure tree
var treeStructure = d3.tree().size([500, 300]);
var root = treeStructure(dataStructure);

var nodes = root.descendants()
  .filter(function(d) { return d.id !== "root"; });

// Custom way to get all links we need to draw
var links = [];
nodes.forEach(function(node) {
  node.data.parents.forEach(function(parentId) {
    var parentNode = nodes.find(function(d) { return d.id === parentId; });
    links.push({
      source: parentNode,
      target: node,
    });
  });
});

//to make the connections curves
var connections = svg.append("g").selectAll("path").data(links);
connections
  .enter()
  .append("path")
  .attr("d", function (d) {
    return (
      "M" +
      d.source.x +
      "," +
      d.source.y +
      " C " +
      d.source.x +
      "," +
      (d.source.y + d.target.y) / 2 +
      " " +
      d.target.x +
      "," +
      (d.source.y + d.target.y) / 2 +
      " " +
      d.target.x +
      "," +
      d.target.y
    );
  });

//creating the circles with data info
var circles = svg
  .append("g")
  .selectAll("circle")
  .data(nodes);

//placing the circles
circles
  .enter()
  .append("circle")
  .attr("cx", function (d) {
    return d.x;
  })
  .attr("cy", function (d) {
    return d.y;
  })
  .attr("r", 7)
  .append("text");

//names
var names = svg.append("g").selectAll("text").data(nodes);
names
  .enter()
  .append("text")
  .text(function (d) {
    return d.id;
  })
  .attr("x", function (d) {
    return d.x + 7;
  })
  .attr("y", function (d) {
    return d.y + 4;
  });
Run Code Online (Sandbox Code Playgroud)
circle {
  fill: rgb(88, 147, 0);
}

path {
  fill: none;
  stroke: black;
}
Run Code Online (Sandbox Code Playgroud)
<script src="https://d3js.org/d3.v6.min.js"></script>
Run Code Online (Sandbox Code Playgroud)

但是,您可以看到这存在一些问题。我们希望链接尽可能短,因此我们应该按其父节点对节点进行排序 - 具有相同父节点的子节点应该位于父节点之间的某个位置,并且如果两个父节点具有合伙。

最简单的方法可能是使用节点链接图。通过施加正确的力,您可以将节点推向最佳布局。

我强制链接较短,树位于图的中间,节点位于正确的级别,并且每个节点与任何其他节点相距 100 像素。

我运行了 100 次模拟,并在渲染任何内容之前停止它,从而避免了您通常看到的移动和抽搐。就用户体验而言,它看起来就像一个正常的树状结构。

d.dy我还在节点上使用自定义属性作为“所需的 y 值”。该值是模拟时吸引节点的值,但由于它可能会有点偏差,因此可能看起来很混乱。通过像这样捕捉 y 轴值,我避免了这种情况并使其看起来更加结构化。

var levelRadius = 50;
var radius = 10;
var size = 500;
var margin = 50;

var svg = d3
  .select("body")
  .append("svg")
  .attr("width", size + 2 * margin)
  .attr("height", size + 2 * margin)
  .append("g")
  .attr("transform", "translate(" + [margin, margin] + ")");

//tree data
var data = [{
    child: "Alice",
    parents: []
  },
  {
    child: "Bob",
    parents: []
  },
  {
    child: "Carol",
    parents: []
  },
  {
    child: "Dave",
    parents: ["Alice", "Bob"]
  },
  {
    child: "Eve",
    parents: ["Alice", "Bob"]
  },
  {
    child: "Francis",
    parents: ["Bob", "Carol"]
  },
  {
    child: "Graham",
    parents: ["Carol"]
  },
  {
    child: "Hugh",
    parents: ["Eve", "Graham"]
  },
];

data.forEach(function(d, i) {
  d.level = d.parents.length ?
    data.find(function(p) {
      return p.child === d.parents[0];
    }).level + 1 :
    0;

  // Desired y level, otherwise try for sensible defaults
  d.dy = d.y = d.level * levelRadius * 2;
  d.x = size / 2 - i * levelRadius;
});

var nodes = data;

// Custom way to get all links we need to draw
var links = [];
nodes.forEach(function(node) {
  node.parents.forEach(function(parentId) {
    var parentNode = nodes.find(function(d) {
      return d.child === parentId;
    });
    links.push({
      id: parentNode.child + " - " + node.child,
      source: parentNode,
      target: node,
    });
  });
});

var simulation = d3.forceSimulation(nodes)
  .force("link", d3.forceLink(links).id(d => d.id))
  .force("collide", d3.forceCollide(levelRadius))
  .force("x", d3.forceX()
    .x(function(d) {
      return size / 2;
    })
    .strength(0.2)
  )
  .force("y", d3.forceY()
    .y(function(d) {
      return d.dy;
    })
    .strength(5)
  );
// Run the simulation once, even before rendering anything
simulation.tick(100)
  .stop();

//to make the connections curves
var link = svg.append("g").selectAll("path")
  .data(links)
  .enter()
  .append("path")
  .attr("d", function(d) {
    return (
      "M" +
      d.source.x +
      "," +
      d.source.dy +
      " C " +
      d.source.x +
      "," +
      (d.source.y + d.target.dy) / 2 +
      " " +
      d.target.x +
      "," +
      (d.source.y + d.target.dy) / 2 +
      " " +
      d.target.x +
      "," +
      d.target.dy
    );
  });

var node = svg
  .selectAll(".node")
  .data(nodes)
  .enter()
  .append("g")
  .attr("class", "node")
  .attr("transform", function(d) {
    // Use d.dy to snap the node to the level that we want it at
    return "translate(" + [d.x, d.dy] + ")";
  });

node.append("circle")
  .attr("r", radius);
node.append("text")
  .attr("dominant-baseline", "middle")
  .attr("dx", radius + 3)
  .text(function(d) {
    return d.child;
  });
Run Code Online (Sandbox Code Playgroud)
circle {
  fill: rgb(88, 147, 0);
}

path {
  fill: none;
  stroke: black;
}
Run Code Online (Sandbox Code Playgroud)
<script src="https://d3js.org/d3.v6.min.js"></script>
Run Code Online (Sandbox Code Playgroud)