HTML绘制拓扑简图,⽤最简单的⽅式画拓扑图
前⾔
前段时间重构了下⾯这样⼀个页⾯(产品页⾯不⽅便截图):
类似于拓扑图的配置,原来是使⽤go.js实现的,类似的库还有antv g6。重构主要是为了提⾼代码质量,降低维护成本,产品上需要更强的定制化能⼒(对付产品经理的变态需求),所以经过⼀番研究之后,最后决定放弃使⽤现成的库。原因如下:
维护成本⾼:类似的库(antv/g6, go.js)都是基于canvas实现,也都⼤同⼩异的定义了⼀套组件,有⼀定的学习成本,同时基于这样的库写出来的代码都相对复杂;
灵活性差:因为是canvas实现,元素⼀般需要指定尺⼨,所以在⼀些需要元素⼤⼩⾃适应的地⽅并没有DOM元素好实现;
定制化能⼒差。只能使⽤库⾥定义的api和事件,遇到⼀些⽐较极端的需求时⽆能为⼒。
当然,以上两个库还是相当强⼤的,不过基于这些原因,⾃⼰基于(DOM + SVG)撸⼀个拓扑图配置的⼯具库topology-byfe
demo演⽰
源代码
import React from 'react';
import { Topology, topologyWrapper, TemplateWrapper } from 'topology-byfe';
import { ITopologyNode, ITopologyData, IWrapperOptions } from 'topology-byfe/lib/declare';
import './index.less';
interface FlowState {
data: ITopologyData;
}
class Flow extends React.Component {
state: FlowState = {
data: { lines: [], nodes: [] },
};
generatorNodeData = (isBig: boolean) => ({
id: `${w()}`,
name: isBig ? '宽节点' : '窄节点',
content: isBig ? '这是⼀个宽节点' : '这是⼀个窄节点',
branches: isBig ? ['锚点1', '锚点2', '锚点3'] : ['锚点1'],
});
handleSelect = (data: ITopologyData) => {
console.log(data);
}
renderTreeNode = (data: ITopologyNode, { anchorDecorator }: IWrapperOptions) => { const {
name = '',
content = '',
branches = [],
} = data;
return (
{name}
{content}
{branches.length > 0 && (
{branches.map(
(item: string, index: number) => anchorDecorator({
anchorId: `${index}`,
})(
{item}
),
)}
)}
);
};
onChange = (data: ITopologyData, type: string) => {
this.setState({ data });
console.log('change type:', type);
};
render() {
const { data } = this.state;
return (
宽节点
窄节点
onSelect={this.handleSelect}
renderTreeNode={derTreeNode}
/>
);
}
}
export default topologyWrapper(Flow);
复制代码
效果图
可以看到,包⾥只提供了极少的api,⽂档⼏分钟就能看完,之所以少,是因为库只负责将节点放到正确的位置,连上线就好了,其他
的“概不负责”,修改样式可以在你的div上加个class,添加事件就再上个onXXX,想做啥做啥。
为什么选择DOM + SVG?
最主要的原因就是为了“简单”!对于拓扑图这样
的场景,画⼀个简单的节点,⽤DOM实现只需要简单的⼏⾏代码,⽤canvas的话就要写⼀⼤堆了,别⼈来看估计就是“⼀⼤坨”了。同时相对于需要先学习⼀套组件,然后⽤那些“奇奇怪怪”的api去写交互和样式,直接上⼿就开始撸⾃⼰最熟悉的div + css岂不是很开⼼?对于开发⽽⾔,调试DOM能够看到每⼀个元素的细节,⽽canvas就⽆能为⼒了。
如何使⽤svg图
核⼼部分代码:
onSelect={this.handleSelect}
renderTreeNode={derTreeNode}
/>
复制代码
data
data = {
nodes: [
{ id: '1', position: { x: 0, y: 0 } },
{ id: '2', position: { x: 100, y: 100 } }
],
lines: [
{ start: '1-0', end: '2' }
]
}
复制代码
data包含两个属性:nodes和lines,nodes记录节点信息,每个node含有id和position属性,id是必选的,position记录了节点的位置,如果不包含position,点击⾃动布局,将会⾃动⽣成。
data.lines
lines记录节点与节点间的关系,start记录起点信息,格式为:'起点id-锚点id',end记录终点信息,格式为:'终点id'。
autoLayout
上⾯的效果图可以看到右下⾓第⼆个图标点击⾃动布局功能,为了⽅便排版,⾃动布局会根据树结构计算节点的位置。当初始数据没有position字段时,如果autoLayout为true,组件会⾃动触发布局功能,相当于点击了⾃动布局按钮。
onChange
组件使⽤类似input或者select,当有新增节点或者连线发⽣时,触发onChange,onChange带有两个参数,newData和changeType, 整个过程完全受控,你可以在onChange中做⼀些校验,决定数据是否更新。
onSelect
当选择节点或者线段时触发,参数selectData格式同data。
renderTreeNode
renderTreeNode接收两个参数:nodeData, decorators,返回节点的DOM。
renderTreeNode = (data: ITopologyNode, { anchorDecorator }) => {
// name、content、branches都是⾃定义的字段,通过模板节点⽣成,详见TemplateWrapper
const {
name = '',
content = '',
branches = [],
} = data;
return (
{name}
{content}
{branches.length > 0 && (
{branches.map(
(item: string, index: number) => anchorDecorator({
anchorId: `${index}`,
})(
{item}
),
)}
)
}
);
};
复制代码
锚点,decorators.anchorDecorator
anchorDecorator({ anchorId: `${index}` })(
{item}
)
复制代码
anchorDecorator是⼀个装饰器函数,接受⼀个options,⽬前只包含⼀个anchorId属性,即锚点id,如果不传的话,内部会⾃动⽣成⼀个⾃增id。可以看到,锚点长什么样,放到哪⼉完全由你⾃⼰决定。
templateWrapper
宽节点
窄节点
复制代码
通过templateWrapper包装⽣成⼀个模板节点,接收⼀个generator函数,当添加节点时,会调⽤这个函数,⽣成节点的初始数据,⾥⾯包含什么值由你决定,但必须包含⼀个唯⼀的id值。
topologyWrapper
export default topologyWrapper(Flow);
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论