用最简单的方式画拓扑图!!!

栏目: Html5 · 发布时间: 4年前

内容简介:前段时间重构了下面这样一个页面(产品页面不方便截图):类似于拓扑图的配置,原来是使用go.js实现的,类似的库还有antv g6。重构主要是为了提高代码质量,降低维护成本,产品上需要更强的定制化能力(对付产品经理的变态需求),所以经过一番研究之后,最后决定放弃使用现成的库。原因如下:当然,以上两个库还是相当强大的,不过基于这些原因,自己基于(DOM + SVG)撸一个拓扑图配置的工具库topology-byfe

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

用最简单的方式画拓扑图!!!

类似于拓扑图的配置,原来是使用go.js实现的,类似的库还有antv g6。重构主要是为了提高代码质量,降低维护成本,产品上需要更强的定制化能力(对付产品经理的变态需求),所以经过一番研究之后,最后决定放弃使用现成的库。原因如下:

  1. 维护成本高:类似的库(antv/g6, go.js)都是基于canvas实现,也都大同小异的定义了一套组件,有一定的学习成本,同时基于这样的库写出来的代码都相对复杂;
  2. 灵活性差:因为是canvas实现,元素一般需要指定尺寸,所以在一些需要元素大小自适应的地方并没有DOM元素好实现;
  3. 定制化能力差。只能使用库里定义的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<{}, FlowState> {
    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 (
            <div className="topology-node">
                <div className="node-header">{name}</div>
                <p className="node-content">{content}</p>
                {branches.length > 0 && (
                    <div className="flow-node-branches-wrapper">
                        {branches.map(
                            (item: string, index: number) => anchorDecorator({
                                anchorId: `${index}`,
                            })(<div className="flow-node-branch">{item}</div>),
                        )}
                    </div>
                )}
            </div>
        );
    };

    onChange = (data: ITopologyData, type: string) => {
        this.setState({ data });
        console.log('change type:', type);
    };

    render() {
        const { data } = this.state;
        return (
            <div className="topology">
                <div className="topology-templates">
                    <TemplateWrapper generator={() => this.generatorNodeData(true)}>
                        <div className="topology-templates-item">宽节点</div>
                    </TemplateWrapper>
                    <TemplateWrapper generator={() => this.generatorNodeData(false)}>
                        <div className="topology-templates-item">窄节点</div>
                    </TemplateWrapper>
                </div>
                <div style={{ width: '100%', height: 800 }}>
                    <Topology
                        data={data}
                        autoLayout
                        onChange={this.onChange}
                        onSelect={this.handleSelect}
                        renderTreeNode={this.renderTreeNode}
                    />
                </div>
            </div>
        );
    }
}

export default topologyWrapper(Flow);

复制代码

效果图

用最简单的方式画拓扑图!!!

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

为什么选择DOM + SVG?

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

如何使用

核心部分代码:

<Topology
    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 (
            <div className="topology-node">
                <div className="node-header">{name}</div>
                <p className="node-content">{content}</p>
                {branches.length > 0 && (
                    <div className="flow-node-branches-wrapper">
                        {branches.map(
                            (item: string, index: number) => anchorDecorator({
                                anchorId: `${index}`,
                            })(<div className="flow-node-branch">{item}</div>),
                        )}
                    </div>
                )}
            </div>
        );
    };
复制代码

锚点,decorators.anchorDecorator

anchorDecorator({ anchorId: `${index}` })(
    <div className="flow-node-branch">
        {item}
    </div>
)
复制代码

anchorDecorator是一个装饰器函数,接受一个options,目前只包含一个anchorId属性,即锚点id,如果不传的话,内部会自动生成一个自增id。可以看到,锚点长什么样,放到哪儿完全由你自己决定。

templateWrapper

<div className="topology-templates">
        <TemplateWrapper generator={() => this.generatorNodeData(true)}>
            <div className="topology-templates-item">宽节点</div>
        </TemplateWrapper>
        <TemplateWrapper generator={() => this.generatorNodeData(false)}>
            <div className="topology-templates-item">窄节点</div>
        </TemplateWrapper>
</div>
复制代码

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

topologyWrapper

export default topologyWrapper(Flow);
复制代码

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

export const topologyWrapper = DragDropContext(HTML5BackEnd);
复制代码

总结

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

最后

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


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Head First Web Design

Head First Web Design

Ethan Watrall、Jeff Siarto / O’Reilly Media, Inc. / 2009-01-02 / USD 49.99

Want to know how to make your pages look beautiful, communicate your message effectively, guide visitors through your website with ease, and get everything approved by the accessibility and usability ......一起来看看 《Head First Web Design》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具