首页 > 解决方案 > 如何在 Vega JS 中实现树节点切换?

问题描述

我正在使用Vega JS构建树形图。一般来说,我的问题如下:

Vega 文档中有一个很好的树形布局示例。如何通过折叠和扩展其节点的能力来扩展它?

更具体地说,让我们考虑一个我在 Vega Editor 中构建的树形图示例。

如果您单击节点,它们将切换(展开或折叠),从而允许您查看树的特定分支。除非您尝试折叠顶级节点(区域)同时保持二级节点(区域)展开,否则此功能可以正常工作。在这种情况下,树将如下所示:

断树截图

发生这种情况是因为我处理这种交互的方式:

  1. 当您单击一个节点时,toggledNode会触发信号,进而触发数据数组toggle中的动作。expandedNodes即通过单击一个节点,我向expandedNodes数组添加或删除该节点(更准确地说,我们添加/删除只有name属性的缩减对象)
  2. 因此expandedNodes,数据包含有关哪些节点被显式扩展的信息。但它不知道这些展开的节点是否在折叠的父节点内。
  3. 然后,为了找出哪些节点实际上是可见的,我使用visibleNodes数据。在那里,我使用filter以下表达式应用变换:!datum.parent || indata('expandedNodes', 'name', datum.parent). 即我只检查一个级别:如果节点的父节点存在于expandedNodes数组中,我认为该节点是可见的。

问题如下:我找不到跨多个级别扩展此功能的任何方法。

可能我可以编写一些钩子来检查 2 或 3 个级别的相同条件,例如:

!datum.parent ||
  indata('expandedNodes', 'name', datum.parent) && 
  indata('expandedNodes', 'name', datum.myCustomFieldWithParentNode.parent) && 
  indata('expandedNodes', 'name', datum.myCustomFieldWithParentNode.myCustomFieldWithParentNode.parent)

但是对于这样一个简单的问题来说,它似乎太复杂了,而且它也不是最终的解决方案。理论上,一棵树可能包含几十个嵌套级别:那该怎么办?

我在 Vega 中发现了一个有用的表达式:treeAncestors。我可以很容易地用 JavaScript 编写一个解决方案,其中我有循环和数组方法,例如.some().every()。但显然 Vega 不支持任何表达式来迭代数组。因此,即使我可以获得具有treeAncestors函数的树节点祖先数组,我也无法用它做任何事情来验证所有祖先是否都已展开。

要么我的方法是错误的,有人可以找到更好的算法来做同样的事情,它不需要迭代数组(dataindata表达式除外)——或者这是 Vega 的当前限制。

标签: javascriptchartstreevega

解决方案


您可以使用 treeAncestors,然后使用展平变换来获取可以查询的数据集。在您的情况下,它看起来像:

{
  "transform": [
    {
      "as": "treeAncestors",
      "type": "formula",
      "expr": "treeAncestors('tree', datum.id, 'root')"
    }
  ],
  "name": "tree-ancestors",
  "source": "tree"
},
{
  "transform": [{"fields": ["treeAncestors"], "type": "flatten"}],
  "name": "tree-ancestors-flatt",
  "source": "tree-ancestors"
},
{
  "transform": [
    {
      "type": "filter",
      "expr": "indata('selected', 'value', datum.treeAncestors.id)"
    }
  ],
  "name": "filtered",
  "source": "tree-ancestors-flatt"
},
{
  "transform": [{"type": "aggregate", "groupby": ["id"]}],
  "name": "filtered-aggregate",
  "source": "filtered"
},
{
  "transform": [
    {
      "type": "filter",
      "expr": "indata('filtered-aggregate', 'id', datum.id) "
    }
  ],
  "name": "filtered-tree",
  "source": "tree"
}

推荐阅读