コンテンツへスキップ
Pivotick
Main Navigation ホーム
はじめに
設定
生成された API ドキュメント
ギャラリープロジェクト試してみる!

日本語

English
简体中文
Français
Deutsch
Português
Nederlands
العربية
Español

日本語

English
简体中文
Français
Deutsch
Português
Nederlands
العربية
Español

外観

Sidebar Navigation

はじめに

Pivotick を使ったプロジェクト

設定

コールバック

エッジレイヤー

簡略化

レイアウト

描画

シミュレーション

UI のカスタマイズ

サイドバー

ツールチップ

コンテキストメニュー

フィルター

凡例

データ表

UI のスタイル

UI の翻訳

プラグイン

Pivotick API

ピボットとデータ拡充

ピボット結果の保存

元に戻す操作と履歴

セキュリティ

API ドキュメント

ギャラリー

基本

ノードのスタイル

エッジのスタイル

レイアウト

イベントとコールバック

UI のカスタマイズ

編集と作成

メモと注釈

フィルター・検索・階層

プログラムによる制御

テーマとパフォーマンス

ピボットとデータ拡充

注目のサンプル

削除、編集、作成を維持する ​

グラフが永続レコードのビュー である場合、書き込みは楽観視できません。 3 before-hook は、ユーザーが開始したすべての書き込みに、消費者が決定する提案を作成します。 キャンバスとデータベースが一致しないことはありません。

  • onBeforeDelete — すべての削除アフォーダンス (サイドバーの一括行、 ノード / エッジ / ノート コンテキスト メニュー エントリ) ターゲット セット全体が解決されると、 cascadingEdges: ノードを削除すると破壊される関係。 false を拒否権に戻すか、{ accept: true, nodes, edges } を戻して、 実際にサーバーは削除されました。 ctx.confirm() は組み込みの確認モーダルです。
  • onBeforeNodeCreate — 作成 ▸ ノードの追加 およびキャンバス メニューの ここにノードを追加、グラフ空間position とpromptData() フォームを使用します。何 返された値がグラフに入ります。
  • onBeforeEdgeEditCommit — エッジ編集がコミットされると起動します (エッジ編集を右クリックします) エッジ ▸ エッジを編集)。それを拒否すると、エッジはデータを保持します。

3 つすべてが非同期である可能性があり、1 つが保留されている間、2 つ目のジェスチャは無視されます。 プログラム的な graph.removeNode() / addNode() は決してゲートされないため、独自のコード 独自のフックによってブロックされません。

試してみましょう: ノードを選択し、サイドバーで 削除 を押します (published レコードが表示されます) 拒否されました)、エッジを右クリックして関係を変更するか、作成▸追加を使用します ノード。 editors.deletion.enabled: false を設定して削除アフォーダンスを削除します すべてのクリックを拒否するのではなく、完全に拒否します。

js
const options = {
    // Label each attribute with its value, so an edit's effect is visible.
    render: {
        defaultNodeStyle: {
            size: 16,
            textColor: '#334155',
            text: (node) => node.getData()?.label,
            textVerticalShift: -1.6
        }
    },
    UI: {
        mode: 'full',
        // Off: `full` mode brings a minimap and the data dock, and this card is about
        // neither (see the Minimap and Data table cards for those).
        minimap: false,
        table: false,
        // Give the edge editor a relationship dropdown instead of a free-text field.
        editors: {
            edgeEditor: {
                fields: [{
                    key: 'label',
                    label: 'Relationship',
                    type: 'select',
                    options: RELATIONSHIPS.map((value) => ({ value, label: value }))
                }]
            }
        }
    },
    callbacks: {
        // Nothing leaves the model until the server agrees. The library resolves the
        // whole target set first — including `cascadingEdges`, the relationships that
        // removing a node would take with it — so you can persist the real consequence.
        onBeforeDelete: async ({ nodes, edges, cascadingEdges, origin, confirm }) => {
            const links = edges.length + cascadingEdges.length
            const ok = await confirm({
                title: 'Delete from event?',
                body: `${nodes.length} attribute(s) and ${links} relationship(s) will be `
                    + `deleted server-side. (asked from the ${origin})`,
                confirmLabel: 'Delete'
            })
            if (!ok) return false

            graph.notifier.info('Saving…', 'POST /events/delete')
            await wait(700) // a backend call that could fail

            // The server refused the published record: keep exactly what it deleted.
            const refused = nodes.filter((node) => node.getData().published)
            if (refused.length) {
                graph.notifier.warning('Kept', `${refused[0].getData().label} is published — not deleted.`)
            }
            return { accept: true, nodes: nodes.filter((node) => !node.getData().published) }
        },

        // Create ▸ Add node, or the canvas menu's "Add Node Here". `promptData` opens a
        // form; the id/data you return is what enters the graph.
        onBeforeNodeCreate: async ({ position, promptData }) => {
            const values = await promptData({
                title: 'New attribute',
                fields: [
                    { key: 'label', label: 'Value', type: 'text' },
                    {
                        key: 'type', label: 'Type', type: 'select', defaultValue: 'ip-src',
                        options: ['ip-src', 'domain', 'md5', 'url'].map((v) => ({ value: v, label: v }))
                    }
                ]
            })
            if (!values) return false // cancelled

            graph.notifier.info('Saving…', 'POST /attributes/add')
            await wait(500)
            graph.notifier.success('Created', `${values.label} at ${Math.round(position.x)},${Math.round(position.y)}`)
            return { accept: true, id: `attr-${values.label}`, data: values }
        },

        // Right-click an edge ▸ Edit Edge. The commit is a proposal too: refuse it and
        // the edge keeps its data, with the modal still open to correct.
        onBeforeEdgeEditCommit: async ({ previousData, nextData }) => {
            if (!RELATIONSHIPS.includes(nextData.label)) {
                graph.notifier.warning('Rejected', `"${nextData.label}" is not a known relationship.`)
                return false
            }
            graph.notifier.info('Saving…', 'PUT /object_references')
            await wait(500)
            graph.notifier.success('Updated', `${previousData.label} → ${nextData.label}`)
            return true
        }
    }
}
js
const data = {
    nodes: [
        { id: 'ip-8888', data: { label: '8.8.8.8', type: 'ip-src' } },
        { id: 'dom-evil', data: { label: 'evil.example.com', type: 'domain' } },
        { id: 'md5', data: { label: 'd41d8cd9…f8427e', type: 'md5' } },
        // The server refuses to delete this one — a published record.
        { id: 'file-obj', data: { label: 'invoice.doc', type: 'file', published: true } }
    ],
    edges: [
        { id: 'r1', from: 'file-obj', to: 'md5', data: { label: 'contains' } },
        { id: 'r2', from: 'dom-evil', to: 'ip-8888', data: { label: 'resolves-to' } }
    ]
}
Pager
次のページはじめに