はい、承知いたしました。React Flowを使ったインタラクティブなフロー図作成について、初心者向けの詳細な解説記事を作成します。約5000語を目指し、セットアップから基本機能、インタラクション、カスタマイズまでを網羅的に説明します。
【初心者向け】React Flowでインタラクティブなフロー図を作成
はじめに:なぜReact Flowなのか?
Webアプリケーションでフロー図やノードベースのエディタを作成したいと思ったことはありませんか? プロセスを視覚化したり、データフローを設計したり、複雑なシステムの関係性を示したりする場合、インタラクティブなフロー図は非常に強力なツールとなります。
しかし、このような図をゼロから実装するのは非常に骨労がかかります。ノードの配置、エッジの描画、ドラッグ操作、ズーム、ミニマップ、さらにはコネクションの管理… これらすべてを自分で実装しようとすると、膨大な時間と複雑なコードが必要になります。
そこで登場するのが React Flow です。
React Flowは、Reactアプリケーション内で高品質でインタラクティブなノードベースのダイアグラム(フロー図、ノードエディタ、グラフなど)を簡単に構築するためのライブラリです。以下の特徴を持っています。
- Reactフレンドリー: Reactのコンポーネントモデルに自然に統合されます。
- 高性能: 大量のノードやエッジを扱ってもスムーズな操作を提供します。
- カスタマイズ可能: ノード、エッジ、ハンドルなどを自由にカスタマイズできます。
- インタラクティブ: ドラッグ、ズーム、パン(移動)、選択、接続などの操作がデフォルトで組み込まれています。
- 機能豊富: ミニマップ、コントロールパネル、背景パターンなどの便利機能も提供します。
この記事では、React Flowを使って基本的なフロー図を作成し、それをインタラクティブにする方法を、プログラミング初心者の方にも分かりやすくステップバイステップで解説します。Reactの基本的な知識(コンポーネント、State, Props, Hooksなど)があることを前提としますが、React Flowについてはゼロから学べます。
この記事を読み終える頃には、あなたも自分だけのインタラクティブなフロー図をReactで作成できるようになっているでしょう。
さあ、React Flowの世界へ飛び込みましょう!
準備:プロジェクトのセットアップ
React Flowを使うためには、まずReactプロジェクトが必要です。ここでは、現代的なReact開発の標準となっているViteを使ったプロジェクト作成方法と、より伝統的なcreate-react-appを使った方法の両方を紹介します。どちらかお好きな方を選んでください。
Node.jsのインストール
Reactプロジェクトを開発するためには、Node.jsが必要です。まだインストールしていない場合は、以下の公式サイトからダウンロードしてインストールしてください。
インストールが完了したら、ターミナルやコマンドプロンプトで以下のコマンドを実行し、バージョンが表示されることを確認してください。
bash
node -v
npm -v
プロジェクトの作成(Viteの場合)
Viteは、高速な開発サーバーとビルドツールを提供するモダンなフロントエンドビルドツールです。現在、Reactプロジェクトの作成によく使われています。
ターミナルで、プロジェクトを作成したいディレクトリに移動し、以下のコマンドを実行します。
bash
npm create vite@latest my-react-flow-app --template react
my-react-flow-appはあなたのプロジェクト名です。好きな名前に変更してください。--template reactはReactテンプレートを使用することを指定しています。
コマンドを実行すると、いくつかの質問が表示される場合があります。通常はデフォルトのオプション(JavaScript + SWC など)を選択すれば問題ありません。
プロジェクトが作成されたら、そのディレクトリに移動し、必要なパッケージをインストールします。
bash
cd my-react-flow-app
npm install
プロジェクトの作成(create-react-appの場合)
create-react-appは、React公式が提供していたプロジェクト作成ツールです。現在ではメンテナンスモードに入っていますが、多くの既存プロジェクトで使われています。
ターミナルで、プロジェクトを作成したいディレクトリに移動し、以下のコマンドを実行します。
bash
npx create-react-app my-react-flow-app
my-react-flow-appはあなたのプロジェクト名です。好きな名前に変更してください。
プロジェクトが作成されたら、そのディレクトリに移動します。
bash
cd my-react-flow-app
React Flowのインストール
プロジェクトディレクトリに移動したら、React Flowライブラリをインストールします。
“`bash
Viteまたはcreate-react-appのプロジェクトディレクトリ内で実行
npm install reactflow
“`
これで、React Flowを使用するための準備が整いました。
最初のReact Flowを描画してみよう!
準備ができたので、React Flowのコンポーネントを使って簡単なフロー図を表示させてみましょう。まずはノードとエッジをいくつか定義し、それらを表示するところから始めます。
React Flowのコアとなるコンポーネントは <ReactFlow> です。このコンポーネントの中に、フロー図を描画します。
React Flowは、ノード(Nodes) と エッジ(Edges) という2つの主要なデータの配列を受け取って描画を行います。
- ノード: フロー図の各要素(箱や図形)です。一意のID、位置、表示するデータ(ラベルなど)を持ちます。
- エッジ: ノード間を結ぶ線です。一意のID、接続元のノードID (
source)、接続先のノードID (target) を持ちます。
早速コードを見ていきましょう。プロジェクトの src ディレクトリにある App.js (または App.jsx) ファイルを編集します。既存のコードは削除して、以下のコードに置き換えてみてください。
“`jsx
// src/App.jsx または src/App.js
import React from ‘react’;
import ReactFlow, { Controls, MiniMap, Background, ReactFlowProvider } from ‘reactflow’;
// React Flowのデフォルトスタイルをインポート
// node_modules/reactflow/dist/style.css のパスは環境によって異なる可能性があります
// もしエラーが出る場合は、node_modules/reactflow/dist/ に style.css があるか確認し、
// なければ style.min.css を試すか、reactflowのドキュメントで正しいパスを確認してください。
// 多くの場合、以下で動作します。
import ‘reactflow/dist/style.css’;
// 最初のノードとエッジを定義します
// ノードはオブジェクトの配列です。
const initialNodes = [
{
id: ‘1’, // 一意のID
type: ‘input’, // 特殊なタイプのノード (入力ノード)
data: { label: ‘Node 1’ }, // ノードに表示するデータ
position: { x: 250, y: 5 }, // ノードの表示位置
},
{
id: ‘2’,
data: { label: ‘Node 2’ },
position: { x: 100, y: 100 },
},
{
id: ‘3’,
type: ‘output’, // 特殊なタイプのノード (出力ノード)
data: { label: ‘Node 3’ },
position: { x: 400, y: 100 },
},
{
id: ‘4’,
data: { label: ‘Node 4’ },
position: { x: 400, y: 200 },
},
];
// エッジはオブジェクトの配列です。
const initialEdges = [
{ id: ‘e1-2’, source: ‘1’, target: ‘2’, animated: true }, // sourceノードのIDからtargetノードのIDへエッジを引く
{ id: ‘e1-3’, source: ‘1’, target: ‘3’ },
{ id: ‘e3-4’, source: ‘3’, target: ‘4’, label: ‘エッジラベル’ }, // エッジにラベルを表示することも可能
];
function App() {
// 実際のアプリケーションでは、これらのノードとエッジはStateとして管理し、
// ユーザー操作(ドラッグ、接続など)に応じて更新される必要があります。
// 今はまだ静的なデータとして描画します。
const nodes = initialNodes;
const edges = initialEdges;
return (
// ReactFlowProviderでReactFlowコンポーネントをラップします。
// これは、React Flowの内部状態管理のために必要です。
{/ フロー図の操作に便利なUIコンポーネント /}
);
}
export default App;
“`
スタイルの設定
上記のコードでは、React FlowのCSSをインポートしています。また、<ReactFlow> コンポーネントを表示するために、親要素(ここでは div)に高さが必要です。上記の例では height: '90vh' としていますが、実際のアプリケーションでは親コンテナのサイズに合わせて調整してください。
実行してみよう
コードを保存したら、ターミナルでプロジェクトのルートディレクトリから開発サーバーを起動します。
“`bash
Viteの場合
npm run dev
create-react-appの場合
npm start
“`
ブラウザが開き、定義したノードとエッジが表示されたフロー図が見えるはずです!
コードの解説
import ReactFlow, { ... } from 'reactflow';: React Flowのメインコンポーネントと便利機能をインポートしています。import 'reactflow/dist/style.css';: React Flowの基本的なスタイルを読み込んでいます。これがないと、ノードやエッジの見た目が崩れます。initialNodesとinitialEdges: 表示したいノードとエッジのデータを配列として定義しています。各オブジェクトの構造はコード内のコメントを参照してください。type: 'input'やtype: 'output'は、React Flowにデフォルトで用意されている特別なノードタイプです。<ReactFlowProvider>: React Flowの内部でContext APIを使って状態を共有するために必要です。<ReactFlow>コンポーネネントは必ずこのプロバイダーの中で使用する必要があります。通常、アプリのルートに近い場所で一度だけ使用します。<div style={{ width: '100%', height: '90vh' }}>:<ReactFlow>は親要素のサイズに合わせて表示されます。必ず親要素にサイズ(widthとheight)を指定してください。<ReactFlow nodes={nodes} edges={edges} fitView>: これがReact Flowのメインコンポーネントです。nodes: 表示するノードデータの配列を渡します。edges: 表示するエッジデータの配列を渡します。fitView: このプロパティを渡すと、初回レンダリング時に全てのノードとエッジがビューポート内に収まるように自動的にズームとパンが調整されます。
<Controls />: フロー図の右上に表示されるコントロールパネルです。ズームイン/アウト、フィットビュー、ロックなどのボタンがあります。<MiniMap />: フロー図の右下に表示されるミニマップです。全体の構造を把握し、簡単に移動できます。<Background variant="dots" gap={12} size={1} />: フロー図の背景にパターンを表示します。variantで種類(dots, lines, cross)、gapでパターンの間隔、sizeでパターンのサイズを指定できます。
これで、React Flowを使った基本的なフロー図の表示ができました。まだインタラクティブではありませんが、次はこれを操作できるようにしていきます。
フロー図をインタラクティブにする:State管理とイベントハンドリング
フロー図をインタラクティブにするためには、ユーザー操作(ノードのドラッグ、エッジの接続、クリックなど)に応じてノードやエッジの状態を更新する必要があります。
React Flowでは、ノードとエッジのデータはReactのStateとして管理するのが一般的です。ユーザーが何か操作を行うと、React Flowからイベントが発火され、そのイベントハンドラ内でStateを更新することで、描画されているフロー図が変化します。
React Flow v10以降では、ノードとエッジのState管理を簡単にするために useNodesState および useEdgesState というカスタムフックが提供されています。これらを使うと、ドラッグやズームといったReact Flowが内部で行う変更を自動的にStateに反映させることができます。
App.js (または App.jsx) を以下のように修正してみましょう。
“`jsx
// src/App.jsx または src/App.js
import React, { useCallback } from ‘react’;
import ReactFlow, {
Controls,
MiniMap,
Background,
ReactFlowProvider,
useNodesState, // <– useNodesStateをインポート
useEdgesState, // <– useEdgesStateをインポート
addEdge, // <– エッジを追加する際に便利なユーティリティ関数
} from ‘reactflow’;
import ‘reactflow/dist/style.css’;
const initialNodes = [
{ id: ‘1’, type: ‘input’, data: { label: ‘ドラッグしてみてください’ }, position: { x: 250, y: 5 } },
{ id: ‘2’, data: { label: ‘ここに接続できます’ }, position: { x: 100, y: 100 } },
{ id: ‘3’, type: ‘output’, data: { label: ‘ここからも接続できます’ }, position: { x: 400, y: 100 } },
{ id: ‘4’, data: { label: ‘Node 4’ }, position: { x: 400, y: 200 } },
];
const initialEdges = [
{ id: ‘e1-2’, source: ‘1’, target: ‘2’, animated: true },
{ id: ‘e1-3’, source: ‘1’, target: ‘3’ },
{ id: ‘e3-4’, source: ‘3’, target: ‘4’, label: ‘エッジラベル’ },
];
function App() {
// useNodesStateとuseEdgesStateを使ってノードとエッジをStateとして管理
// これらは [state, setState, onChanges] のタプルを返します
// state: 現在のノード/エッジの配列
// setState: ノード/エッジの状態を更新する関数 (useNodesState/useEdgesStateに最適化されている)
// onChanges: React Flowが検出した変更を適用するためのハンドラ関数
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
// ノード間が接続されたときに呼ばれるコールバック関数
// connectionオブジェクトには、接続元のノードID (source) と接続先のノードID (target) が含まれます
const onConnect = useCallback((connection) => {
// addEdgeユーティリティを使って新しいエッジオブジェクトを作成し、既存のエッジ配列に追加
setEdges((eds) => addEdge(connection, eds));
}, [setEdges]); // setEdgesが変更されないことを保証するために依存配列に含める
return (
nodes={nodes}
edges={edges}
// <– Stateの変更をハンドリングする関数をpropsとして渡す
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect} // <– エッジが接続されたときに呼ばれるハンドラ
fitView
>
);
}
export default App;
“`
コードの解説
import { ..., useNodesState, useEdgesState, addEdge } from 'reactflow';:useNodesState,useEdgesState,addEdgeを新たにインポートしました。const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);:useNodesStateフックは、初期ノード配列initialNodesを受け取り、以下の3つを返します。nodes: 現在のノード配列(ReactのState)。setNodes: ノード配列を更新するための関数。onNodesChange: React Flowがノードの変更(ドラッグによる位置変更、選択状態の変更など)を検出したときに呼び出すべきハンドラ関数。
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);:useEdgesStateも同様に、エッジ配列のState管理を行います。onNodesChange={onNodesChange}およびonEdgesChange={onEdgesChange}: これらのプロパティにuseNodesStateとuseEdgesStateから返されたハンドラ関数を渡すことで、React Flowが自動的にStateを更新してくれるようになります。例えば、ユーザーがノードをドラッグすると、React Flowは新しい位置を計算し、onNodesChangeを呼び出し、useNodesStateがその変更を受け取ってnodesStateを更新し、再レンダリングが行われます。onConnect = useCallback((connection) => { ... }, [setEdges]);: これは、ユーザーがノード間でエッジを接続(ドラッグ&ドロップ)したときにReact Flowが呼び出す関数です。connectionオブジェクトには、接続元のノードID (source)、接続先のノードID (target)、および使用されたハンドルの情報などが含まれています。addEdge(connection, eds)は、新しいconnectionオブジェクトからエッジオブジェクトを作成し、既存のエッジ配列edsに追加して新しい配列を返すユーティリティ関数です。setEdges(...)を使ってStateを更新することで、新しいエッジがフロー図に描画されます。useCallbackは、この関数が不要に再作成されるのを防ぐためのReactフックです。パフォーマンスのためにイベントハンドラによく使われます。
実行してみよう
開発サーバーが起動していれば、ブラウザで確認してみましょう。
- ノードのドラッグ: ノードをクリックしてドラッグしてみてください。ノードがスムーズに移動し、ドラッグを終えると新しい位置に固定されるはずです。
- エッジの接続:
- ノードの端にある小さい丸(これが「ハンドル」です)にマウスカーソルを合わせると、カーソルが変化します。
- そのハンドルをクリックし、ドラッグして他のノードのハンドルまで持っていき、ドロップしてみてください。
- 新しいエッジが自動的に描画されるはずです!
これで、基本的なインタラクション機能を備えたフロー図が完成しました。
その他のインタラクションイベント
React Flowは、様々なユーザー操作に対してイベントハンドラを提供しています。主なものを紹介します。
onNodeClick: ノードがクリックされたときに呼ばれます。クリックされたノードのオブジェクトを受け取ります。onEdgeClick: エッジがクリックされたときに呼ばれます。クリックされたエッジのオブジェクトを受け取ります。onNodeDoubleClick,onEdgeDoubleClick: それぞれノード、エッジがダブルクリックされたときに呼ばれます。onPaneClick: フロー図の背景部分(ノードやエッジがない場所)がクリックされたときに呼ばれます。onPaneReady: React Flowが初期化されて描画準備ができたときに呼ばれます。onMoveStart,onMove,onMoveEnd: ユーザーがパン操作(背景のドラッグ)を開始/実行中/終了したときに呼ばれます。onNodeDragStart,onNodeDrag,onNodeDragStop: ユーザーがノードのドラッグを開始/実行中/終了したときに呼ばれます。
これらのイベントハンドラを <ReactFlow> コンポーネントのプロパティとして渡すことで、様々なカスタムアクションを実行できます。
例えば、ノードをクリックしたときにそのノードの情報をコンソールに表示するには、以下のようにします。
``jsxクリックされたノード: ${node.data.label} (ID: ${node.id})`);
// App.jsx の ReactFlow コンポーネント内に追加
<ReactFlow
// ... 他のprops ...
onNodeClick={(event, node) => {
// event: 元のクリックイベント
// node: クリックされたノードオブジェクト
console.log('ノードがクリックされました:', node);
alert(
}}
{/ … Controls, MiniMap, Background … /}
“`
onNodeClick のハンドラ関数は、第一引数にイベントオブジェクト、第二引数にクリックされたノードオブジェクトを受け取ります。onEdgeClick も同様に、第一引数にイベントオブジェクト、第二引数にクリックされたエッジオブジェクトを受け取ります。
これらのイベントを活用することで、ノードのプロパティ編集画面を表示したり、ノードの削除機能を実装したり、様々なインタラクティブな機能を追加できます。
ノードとエッジの追加/削除
インタラクティブなフロー図では、既存の要素を操作するだけでなく、新しい要素を追加したり、不要な要素を削除したりできると便利です。
これは、ノードとエッジのState (nodes および edges) を操作することで実現できます。
ノードの追加
例えば、ボタンをクリックしたら新しいノードを追加する機能を考えます。
App.jsx にボタンと、ノード追加のロジックを追加します。新しいノードのIDはユニークである必要があります。簡単な例では、タイムスタンプやカウンタを使うことができます。
“`jsx
// App.jsx の ReactFlowProvider の中にボタンを追加
“`
これで、「ノードを追加」ボタンをクリックするたびに、ランダムな位置に新しいノードが表示されるようになります。IDの生成方法は、実際のアプリケーションの要件に合わせて適切に設計してください(例: UUIDライブラリを使うなど)。
ノードの削除
ノードを削除するには、State (nodes) からそのノードを除外した新しい配列を作成し、setNodes で更新します。例えば、ノードをクリックしたときに確認ダイアログを表示し、OKなら削除する、といった実装が考えられます。
先ほど追加した onNodeClick ハンドラを修正してみましょう。
``jsx${node.data.label} を削除しますか?`);
// App.jsx の ReactFlow コンポーネント内
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
// onNodeClick ハンドラを修正
onNodeClick={(event, node) => {
// Ctrl (Cmd) キーを押しながらクリックした場合に削除
if (event.ctrlKey || event.metaKey) {
const confirmDelete = window.confirm(
if (confirmDelete) {
// 削除するノードのID以外のノードで新しい配列を作成
setNodes((nds) => nds.filter((n) => n.id !== node.id));
// そのノードに関連するエッジも削除する必要がある場合
// 削除するノードを source または target に持つエッジを除外
setEdges((eds) => eds.filter((e) => e.source !== node.id && e.target !== node.id));
}
} else {
// 通常クリック時の処理 (例: 情報表示)
console.log(‘ノードがクリックされました:’, node);
}
}}
fitView
{/ … Controls, MiniMap, Background … /}
“`
これで、Ctrl (または Cmd) キーを押しながらノードをクリックすると、削除の確認が表示され、OKを選択するとノードとそのノードに接続されていたエッジが削除されます。
エッジの削除
エッジも同様に、State (edges) から対象のエッジを除外することで削除できます。例えば、エッジをクリックしたときに削除する機能を考えます。
onEdgeClick ハンドラを追加します。
``jsxエッジ (ID: ${edge.id}) を削除しますか?`);
// App.jsx の ReactFlow コンポーネント内に追加
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
onNodeClick={(event, node) => { /* ... ノード削除ロジック ... */ }}
// onEdgeClick ハンドラを追加
onEdgeClick={(event, edge) => {
// Alt (Option) キーを押しながらクリックした場合に削除
if (event.altKey) {
const confirmDelete = window.confirm(
if (confirmDelete) {
// 削除するエッジのID以外のエッジで新しい配列を作成
setEdges((eds) => eds.filter((e) => e.id !== edge.id));
}
} else {
// 通常クリック時の処理
console.log(‘エッジがクリックされました:’, edge);
}
}}
fitView
{/ … Controls, MiniMap, Background … /}
“`
これで、Alt (または Option) キーを押しながらエッジをクリックすると、削除の確認が表示され、OKを選択するとエッジが削除されます。
重要なのは、ノードやエッジの状態変更は、直接DOMを操作するのではなく、ReactのState (nodes, edges) を更新することで行う という点です。React FlowはStateの変更を検知して、適切にUIを再レンダリングしてくれます。setNodes や setEdges を使う際は、元の配列を直接変更するのではなく、concat や filter のように新しい配列を返すメソッドを使うか、スプレッド構文 ([...]) を使って配列のコピーを作成するように注意してください(Stateの不変性)。
ノードをカスタマイズする
React Flowのデフォルトノードはシンプルですが、実際のアプリケーションでは独自の見た目やインタラクションを持つノードが必要になることがほとんどです。React Flowでは、カスタムReactコンポーネントを使ってノードの外観と動作を完全にカスタマイズできます。
カスタムノードを作成する手順は以下の通りです。
- カスタムノードコンポーネントを作成: Reactの関数コンポーネントとして、ノードの見た目を定義します。このコンポーネントはReact Flowからいくつかのプロパティを受け取ります。
<Handle>コンポーネントを配置: エッジを接続するための「ハンドル」をカスタムノード内に配置します。- React Flowにカスタムノードを登録:
<ReactFlow>コンポーネントのnodeTypesプロパティを使って、作成したカスタムノードコンポーネントを登録します。 - ノードデータでカスタムタイプを指定: 表示したいノードデータで、登録したカスタムノードのタイプを指定します。
早速例を見てみましょう。簡単なメッセージを表示するカスタムノードを作成します。
まず、新しいファイル CustomNode.jsx (または CustomNode.js) を作成します。
“`jsx
// src/CustomNode.jsx
import React, { memo } from ‘react’;
import { Handle, Position } from ‘reactflow’;
// React Flowはカスタムノードコンポーネントにいくつかのpropsを渡します
// data: ノードデータ (initialNodes配列で定義したdataオブジェクト)
// isConnectable: このノードが現在接続可能かどうかのフラグ
// その他のpropsも渡されますが、多くの場合dataとisConnectableがあれば十分です
const CustomNode = ({ data, isConnectable }) => {
return (
{/ ノードに表示する内容。ここではdata.labelを使用 /}
{/ dataオブジェクトにdescriptionなど他のプロパティがあれば表示可能 /}
{data.description &&
}
{/* エッジの接続ハンドル */}
{/* type: 'source' はこのハンドルからエッジが出ることを示す */}
{/* position: 'bottom' はノードの下辺にハンドルを配置することを示す */}
{/* id: 複数のソースハンドルがある場合に必要 */}
{/* isConnectable: このハンドルが現在接続可能かどうかのフラグ (親ノードのpropsと同じ) */}
<Handle
type="source"
position={Position.Bottom}
id="a" // 複数のソースハンドルがある場合、IDが必要
isConnectable={isConnectable}
style={{ background: '#555' }}
/>
{/* type: 'target' はこのハンドルにエッジが来ることを示す */}
{/* position: 'top' はノードの上辺にハンドルを配置することを示す */}
<Handle
type="target"
position={Position.Top}
id="b" // 複数のターゲットハンドルがある場合、IDが必要
isConnectable={isConnectable}
style={{ background: '#555' }}
/>
{/* 例: 左側に別のターゲットハンドル */}
<Handle
type="target"
position={Position.Left}
id="c"
isConnectable={isConnectable}
style={{ background: '#555' }}
/>
</div>
);
};
// memo化することで、propsが変更されない限り再レンダリングを防ぎパフォーマンスを向上
export default memo(CustomNode);
“`
ハンドルについて
<Handle> コンポーネントは、React Flowにおいてエッジの接続ポイントとなる非常に重要な要素です。
type:'source'(エッジの始点) または'target'(エッジの終点) を指定します。position: ハンドルをノードのどの辺に配置するかを'top','bottom','left','right'から指定します。id: 同じtypeで複数のハンドルを持つ場合、それぞれにユニークなIDを付ける必要があります。これにより、どのハンドルが接続されたかをonConnectイベントなどで区別できます。isConnectable: ハンドルが現在接続可能であるか(別のハンドルからドラッグしてきたエッジをドロップできるか)を指定します。通常、親ノードコンポーネントから渡されるisConnectablepropsをそのまま渡せば十分ですが、特定の条件で接続を許可/禁止したい場合は独自のロジックをここに適用できます。isValidConnection: これは<Handle>のプロパティとして関数を渡すもので、より高度な接続バリデーションを行います。例えば、「このタイプのノードからは、このタイプのノードにしか接続できない」といったルールを定義できます。関数は({ target, targetHandle, source, sourceHandle }) => booleanのシグネチャを持ち、接続が有効ならtrueを返します。style: ハンドルの見た目をインラインスタイルでカスタマイズできます。
重要な注意点: カスタムノードコンポーネントのルート要素(上記の例では div)には、CSSで position: relative; を指定する必要があります。これは、<Handle> コンポーネントが絶対配置 (position: absolute;) で配置されるため、親要素を基準にするためです。上記の例ではインラインスタイルで指定していますが、実際のCSSファイルに記述するのが一般的です。
カスタムノードの登録と使用
次に、作成した CustomNode を App.jsx でインポートし、React Flowに登録します。
“`jsx
// src/App.jsx または src/App.js
import React, { useCallback } from ‘react’;
import ReactFlow, {
Controls,
MiniMap,
Background,
ReactFlowProvider,
useNodesState,
useEdgesState,
addEdge,
} from ‘reactflow’;
import ‘reactflow/dist/style.css’;
// 作成したカスタムノードコンポーネントをインポート
import CustomNode from ‘./CustomNode’; // ファイルパスに合わせて修正
// Node Typesオブジェクトを定義
// キーがノードタイプとして使用する文字列、値がカスタムノードコンポーネント
const nodeTypes = { custom: CustomNode };
const initialNodes = [
{ id: ‘1’, type: ‘input’, data: { label: ‘デフォルト入力ノード’ }, position: { x: 50, y: 5 } },
{ id: ‘2’, data: { label: ‘デフォルトノード’ }, position: { x: 50, y: 100 } },
{ id: ‘3’, type: ‘output’, data: { label: ‘デフォルト出力ノード’ }, position: { x: 50, y: 200 } },
// <– カスタムノードを追加
{
id: ‘4’,
type: ‘custom’, // <– ここでカスタムノードタイプを指定
data: { label: ‘カスタムノード 1’, description: ‘これはカスタマイズされたノードです。’ },
position: { x: 300, y: 50 },
},
{
id: ‘5’,
type: ‘custom’, // <– ここでカスタムノードタイプを指定
data: { label: ‘カスタムノード 2’ },
position: { x: 300, y: 200 },
},
];
const initialEdges = [
{ id: ‘e1-2’, source: ‘1’, target: ‘2’ },
{ id: ‘e2-3’, source: ‘2’, target: ‘3’, animated: true },
// <– カスタムノード間のエッジや、カスタムノードとデフォルトノード間のエッジを追加
{ id: ‘e4-5’, source: ‘4’, target: ‘5’ }, // カスタムノード同士のエッジ
{ id: ‘e3-5’, source: ‘3’, target: ‘5’ }, // デフォルト出力ノードからカスタムノードへのエッジ
{ id: ‘e4a-2’, source: ‘4’, sourceHandle: ‘a’, target: ‘2’ }, // カスタムノードのID ‘a’ のソースハンドルからデフォルトノードへ
{ id: ‘e1-4c’, source: ‘1’, target: ‘4’, targetHandle: ‘c’ }, // デフォルト入力ノードからカスタムノードのID ‘c’ のターゲットハンドルへ
];
function App() {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const onConnect = useCallback((connection) => {
// connection オブジェクトには source, target, sourceHandle, targetHandle が含まれる
// 例: { source: ‘4’, sourceHandle: ‘a’, target: ‘2’, targetHandle: null }
console.log(‘新しい接続:’, connection);
setEdges((eds) => addEdge(connection, eds));
}, [setEdges]);
return (
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
nodeTypes={nodeTypes} // <– ここでカスタムノードタイプを登録
fitView
>
);
}
export default App;
“`
コードの解説
import CustomNode from './CustomNode';: 作成したカスタムノードコンポーネントをインポートします。const nodeTypes = { custom: CustomNode };: React Flowに登録するノードタイプを定義するオブジェクトです。キー'custom'が、ノードデータのtypeプロパティで使用する名前になります。値は、そのタイプのノードを描画するために使用するReactコンポーネントCustomNodeです。複数のカスタムノードタイプを持つ場合は、このオブジェクトにどんどん追加していきます(例:{ custom: CustomNode, anotherType: AnotherNode })。initialNodes内のカスタムノード定義:id: '4'とid: '5'のノードは、type: 'custom'と指定しています。これにより、React Flowはこのノードを描画する際に、デフォルトノードではなく、登録された'custom'タイプであるCustomNodeコンポーネントを使用します。initialEdges内のカスタムノード関連エッジ:{ id: 'e4a-2', source: '4', sourceHandle: 'a', target: '2' }: カスタムノード'4'の特定のソースハンドル (id: 'a') からデフォルトノード'2'へ接続するエッジです。sourceHandleやtargetHandleプロパティを使うことで、複数のハンドルを持つノードのどのハンドルが使用されているかを明示的に指定できます。
nodeTypes={nodeTypes}:<ReactFlow>コンポーネントのnodeTypesプロパティに、定義したnodeTypesオブジェクトを渡します。
これでブラウザをリロードすると、作成したカスタムノードが表示されているはずです。カスタムノードのハンドルから他のノードへエッジを接続したり、他のノードからカスタムノードのハンドルへエッジを接続したりできることを確認してみてください。
HandleのisValidConnectionプロパティ
前述した isValidConnection プロパティを使うと、さらに高度な接続ルールを設定できます。例えば、カスタムノードのハンドル 'a' (ソース) は、デフォルトの 'target' ハンドルにしか接続できない、といったルールをカスタムノードの <Handle type="source" id="a"> に追加するには以下のようにします。
jsx
// src/CustomNode.jsx 内の Handle コンポーネント
<Handle
type="source"
position={Position.Bottom}
id="a"
isConnectable={isConnectable}
style={{ background: '#555' }}
// isValidConnectionプロパティを追加
isValidConnection={(connection) => {
// connectionオブジェクトには source, sourceHandle, target, targetHandle が含まれる
console.log('接続チェック:', connection);
// このソースハンドル ('a') は、ターゲットがデフォルトノードである場合にのみ有効とする
// これは例であり、実際にはターゲットノードのタイプなどをチェックするロジックになる
// ここではシンプルに、ターゲットノードのIDをチェックする例とする
// 実際は、targetのノードオブジェクトにアクセスしてtypeなどをチェックする必要がある
// targetノードオブジェクトにアクセスするにはuseReactFlowフックなどが必要になる
// より簡単な例として、特定のターゲットハンドル以外には接続できないルールにする
return connection.targetHandle === null; // デフォルトノードのターゲットハンドルはnull
}}
/>
isValidConnection 関数内でより複雑なロジックを実装するには、接続先のノードの情報を取得する必要があります。これには useReactFlow フックや getNodes セレクターなどを使用しますが、これは少し進んだトピックになるため、ここでは概念の説明に留めます。重要なのは、<Handle> レベルで接続の有効性を制御できるという点です。
エッジをカスタマイズする
ノードと同様に、エッジの見た目もカスタマイズできます。デフォルトのエッジタイプ(default, step, smoothstep, straight)に加えて、完全に独自の描画ロジックを持つカスタムエッジタイプを作成できます。
カスタムエッジタイプを作成する手順は、カスタムノードと似ています。
- カスタムエッジコンポーネントを作成: SVGのパスなどを使ってエッジの見た目を定義します。このコンポーネントはReact Flowからエッジの座標やハンドル情報などのプロパティを受け取ります。
- React Flowにカスタムエッジを登録:
<ReactFlow>コンポーネントのedgeTypesプロパティを使って登録します。 - エッジデータでカスタムタイプを指定: 表示したいエッジデータで、登録したカスタムエッジのタイプを指定します。
カスタムエッジコンポーネントは、以下のようなプロパティを受け取ります。
id: エッジのIDsourceX,sourceY,targetX,targetY: 接続元のノードのソースハンドルの中心座標と、接続先のノードのターゲットハンドルの中心座標sourcePosition,targetPosition: 接続元のソースハンドルと接続先のターゲットハンドルの辺 (Position.Top, etc.)source,target: 接続元のノードIDと接続先のノードIDsourceHandleId,targetHandleId: 使用されたハンドルのID (複数ある場合)markerEnd,markerStart: エッジの終端/始端に表示するマーカー(矢印など)の定義data: エッジデータ (optional)
例として、エッジの中央にボタンを表示するカスタムエッジを考えます。
“`jsx
// src/CustomEdge.jsx
import React from ‘react’;
import { BaseEdge, getBezierPath, EdgeLabelRenderer } from ‘reactflow’;
const CustomEdge = ({
id,
sourceX,
sourceY,
targetX,
targetY,
sourcePosition,
targetPosition,
style = {}, // スタイルを受け取れるようにする
markerEnd, // マーカーを受け取れるようにする
}) => {
// getBezierPathは、ベジエ曲線のSVGパスデータとラベルの座標を計算してくれるユーティリティ
const [edgePath, labelX, labelY] = getBezierPath({
sourceX,
sourceY,
sourcePosition,
targetX,
targetY,
targetPosition,
});
return (
<>
{/ ベジエ曲線のパスを描画 /}
{/ エッジ上に要素を表示するためのコンポーネント /}
{/ ラベルの位置にボタンを配置 /}
{/ styleのtransformを使って正確な中央に配置 /}
);
};
export default CustomEdge;
“`
カスタムエッジの登録と使用
App.jsx で CustomEdge をインポートし、React Flowに登録します。
“`jsx
// src/App.jsx または src/App.js
import React, { useCallback } from ‘react’;
import ReactFlow, {
Controls,
MiniMap,
Background,
ReactFlowProvider,
useNodesState,
useEdgesState,
addEdge,
} from ‘reactflow’;
import ‘reactflow/dist/style.css’;
import CustomNode from ‘./CustomNode’;
// 作成したカスタムエッジコンポーネントをインポート
import CustomEdge from ‘./CustomEdge’; // ファイルパスに合わせて修正
const nodeTypes = { custom: CustomNode };
// Edge Typesオブジェクトを定義
// キーがエッジタイプとして使用する文字列、値がカスタムエッジコンポーネント
const edgeTypes = { custom: CustomEdge };
const initialNodes = [
{ id: ‘1’, type: ‘input’, data: { label: ‘デフォルト入力ノード’ }, position: { x: 50, y: 5 } },
{ id: ‘2’, data: { label: ‘デフォルトノード’ }, position: { x: 50, y: 100 } },
{ id: ‘3’, type: ‘output’, data: { label: ‘デフォルト出力ノード’ }, position: { x: 50, y: 200 } },
{
id: ‘4’,
type: ‘custom’,
data: { label: ‘カスタムノード 1’, description: ‘これはカスタマイズされたノードです。’ },
position: { x: 300, y: 50 },
},
{
id: ‘5’,
type: ‘custom’,
data: { label: ‘カスタムノード 2’ },
position: { x: 300, y: 200 },
},
];
const initialEdges = [
{ id: ‘e1-2’, source: ‘1’, target: ‘2’ },
{ id: ‘e2-3’, source: ‘2’, target: ‘3’, animated: true },
{ id: ‘e4-5’, source: ‘4’, target: ‘5’ },
{ id: ‘e3-5’, source: ‘3’, target: ‘5’ },
{ id: ‘e4a-2’, source: ‘4’, sourceHandle: ‘a’, target: ‘2’ },
{ id: ‘e1-4c’, source: ‘1’, target: ‘4’, targetHandle: ‘c’ },
// <– カスタムエッジを追加
{
id: ‘e4-3-custom’,
source: ‘4’,
target: ‘3’,
type: ‘custom’, // <– ここでカスタムエッジタイプを指定
animated: true,
},
];
function App() {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const onConnect = useCallback((connection) => {
console.log(‘新しい接続:’, connection);
// 新しいエッジを追加する際にカスタムタイプを指定することも可能
// setEdges((eds) => addEdge({ …connection, type: ‘custom’, animated: true }, eds));
setEdges((eds) => addEdge(connection, eds)); // ここではデフォルトエッジを追加
}, [setEdges]);
return (
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
nodeTypes={nodeTypes}
edgeTypes={edgeTypes} // <-- ここでカスタムエッジタイプを登録
onNodeClick={(event, node) => { /* ... ノード削除ロジック ... */ }}
onEdgeClick={(event, edge) => { /* ... エッジ削除ロジック ... */ }}
fitView
>
<Controls />
<MiniMap />
<Background variant="dots" gap={12} size={1} />
</ReactFlow>
</div>
</ReactFlowProvider>
);
}
export default App;
“`
コードの解説
import CustomEdge from './CustomEdge';: 作成したカスタムエッジコンポーネントをインポートします。const edgeTypes = { custom: CustomEdge };: React Flowに登録するエッジタイプを定義するオブジェクトです。キー'custom'がエッジデータのtypeプロパティで使用する名前になります。initialEdges内のカスタムエッジ定義:{ id: 'e4-3-custom', source: '4', target: '3', type: 'custom', ... }のように、type: 'custom'と指定することで、このエッジがCustomEdgeコンポーネントで描画されるようになります。edgeTypes={edgeTypes}:<ReactFlow>コンポーネントのedgeTypesプロパティに、定義したedgeTypesオブジェクトを渡します。
ブラウザで確認すると、カスタムエッジタイプを指定したエッジの中央に「削除」ボタンが表示されているはずです。
カスタムエッジは、ノードの座標やハンドルの位置情報を受け取って自分でSVGパスを描画する必要があります。reactflow から提供される getBezierPath, getSmoothStepPath, getStraightPath といったユーティリティ関数を使うと、パスデータの計算が簡単になります。また、EdgeLabelRenderer コンポーネントを使うと、エッジ上にHTML要素やReactコンポーネントを簡単に配置できます。
その他の便利機能と考慮事項
React Flowには、これまで紹介した以外にも便利な機能や、アプリケーション開発で考慮すべき点がいくつかあります。
Viewportの操作
ズームやパンといったビューポートの状態(ズームレベルと中心座標)をプログラムから操作したい場合があります。React Flowでは useReactFlow フックや useViewport フック、そして fitView 関数などが提供されています。
例えば、特定のノードにズームして中央に表示する、といった機能は useReactFlow から取得できる関数を使って実現できます。
“`jsx
// App.jsx 内で useReactFlow フックを使用
import ReactFlow, {
// … 他のインポート …
useNodesState,
useEdgesState,
addEdge,
useReactFlow, // <– useReactFlowをインポート
} from ‘reactflow’;
// … initialNodes, initialEdges, nodeTypes, edgeTypes …
function App() {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const { zoomIn, zoomOut, fitView, setViewport, getViewport, screenToFlowPosition, flowToScreenPosition } = useReactFlow(); // <– フックから関数を取得
const onConnect = useCallback((connection) => { / … / }, [setEdges]);
// 特定のノードにフォーカスする関数
const focusNode = useCallback((nodeId) => {
const node = nodes.find(n => n.id === nodeId);
if (node) {
const x = node.position.x + node.width / 2; // ノードの中心X座標 (幅/高さは自動計算される)
const y = node.position.y + node.height / 2; // ノードの中心Y座標
const zoom = 1.5; // ズームレベル
const duration = 800; // アニメーション時間 (ミリ秒)
// setViewportを使ってアニメーションしながら移動
setViewport({ x: x - window.innerWidth / 2, y: y - window.innerHeight / 2, zoom }, { duration });
}
}, [nodes, setViewport]);
return (
{/ … ノード追加ボタンなど … /}
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
nodeTypes={nodeTypes}
edgeTypes={edgeTypes}
onNodeClick={(event, node) => { /* ... 削除ロジックなど ... */ }}
onEdgeClick={(event, edge) => { /* ... 削除ロジックなど ... */ }}
fitView // 初回ロード時や変更時に全体表示
// fitViewOptions={{ padding: 0.2 }} // fitViewのオプション
>
<Controls />
<MiniMap />
<Background variant="dots" gap={12} size={1} />
</ReactFlow>
</div>
</ReactFlowProvider>
);
}
export default App;
“`
useReactFlow フックは、<ReactFlowProvider> の中で使用する必要があります。これにより、React Flowインスタンスへの様々なアクセスや操作が可能になります。setViewport は、指定した座標とズームレベルにビューポートを移動させる関数で、第二引数に duration を渡すとアニメーションさせることができます。
State管理パターンの選択
この記事では、useNodesState および useEdgesState というReact Flow独自のフックを使用してState管理を行いました。これはReact Flowの状態に特化しており、初心者には非常に扱いやすい方法です。
より複雑なアプリケーションで、フロー図の状態(ノード、エッジ、ビューポートなど)を他のアプリケーションの状態(例: サイドバーの開閉状態、選択中のノードのプロパティなど)と密接に連携させたり、グローバルな状態管理ライブラリ(Zustand, Redux, Context APIなど)と統合したい場合は、別の方法も考えられます。
- Context API +
useState: ReactのContext APIを使って、ノードとエッジのState(useStateで管理)をReact Flowコンポーネントを含むツリー全体で共有する方法。カスタムフックを作成してStateへのアクセスや更新を抽象化すると管理しやすくなります。 - Zustand: 軽量な状態管理ライブラリで、React Flowとの連携が推奨されています。Zustandのストアにノードとエッジを格納し、ストアの更新関数を
onNodesChangeやonEdgesChangeに渡すことで、React Flowの状態をZustandで一元管理できます。React Flowの公式ドキュメントにもZustandを使った例が掲載されています。 - Reduxなど: より大規模なアプリケーションでは、Reduxのようなライブラリを使うことも可能ですが、ボイラープレートが多くなりがちです。Zustandの方がReact Flowとの連携はスムーズなことが多いです。
どのパターンを選択するかは、アプリケーションの規模や複雑さ、チームの開発スタイルによります。シンプルなフロー図や学習目的であれば useNodesState/useEdgesState が最適です。
レイアウトの自動化
手動でノードを配置するだけでなく、特定のアルゴリズム(例: 階層構造、円形配置など)に基づいてノードを自動的に配置したい場合があります。React Flow自体には高度な自動レイアウト機能は組み込まれていませんが、外部のグラフ可視化ライブラリと連携させることで実現できます。
代表的なライブラリとして、DagreやELK (Eclipse Layout Kernel) があります。これらのライブラリは、ノードとエッジの情報を渡すと、それぞれのノードに最適な位置座標を計算して返してくれます。
React Flowで自動レイアウトを実装する一般的な流れは以下のようになります。
- DagreやELKなどのライブラリをインストールします。
- ノードとエッジのデータを、使用するレイアウトライブラリが要求する形式に変換します。
- レイアウトライブラリの関数を呼び出し、計算されたノードの新しい位置座標を取得します。
- 取得した新しい位置座標を使って、React FlowのノードStateを更新します (
setNodesを使って、各ノードオブジェクトのpositionプロパティを更新します)。
これは少し応用的なトピックになりますが、複雑なフロー図を扱う際には非常に役立つ機能です。React Flowの公式ドキュメントにもレイアウト連携の例が掲載されています。
パフォーマンスの最適化
大規模なフロー図(数百、数千のノードやエッジ)を扱う場合、パフォーマンスが問題になることがあります。React Flowはデフォルトで多くの最適化を行っていますが、さらに改善できるポイントがいくつかあります。
- カスタムノード/エッジのmemo化: 作成したカスタムノードやカスタムエッジコンポーネントを
React.memoでラップすることで、propsが変更されない限りコンポーネントが再レンダリングされるのを防ぎ、パフォーマンスを向上させることができます(上記カスタムノードの例でも使用しています)。 - Stateの更新頻度:
onNodesChangeやonEdgesChangeでStateを更新する際に、不要な更新を防ぐように注意します。useNodesStateやuseEdgesStateはこの点である程度最適化されています。 shouldComponentUpdate(Class Components) /React.memo(Functional Components): これらを適切に使用して、コンポーネントツリー全体で不要なレンダリングを防ぎます。- 要素のフィルタリング: 非常に多くの要素がある場合、ビューポート外の要素を描画しないようにフィルタリングするといった高度な最適化も理論上は可能ですが、React Flowの内部で大部分は処理されています。
- ハードウェアアクセラレーション: CSSで
transform: translateZ(0)などを要素に適用することで、ハードウェアアクセラレーションを有効にし、描画パフォーマンスを向上させられる場合があります。
キーボード操作
React Flowはデフォルトでいくつかのキーボードショートカットに対応しています(例: Backspace/Deleteキーで選択中の要素を削除)。独自のキーボードショートカットを追加したい場合は、標準のDOMイベントリスナー(onKeyDown など)を使って実装できます。ただし、React Flowのイベントハンドラと競合しないように注意が必要です。
アクセシビリティ (A11y)
フロー図のような視覚的な要素は、スクリーンリーダーを使用するユーザーにとって情報が伝わりにくくなることがあります。アクセシビリティを考慮する場合、ノードやエッジが表現している情報を、視覚情報だけでなくテキストとしても提供するなどの配慮が必要です。カスタムノードを作成する際に、ARIA属性などを適切に設定することが重要になります。
まとめと次のステップ
この記事では、React Flowを使ってインタラクティブなフロー図を作成するための基本的なステップを詳細に解説しました。
- セットアップ: Reactプロジェクトを作成し、React Flowライブラリをインストールしました。
- 基本的な表示:
initialNodesとinitialEdgesを定義し、<ReactFlow>コンポーネント、<Controls>,<MiniMap>,<Background>を使ってフロー図を描画しました。 - インタラクション:
useNodesState,useEdgesStateを使ってノードとエッジをStateとして管理し、onNodesChange,onEdgesChange,onConnectハンドラを設定することで、ドラッグやエッジ接続といった基本操作を可能にしました。 - 要素の追加/削除: State操作によってノードやエッジを動的に追加・削除する方法を学びました。
- カスタマイズ: カスタムReactコンポーネントと
<Handle>コンポーネントを使って、ノードの外観と動作をカスタマイズする方法を学びました。また、カスタムエッジの概念と基本的な実装についても触れました。 - 応用: Viewportの操作、State管理パターン、自動レイアウト、パフォーマンス、アクセシビリティといった、さらにフロー図アプリケーションをリッチにするための応用的なトピックについても概説しました。
React Flowは非常に柔軟で強力なライブラリであり、この記事で紹介できたのはその機能のほんの一部です。さらに深く掘り下げていくことで、以下のようなことも実現できます。
- 異なるタイプのノード間の接続制限 (
isValidConnectionを本格的に使う) - グループノード(他のノードを含むノード)の作成
- フロー図の状態を保存・復元する
- React ContextやZustandなどを使ったより洗練された状態管理
- Dagreなどのライブラリを使った自動レイアウトの実装
- ミニマップやコントロールパネルのカスタマイズ
- 複雑なカスタムエッジタイプ(パスに沿って動くアイコンなど)
- コピー&ペースト、Undo/Redoなどの高度なエディタ機能
これらの機能やさらに詳しい情報は、React Flowの公式ドキュメントに豊富に記載されています。
- React Flow 公式ドキュメント (英語): https://reactflow.dev/docs/
- React Flow 公式サンプル (英語): https://reactflow.dev/examples
公式ドキュメントには、さまざまなユースケースに対応したサンプルコードがたくさんあります。ぜひこれらのリソースも活用して、React Flowでの開発を楽しんでください。
フロー図は、情報を整理し、複雑な関係性を理解するための素晴らしい手段です。React Flowを使うことで、その作成が格段に容易になります。この記事が、あなたがインタラクティブなフロー図の世界へ踏み出すための一助となれば幸いです。
Happy coding!