import type { Graph, NodeData, SingleLayoutOptions } from '@antv/g6'; import { get } from 'lodash'; import React from 'react'; import type { TextNodeProps } from '../../core/base'; import { CollapseExpandIcon, RCNode } from '../../core/base'; import { formatLabel } from '../../core/utils/label'; import { measureTextSize } from '../../core/utils/measure-text'; import { getNodeSide } from '../../core/utils/node'; import { getBoxedTextNodeStyle, getLinearTextNodeStyle } from '../../core/utils/tree'; import type { GraphOptions } from '../../types'; import type { MindMapOptions } from './types'; const { ArrowCountIcon } = CollapseExpandIcon; const { TextNode } = RCNode; export const DEFAULT_OPTIONS: GraphOptions = { node: { type: 'react', state: { active: { halo: false, }, selected: { halo: false, }, }, }, edge: { type: 'cubic-horizontal', style: { lineWidth: 3, }, }, transforms: (prev) => [ ...prev, { type: 'collapse-expand-react-node', key: 'collapse-expand-react-node', enable: false, trigger: 'icon', iconRender: function (isCollapsed: boolean, data: NodeData) { const side = getNodeSide(this as unknown as Graph, data); return ( ); }, iconPlacement: function (data: NodeData) { const side = getNodeSide(this as unknown as Graph, data); return side === 'left' ? 'left' : 'right'; }, }, ], layout: { type: 'mindmap', direction: 'H', preLayout: false, getWidth: () => 120, getHGap: () => 64, }, animation: { duration: 500, }, }; export function getMindMapOptions({ type, direction, nodeMinWidth, nodeMaxWidth, labelField, }: Pick): GraphOptions { let options: GraphOptions = {}; if (type === 'boxed') { const minWidth = nodeMinWidth || 120; const maxWidth = nodeMaxWidth || 300; options = { node: { style: { component: (data: NodeData) => { const depth = data.depth; const color = data.style?.color; const label = formatLabel(data, labelField); const { font } = getBoxedTextNodeStyle(label, minWidth, maxWidth, depth); const props = { text: label, color, maxWidth, font } as TextNodeProps; Object.assign( props, depth === 0 ? { type: 'filled', color: '#f1f4f5', style: { color: '#252525' } } : depth === 1 ? { type: 'filled' } : { type: 'outlined' }, ); return ; }, size: (data: NodeData) => { const label = formatLabel(data, labelField); return getBoxedTextNodeStyle(label, minWidth, maxWidth, data.depth).size; }, dx: function (data: NodeData) { const side = getNodeSide(this as unknown as Graph, data); const label = formatLabel(data, labelField); const [width] = getBoxedTextNodeStyle(label, minWidth, maxWidth, data.depth).size; return side === 'left' ? -width : side === 'center' ? -width / 2 : 0; }, ports: [{ placement: 'left' }, { placement: 'right' }], }, }, edge: { style: { stroke: function (data) { const source = this.getNodeData(data.source); return get(source, 'style.color', '#99ADD1') as string; }, }, }, transforms: (prev) => [...prev, { type: 'assign-color-by-branch', key: 'assign-color-by-branch' }], layout: { type: 'mindmap', getHeight: (data) => { const label = formatLabel(data, labelField); const [, height] = getBoxedTextNodeStyle(label, minWidth, maxWidth, data.depth).size; return height; }, getVGap: () => 14, }, }; } else if (type === 'linear') { const minWidth = nodeMinWidth || 0; const maxWidth = nodeMaxWidth || 300; options = { node: { style: { component: function (data: NodeData) { const side = getNodeSide(this as unknown as Graph, data); const depth = data.depth; const color = data.style?.color; const label = formatLabel(data, labelField); const { font } = getLinearTextNodeStyle(label, minWidth, maxWidth, depth); const props = { text: label, color, maxWidth, font } as TextNodeProps; Object.assign( props, depth === 0 ? { type: 'filled', color: '#f1f4f5', style: { color: '#252525' } } : { type: 'underlined', style: side === 'left' ? { textAlign: 'right' } : side === 'center' ? { textAlign: 'center' } : {}, }, ); return ; }, size: (data: NodeData) => { const label = formatLabel(data, labelField); return getLinearTextNodeStyle(label, minWidth, maxWidth, data.depth).size; }, dx: function (data: NodeData) { const side = getNodeSide(this as unknown as Graph, data); const label = formatLabel(data, labelField); const [width] = getLinearTextNodeStyle(label, minWidth, maxWidth, data.depth).size; return side === 'left' ? -width : side === 'center' ? -width / 2 : 0; }, dy: function (data: NodeData) { const label = formatLabel(data, labelField); const [, height] = getLinearTextNodeStyle(label, minWidth, maxWidth, data.depth).size; return height / 2; }, ports: function (data: NodeData) { const side = getNodeSide(this as unknown as Graph, data); return side === 'center' ? [{ placement: 'left' }, { placement: 'right' }] : [{ placement: 'left-bottom' }, { placement: 'right-bottom' }]; }, }, }, edge: { style: { stroke: function (data) { const target = this.getNodeData(data.target); return get(target, 'style.color', '#99ADD1') as string; }, }, }, layout: { type: 'mindmap', getHeight: (data) => { const label = formatLabel(data, labelField); const [, height] = getLinearTextNodeStyle(label, minWidth, maxWidth, data.depth).size; return height; }, getVGap: () => 12, }, transforms: (prev) => [ ...prev.filter((t) => (t as any).key !== 'collapse-expand-react-node'), { type: 'assign-color-by-branch', key: 'assign-color-by-branch', }, { ...(prev.find((t) => (t as any).key === 'collapse-expand-react-node') as any), iconOffsetY: (data) => { if (data.depth === 0) return 0; const label = formatLabel(data, labelField); const [, height] = getLinearTextNodeStyle(label, minWidth, maxWidth, data.depth).size; return height / 2; }, }, ], }; } else { const PADDING = [24, 16]; options = { node: { style: { component: (data) => { const label = formatLabel(data, labelField); return ; }, size: (data) => { const label = formatLabel(data, labelField); return measureTextSize(label, PADDING); }, dx: function (data: NodeData) { const side = getNodeSide(this as unknown as Graph, data); const label = formatLabel(data, labelField); const [width] = measureTextSize(label, PADDING); return side === 'left' ? -width : side === 'center' ? -width / 2 : 0; }, ports: [{ placement: 'left' }, { placement: 'right' }], }, }, layout: { type: 'mindmap', getHeight: (data) => { const label = formatLabel(data, labelField); const [, height] = measureTextSize(label, PADDING); return height; }, }, }; } if (direction) { options.layout ||= {} as SingleLayoutOptions; (options.layout as SingleLayoutOptions).direction = direction === 'alternate' ? 'H' : direction === 'left' ? 'RL' : 'LR'; } return options; }