使用 G6关系图类库 开发流程图工具

简介: 官网:https://antv.alipay.com/zh-cn/g6/1.x/index.html注意:此教程基于1.x版本官方已更新到2.x,2.x重构了之前的代码。
img_4101399440384095dba5218e51367383.jpe

官网:https://antv.alipay.com/zh-cn/g6/1.x/index.html
注意:此教程基于1.x版本官方已更新到2.x,2.x重构了之前的代码。

1.从一个示例开始

我们先从官方示例 https://antv.alipay.com/zh-cn/g6/1.x/demo/other/plugin-dagre.html 开始学习如何使用G6

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>插件:统一分层布局</title>
    <style>
        ::-webkit-scrollbar {
            display: none;
        }

        html,
        body {
            overflow: hidden;
        }
    </style>
</head>
<body>
    <div id="mountNode"></div>
    <script>/*Fixing iframe window.innerHeight 0 issue in Safari*/document.body.clientHeight;</script>
    <script src="https://gw.alipayobjects.com/os/antv/assets/g6/1.2.5/g6.min.js"></script>
    <script src="https://gw.alipayobjects.com/os/antv/assets/lib/jquery-3.2.1.min.js"></script>
    <script src="https://gw.alipayobjects.com/os/antv/assets/g6-plugins/1.0.1/g6-plugins.min.js"></script>
    <script type="text/javascript">
        const plugin = new G6.Plugins['layout.dagre']();
        const data = {
            nodes: [
                { id: '0', label: '起始', shape: 'circle', size: 40 },
                { id: '1', label: '步骤一' },
                { id: '2', label: '步骤二' },
                { id: '3', label: '条件' },
                { id: '4.1', label: '分步骤一' },
                { id: '4.2', label: '分步骤二' },
                { id: '5', label: '汇总' },
                { id: '6', label: '结束', shape: 'circle', size: 40 },
            ],
            edges: [
                { id: '0-1', source: '0', target: '1' },
                { id: '1-2', source: '1', target: '2' },
                { id: '2-3', source: '2', target: '3' },
                { id: '3-4.1', source: '3', target: '4.1' },
                { id: '3-4.2', source: '3', target: '4.2' },
                { id: '4.1-5', source: '4.1', target: '5' },
                { id: '4.2-5', source: '4.2', target: '5' },
                { id: '5-6', source: '5', target: '6' },
            ]
        };
        const net = new G6.Net({
            id: 'mountNode',               // dom id
            height: window.innerHeight,    // 画布高
            fitView: 'autoZoom',
            grid: null,
            plugins: [plugin],
        });
        net.source(data.nodes, data.edges);
        net.render();
    </script>
</body>
</html>

首先需要一个DOM容器来创建与初始化图表,比如代码中的<div id="mountNode"></div>

关于创建图表的参数说明可以参考文档https://antv.alipay.com/zh-cn/g6/1.x/api/graph.html我就不一一解释
创建后通过net.source(data.nodes, data.edges)加载数据源,其中数据源格式参考以下说明,文档在https://antv.alipay.com/zh-cn/g6/1.x/api/net.html#_source

img_764228e78a4893b47a99fa60220fe66f.jpe
1.jpg

最后是通过 net.render()来渲染显示

2.实现新增删除节点功能

新增与删除官方示例:https://antv.alipay.com/zh-cn/g6/1.x/demo/other/crud.html

根据示例我们在之前代码基础上先添加按钮用于触发

<!-- 略.... -->
<body>
    <div id="toolBar" style="position: absolute;z-index: 10">
        <button id="addCircle">新增圆</button>
        <button id="addRect">新增矩形</button>
        <button id="delete">删除</button>
    </div>
    <div id="mountNode"></div>
<!-- 略.... -->

给添加网格背景

//....
const net = new G6.Net({
    id: 'mountNode',               // dom id
    height: window.innerHeight,    // 画布高
    fitView: 'autoZoom',
    grid: {//背景网格
        forceAlign: true, // 是否支持网格对齐
        cell: 10,         // 网格大小 
    },
    plugins: [plugin],
});
//....

然后就是绑定事件了(不过删除我更加习惯用键盘的delete进行删除)

//......
var i = 1;
$('#addCircle').on('click', () => {//添加圆形节点
    net.add('node', {
        shape: 'circle',
        id: 'id' + i++,
        x: 50 + i * 10,
        y: 50 + i * 10
    });
    net.refresh();
});

$('#addRect').on('click', () => {//添加矩形
    net.add('node', {
        shape: 'rect',
        id: 'id' + i++,
        x: 50 + i * 10,
        y: 50 + i * 10
    });
    net.refresh();
});

$('#delete').on('click', () => {//根据添加顺序删除
    if (i > 1) {
        i = i - 1;
        const item = net.find('id' + i);
        net.remove(item);
        net.refresh();
    }
});
//......

效果示例:


img_b7a4280e69a1e09398b90d6ff8035fc0.gif

3.图编辑器效果

图编辑器官方示例:https://antv.alipay.com/zh-cn/g6/1.x/demo/other/editor.html

根据示例我们同样在之前代码基础上先添加按钮用于触发

<!-- 略.... -->
<body>
  <div id="toolBar" style="position: absolute;z-index: 10">
        <button id="addCircle">新增圆</button>
        <button id="addRect">新增矩形</button>
        <button id="delete">删除</button>
        <button id="addCustom1">自定义形1</button>
        <button id="addCustom2">自定义形2</button>
        <button id="addCustom3">自定义形3</button>
        <button id="addLine">添加直线</button>
        <button id="addArrowLine">添加箭头直线</button>
        <button id="addSmooth">添加曲线</button>
        <button id="drag">拖拽模式</button>
        <button id="edit">编辑模式</button>
        <button id="consoleJSON">输出json</button>
    </div>
    <div id="mountNode"></div>
<!-- 略.... -->

然后可以根据需要注册自定义节点(注册自定义节点文档:https://antv.alipay.com/zh-cn/g6/1.x/api/index.html#_registerNode)

//示例......
G6.registerNode('customNode1', {
    getAnchorPoints() {
        return [
            [0, 0.25],
            [0, 0.5],
            [0, 0.75],
            [1, 0.25],
            [1, 0.5],
            [1, 0.75],
            [0.25, 0],
            [0.5, 0],
            [0.75, 0],
            [0.25, 1],
            [0.5, 1],
            [0.75, 1]
        ];
    }
}, 'rect');
//......

然后就是绑定事件了

//......
$('#addCustom1').on('click', () => {//添加自定义节点图形
    net.beginAdd('node', {
        label: '[未定义]',
        shape: 'circle'
    });
});

$('#addCustom2').on('click', () => {//添加自定义节点图形
    net.beginAdd('node', {label: '[未定义]'});
});

$('#addCustom3').on('click', () => {//添加自定义节点图形
    net.beginAdd('node', {
        label: '[未定义]',
        shape: 'customNode1'//<====
    });
});

$('#addLine').on('click', () => {//添加普通直线
    net.beginAdd('edge', {
        shape: 'line'//<====
    });
});

$('#addArrowLine').on('click', () => {//添加箭头直线
    net.beginAdd('edge', {
        shape: 'arrow'//<====
    });
});

$('#addSmooth').on('click', () => {//添加曲线
    net.beginAdd('edge', {
        shape: 'smooth'//<====
    });
});

$('#drag').on('click', () => {//拖拽模式
    net.changeMode('drag');
});

$('#edit').on('click', () => {//编辑模式
    net.changeMode('edit');
});

$('#consoleJSON').on('click', () => {//控制台输入json
    const saveData = net.save();
    const json = JSON.stringify(saveData, null, 2);
    console.log(saveData, json); // eslint-disable-line no-console
});
//.....

效果示例:


img_2b441cde9825f64e91ea5d77a895b983.gif

4.保存为图片

官方示例:https://antv.alipay.com/zh-cn/g6/1.x/demo/other/html2canvas.html

添加按钮

<!-- 略 -->
<div id="toolBar" style="position: absolute;z-index: 10">
         <!-- 略 -->
        <button id="downloadImage">保存图片</button>        
    </div>
<!-- 略 -->

引入html2canvas插件,比如:

<script src="https://cdn.bootcss.com/html2canvas/0.5.0-beta4/html2canvas.min.js"></script> 

事件绑定:

$("#downloadImage").on('click', (bool) => {
    const matrixStash = net.getMatrix(); // 缓存当前矩阵
    if (bool) {
        net.autoZoom(); // 图自动缩放以适应画布尺寸
    }
    html2canvas(net.get('graphContainer'), {
        onrendered(canvas) {
            const dataURL = canvas.toDataURL('image/png');
            const link = document.createElement('a');
            const saveName = '图片' + ('.png' || 'graph.png');//<====选中模块名称
            link.download = saveName;
            link.href = dataURL.replace('image/png', 'image/octet-stream');
            link.click();
            net.updateMatrix(matrixStash); // 还原矩阵
            net.refresh();
        }
    });
});

5.添加提示信息

官方示例:https://antv.alipay.com/zh-cn/g6/1.x/demo/other/tooltip-cfg.html

添加提示信息十分简单直接复制粘贴改改就行

//略.....
const net = new G6.Net({
   //略.....
});
net.source(data.nodes, data.edges);
net.tooltip({
    title: '节点信息', // @type {String} 标题
    split: ':',  // @type {String} 分割符号
    dx: 10,       // @type {Number} 水平偏移
    dy: 10        // @type {Number} 竖直偏移
});
net.node().tooltip(['id', 'label']);
net.edge().tooltip(['id']);
net.render();
//略.....

效果:


img_7eb8d6e733a4a8c13b4595f474e026c7.gif

6.实现可编辑标签

可编辑标签可参考官网示例:https://antv.alipay.com/zh-cn/g6/1.x/demo/editor/mind.html
可编辑标签需要用到图表的事件,事件文档 https://antv.alipay.com/zh-cn/g6/1.x/api/graph.html#_on

以下是从官方示例里扣出来的代码,主要就是创建一个input输入框能同步显示到图表对应上方,然后对输入值进行同步绑定

<script> 
   var Util = G6.Util;
   var input = Util.createDOM('<input class="g6-label-input" />', {
       position: 'absolute',
       zIndex: 10,
       display: 'none'
   });

   function hasClass(shape, className) {
       if (shape) {
           const clasees = shape.get('class');
           if (clasees && clasees.indexOf(className) !== -1) {
               return true;
           }
       }
       return false;
   }

   function showInputLabel(node) {
       if (!node) {
           return;
       }
       const group = node.get('group');
       const label = group.findBy(function (child) {
           if (hasClass(child, 'label')) {
               return true;
           }
           return false;
       });
       const rootGroup = net.get('rootGroup');
       const bbox = Util.getBBox(label, rootGroup);
       const borderWidth = 1;
       const text = label.attr('text');
       clearAllActived();

       input.value = text;
       input.show();
       input.css({
           top: bbox.minY - borderWidth + 'px',
           left: bbox.minX - borderWidth + 'px',
           width: bbox.width + 'px',
           height: bbox.height + 'px',
           padding: '0px',
           margin: '0px',
           border: borderWidth + 'px solid #999'
       });
       input.focus();
       input.node = node;
   }

   function updateLabel() {
       if (input.visibility) {
           const node = input.node;
           clearAllActived();
           if (input.value !== node.get('model').name) {
               if (input.value) {
                   net.update(node, {
                       label: input.value
                   });
               }
           }
           input.hide();
       }
   }

   function clearAllActived() {
       net.clearAllActived();
       net.refresh(false);
   }
   input.hide = function () {
       input.css({
           display: 'none'
       });
       input.visibility = false;
   };
   input.show = function () {
       input.css({
           display: 'block'
       });
       input.visibility = true;
   };

   input.on('keydown', ev => {
       if (ev.keyCode === 13) {
           updateLabel();
       }
   });

   input.on('blur', () => {
       updateLabel();
   });
</script>

有了输入框我们只要绑定图表在什么事件下显示隐藏输入框就行了

//.......
const graphContainer = net.get('graphContainer');//获取图表内部容器
graphContainer.appendChild(input);//追加input输入框
graphContainer.oncontextmenu = function (e) { return false; }//阻止默认右键菜单
net.on('contextmenu', ev => {// 鼠标右键点击事件
    console.log("选中类型:", ev.itemType);
    console.log(ev);
});

net.on('itemmouseenter', ev => {//子项鼠标悬浮
    const item = ev.item;
    net.update(item, {
        color: 'red',
    });
    net.refresh();
});
net.on('itemmouseleave', ev => {//子项鼠标离开事件
    const item = ev.item;
    net.update(item, {
        color: null
    });
    net.refresh();
});
net.on('itemmousedown', ev => {//子项鼠标按下
    const item = ev.item;
    net.update(item, {
        color: '#9ef'
    });
});
net.on('itemmouseup', ev => {//子项鼠标弹起
    const item = ev.item;
    net.update(item, {
        color: 'null'
    });
});

net.on('dragmove', () => {//拖拽隐藏
    input.hide();
});

net.on('dblclick', ev => {//双击显示
    const item = ev.item;
    const shape = ev.shape;
    if (hasClass(shape, 'label') && item && item.get('type') === 'node') {//节点的情况下
        showInputLabel(item);
    }
});
//.......

效果:


img_6d142419165f99713dcfeade46e3f93e.gif

7.布局方向

根据文档(https://antv.alipay.com/zh-cn/g6/1.x/api/plugins.html),统一分层布局有四个方向'TB', 'BT', 'LR', or 'RL' 默认值为 'TB',我没找到有啥实时函数能够修改布局方向,所以简单点那就每次初始化进行修改吧。

代码示例:

<!-- 略 -->
<body>
    <div id="toolBar" style="position: absolute;z-index: 10">
    <!-- 略 -->
    <button id="reLayout">自动布局</button>
        <label>视图方向:
            <select id="ViewLy">
                <option selected value="TB">上下</option>
                <option value="BT">下上</option>
                <option value="LR">左右</option>
                <option value="RL">右左</option>
            </select>
        </label>
    <!-- 略 -->
 <script type="text/javascript">
    /** 略 **/
    var data = {/** 略 **/};
    var net = null;
        function reLoadNet(v) {
            const layout_dagre = new G6.Plugins['layout.dagre']({
                rankdir: v,//可取值为: 'TB', 'BT', 'LR', or 'RL' 默认值为 'TB'
                //nodesep:10,//节点间距
                // useEdgeControlPoint:false,//生成边控制点 默认值为 true
            });
            if (net) {
                data = net.save().source;
                net.destroy();
                net = null
            };
            // preview.minimap = new G6.Plugins['tool.minimap']();
            net = new G6.Net({
                id: 'mountNode',              // 容器ID
                mode: 'edit',  // 编辑模式
                fitView: 'cc', // 自适应视口为左上 
                height: window.innerHeight,    // 画布高
                fitView: 'autoZoom',
                grid: {//背景网格
                    forceAlign: true, // 是否支持网格对齐
                    cell: 10,         // 网格大小 
                },
                plugins: [layout_dagre],
            });
            if (data) net.source(data.nodes, data.edges);
            const graphContainer = net.get('graphContainer');
            graphContainer.appendChild(input);
            graphContainer.oncontextmenu = function (e) { return false; }

            net.tooltip({
                title: '节点信息', // @type {String} 标题
                split: ':',  // @type {String} 分割符号
                dx: 10,       // @type {Number} 水平偏移
                dy: 10        // @type {Number} 竖直偏移
            });
            net.node().tooltip(['id', 'label']);
            net.edge().tooltip(['id', 'label']);
            net.render();
        }
        reLoadNet();
        /** 略 **/
       $("#reLayout").on('click', () => {
            var a = net.save();
            net.changeData();
            a.source.edges.forEach((v, i, a) => {
                delete v.x;
                delete v.y;
            });
            a.source.nodes.forEach((v, i, a) => {
                delete v.x;
                delete v.y;
            });
            net.source(a.source.nodes, a.source.edges);
            net.render();
        });
        $("#ViewLy").change(function(){
            reLoadNet(this.value);
        });
</script>
<!-- 略 -->

效果:


img_d8b3f59f73ceebc4c133a9d7f252997f.gif

最终代码效果: http://runjs.cn/detail/92edwvz9

相关文章
|
6月前
|
XML JSON 算法
【软件设计师备考 专题 】编写内部设计文档:构件划分图和接口
【软件设计师备考 专题 】编写内部设计文档:构件划分图和接口
100 0
|
6月前
|
API 开发工具 数据库
OneCode2.0源码结构分析
OneCode12月10日正式更新了其V2.0版本。从OneCode的季度版本生命中,可以看到2.0版本还是一个重量级的版本,笔者在收到2.0更新后第一时间下拉了最新的代码。在参考了OneCode 的技术说明后,根据包结构来分析一下OneCode2.0的结构。
|
存储 人工智能 算法
详细设计工具之盒图(N-S图)
详细设计工具之盒图(N-S图)
1140 0
详细设计工具之盒图(N-S图)
|
XML 数据库 数据格式
嵌入式工作流程开发!工作流 Activiti 框架中子流程的使用指南
本篇文章对工作流Activiti框架中的子流程进行的详尽的分析和说明,在工作流Activiti集成到项目中开发时,可以嵌入子流程进行使用。子流程包括了事件子流程,事务子流程以及调用活动子流程。通过对内嵌子流程的方式的学习,可以帮助我们将工作流框架很好地应用在继承式建模的流程场景下。
1047 0
嵌入式工作流程开发!工作流 Activiti 框架中子流程的使用指南
|
IDE 开发工具 C++
⚡【C++要笑着学】(40) OF框架:OpenFrameworks 框架介绍 | oF 文件结构 | 图形基础 | 使用 ProjectGenerator 创建项目
⚡【C++要笑着学】(40) OF框架:OpenFrameworks 框架介绍 | oF 文件结构 | 图形基础 | 使用 ProjectGenerator 创建项目
175 0
|
6月前
|
传感器 运维
【软件设计师备考 专题 】编写外部设计文档:系统配置图和关系图
【软件设计师备考 专题 】编写外部设计文档:系统配置图和关系图
103 1
|
数据可视化 JavaScript
可视化拖拽组件库一些技术要点原理分析(二)(上)
可视化拖拽组件库一些技术要点原理分析(二)
96 1
|
数据可视化 JavaScript
可视化拖拽组件库一些技术要点原理分析(四)(上)
可视化拖拽组件库一些技术要点原理分析(四)
88 0
可视化拖拽组件库一些技术要点原理分析(四)(上)
|
JSON 数据可视化 前端开发
可视化拖拽组件库一些技术要点原理分析(二)(下)
可视化拖拽组件库一些技术要点原理分析(二)(下)
108 0
|
数据可视化 API
可视化拖拽组件库一些技术要点原理分析(三)(一)
可视化拖拽组件库一些技术要点原理分析(三)
95 0
下一篇
无影云桌面