vue2-mind-map
Version:
`vue2-mind-map` 是一个功能丰富的思维导图可视化组件。该组件提供了完整的思维导图展示和交互功能,支持多种数据格式输入、灵活的布局配置、丰富的样式定制和流畅的用户交互体验。目前基于 Vue 2 构建。
557 lines (556 loc) • 19.8 kB
TypeScript
import { Vector } from "../../common/entities/Core/Vector";
import { ContentType, MindNode } from './MindNode';
import { EventEmitter } from "../../common/entities/Core/EventEmitter";
import { MouseButton } from "../../common/entities/Abilities/Draggable/BaseDraggable";
import { AlignOptions } from "../algorithm/Alignment";
export { MindNode, MouseButton };
/**
* 思维导图树形数据类型(用于描述思维导图节点的结构和内容)
* 通过 children 数组直接表示层级关系
*/
export interface TreeNode {
/** 节点唯一标识符 */
id?: string;
/** 节点位置坐标,可以是对象或 Vector 实例 */
position?: {
x: number;
y: number;
} | Vector;
/** 节点尺寸,可以是对象或 Vector 实例 */
size?: {
x: number;
y: number;
} | Vector;
/** 子节点数组 */
children?: TreeNode[];
/** 节点是否折叠,默认为 false (不折叠) */
collapsed?: boolean;
/**
* 节点内容,可以是文本、HTML 元素、HTML 元素数组,或一个返回文本/HTML元素/HTML元素数组的回调函数
* 如果是函数,它将接收原始的 TreeNode 数据作为参数
* 优先级比传入 MindMap 的 contentCallback 高
*/
content?: ContentType<TreeNode>;
/** 允许任意额外属性 */
[key: string]: any;
}
/**
* 思维导图扁平数据类型(用于描述思维导图节点的结构和内容)
* 通过 parentId 间接表示层级关系
*/
export interface FlatNode {
/** 节点唯一标识符 */
id?: string;
/** 父节点ID */
parentId?: string;
/** 节点位置坐标,可以是对象或 Vector 实例 */
position?: {
x: number;
y: number;
} | Vector;
/** 节点尺寸,可以是对象或 Vector 实例 */
size?: {
x: number;
y: number;
} | Vector;
/** 节点是否折叠,默认为 false (不折叠) */
collapsed?: boolean;
/**
* 节点内容,可以是文本、HTML 元素、HTML 元素数组,或一个返回文本/HTML元素/HTML元素数组的回调函数
* 如果是函数,它将接收原始的 FlatNode 数据作为参数
* 优先级比传入 MindMap 的 contentCallback 高
*/
content?: ContentType<FlatNode>;
/** 允许任意额外属性 */
[key: string]: any;
}
/**
* 思维导图样式配置类型
* 用于定制思维导图的视觉外观
*/
export declare type MindMapStyle = {
/** 连接线宽度 */
lineWidth?: number;
/** 连接线颜色 */
lineColor?: string;
/** 节点边框宽度 */
nodeBorderWidth?: number;
/** 节点背景颜色 */
nodeBgColor?: string;
/** 节点边框颜色 */
nodeBorderColor?: string;
};
/**
* 思维导图对齐选项类型
* 控制思维导图节点的对齐方式
*/
export declare type MindMapAlign = {
/**
* 对齐模式
* - cross-axis-start-edge: 交叉轴起始边与视口起始边对齐
* - cross-axis-center: 交叉轴居中对齐到视口中心
* - node-center: 节点中心居中对齐到视口中心
*/
mode: AlignOptions['mode'];
/** 目标节点ID,仅用于 'node-center' 模式 */
targetNodeId?: AlignOptions['targetNodeId'];
};
/**
* 思维导图拖拽配置类型
* 定制思维导图的拖拽行为
*/
export declare type MindMapDragOptions = {
/** 是否启用节点拖拽 */
enableNodeDrag?: boolean;
/** 是否启用画布拖拽 */
enableCanvasDrag?: boolean;
/** 拖拽节点的鼠标按钮 */
dragNodeButton?: MouseButton[] | MouseButton;
/** 拖拽画布的鼠标按钮 */
dragCanvasButton?: MouseButton[] | MouseButton;
/** 是否使用节流减少事件触发频率 */
useThrottle?: boolean;
};
/**
* 思维导图缩放配置类型
* 定制思维导图的缩放行为
*/
export declare type MindMapZoomOptions = {
/** 是否启用滚轮缩放 */
enableWheelZoom?: boolean;
/** 每次缩放步长,如 0.1 表示 10% */
zoomStep?: number;
/** 是否反转滚轮方向 */
invertWheelDirection?: boolean;
/** 最小缩放比例,如 0.1 表示最小缩放到 10% */
minZoom?: number;
/** 最大缩放比例,如 5.0 表示最大缩放到 500% */
maxZoom?: number;
};
/**
* 思维导图调试选项类型
* 不对外公开,仅用于开发和调试
*/
export declare type DebugOptions = {
rootContainerBgColor?: string;
drawGridBackground?: boolean;
};
export declare type NodeLike = MindNode | TreeNode | FlatNode;
/**
* MindMap 节点拖拽事件数据
*/
export interface MindMapNodeEvent {
/** 原始鼠标事件 */
originalEvent: MouseEvent;
/** 按下的鼠标按钮 */
button: MouseButton | null;
/** 被拖拽的 MindNode 节点 */
node: MindNode;
/** 拖拽开始时鼠标在容器内的 X 坐标 */
mouseX: number;
/** 拖拽开始时鼠标在容器内的 Y 坐标 */
mouseY: number;
/** X 轴拖拽距离 (仅 NODE_DRAG 事件) */
deltaX?: number;
/** Y 轴拖拽距离 (仅 NODE_DRAG 事件) */
deltaY?: number;
}
/**
* MindMap 容器尺寸变化事件数据
*/
export interface MindMapContainerResizeEvent {
/** 新的容器宽度 */
width: number;
/** 新的容器高度 */
height: number;
}
/**
* MindMap 画布拖拽事件数据
*/
export interface MindMapCanvasEvent {
/** 原始鼠标事件 */
originalEvent: MouseEvent;
/** 按下的鼠标按钮 */
button: MouseButton | null;
/** 拖拽开始时鼠标在容器内的 X 坐标 */
mouseX: number;
/** 拖拽开始时鼠标在容器内的 Y 坐标 */
mouseY: number;
/** X 轴拖拽距离 (仅 CANVAS_DRAG 事件) */
deltaX?: number;
/** Y 轴拖拽距离 (仅 CANVAS_DRAG 事件) */
deltaY?: number;
}
/**
* MindMap 缩放事件数据
*/
export interface MindMapZoomEvent {
/** 原始滚轮事件 (可选) */
originalEvent?: WheelEvent;
/** 当前的缩放比例向量 {x, y} */
scale: Vector;
/** 缩放中心在容器内的 X 坐标 */
mouseX: number;
/** 缩放中心在容器内的 Y 坐标 */
mouseY: number;
}
/**
* MindMap 布局更新事件数据
* 当思维导图的布局(例如,从水平切换到垂直)发生变化并更新完成后触发
*/
export interface MindMapLayoutEvent {
direction: 'horizontal' | 'vertical';
}
/**
* MindMap 节点销毁事件数据
*/
export interface MindMapNodeDestroyEvent {
/** 被销毁节点对应的原始数据 */
node: NodeLike;
}
/**
* MindMap 节点折叠事件数据
*/
export interface MindMapNodeCollapseEvent {
/** 折叠状态发生变化的节点 ID */
nodeId: string;
/** 当前折叠状态 */
collapsed: boolean;
/** 被折叠/展开的节点对应的原始数据 */
node: NodeLike;
}
/**
* 思维导图事件枚举
*/
export declare enum MindMapEvent {
/** 画布开始拖拽 */
CANVAS_DRAG_START = "mindmap:canvas-dragstart",
/** 画布拖拽中 */
CANVAS_DRAG = "mindmap:canvas-drag",
/** 画布结束拖拽 */
CANVAS_DRAG_END = "mindmap:canvas-dragend",
/** 缩放变化 */
ZOOM_CHANGE = "mindmap:zoom-change",
/** 容器尺寸变化 */
CONTAINER_RESIZE = "mindmap:container-resize",
/** 布局更新完成 */
LAYOUT_UPDATED = "mindmap:layout-updated",
NODE_COLLAPSE_CHANGED = "mindmap:node-collapse-changed",
/** 节点销毁 */
NODE_DESTROYED = "node:destroyed",
/** 思维导图销毁 */
MINDMAP_DESTROYED = "mindmap:destroyed"
}
/**
* MindMap 事件名称到事件数据类型的映射
*/
export interface MindMapEventMap {
[MindMapEvent.CANVAS_DRAG_START]: MindMapCanvasEvent;
[MindMapEvent.CANVAS_DRAG]: MindMapCanvasEvent;
[MindMapEvent.CANVAS_DRAG_END]: MindMapCanvasEvent;
[MindMapEvent.ZOOM_CHANGE]: MindMapZoomEvent;
[MindMapEvent.CONTAINER_RESIZE]: MindMapContainerResizeEvent;
[MindMapEvent.LAYOUT_UPDATED]: MindMapLayoutEvent;
[MindMapEvent.NODE_COLLAPSE_CHANGED]: MindMapNodeCollapseEvent;
[MindMapEvent.NODE_DESTROYED]: MindMapNodeDestroyEvent;
[MindMapEvent.MINDMAP_DESTROYED]: void;
}
/**
* 思维导图核心类
* 提供思维导图的创建、布局计算、渲染和交互能力
*/
export declare class MindMap extends EventEmitter<MindMapEventMap> {
private rootNode;
private outerContainer;
private rootContainer;
private elementToMindNodeMap;
private linesMap;
private mindNodeToOriginalDataMap;
private _isHorizontal;
private _siblingSeparation;
private _levelSeparation;
private _initialOffset?;
private _lineShape;
private _debugOptions?;
private align;
private style;
private draggableManager?;
private dragOptions;
private wheelManager?;
private zoomOptions;
/** 存储构造函数传入的 contentCallback */
private _nodeContentCallback?;
/**
* 创建思维导图实例
* @param data - 思维导图初始数据,可以是 MindNode 实例、TreeNode 对象或 FlatNode 数组
* @param containerDom - 承载思维导图的 DOM 容器元素
* @param options - 思维导图配置选项
* @param options.isHorizontal - 是否水平布局,默认为 true
* @param options.siblingSeparation - 同级节点间距(边界到边界),默认为 16
* @param options.levelSeparation - 层级间的最小距离(边界到边界),默认为 32
* @param options.initialOffset - 初始偏移量,可选
* @param options.lineShape - 连接线形状,可选值为 'straight'|'curved'|'zigzag',默认为 'curved'
* @param options.contentCallback - 节点显示内容回调函数
* @param options.align - 思维导图对齐选项
* @param options.style - 思维导图样式配置
* @param options.dragOptions - 拖拽相关配置
* @param options.zoomOptions - 缩放相关配置
* @param options.debugOptions - 调试选项
*/
constructor(data: MindNode | TreeNode | FlatNode[], containerDom: HTMLElement, options?: {
isHorizontal?: boolean;
siblingSeparation?: number;
levelSeparation?: number;
initialOffset?: {
x?: number;
y?: number;
};
lineShape?: 'straight' | 'curved' | 'zigzag';
contentCallback?: (originalData: NodeLike) => string | HTMLElement | HTMLElement[];
align?: MindMapAlign;
style?: MindMapStyle;
dragOptions?: MindMapDragOptions;
zoomOptions?: MindMapZoomOptions;
debugOptions?: DebugOptions;
});
/**
* 获取思维导图当前布局方向
* @returns 是否为水平布局
*/
get isHorizontal(): boolean;
/**
* 输出思维导图容器的调试信息
*/
debug(): void;
/**
* 重置思维导图布局并重新渲染
* @todo 考虑是否设置为私有
*/
rebuild(): void;
/**
* 清空思维导图实例的所有内容和状态
* 包括清除容器、映射以及释放事件监听器
* @todo 考虑是否设置为私有
*/
clear(): void;
/**
* 销毁思维导图实例,释放所有相关资源。
* 调用此方法后,MindMap 实例将不可用。
*/
destroy(): void;
/**
* 设置新的思维导图数据
* 这将会清空当前的思维导图,并使用新的数据重新构建和渲染
* @param data - 新的思维导图数据,可以是 MindNode 实例、TreeNode 对象或 FlatNode 数组
*/
setRootNodeData(data: MindNode | TreeNode | FlatNode[]): void;
/**
* 设置思维导图布局方向
* @param isHorizontal - 是否水平布局
*/
setHorizontal(isHorizontal: boolean): void;
/**
* 放大思维导图
* @param cx - 缩放中心点的 X 坐标,默认为 0
* @param cy - 缩放中心点的 Y 坐标,默认为 0
* @param originalEvent - 原始滚轮事件,可选
*/
zoomIn(cx?: number, cy?: number, originalEvent?: WheelEvent): void;
/**
* 缩小思维导图
* @param cx - 缩放中心点的 X 坐标,默认为 0
* @param cy - 缩放中心点的 Y 坐标,默认为 0
* @param originalEvent - 原始滚轮事件,可选
*/
zoomOut(cx?: number, cy?: number, originalEvent?: WheelEvent): void;
/**
* 重置思维导图缩放比例至默认值
* @param cx - 缩放中心点的 X 坐标,默认为 0
* @param cy - 缩放中心点的 Y 坐标,默认为 0
*/
resetZoom(cx?: number, cy?: number): void;
/**
* 设置指定节点的尺寸
* @param nodeId - 要设置尺寸的节点 ID
* @param size - 新的尺寸配置,可以只设置宽度或高度
* @param size.width - 新的宽度,可选
* @param size.height - 新的高度,可选
* @returns 如果节点找到并成功设置尺寸,则返回 true;否则返回 false
*/
setNodeSize(nodeId: string, size: {
width?: number;
height?: number;
}): boolean;
/**
* 根据 ID 查找 MindNode
* @param nodeId - 要查找的节点 ID
* @returns 找到的 MindNode,如果未找到则返回 undefined
*/
findNodeById(nodeId: string): MindNode | undefined;
/**
* 将指定 ID 的节点聚焦到视口中心
* @param nodeId - 要聚焦的节点的 ID
* @returns 如果节点找到并成功聚焦,则返回 true;否则返回 false
*/
focusOnNode(nodeId: string): boolean;
/**
* 切换节点的折叠状态
* @param nodeId - 要切换折叠状态的节点 ID
* @returns 如果节点找到并成功切换状态,则返回 true;否则返回 false
*/
toggleNodeCollapse(nodeId: string): boolean;
/**
* 设置节点的折叠状态
* @param nodeId - 要设置折叠状态的节点 ID
* @param collapsed - 要设置的折叠状态
* @returns 如果节点找到并成功设置状态,则返回 true;否则返回 false
*/
setNodeCollapsed(nodeId: string, collapsed: boolean): boolean;
/**
* 获取节点的折叠状态
* @param nodeId - 要查询的节点 ID
* @returns 节点的折叠状态,如果节点不存在则返回 undefined
*/
getNodeCollapsed(nodeId: string): boolean | undefined;
/**
* 展开所有节点
* @returns 成功展开的节点数量
*/
expandAllNodes(): number;
/**
* 折叠所有非根节点
* @returns 成功折叠的节点数量
*/
collapseAllNodes(): number;
/**
* 更新指定节点的内容
* 此方法会根据传入的节点 ID 数组,并根据节点自身的 `content` 属性或外部传入的 `contentCallback`
* 来重新解析和设置节点内部的内容。
* @param nodeIds - 要更新内容的节点 ID 数组,不传参时默认更新所有节点
* @returns 成功更新的节点个数
*/
updateNodeContents(nodeIds?: string[]): number;
/**
* 执行布局计算、创建元素和连接线,添加元素到容器并渲染思维导图
*/
private layoutAndCreateElementsAndRender;
/**
* 初始化或重新初始化拖拽管理器
* 配置节点和画布的拖拽行为并绑定相关事件
*/
private initializeDraggableManager;
/**
* 初始化鼠标滚轮管理器
* 配置滚轮缩放行为并绑定相关事件
*/
private initializeWheelManager;
/**
* 设置容器尺寸变化监听器
* @private
*/
private setupContainerResizeListener;
/**
* 处理节点拖拽的逻辑
* @param draggedElement - 被拖拽的 DOM 元素对应的 Element 对象
* @param deltaX - X 轴拖拽距离
* @param deltaY - Y 轴拖拽距离
*/
private handleNodeDrag;
/**
* 处理画布拖拽的逻辑
* @param deltaX - X 轴拖拽距离
* @param deltaY - Y 轴拖拽距离
*/
private handleCanvasDrag;
/**
* 更新指定节点相关的连接线(到父节点和到子节点)
* @param node - 需要更新连接线的节点
*/
private updateConnectionsForNode;
/**
* 计算思维导图布局
* @returns 布局计算后的根节点
*/
private calculateLayoutInternal;
/**
* 将 MindNode 转换为 Element 和 Line 数组
* @param node - 要转换的思维导图节点
* @param isHorizontal - 是否为水平布局
* @returns 包含元素和连接线的元组
*/
private mindNodeToElementsAndLinesRecursive;
/**
* 创建节点元素
* @param node - 节点对象
* @returns 节点元素对象
*/
private createNodeElement;
/**
* 解析节点内容。
* 此方法会决定最终用于节点显示的内容。
* 如果原始数据 nodeData.content 提供了内容定义,并且与 mindNode.content 中缓存的定义不同,
* 则会使用 nodeData.content 更新 mindNode.content。
* @param mindNode - 思维导图节点
* @returns 解析后的节点显示内容 (字符串、HTML元素或其数组)
*/
private resolveNodeContent;
/**
* 创建连接线
* @param from - 起始节点
* @param to - 结束节点
* @param isHorizontal - 是否水平连接
* @returns 连接线对象
*/
private createConnectionLine;
/**
* 基于两个节点的位置和尺寸计算边缘连接点
* @param from - 起始节点
* @param to - 结束节点
* @param isHorizontal - 是否水平连接,否则为垂直连接
* @returns 起始点和结束点的坐标
*/
private calculateConnectionPoint;
/**
* 获取与给定 MindNode 关联的原始数据对象 (TreeNode 或 FlatNode)
* 如果 MindMap 是直接用 MindNode 实例初始化的,则返回该 MindNode 实例本身
* @param node - 要获取原始数据的 MindNode 实例
* @returns 关联的原始数据对象,或者 MindNode 实例本身
*/
private getOriginalDataForNode;
/**
* 递归查找具有指定 ID 的 MindNode
* @param node - 当前开始搜索的节点
* @param nodeId - 要查找的节点 ID
* @returns 找到的 MindNode,如果未找到则返回 undefined
*/
private findNodeByIdRecursive;
/**
* 当节点尺寸变化时,重新计算布局并更新受影响的节点位置
* @param changedNode - 尺寸发生变化的节点
*/
private updateLayoutForNodeSizeChange;
/**
* 更新所有 Element 的位置以匹配其对应 MindNode 的位置
*/
private updateAllElementPositions;
/**
* 更新所有连接线的位置
*/
private updateAllConnectionLines;
/**
* 更新容器边界以适应新的内容尺寸
*/
private updateContainerBounds;
/**
* 从外部树形对象创建 MindNode 树结构
* @param data - 树形节点数据
* @returns 构建的根 MindNode 实例
*/
private createMindNodesFromTreeData;
/**
* 从扁平数据数组创建 MindNode 树结构
* @param flatNodes - 扁平结构的节点数组
* @returns 构建的根 MindNode 实例
*/
private createMindNodesFromFlatData;
}