انتقل إلى المحتوى
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

الإعدادات

دوال الاستدعاء

طبقات الحواف

التبسيط

التخطيط

الرسم

المحاكاة

تخصيص الواجهة

الشريط الجانبي

التلميح

القائمة السياقية

المرشحات

وسيلة الإيضاح

جدول البيانات

تنسيق الواجهة

ترجمة الواجهة

الإضافات

واجهة Pivotick البرمجية

الاستكشاف والإثراء

حفظ نتائج الاستكشاف

التراجع والسجل

الأمان

توثيق API

المعرض

الأساسيات

تنسيق العقد

تنسيق الحواف

التخطيطات

الأحداث ودوال الاستدعاء

تخصيص الواجهة

التحرير والتأليف

الملاحظات والتعليقات

التصفية والبحث والتسلسل الهرمي

التحكم البرمجي

السمات والأداء

الاستكشاف والإثراء

نماذج مميزة

إضافة جزء قفص الاتهام ​

الرصيف السفلي هو منطقة مشتركة، وليس ملكية خاصة لجدول البيانات: شبكة واحدة صف، ارتفاع واحد، طية واحدة، مهما كان عدد الأجزاء فيه. addDockTab() هي الطريقة التي تضع بها شيء آخر هناك - نفس الباب الذي تأتي من خلاله الطاولة المدمجة، لذلك جزء منك التسجيل يساويه وليس ضيفه.

تضيف هذه البطاقة جزء ملخص يعرض الرسم البياني حسب المالك أو حسب النوع. الرصيف يقرأ الشريط Table │ Summary؛ انقر عبر الجسم و تتغير عناصر التحكم في الرأس بها لأن Select all / CSV / Columns تنتمي إلى الطاولة ولا تعني شيئًا على نشمر.

علامة تبويب واحدة هي جزء واحد ​

يعرض جزء الملخص الرسم البياني بطريقتين — حسب المالك وحسب النوع — وهؤلاء هم مشاهدة جزء واحد، وليس جزأين. لذلك يقوم بتسجيل علامة تبويب قفص الاتهام مفردة ويرسم علامة تبويب خاصة به قم بالتبديل، واتصل بالرقم refresh() لتغيير الجسم.

هذا التمييز هو التصميم كله. تسجيل طريقتي العرض كما تفعل علامتي تبويب قم بإدراجها في شريط الرصيف بجانب Table، بدعوى أن منظر الملخص و جدول البيانات بأكمله هو نفس النوع من الأشياء. الجدول المدمج يفعل هذا بالضبط يقوم الجزء بما يلي: Nodes وEdges عبارة عن علامات تبويب، مرسومة على الشريط الخاص به.

يتم رسم المستويين بشكل مختلف حتى يتمكنوا من الجلوس بجانب بعضهم البعض وما زالوا يقرأون كما هو الخارجي والداخلي:

انظرفئة
Table │ Summaryعلامات تبويب كاملة الارتفاع، يتم وضع خط تحتها عندما تكون نشطة، ويتم إغلاقها بقاعدةpvt-dock-tabs / pvt-dock-tab
Nodes │ Edges, By owner │ By kindمجموعة حبوب صغيرةpvt-dock-views / pvt-dock-view

الزوج الداخلي عام، وهذا هو السبب في أن المفتاح أدناه عبارة عن ثلاثة أسماء فئات بدلاً من أ كتلة من النمط المضمن: يحصل الجزء الخاص بك على نفس التحكم الذي يتمتع به الجدول المدمج، و المستهلك الذي يعيد --pvt-theme-primary أو يتجاوز أيًا من الفئتين يعيد كلا المستويين مرة واحدة.

refresh() ليس أدبًا اختياريًا ​

يحتفظ المرسى ** بالعنصر الذي تم إرجاعه render** ويعيد إرفاقه عند الجزء الخاص بك يعود إلى الأمام. الجزء الذي قام بتبديل DOM الخاص به سيترك الرصيف محتفظًا بـ العقدة التي لا معنى لها لتسليمها في وقت لاحق. لذا فإن تغيير الجسم يمر عبر refresh()، وهو ما يستدعي render مرة أخرى.

معرفة عندما لا أحد يبحث ​

onActivate / onDeactivate هي الإشارة الوحيدة التي يحصل عليها الجزء بأنه خارج الشاشة، و ما يجب القيام به معهم هو لك أن تختار:

  • المحتوى الذي يمثل وظيفة للحالة الحالية للرسم البياني يمكن أن يتوقف عن العمل أثناء مخفية وإعادة اشتقاقها عند العودة. وهذا ما يفعله هذا الجزء، وما يفعله جدول البيانات يفعل.
  • المحتوى الذي يفتقد شيء ما يجب أن يستمر في العمل ويتوقف عن الرسم — على سبيل المثال، جزء يراقب حافلة حية، حيث أن الحدث قد انتهى بمجرد إطلاقه.

لا شيء عن الخطافات يفضل أيضًا.

يجلب الرصيف معه ​

يتم تثبيت المكونات الإضافية بعد إنشاء واجهة المستخدم، لذا تصل علامة تبويب الإرساء دائمًا بعد المنطقة تم تشغيل بوابة الوضع الخاصة. تسجيل واحد وبالتالي يبني قفص الاتهام - البرنامج المساعد الخاص بك يعمل مع UI.table: false ولا يحتاج إلى تشغيل أي شيء سوى وضع full. مع الجدول قبالة و جزء واحد خاص بك، دون رسم أي شريط على الإطلاق: الرصيف هو ببساطة الجزء الخاص بك.

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
الصفحة التاليةالبدء