如何在有很多子节点时显示所有子节点

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何在有很多子节点时显示所有子节点相关的知识,希望对你有一定的参考价值。

我正在D3可视化上工作,以显示父子关系。

当我的孩子数较少时,我就可以将数据完美地可视化,但是当孩子数较多时,子节点会重叠。如何修改图表,以便可以看到根(左侧)左侧的所有节点?例如,使节点围绕根以圆形方式存在,一些节点靠近根,而某些节点以某种顺序排列。蜜蜂最好的展示方式是什么?

这是我的代码。

var data = 
  "name": "Root",
  "img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png",
  "children": [
    "name": "3",
    "img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
  , 
    "name": "4",
    "img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
  , 
    "name": "1",
    "img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
  , 
    "name": "2",
    "img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
  , 
    "name": "1",
    "img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
  , 
    "name": "2",
    "img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
  , 
    "name": "1",
    "img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
  , 
    "name": "2",
    "img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
  , 
    "name": "1",
    "img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
  , 
    "name": "2",
    "img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
  , 
    "name": "1",
    "img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
  , 
    "name": "2",
    "img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
  , 
    "name": "1",
    "img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
  , 
    "name": "2",
    "img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
  , 
    "name": "1",
    "img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
  , 
    "name": "2",
    "img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
  ],
  "parent": [
    "name": "1",
    "img": "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
  ]
;
var bgColors = ['#fd90b5', '#6ca1e9', '#fa975c', '#eb7092', '#f88962', '#a094ed', '#7f8de1'];
var dr = 0;
// Left data
var data1 = 
  "name": data.name,
  "img": data.img,
  "children": JSON.parse(JSON.stringify(data.children))
;

// Right data
var data2 = 
  "name": data.name,
  "img": data.img,
  "children": JSON.parse(JSON.stringify(data.parent))
;

// Create d3 hierarchies
var right = d3.hierarchy(data1);
var left = d3.hierarchy(data2);

// Render both trees
drawTree(right, "right")
drawTree(left, "left")

// draw single tree
function drawTree(root, pos) 
  var refType;
  if (pos == 'left')
    refType = 'left';
  else
    refType = 'right';

  var SWITCH_CONST = 1;
  if (pos === "left") 
    SWITCH_CONST = -1;
  

  var svg = d3.select("svg"),
    width = +svg.attr("width"),
    height = +svg.attr("height")

  var g = svg.append("g").attr("transform", "translate(" + width / 2 + ",0)");

  var tree = d3.tree()
    .size([height, SWITCH_CONST * (width - 150) / 2]);

  tree(root)

  var nodes = root.descendants();
  var links = root.links();
  nodes[0].x = height / 2

  // Create links
  var link = g.selectAll(".link")
    .data(links)
    .enter()

  link.append("path")
    .attr("class", "link")
    .attr("d", function(d) 
      //first return returns a curve and the second will return straight lines in
      //return "M" + d.target.y + "," + d.target.x + "C" + (d.target.y + d.source.y) / 2.5 + "," + d.target.x + " " + (d.target.y + d.source.y) / 2 + "," + d.source.x + " " + d.source.y + "," + d.source.x;
      return "M" + d.target.y + "," + d.target.x + "A" + dr + "," + dr + " 1,0 0 " + d.source.y + "," + d.source.x;

    );


  link.append("text")
    .attr("font-family", "Arial, Helvetica, sans-serif")
    .attr("fill", "Black")
    .style("font", "normal 12px Arial")
    .attr("transform", function(d) 
      return "translate(" +
        ((d.source.y + d.target.y) / 2) + "," +
        ((d.source.x + d.target.x) / 2) + ")";
    )
    .attr("dy", ".35em")
    .attr("text-anchor", "middle")
    .data(nodes)
    .text(refType);

  // Create nodes
  var node = g.selectAll(".node")
    .data(nodes)
    .enter()
    .append("g")
    .attr("class", function(d) 
      return "node" + (d.children ? " node--internal" : " node--leaf");
    )
    .attr("transform", function(d) 
      return "translate(" + d.y + "," + d.x + ")";
    )

  node.append('circle')
    .attr('class', 'icon-wrap')
    .attr('x', 0)
    .attr('y', 0)
    .attr('r', 25)
    .style('fill', 'black');


  node.append('image')
    .attr('href', d => d.data.img)
    .attr('x', '-25')
    .attr('y', '-25')
    .attr('height', '50')
    .attr('width', '50');

  node.append("text")
    .attr("dy", 45)
    .style("text-anchor", "middle")
    .text(d => d.data.name);
.node circle 
  fill: #999;


.node text 
  font: 12px sans-serif;


.node--internal circle 
  fill: #555;


.link 
  fill: none;
  stroke: #555;
  stroke-opacity: 0.4;
  stroke-width: 1.5px;
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<svg width="700" height="400"></svg>

这里是孩子人数少的样子。

enter image description here

请让我知道如何优化代码以适合最多30个子节点。

谢谢

答案

一种可能的解决方案是创建一个递归函数,该函数将在root函数中调整drawTree数据坐标。

这里是一个递归函数,它将使节点左右错开。请注意注释,其中提到了代码的哪一部分控制坐标的计算。

function adjustClashes(data, siblings = 1, index = 1, radius = 20, height = 400) 
  //can the node fit in the current x level?
  // if not adjust it
  let heightneeded = siblings * radius * 2;
  if (heightneeded > height) 
    // the code in this if statement will control the calculations for your new coordinates
    // In the simplest case we adjust the nodes by staggering odd and even nodes
    if (index % 2 != 0)
      data.y = data.y + (radius * 2)
     else 
      data.y = data.y - (radius * 2)
    
  

  // if there are children go deeper and perform same adjustment
  if (data.children) 
    data.children.forEach((f, i) => 
      return adjustClashes( f, data.children.length, i )
    )
   else 
    return;
  
  // finally return the data
  return data

完整代码段:

var data = 
  name: "Root",
  img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png",
  children: [
    
      name: "3",
      img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
    ,
    
      name: "4",
      img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
    ,
    
      name: "1",
      img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
    ,
    
      name: "2",
      img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
    ,
    
      name: "1",
      img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
    ,
    
      name: "2",
      img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
    ,
    
      name: "1",
      img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
    ,
    
      name: "2",
      img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
    ,
    
      name: "1",
      img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
    ,
    
      name: "2",
      img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
    ,
    
      name: "1",
      img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
    ,
    
      name: "2",
      img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
    ,
    
      name: "1",
      img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
    ,
    
      name: "2",
      img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
    ,
    
      name: "1",
      img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
    ,
    
      name: "2",
      img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
    
  ],
  parent: [
    
      name: "1",
      img: "https://www.freelogodesign.org/Content/img/logo-samples/flooop.png"
    
  ]
;

var bgColors = [
  "#fd90b5",
  "#6ca1e9",
  "#fa975c",
  "#eb7092",
  "#f88962",
  "#a094ed",
  "#7f8de1"
];
var dr = 0;
// Left data
var data1 = 
  name: data.name,
  img: data.img,
  children: JSON.parse(JSON.stringify(data.children))
;

// Right data
var data2 = 
  name: data.name,
  img: data.img,
  children: JSON.parse(JSON.stringify(data.parent))
;

// Create d3 hierarchies
var right = d3.hierarchy(data1);
var left = d3.hierarchy(data2);

// Render both trees
drawTree(right, "right");
drawTree(left, "left");

// draw single tree
function drawTree(root, pos) 
  var refType;
  if (pos == "left") refType = "left";
  else refType = "right";

  var SWITCH_CONST = 1;
  if (pos === "left") 
    SWITCH_CONST = -1;
  

  var svg = d3.select("svg"),
    width = +svg.attr("width"),
    height = +svg.attr("height");

  var g = svg.append("g").attr("transform", "translate(" + width / 2 + ",0)");

  var tree = d3.tree().size([height, (SWITCH_CONST * (width - 150)) / 2]);

  tree(root);

  function adjustClashes(
    data,
    siblings = 1,
    index = 1,
    radius = 20,
    height = 400
  ) 
    //can the node fit in the current x level?
    // if not adjust it
    let heightneeded = siblings * radius * 2;
    if (heightneeded > height) 
      // the code in this if statement will control the calculations for your new coordinates
      // In the simplest case we adjust the nodes by staggering odd and even nodes
      if (index % 2 != 0) 
        data.y = data.y + radius * 2;
       else 
        data.y = data.y - radius * 2;
      
    

    // if there are children go deeper and perform same adjustment
    if (data.children) 
      data.children.forEach((f, i) => 
        return adjustClashes(f, data.children.length, i);
      );
     else 
      return;
    
    // finally return the data
    return data;
  

  root = adjustClashes(root);

  var nodes = root.descendants();
  var links = root.links();
  nodes[0].x = height / 2;

  // Create links
  var link = g.selectAll(".link").data(links).enter();

  link
    .append("path")
    .attr("class", "link")
    .attr("d", function (d) 
      //first return returns a curve and the second will return straight lines in
      //return "M" + d.target.y + "," + d.target.x + "C" + (d.target.y + d.source.y) / 2.5 + "," + d.target.x + " " + (d.target.y + d.source.y) / 2 + "," + d.source.x + " " + d.source.y + "," + d.source.x;
      return (
        "M" +
        d.target.y +
        "," +
        d.target.x +
        "A" +
        dr +
        "," +
        dr +
        " 1,0 0 " +
        d.source.y +
        "," +
        d.source.x
      );
    );

  link
    .append("text")
    .attr("font-family", "Arial, Helvetica, sans-serif")
    .attr("fill", "Black")
    .style("font", "normal 12px Arial")
    .attr("transform", function (d) 
      return (
        "translate(" +
        (d.source.y + d.target.y) / 2 +
        "," +
        (d.source.x + d.target.x) / 2 +
        ")"
      );
    )
    .attr("dy", ".35em")
    .attr("text-anchor", "middle")
    .data(nodes)
    .text(refType);

  // Create nodes
  var node = g
    .selectAll(".node")
    .data(nodes)
    .enter()
    .append("g")
    .attr("class", function (d) 
      return "node" + (d.children ? " node--internal" : " node--leaf");
    )
    .attr("transform", function (d) 
      return "translate(" + d.y + "," + d.x + ")";
    );

  node
    .append("circle")
    .attr("class", "icon-wrap")
    .attr("x", 0)
    .attr("y", 0)
    .attr("r", 25)
    .style("fill", "black");

  node
    .append("image")
    .attr("href", (d) => d.data.img)
    .attr("x", "-25")
    .attr("y", "-25")
    .attr("height", "50")
    .attr("width", "50");

  node
    .append("text")
    .attr("dy", 45)
    .style("text-anchor", "middle")
    .text((d) => d.data.name);
.node circle 
  fill: #999;


.node text 
  font: 12px sans-serif;


.node--internal circle 
  fill: #555;


.link 
  fill: none;
  stroke: #555;
  stroke-opacity: 0.4;
  stroke-width: 1.5px;
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<svg width="700" height="400"></svg>

以上是关于如何在有很多子节点时显示所有子节点的主要内容,如果未能解决你的问题,请参考以下文章

java如何实现二级树形菜单动态显示。要求加载页面时显示一级,点击一级菜单再去数据库查找出二级菜单

SQL (根据子节点查询父节点信息)

mysql如何根据很多子节点查询出父节点,只要一条路径上的

如何在 CRM 的子网格形式中单击按钮时显示查找窗口?

如何在放大时显示全文并在缩小时截断它

在 Flutter 中让 Row 子节点尽可能宽(父节点的剩余宽度)?