Vue版本已开源,欢迎移步github,Vue版本的介绍文章链接点击这里

一、概况

接到了数据血缘的需求,前端要求效果类似sqlflow。通过大佬的类似demo发现了jsplumb这个连线库。然后看文档和github一些demo捣鼓出来了。基本效果如下:
连线样式为贝塞尔曲线的表现:

连线样式为状态机的表现:

项目地址 github:jsplumb-dataLineage

https://github.com/mizuhokaga/jsplumb-dataLineage

(后端示例json项目附带,后端项目待开源。可参考格式,需注意github中提到的json的node对象的属性id不能带特殊符号和数字!)
目前已实现效果:

二、主流程

设计思想参考无临时表的sqlflow。在没有临时表的情况下,数据血缘只有两种表,起源表和目标(结果)表。起源表在画布左边,仅需要右边的锚点(锚点是jsPlumb的概念,参考jsplumb中文文档)目标表在画布右边仅需要左边的锚点。设计目标类似下图,注意我关闭了show intermediate recordset,即不显示临时表。

所以我先根据后端json数据依靠模板渲染出不同类型的节点(节点就是起源表和目标表)设置好锚点,再利用jsplumb连线、绑定事件。

1.血缘里有两种表,起源表和目标表,所以我们需要两个js模板

   <!--    起源表-->
<script id="tpl-Origin" type="text/html"><div class="pa" id='{{id}}' style='top:{{top}}px;left:{{left}}px'><div class="panel panel-node panel-node-origin" id='{{id}}-inner'><div id='{{id}}-heading' data-id="{{id}}" class="table-header">{{name}}</div><ul id='{{id}}-cols' class="col-group"></ul></div></div>
</script><!--    目标表-->
<script id="tpl-RS" type="text/html"><div class="pa" id='{{id}}' style='top:{{top}}px;left:{{left}}px'><div class="panel  panel-node panel-node-rs" d='{{id}}-inner'><div id='{{id}}-heading' data-id="{{id}}" class="table-header"style="background-color: #d26b58;color: white"> {{name}}</div><ul id='{{id}}-cols' class="col-group"></ul></div></div>
</script>

2.发请求给接口获取血缘json数据

 function main() {jsPlumb.setContainer('bg');// 请求接口血缘json$.get(requestURL, function (res, status) {if (status === "success") {jsonData = res;DataDraw.draw(jsonData)}}, 'json');// 或使用本地数据// DataDraw.draw(json);}

3.根据后端传来的数据来渲染节点和连线

 var DataDraw = {// 核心方法draw: function (json) {var $container = $(areaId)var that = this//遍历渲染所有节点json.nodes.forEach(function (item, key) {var data = {id: item.id,name: item.id,top: item.top,left: item.left,};//根据不同类型的表获取各自的模板并填充数据var template = that.getTemplate(item);$container.append(Mustache.render(template, data));//根据json数据添加表的每个列//将类数组对象转换为真正数组避免前端报错 XX.forEach is not a functionitem.columns = Array.from(item.columns);//将该表的所有列item.columns.forEach(col => {var ul = $('#' + item.id + '-cols');//这里li标签的id应该和 addEndpointOfXXX方法里的保持一致 col-group-itemvar li = $("<li id='id-col' class='panel-node-list' >col_replace</li>");//修改每个列名所在li标签的id使其独一无二li[0].id = item.name + '.' + col.name//填充列名li[0].innerText = col.name;ul.append(li);});//根据节点类型找到不同模板各自的 添加端点 方法if (that['addEndpointOf' + item.type]) {that['addEndpointOf' + item.type](item)}});//最后连线this.finalConnect(json.nodes, json.relations)},

根据不同类型的模板添加节点的方法:

   addEndpointOfOrigin: function (node) {//节点设置可拖拽addDraggable(node.id);node.columns = Array.from(node.columns);node.columns.forEach(function (col) {//这里的id应该和draw方法里设置的id保持一致setOriginPoint(node.id + '.' + col.name, 'Right')})},addEndpointOfRS: function (node) {addDraggable(node.id)node.columns = Array.from(node.columns);node.columns.forEach(function (col) {setRSPoint(node.id + '.' + col.name, 'Left')})},

连线的方法,注释地很详细:

  //根据节点类型找到对应的渲染方法finalConnect: function (nodes, relations) {var that = this;nodes.forEach(function (node) {//RS表要排除,if (node.id != 'RS' && node.type != 'RS') {//遍历每个表的每个列node.columns.forEach(col => {relations.forEach(relation => {var relName = relation.source.parentName + '.' + relation.source.column;var nodeName = node.name + '.' + col.name;//如果关系中的起始关系等于当前表节点的列,就构建连接if (relName === nodeName) {//这里sourceUUID、targetUUID应该和addEndpoint里设置的uuid一致var sourceUUID = nodeName + "-OriginTable";var targetUUID = relation.target.parentName + '.' + relation.target.column + '-RSTable';that.connectEndpoint(sourceUUID, targetUUID);//鼠标移动到连接线上后,两边的列高亮jsPlumb.bind("mouseover", function (conn, originalEvent) {var src_name = conn.sourceId.split(".");var tar_name = conn.targetId.split(".");//注意 . 的转义,参考 https://blog.csdn.net/qq_44831907/article/details/120899676$("#" + src_name[0] + "-cols").find("#" + src_name[0] + "\\." + src_name[1]).css("background-color", "#faebd7");$("#" + tar_name[0] + "-cols").find("#" + tar_name[0] + "\\." + tar_name[1]).css("background-color", "#faebd7");});jsPlumb.bind("mouseout", function (conn, originalEvent) {var src_name = conn.sourceId.split(".");var tar_name = conn.targetId.split(".");$("#" + src_name[0] + "-cols").find("#" + src_name[0] + "\\." + src_name[1]).css("background-color", "#fff");$("#" + tar_name[0] + "-cols").find("#" + tar_name[0] + "\\." + tar_name[1]).css("background-color", "#fff");});}});});}})},//真正调用的方法还是jsplumb的连接方法connectEndpoint: function (from, to) {// 通过编码连接endPoint需要用到uuidjsPlumb.connect({uuids: [from, to]})},

获取模板的方法:

  getTemplate: function (node) {return $('#tpl-' + node.type).html();},

几个通用方法:

// 获取基本配置function getBaseNodeConfig() {return Object.assign({}, visoConfig.baseStyle)};// 让元素可拖动function addDraggable(id) {jsPlumb.draggable(id, {containment: '#bg'})};// 设置起源表每一列的端点function setOriginPoint(id, position) {var config = getBaseNodeConfig()config.isSource = true//一个起源表的字段可能是多个RS字段的来源 这里-1不限制连线数config.maxConnections = -1jsPlumb.addEndpoint(id, {anchors: [position || 'Right',],uuid: id + '-OriginTable'}, config)};// 设置RS端点function setRSPoint(id, position) {var config = getBaseNodeConfig()config.isTarget = true//RS表一个字段可能是来自多个起源表字段 这里-1不限制连线数config.maxConnections = -1;jsPlumb.addEndpoint(id, {anchors: position || 'Left',uuid: id + '-RSTable'}, config)};

三、几个功能实现的记录

1.流程图下载为png图片
利用html2canvas这个js,由于jsplumb的线是svg无法被html2canvas识别,所以需要额外处理一下,参考这篇文章

 function download_png() {if (typeof html2canvas !== 'undefined') {var nodesToRecover = [];var nodesToRemove = [];var svgElem = $("#bg").find('svg');//注意修改选取的dom元素//将边(svg)转化了canvas的形式svgElem.each(function (index, node) {var parentNode = node.parentNode;var svg = node.outerHTML.trim();//canvas 容器var canvas = document.createElement('canvas');canvg(canvas, svg);if (node.style.position) {canvas.style.position += node.style.position;canvas.style.left += node.style.left;canvas.style.top += node.style.top;}nodesToRecover.push({parent: parentNode,child: node});parentNode.removeChild(node);nodesToRemove.push({parent: parentNode,child: canvas});parentNode.appendChild(canvas);})}//scala属性解决生成的canvas模糊问题html2canvas($("#bg"), {taintTest: false, scale: 2}).then(canvas => {var a = document.createElement('a');//转换图片格式方法来自 https://blog.csdn.net/yzding1225/article/details/119215395var blob = this.dataURLToBlob(canvas.toDataURL('image/png'));//这块是保存图片操作  可以设置保存的图片的信息a.setAttribute('href', URL.createObjectURL(blob));//图片名称是当前 时间戳+uuida.setAttribute('download', new Date().getTime() + this.getUUID() + '.png');a.click();URL.revokeObjectURL(blob);a.remove();//由于生成图片将svg转换了canvas导致边的hover事件失效,需要重新填入数据 or 刷新页面//TODO:目前直接刷新整个页面location.reload()});};

2.流程图下载为json
这里偷懒,直接把后端传过来的json下载了

   function download_json() {//如果血缘信息json是直接从后端请求过来的,直接下载接口数据var datastr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(jsonData));var a = document.createElement('a');a.setAttribute("href", datastr);a.setAttribute("download", new Date().getTime() + this.getUUID() + '.json');a.click();a.remove();};

3.流程图缩放
没什么好方法,暂时用的css的scala属性实现的

 //原始尺寸var baseZoom = 1;//重置缩放function reset() {if (this.baseZoom !== 1) {this.baseZoom = 1;const zoom = this.baseZoom;this.zoom(zoom);jsPlumb.setZoom(baseZoom);}}//缩放是整个画布及其内容一起缩放//参考 https://blog.csdn.net/KentKun/article/details/105230475function zoom(scale) {$("#bg").css({"-webkit-transform": `scale(${scale})`,"-moz-transform": `scale(${scale})`,"-ms-transform": `scale(${scale})`,"-o-transform": `scale(${scale})`,"transform": `scale(${scale})`,"transform-origin": "0% 0%"})};
//放大function zoomin() {this.baseZoom += 0.1;const zoom = this.baseZoom;this.zoom(zoom);jsPlumb.setZoom(zoom);};//缩小function zoomout() {this.baseZoom -= 0.1;const zoom = this.baseZoom;this.zoom(zoom);jsPlumb.setZoom(zoom);}

4.流程图拖动
本来想实现画布拖动,最后实现是把流程图中所有节点全部移动造成的假象,参考这里

   X = 0;Y = 0;bgX = $("#bg").width();bgY = $("#bg").height();//拖动功能不够完善又缺陷。参考 https://blog.csdn.net/join_null/article/details/80266993//松开鼠标右键function mouseup(event) {if (event.button == 2) {$("#bg").css("cursor", "Auto")this.flag = false;}// console.log(this.X+"|"+this.Y)}//按下鼠标右键function mousedown(event) {if (event.button == 2) {this.flag = true;$("#bg").css("cursor", "Grabbing");var bx = event.offsetX;var by = event.offsetY;this.X = bx;this.Y = by;// console.log(this.X + "|" + this.Y)}}//按住右键拖动,血缘关系图会在框架内移动function move(event) {if (flag && baseZoom===1) {//获取相对父元素的坐标var ax = event.offsetX;var ay = event.offsetY;var tmp_x = (ax - this.X), tmp_y = (ay - this.Y);// console.log(tmp_x + "t|" + tmp_y)if (this.flag) {$("#bg .pa").each(function (index, node) {var a = tmp_x + $(node).position().left;var b = tmp_y + $(node).position().top;if (a >= bgX || a <= 0) a = bgX - $(node).width();else if (b >= bgY || b <= 0) b = bgY - $(node).height();else {$(node).css('left', $(node).position().left+tmp_x/25);$(node).css('top', $(node).position().top+tmp_y/25);}});jsPlumb.repaintEverything();}}};

5.选择连线后线两端节点高亮
利用jsplumb的连线事件实现的

//连线that.connectEndpoint(sourceUUID, targetUUID);
//鼠标移动到连接线上后,两边的列高亮
jsPlumb.bind("mouseover", function (conn, originalEvent) {var src_name = conn.sourceId.split(".");var tar_name = conn.targetId.split(".");//注意 . 的转义,参考 https://blog.csdn.net/qq_44831907/article/details/120899676$("#" + src_name[0] + "-cols").find("#" + src_name[0] + "\\." + src_name[1]).css("background-color", "#faebd7");$("#" + tar_name[0] + "-cols").find("#" + tar_name[0] + "\\." + tar_name[1]).css("background-color", "#faebd7");});jsPlumb.bind("mouseout", function (conn, originalEvent) {var src_name = conn.sourceId.split(".");var tar_name = conn.targetId.split(".");$("#" + src_name[0] + "-cols").find("#" + src_name[0] + "\\." + src_name[1]).css("background-color", "#fff");$("#" + tar_name[0] + "-cols").find("#" + tar_name[0] + "\\." + tar_name[1]).css("background-color", "#fff");});

四 一些编写途中遇到的坑与解决方案记录

  • html2canvas截图模糊问题
  • jsplumb清空画布解决方案

【已开源】基于jsPlumb.js的模仿sqlFlow数据血缘图的前端页面相关推荐

  1. Pomelo:网易开源基于 Node.js 的游戏服务端框架

    Pomelo 是基于 Node.js 的高性能.分布式游戏服务器框架.它包括基础的开发框架和相关的扩展组件(库和工具包),可以帮助你省去游戏开发枯燥中的重复劳动和底层逻辑的开发.Pomelo 不但适用 ...

  2. 移动端cube界面设计html,滴滴开源基于 Vue.js 的移动端组件库 cube-ui

    原标题:滴滴开源基于 Vue.js 的移动端组件库 cube-ui 开源最前线(ID:OpenSourceTop) 猿妹 整编 综合自:https://didi.github.io/cube-ui/ ...

  3. Gio.js -- 一个基于 Three.js 的 Web3D 地球数据可视化库(一)

    Gio.js 开始使用 安装 使用 基础元素 简介 表面 国家 连接线 背景 光晕 海洋 性能监控 配置 通过构造函数配置 通过API配置 配置参数表 功能设计型API 设置初始国家 高亮被提及国家 ...

  4. Gio.js -- 一个基于 Three.js 的 Web3D 地球数据可视化库(二)

    Gio,js 颜色风格类API 设置输出颜色 设置输入颜色 设置光晕颜色 设置背景颜色 设置海洋亮度 设置相关国家亮度 设置被提到国家亮度 数据 添加数据 清除数据 切换数据集 输入数据到洲 异步添加 ...

  5. [开源] Gio.js -- 一个基于 Three.js 的 Web3D 地球数据可视化库

    在这里和大家分享一个和小伙伴们一起开发的开源库 Gio.js .Gio.js 是一个基于 Three.js 的 web 3D 地球数据可视化的开源组件库.使用 Gio.js 的网页应用开发者,可以快速 ...

  6. 基于 Vue.js 2.0 酷炫自适应背景视频登录页面的设计

    本文讲述如何实现拥有酷炫背景视频的登录页面,浏览器窗口随意拉伸,背景视频及前景登录组件均能完美适配,背景视频可始终铺满窗口,前景组件始终居中,视频的内容始终得到最大限度的保留,可以得到最好的视觉效果. ...

  7. 马哈鱼SQLFLow数据血缘分析器增量分析血缘任务

    马哈鱼数据血缘分析器是一个分析数据血缘关系的平台,支持对大量复杂的数据快速准确的分析,支持分批增量分析血缘. 本文主要介绍如何利用马哈鱼增量分析任务. 使用步骤 登录sqlflow-api(SQLFL ...

  8. 基于原生JS写的异形轮播图--效果如网易云、QQ音乐播放器中轮播图

    css部分 <style>#box{height:500px;width:1000px;position: relative;margin:100px auto;overflow: hid ...

  9. html 实时曲线 js,基于d3.js实现实时刷新的折线图

    先来看看效果图 下面直接上源代码,html文件 实时刷新折线图 .axis path, .axis line{ fill: none; stroke: black; shape-rendering: ...

最新文章

  1. juery mobile select下来菜单选项提交form问题
  2. B端产品思维全解析,提升产品经理核心竞争力
  3. 阿里云多个智物新品集体出道,持续加速产业智能化
  4. 利用数组实现栈java,用java编写出来:用数组实现一个栈
  5. 深刻理解HDFS工作机制
  6. 华为大佬:做一个快乐的程序员,而不是码农
  7. a5 1c语言实现,初识C语言1_qq5fb3b05a5f322的技术博客_51CTO博客
  8. FFMPEG 库移植到 VC 需要的步骤
  9. 新建3台linux7.5部署k8s,之后的软件安装全部都在k8s
  10. 190129每日一句
  11. codesmith mysql 注释_完美解决CodeSmith无法获取MySQL表及列Description说明注释的方案...
  12. 微信公众平台开发实战(03) 运行日志写入SAE数据库
  13. 2021年全球与中国测光表行业市场规模及发展前景分析
  14. Android集成阿里云一键登录步骤
  15. Linux 怎么防止 ssh 被暴力破解
  16. 厨神之路三--自制饮品
  17. s8 android z,三星Galaxy S8领衔:2017年十佳Android智能手机
  18. 2019/12/31 教养
  19. 【云原生】学习K8s,读完这篇就够了
  20. teablue数据分析_智能图像分析–智能外币检测与识别-艾科瑞特(iCREDIT)【最新版】_图像识别_商业智能_数据API-云市场-阿里云...

热门文章

  1. lighttp支持PHP移植到imx6,[Qt开发指南]飞凌嵌入式iMX6开发板QT移植
  2. iOS系统如何彻底删除微信聊天记录?分布式删除,挖掘深层数据!
  3. Linux readlink
  4. scrapyd部署方法
  5. input 输入框限制只能输入两位有效小数
  6. Android+8.0+微信表情,微信表情包不会动是怎么回事 安卓微信8.0更新表情特效怎么弄?...
  7. 63GB,2.35 亿 Twitter 用户的姓名、邮件在裸奔……
  8. Mysql无法启动报错19884 ExecStart=/usr/sbin/mysqld --daemonize --pid-file=/var/run/mysqld/mysqld.pid
  9. 中正平和的机器人学笔记——4. 雅可比矩阵(附MTALB代码)
  10. 【编程数学】001 整除与被整除、除与除以、整数