mcpField
N
neo4j-nvl-skill

neo4j-nvl-skill

github.com/neo4j-contrib開発118731 回インストール1 週間前に更新
リポジトリ

neo4j-nvl-skill について

ブラウザーアプリで NVL を使った Neo4j グラフ表示を構築する開発者向けのスキルです。JavaScript と React、描画、操作、レイアウト、ドライバー結果の変換を扱い、カスタム表示を支援します。

主な機能

  1. 01Canvas と WebGL によるグラフ描画
  2. 02React ラッパーと JavaScript API
  3. 03ズーム、ドラッグ、選択操作
  4. 04グラフレイアウト、クラスタリング、更新
  5. 05Neo4j ドライバー結果の変換

活用シーン

  1. 01React やブラウザーアプリでの Neo4j グラフ表示
  2. 02ノードのクリック、ドラッグ、範囲選択の実装
  3. 03大規模グラフの探索や表示の更新

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/neo4j-contrib/neo4j-skills --skill neo4j-nvl-skill

インストール後、Claude Code で/neo4j-nvl-skillを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

README.md
references/
api-surface.md
troubleshooting.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 を使用してください):


チェックリスト

  • コンテナーに明示的な 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 製品)

コメント