bade-mind
- 脑图内核,并不依赖于任何UI框架,只实现逻辑以及链接渲染
- 内核不负责节点渲染
- node
sizeof
函数为用户需要重点注意以及实现的点 - 内核直接使用比较复杂,可使用UI框架封装版本
Live demo
Installation
npm install bade-mind
Simple demo
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>bade-mind demo</title>
<style>
body,#root{
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
#container{
position: relative;
width: fit-content;
height: fit-content;
}
#node-container{
position: absolute;
left: 0;
top: 0;
}
.mind__lines{
stroke: #2775b6;
stroke-width: 3px;
}
.node{
position: absolute;
left: 0;
top: 0;
}
.node-content{
background: #57c3c2;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
}
.root-node{
color: white;
background: #440e25;
}
</style>
</head>
<body>
<div id="root">
<div id="container">
<div id="node-container"></div>
</div>
</div>
<script src="./index.js" type="module"></script>
</body>
</html>
import { Mind } from 'bade-mind'
const generateRoot = () => ({
negative: [
{
children: [
{
id: 'n-2-l',
sizeof: () => ({
height: 25,
width: 50
})
}
],
id: 'n-1-w-c',
sizeof: () => ({
height: 25,
width: 50
})
},
{
id: 'n-1-l',
sizeof: () => ({
height: 50,
width: 100
})
}
],
node: {
id: 'root',
sizeof: () => ({
width: 100,
height: 100
})
},
positive: [
{
children: [
{
id: 'p-2-l',
sizeof: () => ({
height: 25,
width: 50
})
}
],
id: 'p-1-w-c',
sizeof: () => ({
height: 25,
width: 50
})
},
{
id: 'p-1-l',
sizeof: () => ({
height: 50,
width: 100
})
}
]
})
window.onload = () => {
const viewport = document.getElementById('root')
const container = document.getElementById('container')
const nodeContainer = document.getElementById('node-container')
let graphic = undefined
const root = generateRoot(viewport)
graphic = new Mind.Graphic(viewport, container, {
callback: {
onNodeVisibleChange: (nodes) => {
nodeContainer.innerHTML = nodes
.map((node) => {
const anchor = graphic.getNodeAnchorCoordinate(node.id)
let content = ''
const isRoot = node.id === root.node.id
content = `
<div class="${isRoot ? 'root-node' : ''} node-content"
style="
width: ${node.sizeof().width}px;
height: ${node.sizeof().height}px;
">
${isRoot ? 'root' : ''}
</div>`
return `<div class="node" style="transform: translateX(${anchor.x}px) translateY(${anchor.y}px)">${content}</div>`
})
.join('')
}
},
childAlignMode: Mind.ChildAlignMode.structured
})
graphic.setData(root)
}
Usage
1. sizeof
首先需要自定义实现每一个节点中的sizeof
函数,内核会在计算各节点图层位置时调用此函数获取节点尺寸
- 如果
sizeof
函数需要耗费大量计算资源,则需要外部自行使用缓存等方案,内核将不会缓存sizeof
结果
2. container & viewport
准备两个dom作为容器
3. onNodeVisibleChange & judgeNodeVisible
4. render
内核交由节点渲染部分给用户自行渲染
5. setData、setOptions
使用setData
函数设置/更新数据,启动重渲染
- 内核将不会做任何数据比较,只要调用了函数,则视作数据变化,启动渲染
使用setOptions
函数设置/更新配置信息,如果需要重渲染,则需要手动调用setData
Class
Graphic
脑图绘制控制类
constructor
constructor(
viewport: HTMLElement,
container: HTMLElement,
options?: Mind.Options
)
-
param viewport
视窗(可视区域)
-
param container
容器
-
param options
配置参数
getNodeAnchorCoordinate
获取节点定位锚点(左上角)位置,可在节点绘制的时候确定其位置
- 推荐所有节点使用
position:absolute;left:0;top:0;
并且配合transform
来定位,避免出现绘制异常
function getNodeAnchorCoordinate(id: string): Mind.Coordinate | undefined
unbind
注销事件绑定
function unbind(): void
judgeNodeVisible
判断节点是否可视
function judgeNodeVisible(id: string): boolean
setOptions
设定 options
- 函数不会自动执行重渲染,如果改变的
options
需要重新计算布局等操作,推荐使用 setData
驱动数据重渲染
function setOptions(options?: Mind.Options, isMerge: boolean = false): voi
- @param
options
设定选项 - @param
isMerge
是否与之前的options
做合并操作
dragControllerBuilder
生成拖动控制器
function dragControllerBuilder(drag: Mind.Node | string): Drag | undefined
getLayoutSize
获取渲染层尺寸
function getLayoutSize(): Mind.Size | undefined
getNode
获取id
对应节点
function getNode(id: string): Mind.Node | undefined
getParent
获取id
对应父节点
function getParent(id: string): Mind.Node | undefined
@param id
节点id
getNodeOrientation
获取id
对应节点渲染方位
function getNodeOrientation(id: string): Mind.Orientation | undefined
@param id
节点id
setTransform
主动设置位移缩放
function setTransform(transform: Partial<Mind.Transform>,duration?: number): void
translate
设定位移
- 此方法受到
zoomExtent.translate
限制
function translate(translate: Mind.Coordinate,duration?: number): void
scale
设定缩放
- 此方法受到
zoomExtent.translate
限制
- 此方法受到
zoomExtent.scale
限制
function scale(
scale: number,
point?: Mind.Coordinate,
duration?: number): void
nodeTranslateTo
将某一个节点中心从某个相对位置做位移(其尺度为屏幕尺度)操作
- 此方法不受
zoomExtent.translate
限制
function nodeTranslateTo(
config: {id: string, diff: Mind.Coordinate, relative: Mind.Relative},
duration?: number): void
- @param
config
配置参数 - @param
config.id
节点id - @param
config.diff
位移差 - @param
config.relative
相对位置 - @param
duration
动画周期,如配置,则位移会附带动画效果
getTransform
获取位移缩放信息
function getTransform(): Mind.Transform
setAnchor
设置锚点节点
function setAnchor(id?: string): void
- @param
id
锚定节点id(如不设定,则清空锚点,根节点居中,缩放比归一)
setData
设置/更新数据,启动重渲染
- 在重计算定位时,将保持
anchor
对应节点在屏幕上的相对位置不变 - 如果
anchor
没有设定,或者找不到对应节点,则,根节点居中,缩放比重置为1
function setData(root: Mind.Root): void
refresh
刷新布局,启动重渲染
其用法与setData
一致,使用的是内部存储的数据
Drag
拖动逻辑相关控制类,实现拖拽计算逻辑,不与特定手势关联
- 推荐使用
Graphic.dragControllerBuilder
生成,自动注入所需数据,不推荐手动new
初始化对象 - 需要当前使用的布局类型支持拖拽
constructor
constructor(context: {
options: Required<Mind.Options>,
cacheMap: Mind.CacheMap,
root: Mind.Root,
dragNode: Mind.Node,
container: HTMLElement
})
calcDropIndex
获取拖动节点插入到关联节点子代的下标
function calcDropIndex(
attachedNodeChildren: BadeMind.Node[] | undefined,
dropPosition: BadeMind.Coordinate,
dragNode: BadeMind.Node,
attachedNode: BadeMind.Node): number
-
param attachedNodeChildren
关联节点的子代
-
param dropPosition
拖动节点镜像中心位置
-
param dragNode
拖动节点
-
param attachedNode
被关联的节点
-
return 期望插入位置
drag
通知控制器正在执行拖动操作,计算链接信息
function drag(position: BadeMind.Coordinate,canBeAttachedNodes: BadeMind.Node[]): {
orientation: "negative" | "positive",
attach: BadeMind.Node
} | undefined
end
通知控制器拖动操作结束
function end(): void
Types
Options
export interface Options {
direction?: Direction
nodeSeparate?: number
rankSeparate?: number
childAlignMode?: ChildAlignMode
viewportPreloadPadding?: number
callback?: Callback
event?: Event
lineStyle?: LinkStyle
pathRender?: PathRender | undefined
zoomExtent?: ZoomExtent
layoutProcess?: { new (): Process.Lifecycle }
}
Root
根节点
- 脑图可以向左右或者上下扩展,故而需要划分
positive
、negative
interface Root {
node: Omit<Node, 'children'>
positive?: Node[]
negative?: Node[]
}
Node
节点信息
interface Node {
sizeof: () => Size
id: string
children?: Node[]
fold?: boolean | boolean[]
attachData?: any
}
Basic
Callback
回调集合
interface Callback {
onTransformChange?: (transform: Transform) => void
onNodeVisibleChange?: (nodes: BadeMind.Node[]) => void
}
onTransformChange
通知外部transform
相关信息发生了改变,常用于辅助额外控制行为,举个🌰:实现滚动条、缩放器等辅助控制
onNodeVisibleChange
可见节点发生改变
Event
内核事件
interface Event {
onViewportContextMenu?: (e: MouseEvent) => void
onZoomEventTrigger?: ZoomEvent
}
viewport
右键上下文事件触发,可通过此事件自定义右键菜单
- 由于右键拖动,移动脑图面板,故而库默认禁用了
viewport
的右键菜单事件
onZoomEventTrigger
缩放位移相关按钮手势事件触发
- 右键拖动、Ctrl+滚轮缩放,在这些行为下库会拦截其对应事件,导致外部无法绑定事件
ZoomEvent
缩放事件
type ZoomEventFunc = (event: any) => void
interface ZoomEvent {
start?: ZoomEventFunc
zoom?: ZoomEventFunc
end?: ZoomEventFunc
}
ZoomExtent
缩放、位移边界设定
interface ZoomExtent {
translate?: [Coordinate, Coordinate]
scale?: [number, number]
}
LinkStyle
内核预设链接风格
type LinkStyle = "line" | "bezier"
line
线性链接
- 线性只有在
ChildAlignMode.structured
风格下表现最佳
Size
尺寸
interface Size {
width: number
height: number
}
Coordinate
坐标
interface Coordinate {
x: number
y: number
}
Direction
渲染方向
type Direction = 'x' | 'y'
ChildAlignMode
内核内置布局方式
type ChildAlignMode = "heir-center" | "structured" | "descendant-center"
descendant-center
子代对齐模式(同一父节点的子代视作整体对齐)
heir-center
直系子代对齐模式(同一父节点直系子代对齐)
structured
结构化规整模式(同一父节点直系子代边缘对齐)
Transform
渲染区域位移缩放转化信息
interface Transform {
x: number
y: number
scale: number
}
Relative
相对位置
type RelativeX = "middle" | "left" | "right"
type RelativeY = "middle" | "top" | "bottom"
interface Relative {
x: RelativeX
y: RelativeY
}
Advanced
PathRender
自定义路径渲染器,其返回值将作为链接线path
的d
属性值
type PathRender = (context: PathRenderContext) => string
🌰:把所有节点用直线链接起来
const linePathRender: PathRender = (context) => {
const { source, target } = context
return `M${source.x},${source.y}L${target.x},${target.y}`
}
PathRenderContext
interface PathData extends Line {
node: Node
}
interface PathRenderContext extends PathData {
options: Required<Options>
cacheMap: CacheMap
}
Line
连线信息
interface Line {
source: Coordinate
target: Coordinate
}
Orientation
节点与根节点之间的位置关系
type Orientation = "negative" | "root" | "positive"
-
negative
节点位于根负向区域
-
positive
节点位于根正向区域
-
root
节点为根节点
Visible
节点可见信息
interface Visible {
node: boolean
lineAttachParent: boolean
}
CacheMap
内核缓存信息
type CacheMap = Map<string, NodeCache>
NodeCache
节点缓存信息
interface NodeCache {
orientation: Orientation
node: Node
rect: Size & Coordinate
visible: Visible
parent?: Node
processCache: any
line: Line
layoutSize?: Size
}
DraggableLayout
可拖拽布局类
layoutProcess
需要继承此类,并且实现calcDragAttach
、calcDropIndex
静态方法才可正常使用拖拽功能
class DraggableLayout {
public static calcDragAttach = (context: {
cacheMap: CacheMap
draggingRect: Coordinate & Size
canBeAttachedNodes: Node[]
ignoreNodes: Node[]
root: Root
options: Required<Options>
}): DragAttach | undefined => {
...
}
public static calcDropIndex = (context: {
cacheMap: CacheMap
attachedNodeChildren: Node[] | undefined
dropPosition: Coordinate
attachedNode: Node
dragNode: Node
root: Root
options: Options
}): number => {
...
}
public static isValidExtendsClass = (classObject: any) => {
...
}
}
Process
处理器为拓展自定义功能
Lifecycle
interface Lifecycle<S = void, E = void, AE = void, END = void> {
start?: (context: StartContext) => S
every?: (context: EveryContext) => E
afterEvery?: (context: EveryContext) => AE
end?: () => END
}
StartContext
开始处理节点之前的上下文
interface StartContext {
options: Required<Options>
root: Root
getRootHeirOrientation: (id: string) => Orientation
cacheMap: CacheMap
preCacheMap?: CacheMap
viewport: HTMLElement
container: HTMLElement
transform: Transform
anchor?: string
zoom: Zoom
}
EveryContext
处理每一个节点的上下文
interface EveryContext {
cache: NodeCache
fold?: boolean | boolean[]
}