コンテンツへスキップ
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 のカスタマイズ

編集と作成

メモと注釈

フィルター・検索・階層

プログラムによる制御

テーマとパフォーマンス

ピボットとデータ拡充

注目のサンプル

ドックペインを追加する ​

一番下のドックは 共有領域 であり、データテーブルのプライベートプロパティではありません: 1 つのグリッド 行、高さは 1、折り目は 1 つですが、その中に含まれるペインの数は関係ありません。 addDockTab() はあなたの置き方です そこには何か別のものがあります - 内蔵テーブルが通るのと同じドアなので、窓ガラスはあなたが register はゲストではなく同等です。

このカードは、グラフを所有者ごとまたは種類ごとにロールアップする 概要 ペインを追加します。ドックの ストリップにはTable │ Summaryと書かれています。横をクリックすると本文 * と * ヘッダー コントロールが変わります Select all / CSV / Columns はテーブルに属しており、何も意味しないためです。 ロールアップの上に。

1 つのタブが 1 つのペインになります ​

[概要] ペインには、所有者別 および 種類別 の 2 つの方法でグラフが表示されます。 2 つのペインではなく、1 つのペインのビュー。したがって、single ドック タブを登録し、独自のタブを描画します 切り替えて、refresh() を呼び出して本体を変更します。

その違いがデザイン全体です。 2 つのビューを 2 つのドック タブとして登録すると、 それらをドックのTableの横のストリップにリストし、概要と データテーブル全体は同じ種類のものです。組み込みテーブルはまさにこれを実行します ペインの機能: Nodes と Edges は その タブであり、独自のバーに描画されます。

2 つのレベルは別々に描画されているため、隣り合っても次のように読むことができます。 外側と内側:

見てくださいクラス
Table │ Summaryフルハイトのタブ、アクティブな場合は下線が引かれ、ルールで閉じられますpvt-dock-tabs / pvt-dock-tab
Nodes │ Edges, By owner │ By kind小さな錠剤グループpvt-dock-views / pvt-dock-view

内側のペアはパブリックであるため、以下のスイッチは 1 つのクラス名ではなく 3 つのクラス名になっています。 インライン スタイルのブロック: ペインは組み込みテーブルと同じコントロールを取得し、 --pvt-theme-primary を retint するか、いずれかのクラスをオーバーライドする消費者は、両方のレベルを retint します。 すぐに。

refresh() は省略可能な丁寧さではありません ​

ドック ** は、render が返した要素** を保持し、ペインが閉じられたときにそれを再アタッチします。 前に戻ってきます。独自の DOM を交換したペインは、ドックに 後で返却する古いノード。したがって、身体の変化は refresh() を通過します。 またrender。

誰も見ていないときを知る ​

onActivate / onDeactivate は、ペインが画面外にあることを示す唯一の信号です。 それらをどうするかはあなた次第です:

  • グラフの現在の状態の関数であるコンテンツは、実行中に動作を停止する可能性があります。 非表示にして、戻ったときに再派生します。それがこのペインの役割であり、データ テーブルの役割です。 そうです。
  • 何かを 見逃し にするコンテンツは機能し続け、単に描画を停止する必要があります — たとえば、イベントが発生するとイベントは消滅するため、ライブバスを監視するペインです。

フックについても好みのものはありません。

ドックも一緒に持ち運べる ​

プラグインは * UI が構築された後に * をインストールするため、ドック タブは常にリージョンの後に到着します。 独自モードゲートが動作しました。したがって、登録すると ** がドックに ** を構築します。プラグインは機能します。 UI.table: false を使用しており、full モード以外は何もオンにする必要はありません。テーブルを外した状態で、 独自の 1 つのペインであり、ストリップはまったく描画されません。ドックは単にあなたのペインです。

UI.dock はリージョン自体を構成します (open、collapsed、height) — リージョンに到達します テーブルのスイッチがオフのときは UI.table ではなく、そこが唯一のドアであるためです。

契約については、プラグインを参照してください。 ドック独自のオプションのデータテーブル。

js
// The bottom dock is a shared region: one row, one height, one fold, however many panes
// are in it. `addDockTab` is the door — the same one the built-in data table comes
// through, so a pane you register is its equal rather than its guest.
//
// **One tab is one pane, not one view of one.** This pane rolls the graph up two ways,
// by owner and by kind. Those are two views of *this* pane, so it registers a single
// dock tab and draws its own switch, calling `refresh()` to change body. Registering
// them as two dock tabs would put them in the dock's strip beside `Table`, claiming a
// view of the summary and the whole data table are the same kind of thing.
//
// That is also why the two levels look different: the dock draws panes as full-height
// underlined tabs, and a pane's own views a lighter pill group. That inner look is public
// — `pvt-dock-views` on the strip, `pvt-dock-view` on each button, `active` on the current
// one — so your pane matches the built-in table for free, and follows the theme when a
// consumer overrides `--pvt-theme-primary` or either class.

const VIEWS = [
    { key: 'owner', label: 'By owner' },
    { key: 'kind', label: 'By kind' },
]

/** Count the nodes under each value of `key`, biggest group first. */
function rollUp(graph, key) {
    const counts = new Map()
    for (const node of graph.getNodes()) {
        const value = node.getData()?.[key] ?? '—'
        counts.set(value, (counts.get(value) ?? 0) + 1)
    }
    return [...counts.entries()].sort((a, b) => b[1] - a[1])
}

/**
 * The pane's body. `render` is called once per pane, lazily, the first time it is
 * opened — and again on every `refresh()`, which is how the view switch works.
 */
function renderSummary(graph, key) {
    const rows = rollUp(graph, key)
    const biggest = Math.max(...rows.map(([, count]) => count), 1)

    const body = document.createElement('div')
    body.style.cssText = 'padding: 8px 12px; font: 12px/1.9 sans-serif'

    for (const [value, count] of rows) {
        const row = document.createElement('div')
        row.style.cssText = 'display: grid; grid-template-columns: 130px 1fr 34px; gap: 10px; align-items: center'

        const name = document.createElement('span')
        name.textContent = value

        // A bar rather than a number alone: the shape of the split is the point.
        const track = document.createElement('span')
        track.style.cssText = 'height: 7px; border-radius: 4px; background: var(--pvt-bg-color-4)'
        const fill = document.createElement('span')
        fill.style.cssText = `display: block; height: 100%; border-radius: 4px; background: var(--pvt-vibrant-blue); width: ${(count / biggest) * 100}%`
        track.appendChild(fill)

        const tally = document.createElement('span')
        tally.textContent = String(count)
        tally.style.cssText = 'text-align: right; color: var(--pvt-text-color-3)'

        row.append(name, track, tally)
        body.appendChild(row)
    }
    return body
}

/**
 * The pane's own controls, in the header slot the dock hands it. `toolbar` is re-invoked
 * on every activation, so these always read the pane's current state.
 */
function renderControls(current, pick) {
    const strip = document.createElement('div')
    strip.className = 'pvt-dock-views'

    for (const view of VIEWS) {
        const button = document.createElement('button')
        button.type = 'button'
        button.className = 'pvt-dock-view'
        button.textContent = view.label
        button.classList.toggle('active', view.key === current)
        button.setAttribute('aria-pressed', String(view.key === current))
        button.addEventListener('click', () => pick(view.key))
        strip.appendChild(button)
    }
    return strip
}

/**
 * Ships as a plugin, so it installs itself and the core never needs to know it exists.
 * A plugin's tab also *builds* the dock if there isn't one — plugins install after the
 * UI, so it always arrives after the region's own gate has run.
 */
function summaryPane() {
    return {
        name: 'summaryPane',
        install(ctx) {
            let view = 'owner'
            // Whether the pane is the one on show, and whether the graph moved while it
            // wasn't. `onActivate` / `onDeactivate` are the only signal you get, and this
            // is the shape most panes want: don't work while hidden, catch up on return.
            let visible = false
            let stale = false

            ctx.addDockTab({
                id: 'summary',
                label: 'Summary',
                render: () => renderSummary(ctx.graph, view),
                toolbar: (pane) => renderControls(view, (key) => {
                    view = key
                    // The dock keeps the element `render` gave it, so ask it to rebuild
                    // rather than swapping the DOM behind its back.
                    pane.refresh()
                }),
                onActivate: (pane) => {
                    visible = true
                    if (stale) { stale = false; pane.refresh() }
                },
                onDeactivate: () => { visible = false },
            })

            // The graph outlives nothing here, but a real plugin should keep the
            // unsubscribe — see the Extend with a plugin card.
            ctx.graph.on('dataBatchChanged', () => {
                if (visible) ctx.refreshDockTab('summary')
                else stale = true
            })
        },
    }
}

const options = {
    UI: {
        mode: 'full',
        // The dock is the subject, so it starts expanded; the table is the pane our own
        // one sits beside, and is what makes the strip appear at all.
        dock: { open: true, height: 0.4 },
        table: { columns: [
            { key: 'label', label: 'Service', type: 'text' },
            { key: 'kind', label: 'Kind', type: 'select' },
            { key: 'owner', label: 'Owner', type: 'select' },
        ] },
        // Full mode's minimap isn't what this card is about.
        minimap: false,
    },
    plugins: [summaryPane()],
}
js
const data = {
    nodes: [
        { id: 'api', data: { label: 'api-gateway', kind: 'service', owner: 'Platform' } },
        { id: 'auth', data: { label: 'auth-service', kind: 'service', owner: 'Identity' } },
        { id: 'billing', data: { label: 'billing-service', kind: 'service', owner: 'Payments' } },
        { id: 'search', data: { label: 'search-service', kind: 'service', owner: 'Discovery' } },
        { id: 'users-db', data: { label: 'users-db', kind: 'datastore', owner: 'Identity' } },
        { id: 'orders-db', data: { label: 'orders-db', kind: 'datastore', owner: 'Payments' } },
        { id: 'index', data: { label: 'search-index', kind: 'datastore', owner: 'Discovery' } },
        { id: 'cache', data: { label: 'edge-cache', kind: 'datastore', owner: 'Platform' } },
        { id: 'mailer', data: { label: 'mailer', kind: 'worker', owner: 'Payments' } },
        { id: 'reindexer', data: { label: 'reindexer', kind: 'worker', owner: 'Discovery' } },
    ],
    edges: [
        { from: 'api', to: 'auth', data: { label: 'authenticates' } },
        { from: 'api', to: 'search', data: { label: 'queries' } },
        { from: 'api', to: 'billing', data: { label: 'charges' } },
        { from: 'api', to: 'cache', data: { label: 'reads' } },
        { from: 'auth', to: 'users-db', data: { label: 'reads' } },
        { from: 'billing', to: 'orders-db', data: { label: 'writes' } },
        { from: 'billing', to: 'mailer', data: { label: 'enqueues' } },
        { from: 'search', to: 'index', data: { label: 'reads' } },
        { from: 'reindexer', to: 'index', data: { label: 'writes' } },
        { from: 'reindexer', to: 'orders-db', data: { label: 'reads' } },
    ],
}
Pager
次のページはじめに