跳转到内容
Pivotick
Main Navigation 首页
快速入门
配置
API 参考
示例库项目在线体验

简体中文

English
Français
Deutsch
Português

简体中文

English
Français
Deutsch
Português

外观

Sidebar Navigation

快速入门

使用 Pivotick 的项目

配置

回调

边图层

简化

布局

渲染

模拟

自定义界面

侧栏

提示框

上下文菜单

筛选

图例

数据表格

界面样式

翻译界面

插件

Pivotick API

拓展与数据增强

保存拓展结果

撤销与历史记录

安全

API 文档

示例库

基础

节点样式

边样式

布局

事件与回调

界面自定义

编辑与创建

备注与注释

筛选、搜索与层级

编程控制

主题与性能

拓展与数据增强

综合示例

自定义HTML节点 ​

当 shape、color、icon 和 text 的组合都不起作用时,请向渲染器提供一些标记。 html 是一个普通样式通道,因此卡片通过 ** 与其他任何卡片相同的优先链** 进行解析 — 这意味着卡片是您给予 * 一种节点* 的东西,而不是整个图。

下面的每个节点都来自一个 nodeStyleMap:

节点声明为你得到什么
三张团队卡shape: 'none' + html卡片是节点——它后面没有形状,它的测量框驱动布局和边缘
随叫随到shape: 'circle' + html圆盘仍然是绘制的,size仍然是地板——一颗药丸骑在一个形状上
staging · prod没有html纯六边形,来自同一张地图
第三季度冻结shape: 'none',无html裸标签:除了文本之外没有绘制任何内容,仍然可以拖动和选择
操作节点自己的style.html一个节点覆盖其类型的卡
工程render.renderNode全局回调,卡住only此节点,其余节点不返回任何内容

有四件事值得一提:

  • 单击卡片。它像任何其他节点一样突出显示。仅限卡片的节点在其下方精确地保留了一个不可见的框,因此选定的和悬停的外观都有可以着陆的东西 - 如果没有,自定义节点根本没有选定状态。
  • 拖动卡片。碰撞力将real卡片框隔开,边缘停止在每张卡片的边界处,而不是在围绕其绘制的圆圈上。无需为此调整任何布局。
  • 比较Operations与On call。都是html。只有一个背后有一个形状,这就是 shape: 'none' 的全部作用。
  • 查看Operations'标记。它的根是width: 100%——用于折叠到占位符框和剪辑上的形状。卡片是在缩小到适合的盒子内进行测量的,因此它会根据其自身的内容进行解析,并且您无需执行任何操作。

仅受信任的标记

无论您返回什么,都会按原样插入,因此这些卡片是使用 textContent 构建的,而不是通过将节点数据插入标记字符串来构建的。请参阅安全。

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' },
    ],
}

请参阅渲染 → HTML节点了解完整合约 - 当测量卡时,卡的功能不关闭,并且仅对某些节点进行梳理。

Pager
下一页快速入门