简体   繁体   中英

How to implement tree nodes toggling in Vega JS?

I'm using Vega JS for building a tree chart. In general, my question is the following:

Vega documentation has a great example of tree layout . How can I extend it with an ability to collapse & expand its nodes?

To be more specific, let's consider an example of tree chart that I'm building here in Vega Editor .

If you click on the nodes, they will toggle (expand or collapse), allowing you to see particular branches of the tree. This feature works fine unless you try to collapse the top-level node (region) while keeping the second-level nodes (districts) expanded. In that case the tree will look like this:

断树截图

It happens because of the way I handle this interaction:

  1. When you click on a node, toggledNode signal is triggered, which in turn triggers toggle action in the expandedNodes data array. Ie by clicking on a node, I add or remove that node to the expandedNodes array (more precisely, we add/remove a reduced object with only name property)
  2. Thus expandedNodes data contains information about which nodes are explicitly expanded . But it doesn't know if those expanded nodes are inside of a collapsed parent node.
  3. Then, to find out which nodes are actually visible, I use visibleNodes data. There I apply filter transform with the following expression: .datum,parent || indata('expandedNodes', 'name'. datum.parent) .datum,parent || indata('expandedNodes', 'name'. datum.parent) . Ie I check only one level up: if the node's parent is present in the expandedNodes array, I consider the node as visible.

The problem is the following: I can't find any way of extending this functionality across multiple levels.

Probably I could write some hooks to check the same condition across 2 or 3 levels, eg:

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

But it seems too complex for such a simple problem, and also it's not a final solution. In theory, a tree may contain dozens of nesting levels: what to do then?

I found one useful expression in Vega: treeAncestors . I could easily write a solution in JavaScript, where I have loops and array methods such as .some() and .every() . But apparently Vega doesn't support any expressions to iterate over an array. So even though I can get an array of tree node ancestors with treeAncestors function, I can't do anything with it to verify that all ancestors are expanded.

Either my approach is wrong, and somebody can find a better algorithm for doing the same, which doesn't require iterating over arrays (except for data and indata expressions) - or it's a current limitation of Vega.

You can use treeAncestors and then use a flatten transform to get a dataset that you can query. In your case it would look something like:

{
  "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"
}

Vega doesn't seem to have a recursive way of solving the problem for your question" hey, if all my parents are expanded, then I am visible as a node ".

You can check indeed conditions for all levels you wish to define.

{
      "type": "filter",
      "expr": "!datum.parent || indata('expandedNodes','name',datum.parent)&&datum.depth==1||(indata('expandedNodes','name',datum.firstParent)&&indata('expandedNodes','name',datum.secondParent)&&datum.depth==2)||(indata('expandedNodes','name',datum.firstParent)&&indata('expandedNodes','name',datum.secondParent)&&indata('expandedNodes','name',datum.thirdParent)&&datum.depth==3)"
}

The code above says to VEGA: " hey check if all of my defined parents are expanded and filter me if any of my parents exist but are not expanded

To see the full solution with your case, please check: spec

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM