Complementos
Un plugin es un paquete autónomo de elementos de interfaz de usuario, combinaciones de teclas y enlaces de ciclo de vida. que se instala en un gráfico sin que el núcleo necesite saber que existe. Registrarse uno declarativo o imperativo:
import { Pivotick, minimap } from 'pivotick'
// declaratively
const graph = new Pivotick(container, data, { plugins: [minimap()] })
// …or at any point later
graph.use(minimap({ position: 'bottom-left' }))Un complemento instalado después de que el gráfico esté activo se actualiza a la fase actual del ciclo de vida, por lo que no es "demasiado tarde" y todo lo que registra se elimina con la interfaz de usuario.
La forma de un complemento
import type { PivotickPlugin } from 'pivotick'
const myPlugin: PivotickPlugin = {
name: 'my-plugin', // used for de-duplication and logging
install(ctx) { /* … */ }, // called once
}La instalación del mismo name dos veces advierte y omite el duplicado, por lo que se puede instalar un complemento. enumerados en plugins y vueltos a aplicar hasta graph.use sin duplicar.
¿Qué se entrega install?
ctx es un {@enlace PluginContext}:
| Miembro | para que es |
|---|---|
graph | el Graph en vivo: nodos, bordes, el bus de eventos de datos, queryEngine |
ui | el UIManager — opciones, notificaciones, el modo tienda |
layout | el andamio DOM, lea live (nunca una instantánea). layout.canvas es donde va el cromo acoplado al lienzo |
addElement(element, slot?) | ponga un UIComponent en el ciclo de vida, montado en slot |
addPanel(panel) / removePanel(id) / refreshPanel(id?) | paneles de barra lateral: la misma puerta que UI.extraPanels |
addDockTab(tab) / removeDockTab(id) | un panel en el muelle inferior: la misma puerta por la que entra la mesa incorporada |
addRailMode(mode) / removeRailMode(id) | un modo en el carril izquierdo, al lado de Seleccionar / Crear / Ver / Física |
addTopBarAction(action) / removeTopBarAction(id) | una pastilla en la barra superior: la misma puerta que UI.topBar.actions |
addPivot(definition) | un enriquecimiento ejecutable: consulte Pivotes y enriquecimiento |
onPhase(phase, cb) | gancho afterMount / graphReady / destroy; devuelve una cancelación de suscripción |
addKeybinding(binding) | un atajo que se elimina cuando se derriba la interfaz de usuario |
keyManager | el registro de combinación de teclas, para cualquier cosa más involucrada |
El key de un enlace son modificadores y luego la clave, unida por +: 'Shift+K', 'Ctrl+F', 'ArrowUp'. Escriba modificadores como Ctrl, Meta (Cmd en macOS), Shift, Alt, en ese ordene y use Mod para hacer coincidir Ctrl o Meta, que es lo que significa un atajo quiere trabajar en ambas plataformas. La clave en sí es KeyboardEvent.key palabra por palabra, por lo que es distingue entre mayúsculas y minúsculas y Mayús lo cambia: las dos mitades de un par de deshacer son 'Mod+z' y 'Mod+Shift+Z'. Los enlaces nunca se activan mientras un campo de texto tiene el foco.
Las ranuras layout que existen dependen del modo: canvas y notification siempre, graphnavigation en todos los modos excepto static, mainheader / modal / slidePanel / moderail / toolpanel / flyout / legend en full y light, y sidebar en full solamente. Lea la ranura en el momento que la necesite en lugar de almacenarla en caché.
Contribuir con un elemento de interfaz de usuario
Amplíe UIComponent y el ciclo de vida será impulsado por usted, incluido el desmontaje de cualquier cosa registrada a través de track / listen / trackInteraction:
import { UIComponent } from 'pivotick'
class Watermark extends UIComponent {
onMount(slot) {
this.element = document.createElement('div')
this.element.textContent = 'draft'
slot?.appendChild(this.element)
}
onGraphReady() {
// `listen` and `trackInteraction` unsubscribe themselves on destroy
this.trackInteraction('canvasZoom', () => this.reposition())
}
onDestroy() {
this.element?.remove()
}
}
const watermark: PivotickPlugin = {
name: 'watermark',
install: (ctx) => ctx.addElement(new Watermark(ctx.ui), ctx.layout?.canvas),
}Las cuatro fases son mount(slot) → afterMount() → graphReady() → destroy(). graphReady se activa una vez que la simulación se ha asentado, por lo que pueden pasar segundos después montar en un gráfico grande: cualquier cosa que deba estar en la pantalla inmediatamente pertenece onMount / onAfterMount (diferió un marco si es necesario graph.renderer, que es construido después de la interfaz de usuario).
Vea la galería Extender con un complemento tarjeta para un ejemplo completo y en vivo.
Contribuyendo con un panel de acoplamiento
ctx.addDockTab coloca un panel en el base inferior, al lado de los datos mesa. El muelle es dueño de la región: su altura, su divisor, su pliegue y la franja que nombra los paneles, y su panel es dueño de lo que contiene:
const auditLog: PivotickPlugin = {
name: 'auditLog',
install: (ctx) => ctx.addDockTab({
label: 'Audit',
render: () => buildPane(ctx.graph), // once, on first activation
toolbar: () => [clearButton], // on every activation
onActivate: () => resumePainting(),
onDeactivate: () => stopPainting(),
}),
}Vale la pena saber cuatro cosas:
- Una pestaña es un panel, no una vista de uno. Si su panel tiene varias vistas propias, permanece en una sola pestaña de acoplamiento y activa su propio interruptor en
toolbar, llamandohandle.refresh()para cambiar de cuerpo, lo que vuelve a invocarrender. Eso es exactamente lo que el la tabla de datos sirve paraNodes/Edges, y por qué la tira del muelle nunca aplana una vistas del panel al lado de otro panel. Cambiar su propio DOM detrás de la base no no funciona: mantiene el elementorenderque le dio y volvería a adjuntar un nodo obsoleto en el próxima activación. Dibuje un interruptor interno como un control segmentado, no como pestañas: el externo El nivel ya parece pestañas.pvt-dock-viewsen la tira ypvt-dock-viewen cada una El botón (másactive) es público, por lo que se parece al interruptor de la tabla y sigue el tema sin que usted lo repita tampoco. - El primer panel crea la región. Los complementos se instalan después se crea la interfaz de usuario, por lo que aparece una pestaña siempre llega demasiado tarde para que la propia puerta de modo del muelle haya dicho que sí en su nombre. Al registrar uno, se crea el muelle, lo que significa que su complemento funciona con
UI.table: falsey no necesita nada activado excepto el modofull. renderse llama una vez, perezosamente, la primera vez que se abre el panel; el elemento es se guarda y se vuelve a adjuntar después, para que mantenga su propia posición de desplazamiento.toolbares reconstruido en cada activación, para que sus controles puedan leer el estado actual de su panel.onActivate/onDeactivateson la única señal de que estás fuera de la pantalla, y qué Qué hacer con ellos depende de su panel. Contenido que es función de la corriente del gráfico. El estado puede dejar de funcionar mientras está oculto y volver a derivar al regresar; eso es lo que la tabla lo hace. Contenido que se perdería algo (un panel que mira un autobús en vivo; un evento desapareció una vez que se ha disparado) tiene que seguir trabajando y simplemente dejar de pintar, eliminando su trabajo atrasado cuando regrese. Tampoco prefiere nada sobre los ganchos.
Una pestaña no es un UIComponent, por lo que nada influye en las fases del ciclo de vida. Cuando tu panel necesita graphReady, mantén presionado un UIComponent, addElement y llama a addDockTab desde su onMount: consulte Contribución de un elemento de interfaz de usuario arriba.
Vea la tarjeta de galería Agregar un panel de acoplamiento para ver un video en vivo, Ejemplo completo: un panel con dos vistas propias, al lado de la tabla de datos.
Aportando un modo ferroviario
Los cuatro modos del carril izquierdo (Seleccionar, Crear, Ver y Física) están integrados. es tuyo: addRailMode pone un modo propio debajo de un divisor, después de ellos, ordenados entre sus pares por order.
Un modo de puntero posee el panel de herramientas contextual. Declara su tools y el panel dibuja ellos de la misma manera que se dibujan Seleccionar y Crear, con el armado, los estados habilitado/deshabilitado y el colapso manejado por usted:
const explore = {
name: 'explore-mode',
install(ctx) {
const selected = () => ctx.graph.renderer.getGraphInteraction().getSelectedNode()?.node
ctx.addRailMode({
id: 'explore',
label: 'Explore',
icon: compassSvg,
shortcut: 'E',
tools: [
{ id: 'expand', label: 'Expand neighbours', icon: plusSvg, kind: 'action',
enabled: () => !!selected(),
run: () => expandFrom(ctx.graph, selected()) },
{ id: 'walk', label: 'Path walk', icon: pathSvg, kind: 'toggle',
run: (armed) => armPathWalk(ctx.graph, armed) },
],
onExit: () => armPathWalk(ctx.graph, false),
})
},
}
new Pivotick(el, data, { plugins: [explore] })El kind de una herramienta decide cómo se comporta. Un 'action' se ejecuta una vez y sale del modo como está era. A 'toggle' arma una herramienta modal: el panel se colapsa y ** el botón del riel se transforma en el ícono y la etiqueta de esa herramienta**, exactamente como la ranura de Seleccionar se convierte en Lasso. Un 'default' es la herramienta en la que se basa el modo; asígnele un nombre en defaultTool y es a lo que vuelve el interruptor.
tools también puede ser una función, vuelva a leer cada vez que se dibuje el panel; utilícela cuando las filas dependen de lo que se seleccione, en lugar de aparecer en gris. Para cualquier cosa una fila no se puede expresar, render() devuelve un elemento añadido debajo de las filas:
ctx.addRailMode({
id: 'explore', label: 'Explore', icon: compassSvg,
tools: () => toolsFor(ctx.graph.renderer.getGraphInteraction().getSelectedNode()),
render: () => depthSlider(),
})Un modo desplegable abre una superposición de configuración, como Ver y Física. Subclase Flyout y mano addRailMode una fábrica: el núcleo monta el panel, lo abre exactamente mientras tu modo está activo y lo quita con el modo:
import { Pivotick, Flyout } from 'pivotick'
class EnrichFlyout extends Flyout {
mode = 'enrich' // must match the definition's id
template() { return this.headerRow(sparklesSvg, 'Enrich') + this.toggleRow('auto', gearSvg, 'Auto-enrich', 'Enrich on load') }
wire() { this.wireToggle('auto', () => toggleAuto(), () => isAuto()) }
}
ctx.addRailMode({
id: 'enrich', label: 'Enrich', icon: sparklesSvg,
kind: 'flyout',
flyout: (ui) => new EnrichFlyout(ui),
})Los modos son mutuamente excluyentes, por lo que el tuyo excluye Vista y Física gratis: una tienda, una modo activo.
Tres cosas que vale la pena saber:
icones un hilo SVG crudo, inyectado como HTML y nunca desinfectado. Debe provenir de una fuente en la que confíes. CSS lo ajusta a 20px en el riel, 18px en el panel.shortcutsigue las reglas normales de combinación de teclas: reclamar una clave ya es otra cosa El propietario lo oculta, con una advertencia, hasta que se elimine su modo.onExites donde se desarma. Se ejecuta cuando se sale del modo y cuando se elimina mientras está activo, en cuyo caso el carril vuelve a Seleccionar. Se ejecuta no en la interfaz de usuario derribo; los trituradores que su complemento ya rastrea cubren eso.
addRailMode devuelve un triturador y funciona en todos los modos: el botón solo se dibuja donde hay un riel, que significa full y light.
Conduciendo la ventana gráfica
Cualquier cosa que navegue por el gráfico (un minimapa, una descripción general, un control de "salto a") utiliza dos métodos de renderizado:
// The extent of everything drawn, in graph coordinates (null when there's nothing).
const bounds = graph.renderer.getContentBounds()
// Put a graph-space point in the middle of the canvas.
graph.renderer.setViewport({ x: 120, y: -40 })
graph.renderer.setViewport({ x: 0, y: 0, scale: 1.5, animate: true })setViewport deja la escala sola a menos que pases una, por lo que es la primitiva para panorámica. Para leer dónde está la vista actualmente is, invierta las esquinas del lienzo en lugar de alcanzar una transformación de zoom; esto es independiente del renderizador:
const rect = graph.UIManager.layout.canvas.getBoundingClientRect()
const topLeft = graph.renderer.screenToGraphCoordinates(rect.left, rect.top)
const bottomRight = graph.renderer.screenToGraphCoordinates(rect.right, rect.bottom)fitAndCenter() (se adapta a todo), zoomIn() / zoomOut() y focusElement(nodeOrEdge) siguen siendo los atajos para los casos comunes.
El minimapa
Un complemento propio: una descripción general en caché de todo el gráfico acoplado en una esquina del lienzo, con un rectángulo que muestra lo que hay en la pantalla. Haga clic para volver a centrar la vista; arrastra el rectángulo para realizar una panorámica. Un interruptor muy pequeño en la esquina a la que se enfrenta lo pliega. solo a ese botón y lo trae de vuelta.
full el modo monta uno para ti: es parte del cromo de ese modo, como el encabezado, la barra lateral y el carril modal. Cualquier otro modo te lo deja a ti:
// full mode: already there, nothing to install
new Pivotick(container, data, { UI: { mode: 'full' } })
// any other mode: ask for it, with `UI.minimap` …
new Pivotick(container, data, { UI: { mode: 'light', minimap: true } })
// … or as the plugin it is
import { Pivotick, minimap } from 'pivotick'
new Pivotick(container, data, { UI: { mode: 'light' }, plugins: [minimap()] })UI.minimap toma el mismo {@link MinimapOptions} que el complemento, así que { minimap: { position: 'top-left' } } configura el modo completo que trae consigo. UI.minimap: false lo suprime. Pasar el tuyo minimap() en plugins también gana - El modo completo se deja a un lado en lugar de montar un segundo, por lo que un existente plugins: [minimap({ … })] sigue funcionando exactamente como antes.
<!-- browser build: it hangs off the global, like Node and Edge -->
<script>new Pivotick(el, data, { plugins: [Pivotick.minimap()] })</script>Opciones
| Opción | Tipo | Predeterminado | que hace |
|---|---|---|---|
position | 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left' | 'bottom-right' | En qué esquina se acopla. 'bottom-right' es la única esquina que el cromo incorporado deja libre en el modo full. |
width | number | 200 | Ancho en CSS píxeles, borde incluido. |
height | number | derivado | Altura en CSS píxeles. Omitido, sigue la relación de aspecto del lienzo (fijada a 70–400 px) para que el rectángulo mantenga la forma de la ventana gráfica real. |
collapsed | boolean | 'auto' | false, o 'auto' para los montajes en modo full | En qué estado se abre. La palanca siempre está ahí; aquí es sólo donde comienza. Consulte Salir del camino para 'auto'. |
Nada más es configurable, porque no es necesario que lo sea: el nivel de detalle y la cadencia de redibujado se adapta al gráfico.
lo que dibuja
Los nodos son puntos del color que les dio el renderizador, los bordes son líneas finas debajo de ellos y Las notas, el único contenido con tamaño propio, están dibujadas a escala como translúcidas. bloques de su propio color, sobre los nodos tal como están en el lienzo. Oculto y el contenido filtrado queda fuera. Pasados unos cuantos miles de nodos, la imagen se convierte en una densidad. mapa en su lugar (un sello por nodo, sin bordes), que se lee mejor que decenas de miles de puntos superpuestos; las notas se dibujan en cualquier dirección. Todo está almacenado en caché y solo se vuelve a rasterizar cuando la imagen realmente cambió: un nodo cayó, una nota se movió, el diseño se acomodó, un filtro aplicado, por lo que la panorámica y el zoom permanecen libres sin importar el tamaño del gráfico.
Una tarjeta HTML (html en shape: 'none') no tiene ningún color que el minimapa pueda leer, por lo que es un punto toma el color del dibujo más pequeño que sea una forma: el estilo base, o bien el primer nivel de este tipo en tiers. Sin ninguno, o con un color transparent, el punto se dibuja en La propia tinta del minimapa (--pvt-minimap-ink). Los puntos tienen el tamaño de lo que cubre el nodo, por lo que un card o un nodo renderNode obtiene un punto del tamaño de una tarjeta en lugar de uno del tamaño de size.
Quitarse del camino
Un minimapa que pediste permanece donde lo pusiste. El modo one full se monta en tu nombre no fue solicitado, entonces abre collapsed: 'auto' y toma en cuenta el lienzo: permanece abierto mientras el lienzo tiene al menos cuatro minimapas de ancho y alto, y se pliega lejos al interruptor debajo de eso. Sigue el lienzo a partir de ese momento, plegándose. cuando la barra lateral se abre sobre ella o la ventana se estrecha, regresando cuando lo hace la habitación.
En el momento en que alguien lo pliega o lo vuelve a traer, ya sea mediante la palanca o mediante setCollapsed() — eso se detiene: se mantiene una elección explícita y no se anula el cambio de tamaño posteriormente eso. Pase un collapsed: true / false explícito para excluirse de 'auto' desde el principio.
La flecha del interruptor apunta a la esquina en la que se acopla el minimapa: la dirección en la que se pliega. de distancia y se voltea una vez colapsado. Plegado no dibuja nada en absoluto, ni siquiera el rectángulo, por lo que no cuesta nada mientras esté fuera del camino; al abrirlo se vuelve a rasterizar para cómo se verá el lienzo para entonces. setCollapsed(boolean) y isCollapsed() en el Minimap la instancia maneja lo mismo desde el código.
Lo que atrae y lo que cuesta.
El minimapa asigna los límites de contenido ** junto con la ventana gráfica actual**, por lo que el El rectángulo siempre es visible y tiene un tamaño honesto: aléjalo y el gráfico se reduce. dentro del marco en lugar de que el rectángulo se deslice fuera de él.
Mantiene el gráfico en un mapa de bits fuera de la pantalla y solo lo vuelve a rasterizar cuando la imagen realmente cambiado: en un cambio de datos, cuando un filtro oculta o restaura nodos, cuando un nodo es se suelta después de arrastrarlo, cada décimo tic de simulación mientras se asienta un diseño y al cambiar el tamaño. Desplazar y hacer zoom vuelve a dibujar una imagen y un rectángulo, por lo que navegar cuesta lo mismo si el gráfico tiene 20 nodos o 50.000.
El detalle se degrada con el tamaño, a propósito:
| Gráfico | Dibujado como |
|---|---|
| ≤ 1500 nodos | un punto por nodo en el color que resolvió el renderizador, más bordes finos (más allá de 4000 bordes) |
| Más de 1500 nodos | un sello por nodo en una sola tinta, acumulación alfa: las regiones densas se leen como un mapa de densidad y no se resuelve ningún estilo por nodo |
Funciona en los modos full, light y viewer, y full es el que lo monta sin que me lo pidan. En static, que no promete interacciones, no está montado y instalarlo allí advierte.
Vea la tarjeta de la galería Minimap para ver una en vivo.