前言

前段时间重构了下面这样一个页面(产品页面不方便截图):

类似于拓扑图的配置,原来是使用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: `${Date.now()}`,

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 (

this.generatorNodeData(true)}>

宽节点

this.generatorNodeData(false)}>

窄节点

data={data}

autoLayout

onChange={this.onChange}

onSelect={this.handleSelect}

renderTreeNode={this.renderTreeNode}

/>

);

}

}

export default topologyWrapper(Flow);

复制代码

效果图

可以看到,包里只提供了极少的api,文档几分钟就能看完,之所以少,是因为库只负责将节点放到正确的位置,连上线就好了,其他的“概不负责”,修改样式可以在你的div上加个class,添加事件就再上个onXXX,想做啥做啥。

为什么选择DOM + SVG?

最主要的原因就是为了“简单”!对于拓扑图这样

的场景,画一个简单的节点,用DOM实现只需要简单的几行代码,用canvas的话就要写一大堆了,别人来看估计就是“一大坨”了。同时相对于需要先学习一套组件,然后用那些“奇奇怪怪”的api去写交互和样式,直接上手就开始撸自己最熟悉的div + css岂不是很开心?对于开发而言,调试DOM能够看到每一个元素的细节,而canvas就无能为力了。

如何使用

核心部分代码:

data={data}

autoLayout

onChange={this.onChange}

onSelect={this.handleSelect}

renderTreeNode={this.renderTreeNode}

/>

复制代码

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

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

this.generatorNodeData(true)}>

宽节点

this.generatorNodeData(false)}>

窄节点

复制代码

通过templateWrapper包装生成一个模板节点,接收一个generator函数,当添加节点时,会调用这个函数,生成节点的初始数据,里面包含什么值由你决定,但必须包含一个唯一的id值。

topologyWrapper

export default topologyWrapper(Flow);

复制代码

包含拖拽部分的最上层组件必须要用topologyWrapper包一下,这是因为使用了react-dnd需要设置backend,这里只是做了一个简单的导出,方便使用:

export const topologyWrapper = DragDropContext(HTML5BackEnd);

复制代码

总结

从需求出发来看,需要拥有更好的定制化能力和灵活性,从重构的角度来看,需要让代码更简单明了。go.js功能十分强大,但对于拓扑图这种相对较简单的场景而言,并不需要那么多复杂的能力,反而可能出现上面说的问题,所有canvas的实现应该都会有类似的问题。所以充分利用DOM的能力,在能实现需求的情况下,本库或许是更好的选择。

最后

项目只实现了简单的功能,存在不足欢迎大佬们提issue,pr。另外我司招人,欢迎大佬们加入:招聘链接!!!

HTML绘制拓扑简图,用最简单的方式画拓扑图!!!相关推荐

  1. 用更简单的方式画森林图

    获取更多R语言知识,请关注公众号:医学和生信笔记 医学和生信笔记,专注R语言在临床医学中的使用,R语言数据分析和可视化.主要分享R语言做医学统计学.meta分析.网络药理学.临床预测模型.机器学习.生 ...

  2. Plotly绘制时间序列图实战:简单时序图、时间范围限制的时序图

    Plotly绘制时间序列图实战:简单时序图.时间范围限制的时序图 # 简单时间序列图: import plotly as py import plotly.graph_objs as gofrom d ...

  3. 虚拟化服务器拓扑结构图,6-4简单虚拟化数据中心拓扑图介绍

    对于系统规划人员来说,拓扑图,可以向用户及他人表达我们的设计思想.设计理念,展示设计的特点.目的.功能. 无论是做网络规划.系统设计.网络管理,画拓扑图是一个最基本的能力. 对于网络管理人员来说,拓扑 ...

  4. 用最简单的方式整合JFreechart和Structs并修改背景色

          JFreeChart是何物?熟悉java web 编程的朋友们不会陌生,它是JAVA平台上的一个开放的图表绘制类库.它完全使用JAVA语言编写,是为applications, applet ...

  5. 最简单的方式实现一棵二叉树

    使用最简单的方式实现看树写代码C++实现 这段代码之所以能够正常工作,是因为数组元素的地址是常量表达式.我们可以使用这种 标记法定义任何链式结构,但是这种初始化方法难以记住,所以在构建这种结构时很容易 ...

  6. 如何用最简单的方式理解傅立叶变换?

    你还在因为傅立叶挂科而头疼不已吗? 傅立叶变换经常被称为大学的杀手课程,傅立叶变换不仅仅是一个数学工具,更是一种可以彻底颠覆一个人以前世界观的思维模式. 但不幸的是,傅立叶变换的公式看起来太复杂了,所 ...

  7. python3 爬虫第二步Selenium 使用简单的方式抓取复杂的页面信息

    Selenium 简介 该系列专栏上一篇爬虫文章点击这里. 网站复杂度增加,爬虫编写的方式也会随着增加.使用Selenium 可以通过简单的方式抓取复杂的网站页面,得到想要的信息. Selenium ...

  8. dubbo 整合 zipkin,最简单的方式,亲测有效

    大家好,我是烤鸭. 之前也试过网上很多版本,看了好多文章.现在分享最简单的方式,代码侵入性最小的. 1. 修改pom,引入jar. <!-- https://mvnrepository.com/ ...

  9. python最简易入门_零基础入门python,用最简单的方式即可入门python,没有那么复杂...

    python已经开始被越来越多的人喜欢,其中有很多是从未学习过编程的人,那么,如果是从零开始学python的话,会很难吗? 其实从零开始学python并不会很难,最简单的方法,往往最有效果,无论你是否 ...

最新文章

  1. 快速排序 python菜鸟教程-快速排序
  2. GraphPad Prism 教程 :标准差与平均值的标准误差有什么区别
  3. 模拟实现顺序表ArrayList1(三级)
  4. JavaWeb学习总结(一):JavaWeb开发入门
  5. ArrayList和LinkedList使用不当,性能差距会如此之大!
  6. 操作系统学习(三)-- CPU调度
  7. OD常用快捷键(对比SoftICE)
  8. c语言工业设计中的案例,工业设计中CMF为什么这么重要?看看设计案例就知道了!...
  9. .nav ul .mall a:hover 是什么意思
  10. c++ mfc加载图片jpg,png,GIF格式
  11. BigDecimal非负数判断
  12. Ansible inventory文件详解
  13. 一个球从100米高度自由下落,每次落地后反跳回原来的高度的一半,再落下;
  14. [telink sig mesh] 周期Publish
  15. 网易云音乐歌单的推荐算法
  16. 【ICCV2019】probabilistic face embeddings 概率人脸嵌入
  17. python上台阶问题_Python解决N阶台阶走法问题的方法
  18. 使用OBS双电脑直播(主副电脑)推流至哔哩哔哩
  19. redis 6.2.6 日志文件输出
  20. Jquery 中 ajaxSubmit 、ajaxForm使用讲解

热门文章

  1. oracle中常见ck和fk是什么,oracle知识整理
  2. 第四方支付接口有哪些?
  3. 场景识别-界面无活动状态识别
  4. 树莓派3.5寸屏幕驱动安装
  5. matlab时间字符串相减,Matlab中时间字符串处理总结
  6. 计算机受控文件夹管理,自定义受控文件夹访问
  7. Mac上超级好用的Mysql工具
  8. 小红书校招笔试编程题 求表达式 f(n)结果末尾0的个数
  9. 测测你一生会爱几个人(男女通用)
  10. POj-1091 跳蚤