Ir al contenido
Pivotick
Main Navigation Inicio
Primeros pasos
Configuración
Documentación generada de la API
GaleríaProyectos¡Pruébalo!

Español

English
简体中文
Français
Deutsch
Português
Nederlands
日本語
العربية

Español

English
简体中文
Français
Deutsch
Português
Nederlands
日本語
العربية

Apariencia

Sidebar Navigation

Primeros pasos

Proyectos que usan Pivotick

Configuración

Funciones de retorno

Capas de aristas

Simplificar

Distribución

Renderizado

Simulación

Personalizar la interfaz

Barra lateral

Información emergente

Menú contextual

Filtros

Leyenda

Tabla de datos

Estilos de la interfaz

Traducir la interfaz

Complementos

API de Pivotick

Pivotes y enriquecimiento

Guardar resultados de pivotes

Deshacer e historial

Seguridad

Documentación de la API

Galería

Fundamentos

Estilos de nodos

Estilos de aristas

Distribuciones

Eventos y funciones de retorno

Personalización de la interfaz

Edición y creación

Notas y anotaciones

Filtros, búsqueda y jerarquía

Control programático

Temas y rendimiento

Pivotes y enriquecimiento

Ejemplos destacados

Agregar un panel de acoplamiento ​

El muelle inferior es una región compartida, no propiedad privada de la tabla de datos: una cuadrícula fila, una altura, un pliegue, por muchos paneles que contenga. addDockTab() es como se pone hay algo más allí: la misma puerta por la que entra la mesa incorporada, por lo que un panel El registro es su igual y no su invitado.

Esta tarjeta agrega un panel Resumen que muestra el gráfico por propietario o por tipo. el muelle la tira dice Table │ Summary; haga clic y el cuerpo y los controles del encabezado cambian con él, porque Select all / CSV / Columns pertenecen a la tabla y no significan nada sobre un rollito.

Una pestaña es un panel ​

El panel Resumen muestra el gráfico de dos maneras: Por propietario y Por tipo, y esas son vistas de un panel, no dos paneles. Entonces registra una pestaña de acoplamiento única y dibuja la suya propia cambiar, llamando a refresh() para cambiar de cuerpo.

Esa distinción es todo el diseño. Registrar las dos vistas como dos pestañas de acoplamiento enumerarlos en la franja del muelle al lado de Table, alegando que una vista del resumen y el toda la tabla de datos es el mismo tipo de cosas. La mesa incorporada hace exactamente lo que esto El panel hace: Nodes y Edges son sus pestañas, dibujadas en su propia barra.

Los dos niveles están dibujados de manera diferente para que puedan sentarse uno al lado del otro y seguir leyendo como un exterior y un interior:

miraclase
Table │ Summarypestañas de altura completa, subrayadas cuando están activas, cerradas por una reglapvt-dock-tabs / pvt-dock-tab
Nodes │ Edges, By owner │ By kindun pequeño grupo de pastillaspvt-dock-views / pvt-dock-view

El par interno es público, por lo que el cambio a continuación son tres nombres de clase en lugar de uno. bloque de estilo en línea: su panel obtiene el mismo control que tiene la tabla incorporada, y un el consumidor que retinta --pvt-theme-primary o anula cualquiera de las clases retinta ambos niveles a la vez.

refresh() no es cortesía opcional ​

El muelle ** mantiene el elemento que tu render devolvió** y lo vuelve a adjuntar cuando tu panel vuelve al frente. Un panel que cambiara su propio DOM dejaría el muelle con un nodo obsoleto para devolverlo más tarde. Entonces el cuerpo cambiante pasa por refresh(), que llama render otra vez.

Saber cuando nadie está mirando ​

onActivate / onDeactivate son la única señal que recibe un panel de que está fuera de la pantalla, y qué hacer con ellos es tuyo para elegir:

  • El contenido que es una función del estado actual del gráfico puede dejar de funcionar mientras oculto y rederivado al regresar. Eso es lo que hace este panel y lo que hace la tabla de datos. lo hace.
  • El contenido que se perdería algo tiene que seguir funcionando y simplemente dejar de pintarse. un panel que observa un autobús en vivo, por ejemplo, ya que un evento desaparece una vez que se ha activado.

Tampoco prefiere nada sobre los ganchos.

Trae el muelle consigo. ​

Los complementos se instalan después de crear la interfaz de usuario, por lo que siempre llega una pestaña de acoplamiento después de la región Se ha ejecutado la puerta de modo propio. Por lo tanto, registrar uno construye el muelle: su complemento funciona con UI.table: false y no necesita nada activado excepto el modo full. Con la mesa apagada y un panel propio, no se dibuja ninguna franja: el muelle es simplemente su panel.

UI.dock configura la región en sí (open, collapsed, height): búsquela en lugar de UI.table cuando la mesa está apagada, ya que esa es la única puerta.

Consulte Plugins para ver el contrato y Tabla de datos para las opciones propias del muelle.

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
Página siguientePrimeros pasos