Aller au contenu
Pivotick
Main Navigation Accueil
Bien démarrer
Configuration
Référence de l’API
GalerieProjetsEssayer !

Français

English
简体中文
Deutsch
Português

Français

English
简体中文
Deutsch
Português

Apparence

Sidebar Navigation

Bien démarrer

Projets utilisant Pivotick

Configuration

Fonctions de rappel

Couches d’arêtes

Simplifier

Disposition

Rendu

Simulation

Personnaliser l’interface

Barre latérale

Infobulle

Menu contextuel

Filtres

Légende

Tableau de données

Style de l’interface

Traduire l’interface

Extensions

API Pivotick

Pivots et enrichissement

Enregistrer les résultats des pivots

Annulation et historique

Sécurité

Documentation de l’API

Galerie

Bases

Style des nœuds

Style des arêtes

Dispositions

Événements et rappels

Personnalisation de l’interface

Édition et création

Notes et annotations

Filtres, recherche et hiérarchie

Contrôle par programmation

Thèmes et performances

Pivots et enrichissement

Démonstrations complètes

Nœud HTML personnalisé ​

Lorsqu'aucune combinaison de shape, color, icon et text ne suffit, donnez au moteur de rendu un balisage. html est un canal de style ordinaire, donc une carte se résout via la même chaîne de priorité que n'importe quelle autre — ce qui signifie qu'une carte est quelque chose que vous donnez à un type de nœud, pas à l'ensemble du graphique.

Chaque nœud ci-dessous sort d'un nodeStyleMap :

NœudDéclaré commeCe que vous obtenez
les trois cartes d'équipeshape: 'none' + htmlla carte est le nœud - aucune forme derrière lui, et sa boîte mesurée détermine la disposition et les bords
Sur appelshape: 'circle' + htmlle disque est toujours dessiné et size est toujours le sol — une pilule chevauchant une forme
staging · prodnon htmlhexagones simples, de la même carte
Gel Q3shape: 'none', non htmlune étiquette nue : rien de dessiné à part le texte, toujours déplaçable et sélectionnable
Opérationsle propre style.html du nœudun nœud remplaçant la carte de son type
Ingénierierender.renderNodeun rappel global qui carde only ce nœud et ne renvoie rien pour le reste

Quatre choses qui méritent d’être provoquées :

  • Cliquez sur une carte. Elle est mise en surbrillance comme n'importe quel autre nœud. Un nœud de carte uniquement conserve une boîte invisible en dessous précisément pour que les apparences sélectionnées et survolées aient quelque chose sur quoi atterrir – sans une, un nœud personnalisé n'avait aucun état sélectionné.
  • Faites glisser les cartes. La force de collision espace les réels boîtes de cartes, et les bords s'arrêtent à la bordure de chaque carte plutôt que sur un cercle dessiné autour d'elle. Rien dans la mise en page n’a dû être réglé pour cela.
  • Comparez Operations à On call. Les deux sont html. Un seul a une forme derrière lui, et c'est tout ce que fait shape: 'none'.
  • Regardez le balisage Operations. Sa racine est width: 100% — la forme qui se réduisait sur la boîte d'espace réservé et le clip. Les cartes sont mesurées à l'intérieur d'une boîte rétractable, elles se résolvent donc par rapport à leur propre contenu et vous n'avez rien à faire.

Balisage approuvé uniquement

Tout ce que vous renvoyez est inséré tel quel, donc ces cartes sont construites avec textContent plutôt qu'en interpolant les données du nœud dans une chaîne de balisage. Voir Sécurité.

js
/** A team card. Built with `textContent`, never by interpolating data into markup. */
function teamCard(node) {
    const d = node.getData()
    const card = document.createElement('div')
    card.setAttribute('style', [
        'display: flex',
        'align-items: center',
        'gap: 8px',
        'padding: 8px 12px',
        'border-radius: 12px',
        'background: #ffffff',
        `border: 2px solid ${d.tone}`,
        'box-shadow: 0 2px 6px rgba(15, 23, 42, 0.18)',
        'font-family: system-ui, sans-serif',
        'white-space: nowrap',
    ].join(';'))

    const emoji = document.createElement('span')
    emoji.style.fontSize = '20px'
    emoji.textContent = d.emoji

    const stack = document.createElement('span')
    stack.setAttribute('style', 'display: flex; flex-direction: column; line-height: 1.2')
    const title = document.createElement('strong')
    title.setAttribute('style', 'font-size: 13px; color: #0f172a')
    title.textContent = d.name
    const lead = document.createElement('span')
    lead.setAttribute('style', `font-size: 11px; color: ${d.tone}`)
    lead.textContent = d.lead
    stack.append(title, lead)

    card.append(emoji, stack)
    return card
}

/** A small pill, drawn on top of the shape it is given rather than replacing it. */
function rotaPill(node) {
    const pill = document.createElement('div')
    pill.setAttribute('style', [
        'display: inline-flex',
        'padding: 2px 8px',
        'border-radius: 999px',
        'background: #0f172a',
        'color: #fff',
        'font: 600 10px system-ui, sans-serif',
        'white-space: nowrap',
    ].join(';'))
    pill.textContent = node.getData().lead
    return pill
}

const options = {
    render: {
        nodeTypeAccessor: (node) => node.getData()?.kind,
        nodeStyleMap: {
            // A card and nothing else. `shape: 'none'` is what makes it the whole node:
            // no disc behind it, and the node's radius and edge anchors come from the
            // measured card instead of from `size`.
            team: {
                shape: 'none',
                html: teamCard,
                // The card carries its own title, so switch the SVG label off. `undefined`
                // would fall through to whatever the chain said; `''` is how you clear it.
                text: '',
            },
            // The other way round: the shape is still drawn, and `size` is still the
            // smallest the node may be, so the pill rides on a coloured disc.
            rota: {
                shape: 'circle',
                size: 30,
                color: '#f59e0b',
                html: rotaPill,
                text: (node) => node.getData()?.name,
                textVerticalShift: -1.5,
            },
            // No card at all — ordinary shapes, from the same style map.
            env: {
                shape: 'hexagon',
                size: 22,
                color: '#16a34a',
                text: (node) => node.getData()?.name,
                textVerticalShift: -1.4,
            },
            // Shapeless with no card either: a label floating on the canvas. Still
            // draggable and selectable, over `2 × size`.
            milestone: {
                shape: 'none',
                size: 26,
                text: (node) => node.getData()?.name,
                // Nothing behind it to shorten against, so draw it whole.
                textTruncate: false,
            },
        },
        // A global callback can card *some* nodes now: returning nothing hands the node
        // back to the chain above, so everything else is styled as usual. Here it draws
        // the badge on whichever team owns the release.
        renderNode: (node) => {
            if (node.id !== 'eng') return undefined
            const card = teamCard(node)
            card.style.borderWidth = '3px'
            const flag = document.createElement('span')
            flag.setAttribute('style', [
                'margin-left: 8px',
                'padding: 2px 6px',
                'border-radius: 6px',
                'background: #fee2e2',
                'color: #b91c1c',
                'font: 700 10px system-ui, sans-serif',
            ].join(';'))
            flag.textContent = 'RELEASE OWNER'
            card.append(flag)
            return card
        },
        defaultEdgeStyle: { markerEnd: 'arrow', strokeWidth: 1.5 },
    },
    simulation: {
        d3LinkDistance: 110,
        d3ManyBodyStrength: -700,
        d3CollideRadiusMultiplier: 1.15,
    },
    UI: {
        mode: 'full',
        minimap: false,
        table: false,
        // This card is about the nodes, so nothing else competes for the canvas.
        legend: false,
    },
}
js
const data = {
    nodes: [
        // Cards, from a `nodeStyleMap` entry.
        { id: 'design', data: { kind: 'team', name: 'Design', lead: 'Lena', emoji: '🎨', tone: '#8b5cf6' } },
        { id: 'eng', data: { kind: 'team', name: 'Engineering', lead: 'Raj', emoji: '⚙️', tone: '#2563eb' } },
        { id: 'data', data: { kind: 'team', name: 'Data', lead: 'Omar', emoji: '📊', tone: '#0891b2' } },
        // A card on a shape: the disc is still drawn behind it.
        { id: 'oncall', data: { kind: 'rota', name: 'On call', lead: 'Maya' } },
        // Plain shapes, from the same style map.
        { id: 'staging', data: { kind: 'env', name: 'staging' } },
        { id: 'prod', data: { kind: 'env', name: 'prod' } },
        // A label and no shape at all.
        { id: 'q3', data: { kind: 'milestone', name: 'Q3 freeze' } },
        // One node overriding its type's card with its own.
        {
            id: 'ops', data: { kind: 'team', name: 'Operations', lead: 'Theo', emoji: '🧭', tone: '#ea580c' },
            style: {
                shape: 'none',
                html: (node) => {
                    const d = node.getData()
                    const card = document.createElement('div')
                    // `width: 100%` and it still measures its content: the card is
                    // measured inside a shrink-to-fit box, not the placeholder.
                    card.setAttribute('style', [
                        'display: flex',
                        'align-items: center',
                        'gap: 8px',
                        'width: 100%',
                        'padding: 10px 14px',
                        'border-radius: 999px',
                        `background: ${d.tone}`,
                        'color: #fff',
                        'font: 600 13px system-ui, sans-serif',
                        'white-space: nowrap',
                    ].join(';'))
                    card.textContent = `${d.emoji} ${d.name} — always on`
                    return card
                },
            },
        },
    ],
    edges: [
        { from: 'design', to: 'eng' },
        { from: 'eng', to: 'data' },
        { from: 'ops', to: 'eng' },
        { from: 'oncall', to: 'ops' },
        { from: 'eng', to: 'staging' },
        { from: 'staging', to: 'prod' },
        { from: 'q3', to: 'prod' },
        { from: 'data', to: 'prod' },
        { from: 'design', to: 'ops' },
    ],
}

Voir Rendu → HTML nœuds pour le contrat complet — lorsque la carte est mesurée, ce qu'une carte ne pas s'éteint, et ne carde que certains nœuds.

Pager
Page suivanteBien démarrer