bade-mind
Bade 思维导图核心,提供绘制所需必要工具功能,不依赖于特定框架
Installation
NPM
npm install bade-mind
Usage
-
库本身并不负责node内容的渲染,其只会渲染链接线以及管理手势系统,计算布局等
-
在使用的时候,需要用户根据使用框架、环境自行实现节点的sizeof
函数,此函数用作获取某个节点的dom尺寸,并会在每一次渲染的时候调用,用于计算布局
-
需要注意的是,普通节点的子代通过children
设置,但根节点的children
会被忽略,需要通过root.positive
或root.negative
来设置
Simple demo
Html
<!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;
}
.bade-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>
</body>
</html>
Simple data
const rootHtml = `<div class="node-content root-node" style="width: 100px;height: 100px;box-sizing: content-box;">Root</div>`
export const measureSize = (html: string, viewport: HTMLElement) => {
const size: BadeMind.Size = {
height: 0,
width: 0
}
const container = document.createElement('div')
container.style.cssText =
'position:fixed;top:0;left:0;pointer-events:none;visibility:hidden;opacity:0;overflow:hidden;'
container.innerHTML = html
viewport.appendChild(container)
size.width = container.clientWidth
size.height = container.clientHeight
viewport.removeChild(container)
return size
}
const generateRoot = (viewport: HTMLElement): BadeMind.Root => ({
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: () => measureSize(rootHtml, viewport)
},
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
})
}
]
})
Logic
window.onload = () => {
const viewport = document.getElementById('root')!
const container = document.getElementById('container')
const nodeContainer = document.getElementById('node-container')
let graphic: BadeMind.Graphic | undefined = undefined
const root = generateRoot(viewport)
graphic = new BadeMind.Graphic(viewport, container, {
callback: {
onNodeVisibleChange: (nodes) => {
nodeContainer.innerHTML = nodes
.map((node) => {
const anchor = graphic.getNodeAnchorCoordinate(node.id)
let content = ''
if (node.id === root.node.id) {
content = rootHtml
} else {
content = `<div class="node-content" style="width: ${node.sizeof().width}px;height: ${
node.sizeof().height
}px;"></div>`
}
return `<div class="node" style="transform: translateX(${anchor.x}px) translateY(${anchor.y}px)">${content}</div>`
})
.join('')
}
},
childAlignMode: BadeMind.ChildAlignMode.structured,
lineStyle: BadeMind.LinkStyle.line,
pathRender: (context) => {
const { source, target } = context
return `M${source.x},${source.y}L${target.x},${target.y}`
}
})
graphic.setData(root)
}
Result
API
Graphic
脑图绘制控制类
constructor
constructor(
viewport: HTMLElement,
container: HTMLElement,
options?: BadeMind.Options
)
-
param viewport
视窗(可视区域)
-
param container
容器
-
param options
配置参数
judgeNodeVisible
判断节点是否可视
function judgeNodeVisible(id: string): boolean
getNodeAnchorCoordinate
获取节点定位锚点(左上角)位置,可在节点绘制的时候确定其位置
- 推荐使用
position:absolute;left:0;top:0;
配合transform
来定位,避免出现绘制异常
function getNodeAnchorCoordinate(id: string): BadeMind.Coordinate | undefined
getLayoutSize
获取渲染层尺寸
function getLayoutSize(): BadeMind.Size | undefined
getNode
获取id
对应节点
function getNode(id: string): BadeMind.Node | undefined
getParent
获取id
对应节点父级
function getParent(id: string): BadeMind.Node | undefined
getNodeOrientation
获取id
对应节点渲染方位
function getNodeOrientation(id: string): BadeMind.Orientation | undefined
dragControllerBuilder
生成拖动控制器
- 根节点不可拖拽
- 当前内置布局方式暂时只有
BadeMind.ChildAlignMode.structured
布局算法支持拖拽功能
function dragControllerBuilder(drag: BadeMind.Node | string): Drag | undefined
-
param drag
拖动节点node对象或id
-
return
unbind
注销事件绑定
function unbind(): void
setOptions
设定 options
- 不会自动执行重渲染,如果改变的
options
需要重新计算布局等操作,推荐使用 setData
驱动数据重渲染
function setOptions(
options?: BadeMind.Options,
isMerge: boolean = false): void
setTransform
主动设置位移缩放
function setTransform(
transform: Partial<BadeMind.Transform>,
duration?: number): void
scale
缩放
function scale(
scale: number,
point?: BadeMind.Coordinate,
duration?: number): void
translate
位移
- 此方法受到
zoomExtent.translate
限制
function translate(
translate: BadeMind.Coordinate,
duration?: number): void
nodeTranslateTo
将某一个节点中心从某个相对位置做位移(其尺度为屏幕尺度)操作
- 此方法不受
zoomExtent.translate
限制
function nodeTranslateTo(
config: {
id: string,
diff: BadeMind.Coordinate,
relative: BadeMind.Relative
},
duration?: number): void
getTransform
获取位移缩放信息
function getTransform(): BadeMind.Transform
setAnchor
设置锚点
function setAnchor(id?: string): void
setData
设置/更新数据,启动重渲染
- 在重计算定位时,将保持
anchor
对应节点在屏幕上的相对位置不变 - 如果
anchor
没有设定,或者找不到对应节点,则,根节点居中,缩放比重置为1
function setData(root: BadeMind.Root): void
Drag
拖动控制器
- 推荐使用
Graphic.dragControllerBuilder
生成,自动注入所需数据
constructor
constructor(context: {
options: Required<BadeMind.Options>,
cacheMap: BadeMind.CacheMap,
root: BadeMind.Root,
maxDistance: number,
dragNode: BadeMind.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
Size
interface Size {
width: number
height: number
}
Coordinate
interface Coordinate {
x: number
y: number
}
Root
interface Root {
node: Omit<Node, 'children'>
positive?: Node[]
negative?: Node[]
}
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 }
}
Node
节点信息
interface Node {
sizeof: () => Size
id: string
children?: Node[]
fold?: boolean | boolean[]
attachData?: any
}
Direction
渲染方向
const Direction = {x: 'x', y: 'y'} as const
type Direction = 'x' | 'y'
ChildAlignMode
const ChildAlignMode = {
descendantCenter: 'descendant-center',
heirCenter: 'heir-center',
structured: 'structured'
} as const
type ChildAlignMode = "heir-center" | "structured" | "descendant-center"
descendant-center
子代对齐模式(同一父节点的子代视作整体对齐)
heir-center
直系子代对齐模式(同一父节点直系子代对齐)
structured
结构化规整模式(同一父节点直系子代边缘对齐)
Transform
interface Transform {
x: number
y: number
scale: number
}
Callback
export 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
}
LinkStyle
库内部预设链接风格
const LinkStyle = {bezier: 'bezier',line: 'line'} as const
type LinkStyle = "line" | "bezier"
-
line
线性链接
- 线性只有在
ChildAlignMode.structured
风格下表现最佳
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}`
}
Line/PathData/PathRenderContext
interface Line {
source: Coordinate
target: Coordinate
}
interface PathData extends Line {
node: Node
}
interface PathRenderContext extends PathData {
options: Required<Options>
cacheMap: CacheMap
}
ZoomExtent
缩放、位移边界设定
interface ZoomExtent {
translate?: [Coordinate, Coordinate]
scale?: [number, number]
}
Advance
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
}
Orientation
const Orientation = {
negative: 'negative',
positive: 'positive',
root: 'root'
} as const
type Orientation = "negative" | "root" | "positive"
-
negative
节点位于根负向区域
-
positive
节点位于根正向区域
-
root
节点为根节点
Visible
interface Visible {
node: boolean
lineAttachParent: boolean
}
DraggableLayout
可拖拽布局类
layoutProcess
需要继承此类,并且实现calcDragAttach
、calcDropIndex
静态方法才可正常使用拖拽功能
class DraggableLayout {
public static calcDragAttach = (context: {
cacheMap: BadeMind.CacheMap
draggingRect: BadeMind.Coordinate & BadeMind.Size
canBeAttachedNodes: BadeMind.Node[]
ignoreNodes: BadeMind.Node[]
root: BadeMind.Root
options: Required<BadeMind.Options>
}): BadeMind.DragAttach | undefined => {
...
}
public static calcDropIndex = (context: {
cacheMap: BadeMind.CacheMap
attachedNodeChildren: BadeMind.Node[] | undefined
dropPosition: BadeMind.Coordinate
attachedNode: BadeMind.Node
dragNode: BadeMind.Node
root: BadeMind.Root
options: BadeMind.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<BadeMind.Options>
root: BadeMind.Root
getRootHeirOrientation: (id: string) => BadeMind.Orientation
cacheMap: BadeMind.CacheMap
preCacheMap?: BadeMind.CacheMap
viewport: HTMLElement
container: HTMLElement
transform: BadeMind.Transform
anchor?: string
zoom: Zoom
}
EveryContext
处理每一个节点的上下文
interface EveryContext {
cache: BadeMind.NodeCache
}
fold?: boolean | boolean[]