フローチャートや組織図など複雑なチャートを描くのは大変です。特に、インタラクティブな操作や編集が必要な場合は、専用のライブラリを使わないと実現が難しいでしょう。

今回はReactアプリの中で使えるReact Flowを試してみました。

Monacaアプリのベース

今回はFramework7 × Reactのテンプレートを使っています。

そして、追加でReact Flowを使うためのライブラリをインストールします。

コマンドラインで以下のように入力します。

npm install reactflow

次に、React Flowを使うためのセットアップです。ライブラリとCSSをインポートする必要があります。以下のようにインポートします。

import ReactFlow from "reactflow";
import "reactflow/dist/style.css";

あとは、ノードとエッジ(線)を定義して、React Flowコンポーネントに渡すだけです。

例えば、以下のようにノードとエッジを定義します。

これを React Flow コンポーネントに渡せば、あっという間にフローチャートが描画されます。

const initialNodes = [
        { id: "1", position: { x: 0, y: 0 }, data: { label: "1" } },
        { id: "2", position: { x: 0, y: 100 }, data: { label: "2" } },
];
const initialEdges = [{ id: "e1-2", source: "1", target: "2" }];

ここで、initialNodesはフローチャートのノード(頂点)を表しています。各ノードには、一意のid、画面上の位置を指定するposition、そしてノードに表示するラベルを含むdataオブジェクトを指定します。

一方、initialEdgesはノード間を結ぶエッジ(線)を表しています。各エッジには、一意のid、エッジの始点となるノードのidを指定するsource、エッジの終点となるノードのidを指定するtargetを指定します。

以下のコードは、React FlowをReactアプリに組み込む例です。

import React from "react";
import { Page } from "framework7-react";
import ReactFlow from "reactflow";
import "reactflow/dist/style.css";

const initialNodes = [
        { id: "1", position: { x: 0, y: 0 }, data: { label: "1" } },
        { id: "2", position: { x: 0, y: 100 }, data: { label: "2" } },
];
const initialEdges = [{ id: "e1-2", source: "1", target: "2" }];

const HomePage = () => {

  return (
  <Page name="home">
    <div style={{ width: "100vw", height: "100vh" }}>
      <ReactFlow nodes={initialNodes} edges={initialEdges} />
    </div>
  </Page>
  );
};

export default HomePage;

このコードでは、まず必要なモジュールをインポートしています。ReactはReactライブラリ自体、PageはFramework7のページコンポーネント、ReactFlowはReact Flowライブラリ、そしてreactflow/dist/style.cssはReact Flowのスタイルシートです。

次に、initialNodesとinitialEdgesを定義しています。これらは、前の例と同じようにノードとエッジの初期設定です。

そして、HomePageコンポーネントを定義しています。このコンポーネントは、Framework7のPageコンポーネントを使用し、その中にdiv要素を配置しています。このdiv要素のスタイルは、幅と高さを100vw(ビューポート幅)と100vh(ビューポート高さ)に設定し、フローチャートが画面全体に表示されるようにしています。

div要素の中には、ReactFlowコンポーネントが配置されており、nodesとedgesのプロパティに、先ほど定義したinitialNodesとinitialEdgesを渡しています。これにより、定義したノードとエッジに基づいてフローチャートが描画されます。

最後に、HomePageコンポーネントをエクスポートしています。これにより、他のファイルからこのコンポーネントを使用できるようになります。

線をつなげる、ノードを動かす

React Flowでは、ノードを動かしたり、ノード同士を線で結んだりするために、useNodesStateuseEdgesState という特別な関数を使います。これらの関数は、ノードと線の状態を管理してくれます。

以下のコードを見てみましょう。

import React, { useCallback } from "react";
import { Page } from "framework7-react";
import ReactFlow, { useNodesState, useEdgesState, addEdge } from "reactflow";

import "reactflow/dist/style.css";

const HomePage = () => {
    const initialNodes = [
        { id: "1", position: { x: 0, y: 0 }, data: { label: "1" } },
        { id: "2", position: { x: 0, y: 100 }, data: { label: "2" } },
    ];
    const initialEdges = [{ id: "e1-2", source: "1", target: "2" }];

    const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
    const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);

    const onConnect = useCallback(
        (params) => setEdges((eds) => addEdge(params, eds)),
        [setEdges],
    );
  return (
  <Page name="home">
    <div style={{ width: "100vw", height: "100vh" }}>
      <ReactFlow
        nodes={nodes}
        edges={edges}
        onNodesChange={onNodesChange}
        onEdgesChange={onEdgesChange}
        onConnect={onConnect}
      />
    </div>
  </Page>
  );
};

export default HomePage;

まず、initialNodesinitialEdgesで、最初のノードと線を定義しています。これは前の例と同じですね。

次に、useNodesStateuseEdgesStateを使って、ノードと線の状態を管理します。これらの関数は、現在のノードと線の状態(nodesedges)、それらを更新するための関数(setNodessetEdges)、そしてノードや線が変更されたときに呼ばれるコールバック関数(onNodesChangeonEdgesChange)を返します。

onConnect関数は、新しい線が引かれたときに呼び出されます。この関数は、新しい線の情報をparamsとして受け取ります。

setEdges関数は、現在の線の状態を更新するための関数です。この関数は、現在の線の状態edsを受け取ります。

onConnect関数の中では、setEdges関数を使って、現在の線の状態edsに新しい線の情報paramsを追加しています。これは、addEdge関数を使って行われます。

最後に、ReactFlowコンポーネントに、nodesedgesonNodesChangeonEdgesChangeonConnectを渡しています。これにより、React Flowは現在のノードと線の状態を知ることができ、ノードや線が変更されたときに適切に反応することができます。

このようにして、React Flowでノードを動かしたり、線で結んだりすることができるのです。少しコードが複雑に見えるかもしれませんが、基本的なアイデアは簡単です。ノードと線の状態を管理し、それらが変更されたときに適切に反応するだけなのです。

より複雑な例

ノードとエッジを追加すれば、より複雑なフローチャートを描くことができます。以下のコードを見てみましょう。

import React, { useCallback } from "react";
import { Page } from "framework7-react";
import ReactFlow, { useNodesState, useEdgesState, addEdge } from "reactflow";
import "reactflow/dist/style.css";

const initialNodes = [
  {
    id: "horizontal-1",
    sourcePosition: "right",
    type: "input",
    data: { label: "Input" },
    position: { x: 0, y: 80 },
  },
  {
    id: "horizontal-2",
    sourcePosition: "right",
    targetPosition: "left",
    data: { label: "A Node" },
    position: { x: 250, y: 0 },
  },
  {
    id: "horizontal-3",
    sourcePosition: "right",
    targetPosition: "left",
    data: { label: "Node 3" },
    position: { x: 250, y: 160 },
  },
  {
    id: "horizontal-4",
    sourcePosition: "right",
    targetPosition: "left",
    data: { label: "Node 4" },
    position: { x: 500, y: 0 },
  },
  {
    id: "horizontal-5",
    sourcePosition: "top",
    targetPosition: "bottom",
    data: { label: "Node 5" },
    position: { x: 500, y: 100 },
  },
  {
    id: "horizontal-6",
    sourcePosition: "bottom",
    targetPosition: "top",
    data: { label: "Node 6" },
    position: { x: 500, y: 230 },
  },
  {
    id: "horizontal-7",
    sourcePosition: "right",
    targetPosition: "left",
    data: { label: "Node 7" },
    position: { x: 750, y: 50 },
  },
  {
    id: "horizontal-8",
    sourcePosition: "right",
    targetPosition: "left",
    data: { label: "Node 8" },
    position: { x: 750, y: 300 },
  },
];

const initialEdges = [
  {
    id: "horizontal-e1-2",
    source: "horizontal-1",
    type: "smoothstep",
    target: "horizontal-2",
    animated: true,
  },
  {
    id: "horizontal-e1-3",
    source: "horizontal-1",
    type: "smoothstep",
    target: "horizontal-3",
    animated: true,
  },
  {
    id: "horizontal-e1-4",
    source: "horizontal-2",
    type: "smoothstep",
    target: "horizontal-4",
    label: "edge label",
  },
  {
    id: "horizontal-e3-5",
    source: "horizontal-3",
    type: "smoothstep",
    target: "horizontal-5",
    animated: true,
  },
  {
    id: "horizontal-e3-6",
    source: "horizontal-3",
    type: "smoothstep",
    target: "horizontal-6",
    animated: true,
  },
  {
    id: "horizontal-e5-7",
    source: "horizontal-5",
    type: "smoothstep",
    target: "horizontal-7",
    animated: true,
  },
  {
    id: "horizontal-e6-8",
    source: "horizontal-6",
    type: "smoothstep",
    target: "horizontal-8",
    animated: true,
  },
];

const HomePage = () => {
    const [nodes, _, onNodesChange] = useNodesState(initialNodes);
    const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
    const onConnect = useCallback((params) => setEdges((els) => addEdge(params, els)), []);

  return (
    <Page name="home">
        <ReactFlow
        nodes={nodes}
        edges={edges}
        onNodesChange={onNodesChange}
        onEdgesChange={onEdgesChange}
        onConnect={onConnect}
        fitView
        attributionPosition="bottom-left"
        ></ReactFlow>
    </Page>
  );
};

export default HomePage;

このコードでは、まずinitialNodesinitialEdgesで複数のノードとエッジを定義しています。各ノードはidsourcePosition(エッジの出る位置)、targetPosition(エッジの入る位置)、data(ノードに表示するデータ)、position(ノードの位置)などのプロパティを持っています。

エッジはidsource(始点のノードID)、target(終点のノードID)、animated(アニメーションの有無)などのプロパティを持っています。

HomePageコンポーネントの中では、前の例と同様にuseNodesStateuseEdgesStateを使ってノードとエッジの状態を管理しています。onConnect関数も同様に定義されています。

ReactFlowコンポーネントには、nodesedgesonNodesChangeonEdgesChangeonConnectに加えて、fitView(全てのノードが画面内に収まるようにズームとパンを調整する)とattributionPosition(帰属情報の位置)が指定されています。

このコードを実行すると、initialNodesinitialEdgesで定義されたノードとエッジを持つ、より複雑なフローチャートが表示されます。ユーザーはノードをドラッグしたり、ノード間に新しいエッジを引いたりすることができます。

このように、React Flowを使うと、複数のノードとエッジを定義することで、より複雑で対話的なフローチャートを簡単に作成することができるのです。

他の機能

React Flowには、他にもミニマップの表示や拡大・縮小などのコントロールを表示する機能があります。

fitViewプロパティを追加すると、フローチャートが画面内に収まるように自動的にサイズが調整されます。これは、フローチャートが画面からはみ出ることを防ぎ、常に全体が見えるようにしてくれます。

これらの機能を使うことで、フローチャートをより見やすく、操作しやすくすることができます。

import ReactFlow, {
  addEdge,
  MiniMap,
  Controls,
  Background,
  useNodesState,
  useEdgesState,
} from "reactflow";

// 描画する際の指定
<ReactFlow
    nodes={nodes}
    edges={edgesWithUpdatedTypes}
    onNodesChange={onNodesChange}
    onEdgesChange={onEdgesChange}
    onConnect={onConnect}
    onInit={onInit}
    fitView
    attributionPosition="top-right"
    nodeTypes={nodeTypes}
>
    <MiniMap style={minimapStyle} zoomable pannable />
    <Controls />
    <Background color="#aaa" gap={16} />
</ReactFlow>

まとめ

React Flowを使うと、複雑な表示のフローチャートをコードで簡単に描けます。さらにドラッグ&ドロップやノード同士の連結なども管理が容易です。編集結果をデータベースに保存すれば、ユーザーが自由にフローチャートを作成・共有できるアプリケーションも作れるでしょう。

ぜひReact Flowを使って、MonacaアプリのUIを充実させてみてください。