Still Trying to Draw a Mind Map with Three.js and React
Tags: javascript, react, threejs, mindmap
Published: 2020-12-03 14:36:54

---
title: Still Trying to Draw a Mind Map with Three.js and React
published: true
description: I'm building a social media network and collaboration tool based on mind maps, documenting my work in this series of blog posts
tags: javascript,react,threejs,mindmap
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/iymu6bwbmvg00q71ggxp.png
series: Nuffshell Coding Diary
---

*I'm building a social media network and collaboration tool based on mind maps, documenting my work in this series of blog posts. Follow me if you're interested in what I've learned along the way about building web apps with React, Tailwind CSS, Firebase, Apollo/GraphQL, three.js and TypeScript.*

In the previous [part I](https://dev.to/pahund/drawing-a-mind-map-with-three-js-and-react-nuffshell-coding-diary-part-i-1g6n) and [part II](https://dev.to/pahund/drawing-a-mind-map-with-three-js-and-react-for-real-this-time-nuffshell-coding-diary-part-ii-1n9m), I've found out how to render React components on sprites in three.js and connect them with lines to make up a mind map root node and the first level of child nodes, displayed around the root in a circle.

# Taking It to the Next Level

Today's goal is to draw the child nodes of the level 1 mind map nodes, which I'll call level 2 nodes.

The challenge will be to calculate the X/Y coordinates of those nodes so that they fan out from their parent node while not overlapping each other.

![Mind map with level 2 nodes](https://dev-to-uploads.s3.amazonaws.com/i/pxtsrnjl62y1gvkx7ngo.png)

This sketch shows what I'm trying to achieve. Root node is pink, level 1 nodes are purple and level 2 nodes are blue.

Some considerations:

* while level 1 nodes are arranged in a circle, the level 2 nodes need to be arranged in semicircles, facing away from the direction of their parent node
* it will probably be non-trivial to make it so that there can be any number of level 1 nodes for a parent level 2 node and prevent them from overlapping; I'll need to adjust the radius of the semicircle of the level 2 nodes depending on the number of nodes
* even trickier: making sure that the semicircles of level 2 nodes don't overlap the adjourning level 2 nodes from other parent nodes

# A Bit of Refactoring

Before I proceed with level 2, I do some refactoring:

*[renderMindMap.js](https://codesandbox.io/s/drawing-a-mind-map-with-threejs-and-react-part-vii-ztkbl?file=/src/renderMindMap.js)*

```javascript
function renderMindMap(div) {
  const { scene, renderer, camera } = initializeScene(div);
  const root = data.find((node) => node.parent === undefined);
  const level1 = data.filter((node) => node.parent === root.id);
  root.x = 0;
  root.y = 0;
  root.level = 0;

  await addMindMapNode(scene, root);
  const radius = 2;
  for (let level1index = 0; level1index < level1.length; level1index++) {
    const { x, y } = calculateLevel1Coordinates({
      numberOfNodes: level1.length,
      parent: root,
      radius,
      index: level1index
    });
    const level1node = {
      ...level1[level1index],
      x,
      y,
      level: 1
    };
    await addMindMapNode(scene, level1node);
    addConnection(scene, {
      color: colors.magenta,
      parentNode: root,
      childNode: level1node
    });
  }
  renderer.render(scene, camera);
}
```

I've moved the calculation of the coordinates for the level 1 nodes to a new module.

*[calculateLevel1Coordinates.js](https://codesandbox.io/s/drawing-a-mind-map-with-threejs-and-react-part-vii-ztkbl?file=/src/calculateLevel1Coordinates.js)*

```javascript
function calculateLevel1Coordinates({
  numberOfNodes,
  parent,
  radius,
  index
}) {
  const slice = (2 * Math.PI) / numberOfNodes;
  const angle = slice * index;
  const x = parent.x + radius * Math.cos(angle);
  const y = parent.y + radius * Math.sin(angle);
  return { x, y };
}
```

# Improving the Level 1 Node Layout

In my last post, I complained that the layout of the level 1 nodes doesn't look pleasing to the eye because the distance between the node rectangles varies. Someone gave me the tip to rotate the level 1 nodes by 90 degrees. Currently, the circle of nodes starts to the right of the root node. I subtract 90° from the angle, so that the circle of level 1 nodes starts above the root node, and lo and behold – looks much better already! 

![Level 1 nodes rotated by 90 degrees](https://dev-to-uploads.s3.amazonaws.com/i/yf43jz7zi36r336xpaob.png)

Thanks [PrudiiArca](https://math.stackexchange.com/users/598157/prudiiarca)!

# Adding Level 2 Nodes

Now I'm ready to add the next level of nodes. For now, I'm just copying the code from *calculateLevel1Coordinates.js* to *calculateLevel2Coordinates.js*, knowing full well that this will have to be adjusted, but let's just see how it turns out without any changes.

In *[renderMindMap.js](https://codesandbox.io/s/drawing-a-mind-map-with-threejs-and-react-part-viii-gxphy?file=/src/renderMindMap.js)*, I add another for-loop to add the level 2 nodes to the scene:

```javascript
for (let level1index = 0; level1index < level1.length; level1index++) {
  const { x, y } = calculateLevel1Coordinates({
    numberOfNodes: level1.length,
    parent: root,
    radius,
    index: level1index
  });
  const level1node = { ...level1[level1index], x, y, level: 1 };
  await addMindMapNode(scene, level1node);
  addConnection(scene, {
    color: colors.magenta,
    parentNode: root,
    childNode: level1node
  });
  const level2 = data.filter((node) => node.parent === level1node.id);
  for (let level2index = 0; level2index < level2.length; level2index++) {
    const { x: x2, y: y2 } = calculateLevel2Coordinates({
      numberOfNodes: level2.length,
      parent: level1node,
      radius,
      index: level2index
    });
    const level2node = { ...level2[level2index], x: x2, y: y2, level: 2 };
    await addMindMapNode(scene, level2node);
    addConnection(scene, {
      color: colors.violet,
      parentNode: level1node,
      childNode: level2node
    });
  }
}
``` 

The result looks weird, as expected:

{% codesandbox gxphy %}

# Arranging the Level 2 Nodes in Half Circles

Being bad at math, I had to do a lot of trial and error to figure out how to arrange the level 2 nodes in a half circle, facing away from the parent node. 

Here's what I came up with:

*[calculateLevel2Coordinates.js](https://codesandbox.io/s/drawing-a-mind-map-with-threejs-and-react-step-9-1jp4g?file=/src/calculateLevel2Coordinates.js)*

```javascript
function calculateLevel2Coordinates({
  numberOfNodes,
  parent,
  radius,
  index
}) {
  const slice = Math.PI / (numberOfNodes - 1);
  const angle = slice * index + parent.angle - (90 * Math.PI) / 180;
  const x = parent.x + radius * Math.cos(angle);
  const y = parent.y + radius * Math.sin(angle);
  return { x, y, angle };
}
```

Now the mind map graph looks good:

{% codesandbox 1jp4g %}

# To Be Continued…

Stay tuned for the next episode where I will either:

* turn my code for rendering the mind map into a recursive function to facilitate arbitrary nesting depth
* OR throw most my work so far away and try rendering the nodes with a force graph (another useful suggestion someone made)

We'll see…