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

Posted

技术标签:

【中文标题】如何将两个父节点连接到一个子节点以及如何务实地为树中的每个节点制作工具提示?在 D3 js (SVG) 中【英文标题】:How can I connect two parent node into one child node and how to make a tooltip for each node in the tree pragmatically? in D3 js (SVG) 【发布时间】:2021-02-11 16:23:52 【问题描述】:

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

例如——如果你运行代码,你会看得更清楚。我想创建一个 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;
  );
circle 
  fill: rgb(88, 147, 0);


path 
  fill: none;
  stroke: black;
<script src="https://d3js.org/d3.v6.min.js"></script>

【问题讨论】:

D3 不是为此而构建的,但也许this answer 会帮助您,它将每个节点视为有一个父节点用于定位,但随后它会覆盖链接的绘制以连接节点父母双方 这个答案可能对你有一些使用的想法***.com/a/61552009/2627160 但正如@RubenHelsloot 所建议的那样,D3 可能不是这项工作的最佳工具 向节点添加工具提示已完成many times,包括v6 【参考方案1】:

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

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;
  );
circle 
  fill: rgb(88, 147, 0);


path 
  fill: none;
  stroke: black;
<script src="https://d3js.org/d3.v6.min.js"></script>

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

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

我强制链接要短,树要居中在图表的中间,节点要处于正确的级别,每个节点要与任何其他节点相距 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;
  );
circle 
  fill: rgb(88, 147, 0);


path 
  fill: none;
  stroke: black;
<script src="https://d3js.org/d3.v6.min.js"></script>

【讨论】:

以上是关于如何将两个父节点连接到一个子节点以及如何务实地为树中的每个节点制作工具提示?在 D3 js (SVG) 中的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的客户端应该只连接到一个子节点集群工作人员时接收来自所有子节点集群工作人员的套接字发射?

SQL语句查询出父节点下的所有子节点

C# winform treeview 怎麼判断是不是包含某个子节点

为树中的节点添加颜色

如何将传入的二进制块连接到视频(webm)文件节点js中?

如何有效地检索树中节点的路径(与帖子“将平面表解析为树?”相关)