【Sigma.js】Reactでネットワークグラフを描画してみる

# はじめに
JavaScriptのネットワークグラフ描画ライブラリである[Sigma.js](https://www.Sigmajs.org/)について学んだことをまとめました。
ネットワークグラフとは、ある「ノード（頂点）」（node）から別のノードに「エッジ（辺）」（edge）が伸びているものの集合体を指します。
    ![Vite___React___TS.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2145496/559bd76a-836c-40e9-a3df-9d15ce74195b.png)

また、今回はReact内で使用することを想定しているため[React Sigma](https://sim51.github.io/react-sigma/)を使用します。

# 使い方
Sigma.jsをReact上で使用する手順
1. ReactアプリをViteなどを用いて作成（ここでは詳細は省略）

    <br>

1. Reactアプリ作成後に以下コマンドをターミナルで実行
    ```
    npm install sigma @react-sigma/core graphology graphology-types lodash @types/lodash
    ```

    <br>
1. 簡単な例として以下のように記述

    ```tsx
    import Graph from "graphology";
    import { SigmaContainer } from "@react-sigma/core";
    import "@react-sigma/core/lib/style.css";
    
    export const DisplayGraph = () => {
      // グラフ生成
      const graph = new Graph();
    
      // ノード追加
      graph.addNode("first", {
        x: 0,
        y: 0,
        size: 10,
        label: "My first node",
        color: "red",
      });
      graph.addNode("second", {
        x: 1,
        y: 1,
        size: 15,
        label: "My second node",
        color: "blue",
      });
    
      // エッジ追加
      graph.addEdge("first", "second", { label: "edge", size: 5, color: "black" });
    
      return (
        <SigmaContainer
          graph={graph}
          style={{ height: "500px", width: "500px" }}
        ></SigmaContainer>
      );
    };
    ```
    または、
    ```tsx
    import Graph from "graphology";
    import { SigmaContainer, useLoadGraph } from "@react-sigma/core";
    import "@react-sigma/core/lib/style.css";
    
    export const LoadGraph = () => {
      // グラフ生成
      const graph = new Graph();
    
      const loadGraph = useLoadGraph();
    
      // ノード追加
      graph.addNode("first", {
        x: 0,
        y: 0,
        size: 10,
        label: "My first node",
        color: "red",
      });
      graph.addNode("second", {
        x: 1,
        y: 1,
        size: 15,
        label: "My second node",
        color: "blue",
      });
    
      // エッジ追加
      graph.addEdge("first", "second", { label: "edge", size: 5, color: "black" });
    
      // グラフ読み込み
      loadGraph(graph);
    
      return null;
    };
    
    export const DisplayGraph = () => {
      return (
        <SigmaContainer style={{ height: "500px", width: "500px" }}>
          <LoadGraph />
        </SigmaContainer>
      );
    };
    ```

    <br>

1. グラフ描画結果
    2つのノードと、それをつなぐエッジが表示される
![Vite___React___TS.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2145496/1eb5586a-0fed-4d36-9586-4a10e0ef8e57.png)

## 注意点

- SigmaContainerを描画するには、以下importが必要
    ```tsx
    import "@react-sigma/core/lib/style.css";
    ```
    <br>
- SigmaContainerにはheightやwidthなどのstyleが必要
    ```tsx
    style={{ height: "500px", width: "500px" }}
    ```

# ノードとエッジのプロパティ
ノードとエッジにはグラフを見やすくするためのプロパティが用意されている。このプロパティには必須や任意のものがある。また、**ノードもエッジも共に任意のプロパティを設定することができます**。
ノードやエッジに対するメソッドは以下を参考。

https://graphology.github.io/

### ノード
| プロパティ | 必須 | 説明 |
|:-:|:-:|:-:|
| id  |  ⚪︎ | edgeで結ぶためのkeyとなる  |
| label  |   | ノード名  |
| x  | ⚪︎  | ノードのx座標  |
| y  | ⚪︎  | ノードのy座標  |
| size  |   | ノードの大きさ  |
| color  |   | ノードの色  |
| image  |   | ノードに画像を付与する  |

### エッジ
| プロパティ | 必須 | 説明 |
|:-:|:-:|:-:|
| id  |  ⚪︎ | addEdgeWithKeyメソッド使用時に必須  |
| source  | ⚪︎  | エッジの始点  |
| target  | ⚪︎  | エッジの終点  |
| label  |   | エッジ名  |
| type  |   | エッジの形状(line, arrowなど)  |
| size  |   | エッジの太さ  |
| color  |   | エッジの色  |


# 自動配置アルゴリズム
ノードはx, y座標を指定することで任意の位置に配置することができます。しかし、全体のノード同士の繋がりを把握できれば良い場合、位置は重要ではないことがあるかと思います。その場合、自動で視覚的に分かりやすい配置になってくれると全体の把握がしやすくなります。
そこで利用できるのが自動配置アルゴリズム「**ForceAtlas2**」です。

https://www.bsimard.com/2018/04/25/graph-viz-with-sigmajs.html#graph-layout-algorithm

上記[サイト](https://www.bsimard.com/2018/04/25/graph-viz-with-sigmajs.html#graph-layout-algorithm)で説明されています。以下の規則に従ってノード同士がもっとも安定する位置に配置されます。

### エッジで繋がっていないノード
エッジで繋がっていないノード同士は反発する
![無題のプレゼンテーション_-_Google_スライド.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2145496/8be4da8e-b670-43b3-aae1-abf30ae32a5f.png)

### エッジで繋がっているノード
エッジで繋がっているノード同士は引き合う
![無題のプレゼンテーション_-_Google_スライド.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2145496/cdc7bbaf-cce8-4b5c-94e6-18459d6ecbd2.png)

## 自動配置アルゴリズムの導入
以下のコマンドを実行
```
npm install @react-sigma/layout-forceatlas2
```

**ForceAtlas2**の効果を確認するため以下のように書き換えます。

```tsx
import Graph from "graphology";
import { SigmaContainer } from "@react-sigma/core";
import "@react-sigma/core/lib/style.css";
import { useWorkerLayoutForceAtlas2 } from "@react-sigma/layout-forceatlas2";
import { useEffect } from "react";

export const Fa2 = () => {
  const { start, kill } = useWorkerLayoutForceAtlas2({
    settings: { slowDown: 10 },
  });

  useEffect(() => {
    // start FA2
    start();

    // kill FA2 on unmount
    return () => {
      kill();
    };
  }, [start, kill]);
  return null;
};

export const DisplayGraph = () => {
  // グラフ生成
  const graph = new Graph();

  // ノード追加
  graph.addNode("1", {
    x: 0,
    y: 0,
    size: 10,
    label: "1",
  });
  graph.addNode("2", {
    x: 1,
    y: 1,
    size: 10,
    label: "2",
  });
  graph.addNode("3", {
    x: 0.8,
    y: 0.4,
    size: 10,
    label: "3",
  });
  graph.addNode("4", {
    x: 0.2,
    y: 0.6,
    size: 10,
    label: "4",
  });
  graph.addNode("5", {
    x: 0.5,
    y: 0.9,
    size: 10,
    label: "5",
  });
  graph.addNode("6", {
    x: 0.3,
    y: 0.8,
    size: 10,
    label: "6",
  });
  graph.addNode("7", {
    x: 0.2,
    y: 0.3,
    size: 10,
    label: "7",
  });

  // エッジ追加
  graph.addEdge("1", "2", { color: "red" });
  graph.addEdge("1", "3", { color: "red" });
  graph.addEdge("1", "4", { color: "red" });
  graph.addEdge("1", "5", { color: "red" });
  graph.addEdge("1", "6", { color: "red" });
  graph.addEdge("1", "7", { color: "red" });
  graph.addEdge("2", "3", { color: "red" });
  graph.addEdge("2", "4", { color: "red" });
  graph.addEdge("2", "5", { color: "red" });
  graph.addEdge("2", "6", { color: "red" });
  graph.addEdge("2", "7", { color: "red" });

  return (
    <SigmaContainer graph={graph} style={{ height: "500px", width: "500px" }}>
      <Fa2 />
    </SigmaContainer>
  );
};
```

ここで重要なのがFa2コンポーネント内の**useWorkerLayoutForceAtlas2**です。このように記述することで、SigmaContainer内のグラフに**ForceAtlas2**が適用されます。

https://sim51.github.io/react-sigma/docs/example/layouts/#worker-layout

https://github.com/sim51/react-sigma/blob/8385cca0409a01a6bfeb108d06828b485e7b8bec/packages/layout-forceatlas2/src/useWorkerLayoutForceAtlas2.ts

Fa2コンポーネントの有無による表示の違いを見てみましょう。（ここではノードのx, y座標を特定の値で指定していますが、ランダムな値でも問題ありません。）

| Fa2コンポーネントなし| Fa2コンポーネントあり |
|:-----------:|:------------:|
| ![Vite___React___TS.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2145496/e2b3dee6-27a4-4883-b2e7-45c0125b2619.png)| ![Vite___React___TS.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2145496/a1bb032a-1493-45b4-b22c-4ecfe04207e2.png)|

Fa2コンポーネントがある場合、**ForceAtlas2**の効果が適用されて各ノードがもっともバランスの取れた位置に配置されていることが確認できます。
**ForceAtlas2**を利用することでノードの関係性が視覚的にわかりやすくなります。

# さらに使いこなす

Sigma.jsを使うことによってネットワークグラフに対して以下の操作ができるようになります。
- ノードを任意の位置へ移動させる
- ノードのクリックイベント（ノードの非表示、詳細情報をハイライトさせるなど）


# 参考

https://www.sigmajs.org/

https://sim51.github.io/react-sigma/

https://github.com/jacomyal/sigma.js/

https://graphology.github.io/

https://www.bsimard.com/2018/04/25/graph-viz-with-sigmajs.html

https://sim51.github.io/react-sigma/docs/example/layouts/#worker-layout

https://zenn.dev/k8shiro/articles/sample-sigma-js