neo4j-nvl-skill について
ブラウザーアプリで NVL を使った Neo4j グラフ表示を構築する開発者向けのスキルです。JavaScript と React、描画、操作、レイアウト、ドライバー結果の変換を扱い、カスタム表示を支援します。
主な機能
- 01Canvas と WebGL によるグラフ描画
- 02React ラッパーと JavaScript API
- 03ズーム、ドラッグ、選択操作
- 04グラフレイアウト、クラスタリング、更新
- 05Neo4j ドライバー結果の変換
活用シーン
- 01React やブラウザーアプリでの Neo4j グラフ表示
- 02ノードのクリック、ドラッグ、範囲選択の実装
- 03大規模グラフの探索や表示の更新
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ neo4j-contrib/ neo4j-skills --skill neo4j-nvl-skillインストール後、Claude Code で/neo4j-nvl-skillを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
README.mdreferences/api-surface.mdtroubleshooting.md詳細
使用する場面
- カスタムの操作、描画、またはデータ形状を使って、ブラウザー内でNeo4jグラフを描画する場合(バニラJS、React、Vite)
driver.executeQueryの結果をインタラクティブなグラフとして可視化する場合- ズーム、パン、ドラッグ、クリック、ホバー、投げ縄選択、または範囲選択の操作を組み込む場合
- NVLを既存のアプリに埋め込み、グラフの状態を同期する場合
使用しない場面
- カスタム操作のない、デフォルト動作のスタイル適用済み埋め込みグラフビュー →
@neo4j-ndl/reactのGraphVisualization(Neo4j Needle/NDLデザインシステム)— デフォルトのNeo4jスタイルでNVLをラップします。以下のUse NVL or the Needle Component?を参照してください。 - Python/Jupyterノートブックでのグラフ可視化 →
neo4j/python-graph-visualization(NVLのPython移植版) - Cypherの作成/最適化 →
neo4j-cypher-skill - ドライバーのセットアップ/executeQuery/セッション →
neo4j-driver-javascript-skill - 描画を伴わないサーバーサイドのデータ取得 →
neo4j-driver-javascript-skill - GDSアルゴリズムの実行 →
neo4j-gds-skillまたはneo4j-aura-graph-analytics-skill - GraphQL API →
neo4j-graphql-skill
NVLとNeedleコンポーネントのどちらを使うか?
| 必要なもの | 使用するもの |
|---|---|
| カスタム操作や描画のない、デフォルトのNeo4jスタイルを使ったグラフビューの埋め込み | @neo4j-ndl/react の GraphVisualization(Neo4j Needle/NDLデザインシステム)— NVLをラップし、{ id, labels, properties: { key: { stringified, type } } } (NeoNode) の形式のレコードを受け付けます |
| カスタム操作、カスタム描画、標準外のデータ形状、またはフレームワークに依存しない埋め込み | このスキル — NVLを直接使用します |
最初の行に該当する場合は、スタイルの作業を重複して行わず、NVLではなくNeedleコンポーネントをインストールして使用してください。
インストール
npm install @neo4j-nvl/base # core (required)
npm install @neo4j-nvl/interaction-handlers # standard interactions (optional, vanilla JS)
npm install @neo4j-nvl/react # React wrappers (optional)
ピア要件:@neo4j-nvl/react にはReact 19が必要です。公開されているpeerDependencyの範囲ではReact 18も引き続き許可されていますが、メジャーバージョンを混在させることは推奨されません。19を対象にしてください。@neo4j-nvl/layout-workers は推移的依存関係です。直接インストールしないでください。neo4j-driver は、nvlResultTransformer を使用する場合に限り、@neo4j-nvl/base のピア依存関係です。
スターターテンプレート:https://github.com/neo4j-devtools/nvl-boilerplates — フレームワークごとの公式スキャフォールドです。手作業でセットアップするより、こちらを優先してください。
ライセンス:NVLはNeo4j Visualization Library Licenseのもとで提供されます。Neo4j製品でのみ使用できます。他のグラフバックエンドには使用できません。
適切なパラダイムを選ぶ
| 必要なもの | 使用するもの |
|---|---|
| Reactアプリ、デフォルトの操作 | @neo4j-nvl/react の <InteractiveNvlWrapper> |
| Reactアプリ、カスタムの操作設定 | <BasicNvlWrapper> と ref を介した独自のハンドラー |
| バニラJS、標準的な操作 | NVL と @neo4j-nvl/interaction-handlers |
| バニラJS、完全にカスタムのイベントロジック | NVL と container.addEventListener と nvl.getHits() |
| 静的なPNG/SVG画像のエクスポート | <StaticPictureWrapper> または nvl.saveToFile()/nvl.saveToSvg() |
適切なレンダラーを選ぶ
| レンダラー | ノードの最大数 | 詳細 | 用途 |
|---|---|---|---|
'canvas'(デフォルト) | 約1,000 | キャプション、アイコン、矢印、ピクセル単位で正確なヒットテスト | 詳細調査、小規模なグラフ |
'webgl' | 100,000以上 | キャプションなし。2.0ではノードアイコンとリレーションシップの矢印をサポート | 大規模なパターン探索 |
const nvl = new NVL(container, nodes, rels, { renderer: 'webgl' })
nvl.setRenderer('canvas') // swap at runtime
NVL 2.0では、GPUレンダラーと力指向物理エンジンにWebGL2が必要です。GPUアクセラレーションを利用できない場合、NVLはCanvas描画とGPUを使わないレイアウト実装にフォールバックできます。WebGLを明示的に使わない場合は、disableWebGL: true を使用してください。
コンテナーのセットアップ
コンテナーには明示的な width と height の両方が必要です。高さがない場合、コンテナーは 0 に縮小し、グラフが表示されなくなります。NVLで最もよく報告されるバグです。
<div id="viz"></div>
<div id="viz" style="width: 100%; height: 600px;"></div>
バニラ — 基本ライブラリ
import { NVL } from '@neo4j-nvl/base'
const container = document.getElementById('viz')
const nodes = [{ id: '1' }, { id: '2' }]
const relationships = [{ id: '12', from: '1', to: '2', type: 'KNOWS' }]
const nvl = new NVL(container, nodes, relationships)
オプションとコールバックを指定する場合:
import { NVL } from '@neo4j-nvl/base'
const options = {
initialZoom: 1.0,
minZoom: 0.1,
maxZoom: 8,
layout: 'forceDirected',
renderer: 'canvas',
styling: { defaultNodeColor: '#0e86d4', defaultRelationshipColor: '#888' }
}
const callbacks = {
onInitialization: () => console.log('NVL ready'),
onLayoutDone: () => nvl.fit([]),
onError: (err) => console.error('NVL error', err)
}
const nvl = new NVL(container, nodes, relationships, options, callbacks)
// On teardown — always:
nvl.destroy()
NVL コンストラクターのシグネチャ:new NVL(frame, nvlNodes?, nvlRels?, options?, callbacks?)。frame 以外はすべて省略可能で、省略時は空になります。
Vanilla — インタラクションハンドラー
既存の NVL インスタンスにハンドラーを組み合わせます。各ハンドラーは .updateCallback(name, fn) でコールバックを登録し、.destroy() で破棄する必要があります。
import { NVL } from '@neo4j-nvl/base'
import {
ZoomInteraction, PanInteraction, DragNodeInteraction,
ClickInteraction, HoverInteraction, BoxSelectInteraction,
LassoInteraction, KeyboardInteraction
} from '@neo4j-nvl/interaction-handlers'
const nvl = new NVL(container, nodes, relationships)
const zoom = new ZoomInteraction(nvl)
const pan = new PanInteraction(nvl)
const drag = new DragNodeInteraction(nvl)
const click = new ClickInteraction(nvl, { selectOnClick: true })
const hover = new HoverInteraction(nvl, { drawShadowOnHover: true })
click.updateCallback('onNodeClick', (node, hits, evt) => console.log('node', node.id))
click.updateCallback('onRelationshipClick', (rel, hits, evt) => console.log('rel', rel.id))
click.updateCallback('onClusterClick', (cluster, hits, evt) => console.log('cluster', cluster.id))
click.updateCallback('onCanvasClick', (evt) => console.log('canvas'))
hover.updateCallback('onHover', (el, hits, evt) => el && console.log('over', el.id))
drag.updateCallback('onDragEnd', (nodes, evt) => savePositions(nodes))
zoom.updateCallback('onZoomAndPan', (level, panX, panY) => console.log(level, panX, panY))
// Teardown — destroy all handlers, then the NVL instance
function teardown() {
for (const h of [zoom, pan, drag, click, hover]) h.destroy()
nvl.destroy()
}
ハンドラーを削除せずにイベントを無効にするには、click.removeCallback('onCanvasClick') を実行します。関数の代わりに true を渡すと、イベントが no-op で有効になります(デフォルトの選択動作に便利です)。
React — InteractiveNvlWrapper
すべてのインタラクションハンドラーを事前に設定します。mouseEventCallbacks でイベントを切り替えます(関数 = 有効 + コールバック、true = 有効、no-op、false/省略 = 無効)。
import { InteractiveNvlWrapper } from '@neo4j-nvl/react'
import type { MouseEventCallbacks } from '@neo4j-nvl/react'
import { useRef } from 'react'
import type { NVL, NvlOptions } from '@neo4j-nvl/base'
export function GraphView({ nodes, rels }) {
const nvlRef = useRef<NVL>(null)
const nvlOptions: NvlOptions = { initialZoom: 1, renderer: 'canvas' }
const mouseEventCallbacks: MouseEventCallbacks = {
onNodeClick: (node, hits, evt) => console.log('node', node.id),
onRelationshipClick: (rel, hits, evt) => console.log('rel', rel.id),
onClusterClick: (cluster, hits, evt) => console.log('cluster', cluster.id),
onCanvasClick: (evt) => console.log('canvas'),
onHover: (el, hits, evt) => {
if (el) console.log('hover', el.id)
if (hits.clusters[0]) console.log('cluster hover', hits.clusters[0].data.id)
},
onDragEnd: (nodes, evt) => persist(nodes),
onZoomAndPan: true, // enable, no callback
onPan: true
}
return (
<div style={{ width: '100%', height: 600 }}>
<InteractiveNvlWrapper
ref={nvlRef}
nodes={nodes}
rels={rels}
nvlOptions={nvlOptions}
interactionOptions={{ selectOnClick: true, drawShadowOnHover: true }}
mouseEventCallbacks={mouseEventCallbacks}
onInitializationError={(err) => console.error('NVL init', err)}
/>
</div>
)
}
ref は基盤となる NVL インスタンスを参照します。任意のメソッドを呼び出せます。nvlRef.current?.fit([])、nvlRef.current?.setRenderer('webgl')、nvlRef.current?.saveToFile() のように使用します。
NVL 2.0 — レイアウトのクラスタリング
clusterBy は、コールバックが同じ文字列または数値を返すノードをグループ化します。独立したノードには undefined を返します。React では useCallback を使ってコールバック参照を安定させてください。
力指向クラスタリングでは、すべてのクラスタリング設定を layoutOptions.clustering の下にネストします。
const clusterBy = (nodeId) => categoryByNodeId[nodeId]
const nvl = new NVL(container, nodes, relationships, {
layout: 'forceDirected',
layoutOptions: {
seedingMethod: 'circle',
clustering: {
clusterBy,
drawOutline: true,
drawHalo: true,
crossClusterRelationshipMultiplier: 0.2
}
},
minFps: 3,
maxFps: 30
})
crossClusterRelationshipMultiplier は実験的な設定で、値は 0..1 に制限されます。値が 1 に近いと、接続の多いグラフが不安定になることがあります。影響するのはクラスタ間のばねの引力のみです。クラスタ内のエッジは引き続き最大強度になります。
階層型クラスタリングでは、clusterBy を layoutOptions に直接指定します。
const nvl = new NVL(container, nodes, relationships, {
layout: 'hierarchical',
layoutOptions: {
direction: 'down',
clusterBy
}
})
クラスタの装飾とクラスタの外枠に対するインタラクションは、力指向の layoutOptions.clustering に適用されます。Cluster はグラフ要素ではなく、{ id: string | number, nodeIds: string[] } を持ちます。クラスタのコールバックが発火するのは、クラスタの外枠内の空白部分がクリックされた場合のみです。ノードとリレーションシップが優先されます。
NVL 1.x から 2.0 への移行
- React ラッパーの
layoutとlayoutOptionsプロパティをnvlOptionsに移動します。<InteractiveNvlWrapper nvlOptions={{ layout, layoutOptions }} />。 - インタラクションコールバックの
onZoomをonZoomAndPan(zoom, panX, panY, event)に置き換えます。 intelWorkaroundと Cytoscape 関連のレイアウト設定を削除します。これらはなくなりました。- WebGL は WebGL2 専用として扱い、WebGL レンダラーでキャプションが表示されることを想定しないでください。
getNodes()とonLayoutStepはNodeWithClientPosition[]を返します。x/yはレイアウト座標で、clientPositionは canvas 要素を基準にした座標です。styling.overlayLineColorを使用します。styling.minimapViewportBoxColorは非推奨です。
React — BasicNvlWrapper + Ref
インタラクションは設定されていません。ref から IncludeMethods<NVL> を通じてすべての NVL メソッドにアクセスできます。React でカスタムのインタラクションロジックを構築する際に使用します。
import { BasicNvlWrapper } from '@neo4j-nvl/react'
import type { NVL } from '@neo4j-nvl/base'
import { useRef } from 'react'
export function MiniGraph({ nodes, rels }) {
const nvlRef = useRef<NVL>(null)
return (
<div style={{ width: '100%', height: 400 }}>
<BasicNvlWrapper
ref={nvlRef}
nodes={nodes}
rels={rels}
nvlOptions={{ initialZoom: 2 }}
nvlCallbacks={{ onLayoutDone: () => nvlRef.current?.fit([]) }}
/>
<button onClick={() => nvlRef.current?.fit(['1', '2'])}>Zoom to 1,2</button>
</div>
)
}
Neo4j ドライバーの結果の接続
@neo4j-nvl/base は、レコードの形状にかかわらずノード/リレーションシップを重複排除する、JS ドライバー用の ResultTransformer をエクスポートします。
import neo4j from 'neo4j-driver'
import { NVL, nvlResultTransformer } from '@neo4j-nvl/base'
const driver = neo4j.driver(process.env.NEO4J_URI,
neo4j.auth.basic(process.env.NEO4J_USERNAME, process.env.NEO4J_PASSWORD))
const { nodes, relationships } = await driver.executeQuery(
'MATCH (a)-[r]-(b) RETURN a, r, b LIMIT 25',
{},
{ database: 'neo4j', resultTransformer: nvlResultTransformer }
)
const nvl = new NVL(document.getElementById('viz'), nodes, relationships)
// ❌ raw EagerResult — records are not Node/Relationship objects
const result = await driver.executeQuery('MATCH (a)-[r]-(b) RETURN a, r, b')
new NVL(container, result.records, []) // breaks
// ✅ use the transformer
const { nodes, relationships } = await driver.executeQuery(
'MATCH (a)-[r]-(b) RETURN a, r, b',
{},
{ database: 'neo4j', resultTransformer: nvlResultTransformer }
)
new NVL(container, nodes, relationships)
ドライバーのライフサイクル、セッション管理、Integer の処理、TypeScript の型については、neo4j-driver-javascript-skill を参照してください。
グラフの更新
| メソッド | 動作 |
|---|---|
addAndUpdateElementsInGraph(nodes, rels) | 新しい要素を挿入し、既存の要素を ID で更新します(指定されたフィールドのみ) |
updateElementsInGraph(nodes, rels) | 既存の要素のみを更新し、不明な ID は無視します |
addElementsToGraph(nodes, rels) | 挿入のみを行い、既存の ID がある場合はエラーをスローします |
removeNodesWithIds(ids) | ノードを削除します。隣接するリレーションシップも自動的に削除されます |
removeRelationshipsWithIds(ids) | リレーションシップを削除します |
setNodePositions(nodes, updateLayout?) | 位置を上書きし、必要に応じてレイアウトを再実行します |
restart(options?, retainPositions?) | 新しいオプションで再起動します。位置の保持は任意です |
差分更新では PartialNode/PartialRelationship を使用します。必須なのは id のみです。
nvl.updateElementsInGraph(
[{ id: '1', color: '#f00', selected: true }], // PartialNode
[{ id: '12', width: 4 }] // PartialRelationship
)
ヒットテスト(手動)
interaction-handlers パッケージを使用しない場合に使用します。2.0 では、getHits() がノード、リレーションシップ、フォース指向レイアウトのクラスター凸包を解決します。デフォルトのターゲットは ['node', 'relationship', 'cluster'] です。
const nvl = new NVL(container, nodes, rels)
container.addEventListener('click', (evt) => {
const { nvlTargets } = nvl.getHits(evt, ['node', 'relationship', 'cluster'], { hitNodeMarginWidth: 4 })
const hitNode = nvlTargets.nodes[0]
const hitRel = nvlTargets.relationships[0]
const hitCluster = nvlTargets.clusters[0]
if (hitNode) console.log('hit node', hitNode.data.id)
else if (hitRel) console.log('hit rel', hitRel.data.id)
else if (hitCluster) console.log('hit cluster', hitCluster.data.id, hitCluster.data.nodeIds)
else console.log('hit canvas')
})
HitTargetNode / HitTargetRelationship には、要素のヒットデータが含まれます。HitTargetCluster には、data、pointerCoordinates、フィッティングされた凸包 hull が含まれます。references/api-surface.md を参照してください。
よくある間違い
| 間違い | 修正方法 |
|---|---|
height を設定していないコンテナー → グラフが表示されない | コンテナーに明示的な width と height を設定します |
driver.executeQuery の結果を直接渡す | nvlResultTransformer を使用し、{ nodes, relationships } を受け取ります |
| ラベルの多い小規模なグラフで WebGL を使用する | 'canvas' を使用します。ラベルは完全にサポートされています |
| 1 万以上のノードで Canvas を使用する | renderer オプションまたは setRenderer で 'webgl' に切り替えます |
React のレンダーごとに新しい NVL を作成する | <InteractiveNvlWrapper> / <BasicNvlWrapper> を使用するか、useEffect + destroy() でラップします |
ティアダウン時に nvl.destroy() を呼び忘れる | アンマウント時に destroy() を呼び出します。React ラッパーでは自動的に処理されます |
| Vanilla のハンドラーをティアダウンし忘れる | nvl.destroy() の前に、すべてのインタラクションで .destroy() を呼び出します |
| Worker の構築がブロックされる(厳格な CSP/サンドボックス化されたランタイム/古いバンドラー) | nvlOptions: { disableWebWorkers: true }(NVL には Worker を使わないフォールバックがあります) |
| 規制対象の環境でテレメトリーが有効になっている | nvlOptions: { disableTelemetry: true } |
| レイアウトがいつまでも安定しない | pinNode(id) でアンカーノードを固定し、layoutTimeLimit を調整します |
selectOnClick が二重に実行される | マウント時に一度だけ切り替えます。レンダーごとに interactionOptions を切り替えないでください |
| ノードの端付近でヒットテストに失敗する | { hitNodeMarginWidth: N } を getHits に渡します |
| WebGL でキャプションが表示されない | 2.0 では仕様です。キャプションは Canvas 専用なので、ラベルが重要な場合は 'canvas' を使用します |
React ラッパーが layout / layoutOptions を無視する | 2.0 では、これらを nvlOptions の中にネストする必要があります |
onZoom が一度も呼び出されない | 2.0 で削除されました。onZoomAndPan を使用してください |
| クラスターのコールバックがノード上のクリックを検出しない | クラスターのコールバックは、空のクラスター凸包領域を対象とします。ノードのヒットが優先されます |
リファレンス
必要に応じて読み込んでください:
- references/api-surface.md — 2.0 の
NVLメソッド一覧。クラスタリング、FPS、シード設定、NodeWithClientPosition、ヒットターゲットとインタラクションコールバックの型、React の props、名前付きエクスポート、nvlResultTransformerを網羅しています - references/troubleshooting.md — コンテナーの高さが 0 の場合、WebGL2 と Canvas のトレードオフ、クラスタリング設定、1.x からの移行時のエラー、Worker のフォールバック、コンテキスト喪失、テレメトリー、ライフサイクル、レイアウト、ヒットテストの問題
公式 Web ドキュメント(上記のリファレンスで不十分な場合は WebFetch を使用してください):
- https://neo4j.com/docs/nvl/current/ — ユーザーガイド(インストール、基本ライブラリ、インタラクションハンドラー、React ラッパー)
- https://neo4j.com/docs/api/nvl/current/ — TypeDoc API リファレンス
- https://neo4j.com/docs/api/nvl/current/examples.html — 実行可能なサンプル
- https://github.com/neo4j-devtools/nvl-boilerplates — サポート対象の各フレームワーク向けの公式スターターテンプレート
- https://github.com/neo4j/python-graph-visualization — NVL の Python 移植版(このスキルは JavaScript/ブラウザーの利用方法にのみ使用してください)
チェックリスト
- コンテナーに明示的な CSS の
widthとheightがある - 選択基準の表に基づいて適切なパラダイム(vanilla/handlers/React)を選択している
- レンダラーが想定ノード数に適している(Canvas は約 1k 以下/WebGL は 100k 以上)
- キャプションが必要な場合は必ず Canvas を選択している
- React の
layoutとlayoutOptionsがnvlOptionsの内側にネストされている - ズームハンドラーでは削除された
onZoomではなく、onZoomAndPanを使用している - React で
clusterByが安定しており、選択したレイアウトに適した位置に配置されている - Driver の
executeQueryの結果がnvlResultTransformerに渡されている - すべての
executeQuery呼び出しでdatabaseが指定されている(neo4j-driver-javascript-skillに委任する) - ティアダウン時に
nvl.destroy()の前にすべてのインタラクションハンドラーで.destroy()を呼び出している - React のアンマウント時に
nvl.destroy()を呼び出している(手動で作成したインスタンスのみ。ラッパーは処理します) - 規制対象またはオフラインの環境では
disableTelemetry: trueを設定している - バンドラーまたは CSP がワーカーの構築をブロックする場合は
disableWebWorkers: trueを設定している - グラフの更新には
restartではなく、addAndUpdateElementsInGraph/updateElementsInGraphを使用している - ライセンスに互換性がある(対象は Neo4j 製品)