• 说明
    项目中需要绘制作业血缘图,网上调研了下也没有找到合适的轮子,于是就自己撸了,这里只有一层依赖关系,及父与子作业。
  • 思路
    根据后端返回的接口数据,计算每个节点的位置坐标,根据节点文本内容计算每个节点的宽度,高度固定。得到这些位置信息后,借用svg的path标签绘制出本依赖关系图。
  • 预览
  • 实现(代码写得比较粗糙,需要整体,这里贴个初版)

接口返回的数据结构如下:

代码实现:

// dag.js
const SVG_NS = 'http://www.w3.org/2000/svg'
export const getTextWidth = text => {let svg = document.createElementNS(SVG_NS, 'svg')let oText = document.createElementNS(SVG_NS, 'text')oText.innerHTML = textsvg.appendChild(oText)document.body.appendChild(svg)let width = oText.getBBox().widthdocument.body.removeChild(svg)return width + 10
}// 创建tag标签
export const createTag = (tag, attrObjs) => {let tagEle = document.createElementNS(SVG_NS, tag)for (let key in attrObjs) {tagEle.setAttribute(key, attrObjs[key])}return tagEle
}// node类
class DagNode {constructor (job = {}, options = {}, containerId) {this.prev = []this.next = []this.job = jobthis.width = Math.max(getTextWidth(job.jobName || job.jobId), 100)this.height = 40this.offsetX = options.offsetX || 0this.offsetY = options.offsetY || 0this.containerId = containerId || 'bloodChart'}// 绘制nodedrawNode () {let oG = createTag('g')let oRect = createTag('rect', {x: this.offsetX,y: this.offsetY,rx: 5,ry: 5,width: this.width,height: this.height,style: 'cursor: pointer; fill: transparent; stroke-width: 2; stroke: #303030'})let oText = createTag('text', {x: this.offsetX + 10,y: this.offsetY + 15,style: `cursor: pointer; fill: #303030`,'data-node': JSON.stringify(this.job)})let tSpan = createTag('tspan', {x: this.offsetX + 10,y: this.offsetY + 15,style: `cursor: pointer; fill: #303030`,'data-node': JSON.stringify(this.job)})tSpan.innerHTML = `${this.job.jobName || this.job.jobId}`let tSpan2 = createTag('tspan', {x: this.offsetX + 10,y: this.offsetY + 35,style: `cursor: pointer; font-size: 12px; fill: #303030`,'data-node': JSON.stringify(this.job)})tSpan2.innerHTML = `${this.job.jobTypeCn} / ${this.job.state}`oText.appendChild(tSpan)oText.appendChild(tSpan2)oG.appendChild(oRect)oG.appendChild(oText)document.getElementById(this.containerId).appendChild(oG)return oG}// 绘制带箭头的曲线条drawLine (start = {}, end = {}, arrow = {}) {let oG = createTag('g')let pathLine = ''if (start.y < end.y) {pathLine = Math.abs(start.y - end.y) < 10 ?` M${start.x} ${start.y} L${end.x} ${end.y} ` :` M${start.x} ${start.y} L${(end.x + start.x) / 2} ${start.y}Q${(end.x + start.x) / 2 + 10} ${start.y} ${(end.x + start.x) / 2 + 10} ${start.y + 10}L${(end.x + start.x) / 2 + 10} ${end.y - 10}Q${(end.x + start.x) / 2 + 10} ${end.y} ${(end.x + start.x) / 2 + 20} ${end.y}L${end.x} ${end.y}`} else {pathLine = Math.abs(start.y - end.y) < 10 ?` M${start.x} ${start.y} L${end.x} ${end.y} ` :` M${start.x} ${start.y} L${(end.x + start.x) / 2} ${start.y}Q${(end.x + start.x) / 2 + 10} ${start.y} ${(end.x + start.x) / 2 + 10} ${start.y - 10}L${(end.x + start.x) / 2 + 10} ${end.y + 10}Q${(end.x + start.x) / 2 + 10} ${end.y} ${(end.x + start.x) / 2 + 20} ${end.y}L${end.x} ${end.y} `}let oLine = createTag('path', {d: pathLine,style: 'fill: none; stroke: #303030; stroke-width: 2;'})let oArrow = createTag('path', {d: `M${arrow.x} ${arrow.y} ${arrow.other} Z`,style: 'fill: #303030; stroke: #303030; stroke-width: 2;'})oG.appendChild(oLine)oG.appendChild(oArrow)document.getElementById(this.containerId).appendChild(oG)return oG}
}export class NodeGraph {constructor (dom, options = {}) {this.center = nullthis.svgChart = domthis.gap = 10}// 设置nodes坐标setNodes (data = {}) {let _that = this, preMaxWidth = 0, nextMaxWidth = 0this.center = new DagNode()// 中心节点let centerNode = new DagNode(data)centerNode.prev = []centerNode.next = []centerNode.offsetX = this.svgChart.getBoundingClientRect().width / 2 - centerNode.width / 2 + 50// 设置父节点nodeif (data.hasOwnProperty('parentList')) {// if (data.parentList.length === 0) {//   centerNode.offsetX = 100// }data.parentList && data.parentList.forEach((item, index) => {let node = new DagNode(item)if (index === 0) {node.offsetX = centerNode.offsetX - 100 - node.widthnode.offsetY = centerNode.offsetY - _that.gap > 60 ? _that.gap : 60preMaxWidth = preMaxWidth > node.width ? preMaxWidth : node.width} else {node.offsetX = centerNode.offsetX - 100 - node.widthnode.offsetY = centerNode.offsetY - _that.gap > 60 ? _that.gap + centerNode.prev[index - 1].offsetY : 60preMaxWidth = preMaxWidth > node.width ? preMaxWidth : node.width}centerNode.prev.push(node)})}// 设置子节点nodeif (data.hasOwnProperty('childrenList')) {data.childrenList && data.childrenList.forEach((item, index) => {let node = new DagNode(item)if (index === 0) {node.offsetX = 100 + centerNode.offsetX + centerNode.widthnode.offsetY = centerNode.offsetY - _that.gap > 60 ? _that.gap : 60nextMaxWidth = nextMaxWidth > node.width ? nextMaxWidth : node.width} else {node.offsetX = 100 + centerNode.offsetX + centerNode.widthnode.offsetY = centerNode.offsetY - _that.gap > 60 ? _that.gap + centerNode.next[index - 1].offsetY : 60nextMaxWidth = nextMaxWidth > node.width ? nextMaxWidth : node.width}centerNode.next.push(node)})}this.center = centerNode}drawSvg () {if (!this.center.job.jobName) returnlet pNode = this.center.prevlet nNode = this.center.next// 设置画板的高this.svgChart.style.height =  Math.max(pNode.length, nNode.length) * 80 || 100 + 'px'// 设置中心nodethis.center.offsetY = this.svgChart.clientHeight / 2 - this.center.height / 2this.center.drawNode()// 计算每个父节点距离顶部的距离let prevTopGap = (parseInt(this.svgChart.style.height) - pNode.reduce((sum, cur, idx) => sum += cur.height, 0)) / (pNode.length + 1)// 计算每个子节点距离顶部的距离let nextTopGap = (parseInt(this.svgChart.style.height) - nNode.reduce((sum, cur, idx) => sum += cur.height, 0)) / (nNode.length + 1)pNode.length && pNode.forEach((node, index) => {if (pNode.length === 1) {node.offsetY = this.center.offsetY + this.center.height / 2 - node.height / 2} else {node.offsetY = index === 0 ? prevTopGap : prevTopGap + pNode[index - 1].height + pNode[index - 1].offsetY}// 绘制节点node.drawNode()// 绘制线条node.drawLine({x: node.offsetX + node.width,y: node.offsetY + node.height / 2}, {x: this.center.offsetX - 4,y: this.center.offsetY + this.center.height / 2}, {x: this.center.offsetX - 2,y: this.center.offsetY + this.center.height / 2,other: 'l-6 -4 v8'})})nNode.length && nNode.forEach((node, index) => {if (nNode.length === 1) {node.offsetY = this.center.offsetY + this.center.height / 2 - node.height / 2} else {node.offsetY = index === 0 ? nextTopGap : nextTopGap + nNode[index - 1].height + nNode[index - 1].offsetY}// 绘制节点node.drawNode()// 绘制线条node.drawLine({x: this.center.offsetX + this.center.width,y: this.center.offsetY + this.center.height / 2}, {x: node.offsetX - 4,y: node.offsetY + node.height / 2}, {x: node.offsetX -2,y: node.offsetY + node.height / 2,other: 'l-6 -4 v8'})})}
}
// JobBlood.vue
<template><div class="the-job-blood-container"><svg id="bloodChart" xmlns="http://www.w3.org/2000/svg"></svg></div>
</template><script>
import { mapGetters, mapMutations } from 'vuex'
import Fetch from '@/api/index'
import { JOB_TYPE, JOB_STATE } from '@/util/common.js'const fetch = new Fetch()
const taskDevelopApi = fetch.apis.taskDevelopimport { NodeGraph } from '@/util/dag.js'
export default {name: 'jobBlood',props: ['currentJob'],data () {return {dagData: {}}},computed: {...mapGetters([...])},watch: {dagData: {handler () {this.svgInit()},deep: true}},created () {...},methods: {// 获取依赖async fetJobDependency () {...},// 绘制svgsvgInit () {let svgDom = document.getElementById('bloodChart')svgDom.innerHTML = ''if (svgDom) {let graph = new NodeGraph(svgDom)graph.setNodes(this.dagData)graph.drawSvg()}},......mapMutations([...])}
}
</script><style lang="less">.the-job-blood-container {text-align: left;height: 100%;width: 100%;svg {min-height: 100%;min-width: 100%;overflow: auto;}}
</style>

svg技术绘制血缘关系相关推荐

  1. 数据治理(一)自研血缘关系

    数据治理(一)血缘关系 一.概念 数据血缘也称为数据血统或谱系,是来描述数据的来源和派生关系.数据来源是数据科学的关键,也是被公认为数据信任的核心的部分.说白了就是这个数据是怎么来的,经过了哪些过程或 ...

  2. 数据治理展示血缘关系的工具_Nebula Graph 在微众银行数据治理业务的实践

    本文为微众银行大数据平台:周可在 nMeetup 深圳场的演讲这里文字稿,演讲视频参见:B站 自我介绍下,我是微众银行大数据平台的工程师:周可,今天给大家分享一下 Nebula Graph 在微众银行 ...

  3. 搜狐 Hive SQL 血缘关系解析与应用

    1. 研究背景 随着企业信息化和业务的发展,数据资产日益庞大,数据仓库构建越来越复杂,在数仓构建的过程中,常遇到数据溯源困难,数据模型修改导致业务分析困难等难题,此类问题主要是由于数据血缘分析不足造成 ...

  4. Linux大家族的血缘关系

    <!-- @page { margin: 2cm } P { margin-bottom: 0.21cm } --> Linux 发行版有多少?是不是太多了?实际情况究竟怎样? 昨天上午, ...

  5. 基于svg开发绘制地铁图

    中国地铁图 中国地铁图,基于svg开发,支持PC.移动端多种浏览器.覆盖北上广多个城市. 线上开源地址 https://github.com/StavinLi/the-subway-of-china ...

  6. 大数据治理入门系列:数据血缘关系

    血缘关系在人类社会中扮演着重要角色.大多数家庭是基于血缘关系形成的,而家庭作为社会的基本单元,对维系社会稳定发挥着重要关系.其实,数据之间也存在类似的血缘关系.数据从产生.加工.流转,一直到消亡,每个 ...

  7. android 动态生成直线,Android SVG技术入门:线条动画实现原理

    SVG技术入门:线条动画实现原理 这是一个有点神奇的技术:一副线条构成的画能自动画出自己,非常的酷.SVG 意为可缩放矢量图形(Scalable Vector Graphics),是使用 XML 来描 ...

  8. SVG动态绘制不规则图形

    作者简介 wuyue 蚂蚁金服·数据体验技术团队 在浏览器中,任意的二维平面图形均可以通过path路径的形式描述.然后底层api 直接静态绘制出来.但是如果想动态的绘制路径,浏览器是没有直接支持方式的 ...

  9. 中国平安旗下「壹账链」和IBM超级账本Fabric的“血缘”关系

    Fabric的优点在于数据同步传输,壹账链也借鉴了这一点. 文 |  茶凉 出品 |  Odaily星球日报(ID:o-daily) 3 月 30 日,国家互联网信息办公室发布了第一批境内区块链信息服 ...

最新文章

  1. Javascript全局变量和delete
  2. ubuntu创建vim php文件,在ubuntu 上配置vim的php开发环境
  3. leetcode解题报告:198 House Robber
  4. C++ 一个例子彻底搞清楚拷贝构造函数和赋值运算符重载的区别
  5. aix oracle 10.2.0.1 升级 10.2.0.4,AIX Oracle RAC 升级到10.2.0.4.0要特别注意的问题 - 爱肯的专栏 ......
  6. 【数据仓库】——星型模型和雪花模型
  7. [ZZ].NET自动探索式测试工具——Pex
  8. JavaWeb之多语言国际化
  9. oracle基础入门(四)
  10. 关于存储pose时.dat类型的文件里面的type问题(细节)
  11. python中每个if条件后面都要使用冒号_每个if条件后面都要使用冒号。
  12. 台式机标准计算机配置清单,台式机组装,教您组装电脑高配置清单
  13. 符号三角形_dfs算法
  14. win10计算机系统慢,解决Win10电脑变慢的一些方法
  15. Matlab求解黎卡提方程
  16. python的算术表达式_python算术表达式
  17. 笔记本当服务器显示器怎么连接,笔记本连接显示器,详细教您笔记本怎么连接显示器...
  18. SQL16号统计1~15号数据,1号统计上月15~月底数据
  19. 【附源码】计算机毕业设计SSM民宿客房管理系统
  20. leetcode 507 完美数

热门文章

  1. 小熊读书:如何自我提升——《软技能》
  2. serial库常见用法
  3. 1024勋章没及时显示的问题
  4. c if sortable html,Rails 5 - html5sortable - sortable不是HTMLDocument.ready中的函数
  5. 升级java包_升级自定义服务包 (Sun Java System Delegated Administrator 6.4 管理指南)
  6. 服务器支持p2v,菜鸟必知 实施P2V迁移成功的五大秘诀
  7. Linux Text file busy文本文件忙
  8. 一级指针赋值与二级指针赋值
  9. 看,资深程序员输给了菜鸟程序员
  10. 代码随想录Day62