使用 d3 或 cytoscape 渲染家谱

2024-02-02

我在用 Javascript 生成漂亮的家谱时遇到问题。

要求:

  • 每个孩子应该连接到树中的两个父母,而不是像某些图中的一个
  • 我希望配偶在树上彼此相邻(相同的垂直位置)
  • 我想把节点按世代垂直组织起来,这样你就能一目了然地看到同一年代出生的人。
  • 随着时间的推移,一个人可以有多个配偶,并且每个配偶都有孩子
  • 父母和孩子可以在树中自由添加,因此不仅仅是“从一个人向上追踪血统”

我试过的最接近这个:

  1. Cytoscape JS https://js.cytoscape.org/ with Dagre https://github.com/dagrejs/dagre/ as layout engine, and curve-style: taxi https://js.cytoscape.org/#style/taxi-edges edges enabled. Family tree

    (随机数据图表。实线为亲子关系,虚线为配偶关系)

    问题在于夫妻之间的关系不一致。 Dagre 历史上支持“等级”作为节点的参数,这意味着您可以强制某些节点处于特定高度(如果愿意,可以将其视为“一代”)。不幸的是,它不再起作用了 https://github.com/dagrejs/dagre/issues/130,以及负责任的开发商不再参与该项目 https://github.com/dagrejs/dagre/issues/159。这可以很好地解决我的问题。

我尝试过但失败的其他事情:

  1. 将 dagre 降级到支持排名的旧版本?

    还没有获得使用任何版本的 dagre 的等级。

  2. D3 https://d3js.org/ with dagre-d3 https://github.com/dagrejs/dagre-d3

    与上面的问题相同,因为dagre-d3是dagre的修改版本,这意味着它不支持分代排名。

  3. yFiles 家谱 https://live.yworks.com/demos/layout/familytree/演示看起来很棒,但是是商业的。出于我的目的(希望任何人都建立自己的家谱),单个开发人员许可证的成本为 26.000 美元(!?!)。显然不能接受。

我的问题

是否可以像我上面描述的那样垂直对齐 cytoscape/dagre 图中的节点?

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

我正在寻找一个看起来类似于 yFiles 解决方案的工作示例,但使用开源工具。


在你深入了解我的答案之前:) 你可能想看看WebCola https://github.com/tgdwyer/WebCola,我在研究约束力定向图时遇到的:

基于 JavaScript 约束的高质量图形布局 使用 D3.js 和其他基于 Web 的图形进行可视化和探索 图书馆。

它可以让你指定 x 和 y 维度约束 https://ialab.it.monash.edu/webcola/examples/alignment.html正如我在下面的示例中对 y 维度所做的那样。我自己没有使用过它,但看起来非常适合您的要求。它可以与 CytoScape 配合使用,因此您可以在已经完成的工作的基础上进行构建...

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

由于您没有处理严格的层次结构(例如,您不是从一个后代开始并逐步向上),一种方法是使用D3 力定向图 https://github.com/d3/d3-force用一个节点来代表每个家庭成员。与线性层次结构相比,这将提供更大的灵活性。

然后,可以通过将节点限制在 y 轴上的固定点来实现您正在寻找的分代布局。

这是概念证明 https://stackblitz.com/edit/js-jbn7hl:

  • 家族三代人
  • 多个配偶由 Alice 和 Bob / Bob 和 Carol 代表
  • 大卫是爱丽丝和鲍勃的孩子
  • 詹姆斯是鲍勃和卡罗尔的孩子
  • 节点生成(或 y 坐标)计算如下assignGeneration基于链接的子节点、伙伴节点和父节点
  • 节点 X 坐标由 d3 处理,我认为这比尝试手动为每个节点分配 x 轴上的位置更稳健
  • Basic styling:
    • 合作伙伴链接是珊瑚色的
    • 子链接是浅蓝色的
    • 兄弟链接是浅绿色的

希望这里有足够的信息供您决定这是否是可行的方法。在父母和孩子之间建立展示性的垂直/水平联系应该相当简单,但可能需要一些实验。

可能需要进行调整(取决于数据量和节点关系等)simulation- 同样,需要进行一些实验才能生成最佳布局。有关可用的不同力量的更多信息here https://github.com/d3/d3-force#simulation_nodes.

<!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>
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 d3 或 cytoscape 渲染家谱 的相关文章

  • 在 JSP 中从 JavaScript/jQuery 调用后端 Java 方法

    我有一个 JSP 其中有一个select包含实体种类名称的列表 当我选择一个实体类型时 我需要填充另一个实体类型select包含所选实体类型的字段名称的列表 为此 我调用了一个 JavaScript 函数onchange event 在 J
  • 如何在 JavaScript 中使用除法 [关闭]

    Closed 这个问题是无法重现或由拼写错误引起 help closed questions 目前不接受答案 我想在 JavaScript 中除以一个数字 它会返回一个十进制值 例如 737 1070 我想要 JavaScript 返回0
  • 如何使用给定 jQuery 选择中找到的元素生成对象

    在以下人员的帮助下 我有以下 jQuery 选择LGSon的回答 https stackoverflow com a 51113888 1375163 to an 先前的问题 https stackoverflow com question
  • 匹配不可打印/非 ASCII 字符并从文本中删除

    我的 JavaScript 很生疏 所以任何有关这方面的帮助都会很棒 我需要检测字符串中的不可打印字符 控制字符 如 SOH BS 等 以及扩展 ascii 字符 如 并将其删除 但我不知道如何编写代码 谁能指出我正确的方向来解决这个问题
  • NodeJS发送POST请求时如何设置Content-Length?

    var https require https var p api username FA AA ZOHO ACTION EXPORT ZOHO OUTPUT FORMAT JSON ZOHO ERROR FORMAT JSON ZOHO
  • JavaScript 符号并不能阻止对象中的名称冲突

    我已经开始研究 JavaScript 中的符号 并开始在我的对象中使用它们来帮助解决名称冲突 但是在使用它们时我仍然可以覆盖属性吗 我很难理解 JavaScript 中符号的意义 它们被谈论了很多 人们说它们很聪明 因为它们不会导致对象中的
  • JavaScript 等待函数响应

    我有以下代码 myFunc bar myFunc 正在发出 ajax 请求 在 myFunc 的请求完成之前 我不想执行 bar 我也不想将对 bar 的调用移至 myFunc 内部 可能的 EDIT 这是我最终得到的代码 var FOO
  • 为什么我不能在 Javascript 中滚动循环?

    我正在开发一个使用 dojo 的网页 并且上面有许多 在我的测试用例中为 6 但通常是可变的 项目小部件 我正在调用 dojo addOnLoad init 并且在 init 函数中我有以下几行 dojo connect dijit byI
  • 当函数定义为无参数时,为什么我可以调用带参数的函数?

    再会 我偶然发现了一些我在 JavaScript 领域从未见过的东西 但我想对于更了解该语言的人来说这是很容易解释的 下面我有以下函数 代码取自书籍 JavaScript Ninja 的秘密 function log try console
  • Jquery toggle() 函数无法与hoverwords() 滑动字母扩展一起使用

    我有 2 个 div 每 3 秒切换一次 现在 对于 div 中的文本 我使用一个名为 滑动字母 的扩展 正如您在此处提供的演示中看到的那样 http tympanus net Development SlidingLetters http
  • NodeJS 中的 uglify-js“找不到模块”

    在这里我正在创建应用程序来压缩 javascript 文件 我所做的步骤 在我的本地机器上安装了nodeJS 检查节点和 npm 正在工作 通过 npm install uglify js g 安装 uglify js 并安装 当我尝试在命
  • 没有 ssl 的 Web 加密 API

    我编写了一个用于安全消息传输的小网络应用程序 以了解有关加密的更多信息 并想向我的朋友展示它并让他们玩一下 所以我将它托管在我的小服务器上 并惊讶地发现 Web Crypto API 我竭尽全力开始工作 因为它的错误消息不是很具体 需要 S
  • 在TestCafé测试中注入的injectScripts中的脚本在哪里?

    我正在以编程方式设置 TestCaf 测试 并且使用injectScripts配置上Runner类来注入函数 根据文档 这些脚本被添加到测试页面的标题中 是否可以从测试本身调用函数 我还没有找到办法做到这一点 我可以看到脚本映射在测试中是可
  • 读取 Nashorn JO4 和 NativeArray

    Java调用代码 import jdk nashorn api scripting myCustomHashMap dataStore new myCustomHashMap ScriptEngineManager sem new Scri
  • 如何在javascript中解压二进制文件?

    我正在尝试将一些现有代码从 python 移植到 javascript 并且不确定如何处理以下行 var1 var2 struct unpack
  • JavaScript 按属性删除对象数组中的元素

    我有一个以下形式的对象数组 prop1 value1 banks id value property2 value2 所以我想要做的是通过搜索 id 值来删除 banks 属性中的元素 然后从banks数组中删除找到的元素 id 属性具有唯
  • Express.js“app.use()需要中间件功能”

    我正在学习 Express js 4 和 Node 但遇到了一个我无法弄清楚的错误 我正在尝试使用 node sass 包来编译我的 sass 代码 但我无法启动并运行它 这是我的主文件的精简版本 var express require e
  • ES6 Bare Import:如何使用以及何时使用?

    ES6 允许我们使用新的导入语法 使用它 我们可以将模块导入到我们的代码中 或者这些模块的一部分 使用示例包括 Import the default export from a module import React from react
  • 删除已从另一个下拉菜单中选择的下拉值

    我在网上搜索了一段时间 但仍然找不到答案 我的网站上有三个下拉菜单 我使用它们来接受用户首选项 以便用户可以控制结果的输出 所以我想知道如果在其中一个下拉列表中选择了该值 是否可以从其他两个下拉列表中取出该值 例如 如果用户在第一个电影中选
  • Google 地图 v3 信息窗口在地图视口外打开

    如果单击地图视口顶部附近的标记 信息窗口将加载到可视区域之外 并且必须拖动地图才能查看信息窗口内容 理想情况下 我不希望地图自动平移 有没有办法以不同的方向加载信息窗口 例如如果标记位于视口的顶部 则以向下的方向显示信息窗口 不 你不能以不

随机推荐

  • Ubuntu 终端 - 使用 gnu parallel 读取文件夹中所有文件中的行

    我正在尝试计算 Ubuntu 下一个非常大的文件夹中所有文件的行数 这些文件是 gz 文件 我使用 zcat wc l 计算所有文件中的所有行 而且很慢 我想使用多核计算来完成这项任务并发现this https www gnu org so
  • Kotlin数据类:如果我在编译时不知道属性的名称,如何读取属性的值?

    如果属性名称仅在运行时已知 如何读取 Kotlin 数据类实例中的属性值 这是一个函数 用于从给定属性名称的类实例中读取属性 如果未找到属性 则抛出异常 但您可以更改该行为 import kotlin reflect KProperty1
  • 在 ant 任务中使用 eclipse 类路径

    我想使用 Ant 交付 JAR 文件 如何在 Ant 任务中使用 Eclipse 类路径 问候 托比亚斯 Try ant4eclipse http ant4eclipse sourceforge net
  • 带 IF 条件的 PL/SQL 游标

    我的代码中有以下光标 CURSOR cur1 IS SELECT a b c d FROM EMP BEGIN Stored procedure logic END 该游标正在从 EMP 表获取信息 但我需要改变如下 有一个包含键值对的表
  • 如何跳转到 Visual Studio 源代码管理资源管理器中的文件

    我喜欢 Visual Studio 2010 的 PowerCommands 扩展的 解决方案资源管理器 gt 右键单击 gt 打开包含文件夹 功能 我想要相当于 跳转到源代码管理资源管理器中的位置 功能 如何找出给定打开文件或解决方案资源
  • 突出显示 Lisp 表单的 Emacs 模式

    什么是 Emacs 模式或包 它突出显示 Lisp 表单 更改背景颜色 以便您所在的表单具有一种颜色 外部表单具有另一种颜色 外部表单具有另一种颜色 依此类推 你可能想尝试mwe 彩盒 http www foldr org michaelw
  • 环边太长

    在下面的点图中 之间的边handleClick and onSelect in COLOROPTION太长了 如何将其变成紧密循环 如果也这样就更好了DIV离右边有点远COLOROPTION 循环边是option p4 e gt optio
  • WPF 控件的公共类修饰符

    我正在创建 Windows 应用程序和类库 类库包含名为 InsertForm xaml 的 WPF 控件 InsertForm 包含名为 eUserName 的文本框 我使用以下代码来显示 InsertForm 这样就成功了 但我无法访问
  • 如何安装列出的类型?

    我正在使用带有 Typescript 的库 并收到编译器错误 public components chatlogs ts 25 19 错误 TS2304 找不到名称 Handsontable 似乎有一个打字 typings search h
  • 工厂方法设计模式

    据书中所述 工厂模式的本质是 定义一个接口 创建一个对象 但让子类决定使用哪个类 实例化 Factory 方法让类将实例化推迟到 子类 假设我有一个 Creator 类 class Product this is what the Fact
  • 使用 javascript 获取当前月份的天数

    对于我的网站 我试图获取特定功能当前月份的天数 我在网上看到过获取指定月份的天数的示例 但是我需要获取当前月份的天数并找出该月还剩多少天 这是我设法组合的代码 function myFunction var today new Date v
  • 生产/heroku 失败:WHERE a.attrelid = '"schools"'::regclass

    我在本地环境中添加了一个名为schools并且在开发中运行良好 事实上 它甚至在暂存 heroku 中工作得很好 但在生产中却失败了很多rake db migrate抛出以下错误 我什至无法预编译资产 使用 RAILS ENV 生产 访问我
  • 使用 git 或 gitLab 的持续集成来部署 Angular 应用程序

    我希望通过 Apache 服务器上的 bitbucket 持续集成来部署 Angular 5 应用程序 现在因为我是该领域的新手 所以我不知道执行此操作的可能选择 比如我们是否需要将 webpack 与 Jenkins 集成 或者我们是否需
  • Node.js 中的 JSON 对象是全局的吗?

    我似乎找不到 Node js 的 JSON parse 的文档 我只是看到它散落在各种脚本周围 我想知道 它从哪里来 是否有某些回调使其可用 或者它是一个全局对象 谢谢 马特 穆勒 它内置于 V8 中 这是他们的实现的链接 http cod
  • 数据网格复选框自动化

    我有一个带有复选框的数据网格 当我单击单元格时 我希望在选择包含该复选框的单元格时自动选中该复选框 它现在所做的是我需要选择单元格然后单击复选框 这对我们来说非常烦人
  • 如何在 Javascript 中使用 onPageLoad?

    我尝试使用 onPageLoad function alert hi 但这行不通 我需要它作为 Firefox 扩展 请问有什么建议吗 如果你想在普通 JavaScript 中执行此操作 只需使用window onload事件处理程序 wi
  • 如何制作 TortoiseSVN diff .dot 和 .dotx Word 模板文件

    TortoiseSVN 具有区分 Microsoft Word 文档修订版的惊人能力 这显然是通过以下脚本实现的 C Program Files TortoiseSVN Diff Scripts它调用 MS Word 进行比较 而不是使用
  • Rails 3 - 如何通过 order by 从模型中获取行号

    我正在制作一个小应用程序 其中包含排行榜概念 基本上 该模型只是一个player name 和一个current score 我想做的是获取特定玩家的排名 并且考虑到新的分数一直在出现 它需要是动态的 显然 我可以使用 order by 子
  • 如何使用 VBA 编辑电源查询的源?

    我正在尝试使用 VBA 编辑我的查询之一的源 这是我到目前为止所拥有的 Dim mFormula As String mFormula let Source Excel Workbook File Contents wbname null
  • 使用 d3 或 cytoscape 渲染家谱

    我在用 Javascript 生成漂亮的家谱时遇到问题 要求 每个孩子应该连接到树中的两个父母 而不是像某些图中的一个 我希望配偶在树上彼此相邻 相同的垂直位置 我想把节点按世代垂直组织起来 这样你就能一目了然地看到同一年代出生的人 随着时