Skip to content

Latest commit

 

History

History
179 lines (147 loc) · 9.74 KB

README.md

File metadata and controls

179 lines (147 loc) · 9.74 KB

WebGL 3D Graph Visualisations aka 'w3-graph'

Walrus

Walrus graph visualization using WebGL via THREE.js lib. It renders tree graphs in a 3D space within sphere.

Usage:

  1. Create two files labels and links
  2. Import labels using Menu > Import labels
  3. Import links using Menu > Import links

Labels should contain pairs name -> id delimited by /t or space:

NAME1 1
NAME2 2

Links should contain neighbourhood matrix (aka adjacency matrix) delimited by /t or space:

1 2 3 4 5 6
2 7 8 9 10

Make sure there is no new line at the end of your files.

Results:

Example

Example2

Implementation details:

Author: Bplotka.

Currently, algorithm is able to distribute & render thousands of nodes in couple of seconds.

The algorithm includes several steps:

  1. Parsing phase. Parsing input in two .CSV to the js graph structure. First .csv contains label -> id map, Second contains adjacency matrix. That data is simply parsed to data structure based on: chenglou/data-structures. It contains list of nodes. Each node includes two list of edges: outEdges, inEdges and couple of other statistics like:

    node = {
      _outEdges: {},
      _inEdges: {},
      name: _name,
      root: _root,
      depth: 0,
      _id: id,
      obj: null,
      position: new THREE.Vector3(0, 0, 0),
      parentId: null,
      direction: new THREE.Vector3(0, 1, 0),
      subNodes: 0, // Meaningful children.
      edgeLength: 0,
      children: 0,
      deltaRotByAxisDir: 0,
      rotByAxisDir: 0,
      rotByAxisPerpDirA: 0,
      rotByAxisPerpDirB: 0,
      distanceToRoot: 0
    }
    
Storing other graph types than `tree` is currently blocked. Each node can have only one parent.
These statistics are needed for further calculations and filled during `addEdge` function.
Important additional structures are `levels`:
![Level](https://github.com/Bplotka/w3-graph/blob/master/doc/w3-walrus-level.PNG)
Level is a group of all nodes without children which share a single parent. They are aggregated and rendered as as half of 3D sphere. Levels are filled in early parsing stage during `addEdge` function.
Lavel structure is simple:

level = { size: 0, members: [] }

This stage is done in `app/data/graph-parser.js`. Structure is defined in `app/data/data-structures.js`

2. _Statistic phase._ Before we render the tree we need to collect some statistic and store it in graph attributes like:
* `maxWidth`: Maximum tree width - maximum number of children having their children and sharing single parent.
* `avgWidth`: Average tree width - average number of children having their children and sharing single parent.
* `nodesWithChildren`: Number of nodes which have children.
* `maxDistanceToRoot`: Longest path from the root to the leaf.
* `depth`: Depth of the tree.
In this stage we also calculate some node's attribute like `rotation directions`, `distanceToRoot`, `subNodes` (number of children with children of per each node), `edgeLength` (based on number of subNodes: more subNodes, longer edge), `deltaRotByAxisDir` (360 degrees / number of subNodes) and `depth` of each node.
Additionally, we calculate the needed `RADIUS` of a sphere in which we render our tree. This is based on longest path in graph from root.
All data are calculated using [BFS](https://en.wikipedia.org/wiki/Breadth-first_search) in file `app/data/data-structures.js`.

3. _Rendering phase._ This stage calculates position of the nodes and edges in the sphere.

![Level-Axis](https://github.com/Bplotka/w3-graph/blob/master/doc/w3-walrus-level-axis.PNG)

Algorithm steps:
1. Use BFS and foreach node:
  1. Get all children of this node.
  2. If the node has not any children then it is a leaf and was rendered in his parent `level` so continue to i.
    1. If it's root node than render it in the CENTER (defined in `app/render/scene.js`)
    2. If it's a different node get his parentNode.
      1. Copy `direction` from his parent. `direction` is a normalized `Vector3` which specifies direction in which node should be placed.
      2. Calculate normalized perpendicular `Vector3` to `direction`. (Using cross)
      3. Apply initial rotation to node `direction` around axis specified by perpendicular direction. (currently it is 45 degree)
      4. Apply rotation around the parent `direction` axis by `rotByAxisDir` calculated in previous stage to `direction`.
      5. Apply additional rotation around perpendicular direction axis by different angles to `direction` using this algorithm:
        1. For root children:
          * Each fourth child of parent will apply by `rotByAxisPerpDirB` angle. (Currently: -22 degrees)
          * Each fourth + 1 child of parent will apply by 2 * `rotByAxisPerpDirB` angle. (Currently: -44 degrees)
          * Each fourth + 2 child of parent will apply by `rotByAxisPerpDirA` angle. (Currently: 75 degrees)
          * Each fourth + 3 child of parent will apply by 2 * `rotByAxisPerpDirA` angle. (Currently: 150 degrees)
        2. else:
          * Always apply by `rotByAxisPerpDirB` angle. (Currently: -22 degrees)
      6. Sum `rotByAxisDir` with parent `deltaRotByAxisDir`
      7. Copy `position` from his parent.
      8. Move `position` by adding `direction` multiplied by parentNode `edgeLength` calculated in previous stage.
      9. Render vertex knowing `position`, `direction` (if rendering is detailed and includes cubes we need to know the rotation), `sizeNode` and node `name`
      10. Render edge from parent to node.
  3. If it has leaf children then render `level` in specified node `direction`:
    * Check the _rendering level phase_ below
  4. Add children to `nodesToVisis` BFS list
This stage is done in `app/render/model/graph.js`.

4. _Rendering level phase._ This stage is included in _Renderig phase_ but it also quite complex so i have created additional phase for that.

![Level-Algorithm](https://github.com/Bplotka/w3-graph/blob/master/doc/w3-walrus-level-alg.png)

The algorithm goes as follows:

1. Calculate perpendicular `Vector3` to given `direction` of the owner.
2. Calculate area of the square made with all `members` of the `level` with some interval between (all leafs of the owner)
3. Find radius of a circle which covers area calculated in 2
4. Check if radius is not smaller than MIN_LVL_RADIUS (which is sphere radius / 10)
5. Calculate how many sub levels (rings) can be placed within our circle.
6. Calculate relative X angle (How we should move down in 3D sphere around the relative X axis). Divide 120 degrees by number of `subLevels` (We want to have this special `flower` look)
7. Calculate initial `sphereVec` which simply indicates the movement from owner to leaf localization.
8. `numNodesOnSubLvl` = 1 since we have only one leaf on the top.
9. Init `angleRelY` (rotation around relative Y axis). For the first leaf it does not matter. It will indicates on how many degree we should rotate around center of the sphere (360 / `numNodesOnSubLvl`)
10. Foreach `level` `member`:
  1. Copy `position` from `owner`. Compose base position.
  2. If we need to move down (if `numNodesOnSubLvl` <= 0)
  3. Apply a `angleRelX` rotation to `sphereVec` to move down the sphere by perpendicular to direction vector
  4. Add `angleRelX` to `summaricAngleRelX`
  5. Calculate how many nodes can be in the given radius after moving down:
  6. Calculate `subRadius` (sin(`summaricAngleRelX`) * radius)
  7. Calculate circuit length
  8. Calculate how many nodes (leafs) can be placed within given circuit
  9. If it is the last `subLvl` or there is no space for any leaf on `subLvl` or there are less nodes to be placed in next `subLvl` than in current:
    1. Take all leafs which have not been placed yet and place to `numNodesOnSubLvl`.
  10. Create reverse calculation to obtain the needed `angleRelX` from circuit length made by all `numNodesOnSubLvl`. (This is needed to fix the overlapping leafs)
  11. Apply new rotation to the `sphereVec`
  12. Calculate `angleRelY` knowing the number of nodes in `subLvl`
  13. Move `position` using `sphereVec` indicator.
  14. Render vertex in `position` in `direction` of the owner
  15. Render edge from owner to leaf
  16. Apply a `angleRelY` rotation to `sphereVec` (our direction & distance indicator)
  17. Decrement `numNodesOnSubLvl`

This stage is done in `app/render/model/graph.js`

## Next Steps & TODOs:

1. When the input contains large, wide tree, the visualization could be not clear due to many intersections. When the input contains large, deep tree, the `w3-walrus` will handle it gracefully. _This should be fixed_ as a first next step. The ideas to mitigate that:

* Improve distribution of the children using more various angles based on the adjacent nodes. (check  `3 _Rendering phase._ -> i -> b -> b-> e` algorithm step)
* Improve rendering algorithm with conflicts detection. If we firstly prepare the `levels` we will be aware of their size. As a result we can implement the [Knapsack algorithm](https://en.wikipedia.org/wiki/Knapsack_problem) which will distribute the `level` properly without conflicts and intersections. This will take a lot of work though. ((:

2. Walrus could have panel for node information retrieval & editing. Work items:
1. Extend node info in `app/data/data-structures.js`
2. Create UI panel for parameters presentation and editing
3. Exporting to `csv` module

3. Support for other graph formats e.g `GML`, `GRXL`
4. Validation of graph (too many nodes etc)
5. Benchmarking (Max number of nodes)