SOURCE

console 命令行工具 X clear

                    
>
console
const { Graph, Cell, Node, Dom } = X6;
// 自定义节点
Graph.registerNode(
    'org-node',
    {
        width: 72,
        height: 100,
        markup: [
            {
                tagName: 'rect',
                attrs: {
                    class: 'card',
                },
            },
            {
                tagName: 'path',
                attrs: {
                    class: 'image'
                },
            },
            {
                tagName: 'text',
                attrs: {
                    class: 'rank',
                },
            },
            {
                tagName: 'text',
                attrs: {
                    class: 'name',
                },
            },
            {
                tagName: 'text',
                attrs: {
                    class: 'third',
                },
            }
        ],
        attrs: {
            '.card': {
                rx: 50,
                ry: 50,
                refY: 4,
                width: 72,
                height: 72,
                fill: '#1890FF',
                pointerEvents: 'visiblePainted',
            },
            '.image': {
                refX: 12,
                refY: 16,
                fill: '#ffffff',
                event: 'node:expand'
            },
            '.rank': {
                refX: 0.5,
                refY: 95,
                fill: '#000',
                fontFamily: 'Courier New',
                fontSize: 14,
                fontWeight: '600',
                textAnchor: 'middle',
                textVerticalAnchor: 'middle',
            },
            '.name': {
                refX: 0.5,
                refY: 110,
                fill: '#000000',
                fontFamily: 'Arial',
                fontSize: 12,
                textAnchor: 'middle',
            },
            '.third': {
                refX: 0.5,
                refY: 130,
                fill: '#333333',
                fontFamily: 'Arial',
                fontSize: 12,
                textAnchor: 'middle',
            }
        },
    },
    true,
)

// 自定义边
Graph.registerEdge(
    'org-edge',
    {
        zIndex: -1,
        attrs: {
            line: {
                strokeWidth: 2,
                strokeDasharray: "5, 5",
                stroke: '#A2B1C3',
                sourceMarker: null,
                targetMarker: null,
            },
        },
    },
    true,
)

const clockIcon = 'm24,0c13.26,0,24,10.74,24,24s-10.74,24-24,24S0,37.26,0,24,10.74,0,24,0Zm-.16,9c-.54,0-.99.41-1.04.94l-1.8,17.95h0s0,0,0,0h0s0,0,0,0l13.59,4.38c.51.16,1.06-.07,1.29-.55v-.02c.24-.49.06-1.07-.39-1.36l-9.16-5.75-1.47-14.67c-.05-.53-.49-.93-1.02-.94h-.02Z';
// 布局方向
const dir = 'TB' // LR RL TB BT

// 创建画布
const graph = new Graph({
    container: document.getElementById('canvasBox'),
    panning: true,
    interacting: false,
    background: {
        color: '#ffffff'
    },
    mousewheel: {
        enabled: true,
        zoomAtMousePosition: true,
        minScale: 0.2,
        factor: 1.1,
        maxScale: 2
    }
})

// 监听自定义事件
function setup() {
    graph.on('node:expand', ({ e, node }) => {
        e.stopPropagation()
        const nodeData = node.getData();
        if (nodeData.loaded) {
            const expanded = nodeData.expanded;
            nodeData.expanded = !nodeData.expanded;
            const collapsed = !nodeData.expanded
            const run = (pre) => {
                const succ = graph.getSuccessors(pre, { distance: 1 })
                if (succ) {
                    succ.forEach((node) => {
                        node.toggleVisible(!collapsed)
                        if (expanded) {
                            run(node)
                        }
                    })
                }
            }
            run(node)
        } else {
            nodeData.expanded = true;
            node.setData({
                loaded: true
            })
            const member = createNode(
                'Employee',
                'New Employee',
                'New Employee'
            )
            graph.addCell([member, createEdge(node, member)])
        }
        layout()
    })
}

// 自动布局
function layout() {
    const nodes = graph.getNodes()
    const edges = graph.getEdges()
    const g = new dagre.graphlib.Graph()
    g.setGraph({ rankdir: dir, nodesep: 20, ranksep: 80 })
    g.setDefaultEdgeLabel(() => ({}))

    const width = 260
    const height = 90
    nodes.forEach((node) => {
        g.setNode(node.id, { width, height })
    })

    edges.forEach((edge) => {
        const source = edge.getSource()
        const target = edge.getTarget()
        g.setEdge(source.cell, target.cell)
    })

    dagre.layout(g)

    g.nodes().forEach((id) => {
        const node = graph.getCellById(id)
        if (node) {
            const pos = g.node(id)
            node.position(pos.x, pos.y)
        }
    })

    edges.forEach((edge) => {
        const source = edge.getSourceNode()
        const target = edge.getTargetNode()
        const sourceBBox = source.getBBox()
        const targetBBox = target.getBBox()

        if ((dir === 'LR' || dir === 'RL') && sourceBBox.y !== targetBBox.y) {
            const gap =
                dir === 'LR'
                    ? targetBBox.x - sourceBBox.x - sourceBBox.width
                    : -sourceBBox.x + targetBBox.x + targetBBox.width
            const fix = dir === 'LR' ? sourceBBox.width : 0
            const x = sourceBBox.x + fix + gap / 2
            edge.setVertices([
                { x, y: sourceBBox.center.y },
                { x, y: targetBBox.center.y },
            ])
        } else if (
            (dir === 'TB' || dir === 'BT') &&
            sourceBBox.x !== targetBBox.x
        ) {
            const gap =
                dir === 'TB'
                    ? targetBBox.y - sourceBBox.y - sourceBBox.height
                    : -sourceBBox.y + targetBBox.y + targetBBox.height
            const fix = dir === 'TB' ? sourceBBox.height : 0
            const y = sourceBBox.y + fix + gap / 2
            edge.setVertices([
                { x: sourceBBox.center.x, y },
                { x: targetBBox.center.x, y },
            ])
        } else {
            edge.setVertices([])
        }
    })
}

function createNode(rank, name, third) {
    const attrs = {
        '.image': { d: clockIcon },
        '.rank': {
            text: Dom.breakText(rank, { width: 160, height: 45 }),
        },
        '.name': {
            text: Dom.breakText(name, { width: 160, height: 45 }),
        }
    }
    if (third) {
        attrs['.third'] = {
            text: Dom.breakText(third, { width: 160, height: 45 }),
        }
    }
    return graph.createNode({
        shape: 'org-node',
        data: {
            loaded: false,
            expanded: false
        },
        attrs
    })
}

function createEdge(source, target) {
    return graph.createEdge({
        shape: 'org-edge',
        source: { cell: source.id },
        target: { cell: target.id },
    })
}

const nodes = [
    createNode('Founder & Chairman', 'Pierre Omidyar'),
    createNode('President & CEO', 'Margaret C. Whitman'),
    createNode('President, PayPal', 'Scott Thompson'),
    createNode('President, Ebay Global Marketplaces', 'Devin Wenig'),
    createNode('Senior Vice President Human Resources', 'Jeffrey S. Skoll'),
    createNode('Senior Vice President Controller', 'Steven P. Westly'),
]

const edges = [
    createEdge(nodes[1], nodes[2]),
    createEdge(nodes[1], nodes[3]),
    createEdge(nodes[1], nodes[4]),
    createEdge(nodes[1], nodes[5]),
]

graph.resetCells([...nodes, ...edges])
layout()
graph.zoomTo(0.8)
graph.centerContent()
setup()
<script src="https://cdnjs.cloudflare.com/ajax/libs/antv-x6/2.0.0/index.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@antv/x6/dist/index.js"></script>
<script src="https://unpkg.com/@antv/x6/dist/index.js"></script>
<script src="https://dagrejs.github.io/project/dagre/v0.7.5/dagre.min.js"></script>

<div id="canvasBox" class="canvas-box"></div>
html,body {
    padding: 0;
    margin: 0;
}

.canvas-box {
    height: 500px;
    border: 1px solid #cccccc;
}
.canvas-box .x6-node .image {
    cursor: pointer;
}