我在用Javascript生成美观的家谱时遇到问题。

要求:

  • 每个 child 都应该与树中的两个 parent 相连,而不是像某些图中的
  • 一样
  • 我希望配偶在树上彼此相邻(垂直位置相同)
  • 我想按垂直方向组织节点,以便您一眼就能看到在同一十年中出生的人。
  • 一个人在一段时间内可以有多个配偶,每个 child 都有一个
  • 父级和子级可以在树中自由添加,因此不仅仅是“从一个人向上追溯谱系”

  • 我尝试过的最接近的是:
  • Cytoscape JS,其中Dagre作为布局引擎,并且启用了curve-style: taxi边缘。
    javascript - 用d3或cytoscape渲染家谱-LMLPHP

    (带有随机数据的图表。实线是父子关系,虚线是配偶)

    问题是配偶彼此之间没有对齐。 Dagre历史上一直支持将“rank”作为节点的参数,这意味着您可以强制某些节点处于特定的高度(如果可以的话,可以将其视为“世代”)。不幸的是,它是doesn't work any longerresponsible developer does not work on the project any longer。这将很好地解决我的问题。

  • 我尝试过但失败的其他事情:
  • 是否将dagre降级为支持等级的较旧版本?

    还没有使用任何版本的dagre的等级。
  • D3dagre-d3

    与上面相同的问题,因为dagre-d3是dagre的修改版本,这意味着它不支持世代排名。
  • yFiles family tree演示看起来不错,但是很商业。为了我的目的(想让任何人建立自己的家谱),一张开发者许可证的费用是26,000 USD(!?!)。显然不能接受。

    javascript - 用d3或cytoscape渲染家谱-LMLPHP

  • 我的问题

    如上所述,是否可以使cytoscape/dagre图中的节点垂直对齐?

    如果没有,我愿意尝试其他库和其他布局算法。

    我正在寻找一个与yFiles解决方案相似的工作示例,但使用的是开放源代码工具。

    最佳答案

    在深入了解我的答案 :)之前,您可能想看看WebCola,它是我在研究约束力有向图时遇到的:



    就像我在下面的示例中对y维所做的那样,它允许您specify x and y dimensional constraints。我自己没有使用过它,但是看起来非常适合您的要求。而且它可以与CytoScape一起使用,因此您可能可以在已经完成的工作的基础上进行开发...

    将尺寸约束应用于力导向图:

    由于您没有处理严格的等级结构(例如,您不是从一个后代开始而是逐步向上),因此一种方法是使用带有节点的D3 Force Directed Graph代表每个家庭成员。与线性层次结构相比,这将提供更多的灵活性。

    然后,可以通过将节点约束到y轴上的固定点来实现所需的世代布局。

    Here is a proof of concept:

  • 三代家庭成员
  • 爱丽丝(Alice)和鲍勃(Bob)和鲍勃(Carol)和卡罗尔(Carol)代表多个配偶
  • David是Alice和Bob的 child
  • 詹姆斯是鲍勃和卡罗尔的 child
  • assignGeneration基于链接的子节点,伙伴节点和父节点计算的节点生成(或y坐标)
  • 节点X坐标由d3处理,我认为这比尝试手动在x轴上为每个节点分配一个位置更加稳健
  • 基本样式:
  • 合作伙伴链接是珊瑚
  • 子链接为浅蓝色
  • 同级链接为浅绿色

  • 希望这里有足够的信息来决定这是否可行。在 parent 和 child 之间建立演示性的纵向/横向链接应该相当简单,但可能需要进行一些实验。

    可能需要将调整(取决于数据量和节点关系等)应用于simulation-再次,将需要进行一些实验以生成最佳布局。有关可用的不同力量的更多信息here

    <!DOCTYPE html>
    <html>
    
    <head>
      <style>
    svg {
      border: 1px solid gray;
    }
    
    .partner_link {
      stroke: lightcoral;
    }
    
    .child_link {
      stroke: lightskyblue;
    }
    
    .sibling_link {
      stroke: lightseagreen;
    }
      </style>
    </head>
    
    <body>
      <script src="https://d3js.org/d3.v5.min.js"></script>
      <script type="text/javascript">
    
    var nodeData = [{
      id: 1,
      name: 'Alice',
      partners: [2],
      children: [4]
    }, {
      id: 2,
      name: 'Bob',
      partners: [1, 3],
      children: [4,10]
    }, {
      id: 3,
      name: 'Carol',
      partners: [2],
      children: [10]
    }, {
      id: 4,
      name: 'David',
      partners: [7],
      children: [8]
    }, {
      id: 5,
      name: 'Emily',
      partners: [6],
      children: [7, 9]
    }, {
      id: 6,
      name: 'Fred',
      partners: [5],
      children: [7, 9]
    }, {
      id: 7,
      name: 'Grace',
      partners: [4],
      children: [8]
    }, {
      id: 8,
      name: 'Harry',
      partners: null,
      children: null
    }, {
      id: 9,
      name: 'Imogen',
      partners: null,
      children: null
    }, {
      id: 10,
      name: 'James',
      partners: null,
      children: null
    }];
    
    var linkData = [];
    
    nodeData.forEach((node, index) => {
      if (node.partners) {
        node.partners.forEach(partnerID => {
          linkData.push({ source: node, target: nodeData.find(partnerNode => partnerNode.id === partnerID), relationship: 'Partner' });
        })
      }
      if (node.children) {
        node.children.forEach(childID => {
          const childNode = nodeData.find(childNode => childNode.id === childID);
          if (node.children.length > 1) {
            childNode.siblings = node.children.slice(0, node.children.indexOf(childNode.id)).concat(node.children.slice(node.children.indexOf(childNode.id) + 1, node.children.length));
            childNode.siblings.forEach(siblingID => {
              linkData.push({ source: childNode, target: nodeData.find(siblingNode => siblingNode.id === siblingID), relationship: 'Sibling' });
            })
          }
          linkData.push({ source: node, target: childNode, relationship: 'Child' });
        })
      }
    });
    
    linkData.map(d => Object.create(d));
    
    assignGeneration(nodeData, nodeData, 0);
    
    var w = 500,
      h = 500;
    
    var svg = d3.select("body")
      .append("svg")
      .attr("width", w)
      .attr("height", h);
    
    var color = d3.scaleOrdinal(d3.schemeCategory10);
    
    var rowScale = d3.scalePoint()
      .domain(dataRange(nodeData, 'generation'))
      .range([0, h - 50])
      .padding(0.5);
    
    var simulation = d3.forceSimulation(nodeData)
      .force('link', d3.forceLink().links(linkData).distance(50).strength(1))
      .force("y", d3.forceY(function (d) {
        return rowScale(d.generation)
      }))
      .force("charge", d3.forceManyBody().strength(-300).distanceMin(60).distanceMax(120))
      .force("center", d3.forceCenter(w / 2, h / 2));
    
    var links = svg.append("g")
      .attr("stroke", "#999")
      .attr("stroke-opacity", 0.8)
      .selectAll("line")
      .data(linkData)
      .join("line")
      .attr("stroke-width", 1)
      .attr("class", d => {
        return d.relationship.toLowerCase() + '_link';
      });;
    
    var nodes = svg.append("g")
      .attr("class", "nodes")
      .selectAll("g")
      .data(nodeData)
      .enter().append("g")
    
    var circles = nodes.append("circle")
      .attr("r", 5)
      .attr("fill", function (d) {
        return color(d.generation)
      });
    
    var nodeLabels = nodes.append("text")
      .text(function (d) {
        return d.name;
      }).attr('x', 12)
      .attr('y', 20);
    
    var linkLabels = links.append("text")
      .text(function (d) {
        return d.relationship;
      }).attr('x', 12)
      .attr('y', 20);
    
    /*
    // Y Axis - useful for testing:
    var yAxis = d3.axisLeft(rowScale)(svg.append("g").attr("transform", "translate(30,0)"));
    */
    
    simulation.on("tick", function () {
      links
        .attr("x1", d => {
          return d.source.x;
        })
        .attr("y1", d => {
          return rowScale(d.source.generation);
        })
        .attr("x2", d => {
          return d.target.x;
        })
        .attr("y2", d => {
          return rowScale(d.target.generation);
        });
      nodes.attr("transform", function (d) {
        return "translate(" + d.x + "," + rowScale(d.generation) + ")";
      })
    });
    
    function dataRange(records, field) {
      var min = d3.min(records.map(record => parseInt(record[field], 10)));
      var max = d3.max(records.map(record => parseInt(record[field], 10)));
      return d3.range(min, max + 1);
    };
    
    function assignGeneration(nodes, generationNodes, generationCount) {
      const childNodes = [];
      generationNodes.forEach(function (node) {
        if (node.children) {
          // Node has children
          node.generation = generationCount + 1;
          node.children.forEach(childID => {
            if (!childNodes.find(childNode => childNode.id === childID)) {
              childNodes.push(generationNodes.find(childNode => childNode.id === childID));
            }
          })
        } else {
          if (node.partners) {
            node.partners.forEach(partnerID => {
              if (generationNodes.find(partnerNode => partnerNode.id === partnerID && partnerNode.children)) {
                // Node has partner with children
                node.generation = generationCount + 1;
              }
            })
          } else {
            // Use generation of parent + 1
            const parent = nodes.find(parentNode => parentNode.children && parentNode.children.indexOf(node.id) !== -1);
            node.generation = parent.generation + 1;
          }
        }
      });
      if (childNodes.length > 0) {
        return assignGeneration(nodes, childNodes, generationCount += 1);
      } else {
        nodes.filter(node => !node.generation).forEach(function (node) {
          node.generation = generationCount + 1;
        });
        return nodes;
      }
    }
    
      </script>
    </body>
    
    </html>

    关于javascript - 用d3或cytoscape渲染家谱,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/61491039/

    10-13 00:38