echarts 下 用关系图实现拓扑图结构(树形,星型,网状,总线,环形)
树形
网状
环形
星形
总线形
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="./dist/echarts.js"></script> <style> * { margin: 0; padding: 0; } </style> </head> <body> <div style="width: 100vw;height: 100vh;"></div> <script> var data_busNodes = JSON.parse('[{ "id": "bus_0", "name": "bus_0", "value": "0", "x": 1, "y": 1, "label": { "normal": { "show": true, "position": "bottom", "fontSize": "12" } }, "symbol": "image://http:a.png" }, { "id": "bus_1", "name": "bus_1", "value": "1", "x": 300, "y": 100, "label": { "normal": { "show": true, "position": "bottom", "fontSize": "12" } }, "symbol": "image://http:a.png" }, { "id": "bus_2", "name": "bus_2", "value": "2", "x": 50000, "y": 100, "label": { "normal": { "show": true, "position": "bottom", "fontSize": "12" } }, "symbol": "image://http:a.png" }, { "id": "bus_3", "name": "bus_3", "value": "3", "x": 700, "y": 100, "label": { "normal": { "show": true, "position": "bottom", "fontSize": "12" } }, "symbol": "image://http:a.png" }, { "id": "bus_4", "name": "bus_4", "value": "4", "x": 900, "y": 100, "label": { "normal": { "show": true, "position": "bottom", "fontSize": "12" } }, "symbol": "image://http:a.png" }, { "id": "bus_5", "name": "bus_5", "value": "5", "x": 1100, "y": 100, "label": { "normal": { "show": true, "position": "bottom", "fontSize": "12" } }, "symbol": "image://http:a.png" }, { "id": "bus_6", "name": "bus_6", "value": "6", "x": 1300, "y": 100, "label": { "normal": { "show": true, "position": "bottom", "fontSize": "12" } }, "symbol": "image://http:a.png" }, { "id": "bus_7", "name": "bus_7", "value": "7", "x": 1500, "y": 100, "label": { "normal": { "show": true, "position": "bottom", "fontSize": "12" } }, "symbol": "image://http:a.png" }, { "id": "bus_8", "name": "bus_8", "value": "8", "x": 1700, "y": 100, "label": { "normal": { "show": true, "position": "bottom", "fontSize": "12" } }, "symbol": "image://http:a.png" }, { "id": "bus_9", "name": "bus_9", "value": "9", "x": 1900, "y": 100, "label": { "normal": { "show": true, "position": "bottom", "fontSize": "12" } }, "symbol": "image://http:a.png" }, { "id": "bus_10", "name": "bus_10", "value": "10", "x": 2100, "y": 100, "label": { "normal": { "show": true, "position": "bottom", "fontSize": "12" } }, "symbol": "image://http:a.png" }, { "id": "bus_11", "name": "bus_11", "value": "11", "x": 1800, "y": 400, "label": { "normal": { "show": true, "position": "bottom", "fontSize": "12" } }, "symbol": "image://http:a.png" }, { "id": "bus_12", "name": "bus_12", "value": "12", "x": 1600, "y": 400, "label": { "normal": { "show": true, "position": "bottom", "fontSize": "12" } }, "symbol": "image://http:a.png" }, { "id": "bus_13", "name": "bus_13", "value": "13", "x": 1400, "y": 400, "label": { "normal": { "show": true, "position": "bottom", "fontSize": "12" } }, "symbol": "image://http:a.png" }, { "id": "bus_14", "name": "bus_14", "value": "14", "x": 1200, "y": 400, "label": { "normal": { "show": true, "position": "bottom", "fontSize": "12" } }, "symbol": "image://http:a.png" }, { "id": "bus_15", "name": "bus_15", "value": "15", "x": 1000, "y": 400, "label": { "normal": { "show": true, "position": "bottom", "fontSize": "12" } }, "symbol": "image://http:a.png" }, { "id": "bus_16", "name": "bus_16", "value": "16", "x": 800, "y": 400, "label": { "normal": { "show": true, "position": "bottom", "fontSize": "12" } }, "symbol": "image://http:a.png" }, { "id": "bus_17", "name": "bus_17", "value": "17", "x": 600, "y": 400, "label": { "normal": { "show": true, "position": "bottom", "fontSize": "12" } }, "symbol": "image://http:a.png" }, { "id": "bus_18", "name": "bus_18", "value": "18", "x": 400, "y": 400, "label": { "normal": { "show": true, "position": "bottom", "fontSize": "12" } }, "symbol": "image://http:a.png" }, { "id": "bus_19", "name": "bus_19", "value": "19", "x": 200, "y": 400, "label": { "normal": { "show": true, "position": "bottom", "fontSize": "12" } }, "symbol": "image://http:a.png" }]') var params = [{ id: 43, groupId: 5, entityName: '', entityId: '', entityDeviceId: 137, deviceParam: '{"nodeType":""}', deviceName: "歼-15T舰载作战飞机-006" }, { id: 44, groupId: 5, entityName: "003航母全-007", entityId: 137, entityDeviceId: 138, deviceParam: '{"nodeType":""}', deviceName: "歼-15T舰载作战飞机-006" }, { id: 52, groupId: 5, entityName: "003航母全-007", entityId: 137, entityDeviceId: 144, deviceParam: '{"nodeType":""}', deviceName: "歼-15T舰载作战飞机-006" }, { id: 45, groupId: 5, entityName: "003航母全-007", entityId: 137, entityDeviceId: 139, deviceParam: '{"nodeType":""}', deviceName: "歼-15T舰载作战飞机-006" }, { id: 46, groupId: 5, entityName: "003航母全-007", entityId: 139, entityDeviceId: 140, deviceParam: '{"nodeType":""}', deviceName: "歼-15T舰载作战飞机-006" }, { id: 47, groupId: 5, entityName: "003航母全-007", entityId: 139, entityDeviceId: 141, deviceParam: '{"nodeType":""}', deviceName: "歼-15T舰载作战飞机-006" }, { id: 48, groupId: 5, entityName: "003航母全-007", entityId: 141, entityDeviceId: 142, deviceParam: '{"nodeType":""}', deviceName: "歼-15T舰载作战飞机-006" }, { id: 49, groupId: 5, entityName: "003航母全-007", entityId: 141, entityDeviceId: 143, deviceParam: '{"nodeType":""}', deviceName: "歼-15T舰载作战飞机-006" }, { id: 50, groupId: 5, entityName: "003航母全-007", entityId: 138, entityDeviceId: 144, deviceParam: '{"nodeType":""}', deviceName: "歼-15T舰载作战飞机-006" }, { id: 51, groupId: 5, entityName: "003航母全-007", entityId: 138, entityDeviceId: 145, deviceParam: '{"nodeType":""}', deviceName: "歼-15T舰载作战飞机-006" }] function createBusNodeDatas(data_nodes, o) { var num = data_nodes.length var nodeLen = 0 var dataNodes = [] var dataLinks = [] var mockBus = [] // 制作data假节点xy for (i = 0; i < num; i++) { var x var y if (i > num / 2) { x = o.x + (num - i) * 200 y = o.y + 400 } else { x = o.x + i * 200 + 100 y = o.y + 100 } data_nodes[i].x = x data_nodes[i].y = y dataNodes.push(data_nodes[i]) nodeLen++ } // 制作bus假节点 for (i = 0; i < num + 3; i++) { var x = o.x + i * 100 var y = o.y + 250 var node = { id: `busNode_${i}`, name: `busNode_${i}`, value: `busNode_${i}`, x: x, y: y, itemStyle: { opacity: 0 }, symbolSize: 0, label: { normal: { show: true, position: 'bottom', fontSize: '12' } }, symbol: 'image://http:a.png', } dataNodes.push(node) mockBus.push(node) } // 连接各data和bus节点 for (let j = 0; j < num + 3; j++) { // console.log(dataNodes[j], mockBus[j]); // console.log(mockBus.filter(i => i.x === dataNodes[j].x)) var node = mockBus.filter(i => i.x === dataNodes[j].x)[0].id if (j < data_nodes.length) { var dataLink = { source: `bus_${j}`, target: node } dataLinks.push(dataLink) } var busLink = { source: `busNode_${j}`, target: `busNode_${j + 1}` } dataLinks.push(busLink) } var mockBusLen = mockBus.length var width = 200 * mockBusLen var height = 400 return { dataNodes: dataNodes, dataLinks: dataLinks, width: width, height: height } } function createStarNodeDatas(data_nodes, o, radius) { function getNodePositon(n, r) { var angle = 2 * Math.PI / n var sumAngle = 0 var arr = [] for (var i = 0; i < n; i++) { var x = r * Math.sin(sumAngle) var y = r * Math.cos(sumAngle) sumAngle += angle var obj = { x: x, y: y } arr.push(obj) } return arr } var num = data_nodes.length var nodeLen = 0 var dataNodes = [] var dataLinks = [] var mockBus = [] var starR = radius // 制作data假节点 var pointsArr = getNodePositon(num, starR) var node = { id: `starO`, name: `starO`, value: `starO`, x: o.x, y: o.y, itemStyle: { opacity: 0 }, symbolSize: 0, label: { normal: { show: true, position: 'bottom', fontSize: '12' } }, symbol: 'image://http:a.png', } dataNodes.push(node) for (var i = 0; i < pointsArr.length; i++) { data_nodes[i].x = o.x + pointsArr[i].x data_nodes[i].y = o.y + pointsArr[i].y dataNodes.push(data_nodes[i]) nodeLen++ } // 连接各data节点与中心节点 for (let j = 0; j < num; j++) { // 连接自己的环 // var dataLink = { // source: `${data_busNodes[j].id}`, // target: `${data_busNodes[j + 1].id}` // } // dataLinks.push(dataLink) // 连接和中心点的环 dataLink = { source: `starO`, target: `${data_nodes[j].id}` } dataLinks.push(dataLink) } // 连接起始点和终点 // var dataLink = { // source: `${data_busNodes[0].id}`, // target: `${data_busNodes[num - 1].id}` // } // dataLinks.push(dataLink) // var mockBusLen = mockBus.length // var width = 200 * mockBusLen // var height = 400 return { dataNodes: dataNodes, dataLinks: dataLinks, width: starR, height: starR } } function createRingNodeDatas(data_nodes, o, radius) { function getNodePositon(n, r) { var angle = 2 * Math.PI / n var sumAngle = 0 var arr = [] for (var i = 0; i < n; i++) { var x = r * Math.sin(sumAngle) var y = r * Math.cos(sumAngle) sumAngle += angle var obj = { x: x, y: y } arr.push(obj) } return arr } var num = data_nodes.length var nodeLen = 0 var dataNodes = [] var dataLinks = [] var mockBus = [] var starR = radius // 制作data假节点 var pointsArr = getNodePositon(num, starR) var node = { id: `starO`, name: `starO`, value: `starO`, x: o.x, y: o.y, label: { normal: { show: true, position: 'bottom', fontSize: '12' } }, symbol: 'image://http:a.png', } dataNodes.push(node) for (var i = 0; i < pointsArr.length; i++) { data_nodes[i].x = o.x + pointsArr[i].x data_nodes[i].y = o.y + pointsArr[i].y dataNodes.push(data_nodes[i]) nodeLen++ } // 连接各data节点与中心节点 for (let j = 0; j < num; j++) { // 连接自己的环 // var dataLink = { // source: `${data_busNodes[j].id}`, // target: `${data_busNodes[j + 1].id}` // } // dataLinks.push(dataLink) // 连接和中心点的环 dataLink = { source: `starO`, target: `${data_nodes[j].id}` } dataLinks.push(dataLink) } // 连接起始点和终点 // var dataLink = { // source: `${data_busNodes[0].id}`, // target: `${data_busNodes[num - 1].id}` // } // dataLinks.push(dataLink) // 连接终点和中心点 // var dataLink = { // source: `starO`, // target: `${data_busNodes[num - 1].id}` // } // dataLinks.push(dataLink) // var mockBusLen = mockBus.length // var width = 200 * mockBusLen // var height = 400 return { dataNodes: dataNodes, dataLinks: dataLinks, width: starR, height: starR } } function createTreeNodeDatas(data_nodes, o, radius) { var num = data_nodes.length var nodeLen = 0 var dataNodes = [] var dataLinks = [] var mockBus = [] var levelArr = [] var tree var flagNode = { node: '', times: 1 } var levelNum = 1 var nowRadius data_nodes.forEach(e => { e.name = e.entityDeviceId e.value = e.deviceName e.label = { "normal": { "show": true, "position": "bottom", "fontSize": "12" } } e.symbolSize = 20 }) if (data_nodes[0].entityId === '') { tree = data_nodes[0] } (function createTree(node, datas, level) { node.children = data_nodes.filter(i => i.entityId == node.entityDeviceId) for (var i = 0; i < node.children.length; i++) { node.children[i].levelTimes = i } var levelTime = node.levelTimes// 当前层级循环的次数 // 设置当前接节点xy坐标 if (node.entityId === '') { node.x = o.x node.y = o.y nowRadius = 2 * radius } else { var levelNumber = data_nodes.filter(i => i.entityId === node.entityId).length var x = data_nodes.filter(i => i.entityDeviceId === node.entityId)[0].x + (nowRadius / levelNumber) * (levelTime) // - nowRadius / 2 var y = data_nodes.filter(i => i.entityDeviceId === node.entityId)[0].y + radius node.x = x node.y = y } if (node.children.length > 0) { level++ nowRadius = nowRadius - (level - 1) * nowRadius / 5 node.children.forEach(i => arguments.callee(i, datas, level)) } })(tree, data_nodes, 0); (function createLevel(node, level) { levelArr.push({ level: level, node: node }) if (node.children && node.children.length > 0) { level++ node.children.forEach(i => arguments.callee(i, level)) } })(tree, 1); levelArr.sort(function (i, j) { return i.level - j.level }); levelArr.forEach(element => { dataNodes.push(element.node) }); // 连接线 levelArr.forEach(element => { var node = element.node if (node.entityId !== '') { var nodeId = data_nodes.filter(e => e.entityDeviceId === node.entityId)[0].id dataLinks.push({ source: `${nodeId}`, target: `${node.id}` }) } }) // var width = 200 * mockBusLen // var height = 400 return { dataNodes: dataNodes, dataLinks: dataLinks, // width: width, // height: height } } function createNetNodeDatas(data_nodes, o, radius) { function getNodePositon(n, r) { var angle = 2 * Math.PI / n var sumAngle = 0 var arr = [] for (var i = 0; i < n; i++) { var x = r * Math.sin(sumAngle) var y = r * Math.cos(sumAngle) sumAngle += angle var obj = { x: x, y: y } arr.push(obj) } return arr } var num = data_nodes.length var nodeLen = 0 var dataNodes = [] var dataLinks = [] var mockBus = [] var starR = radius // 制作data假节点 var pointsArr = getNodePositon(num, starR) for (var i = 0; i < pointsArr.length; i++) { data_nodes[i].x = o.x + pointsArr[i].x data_nodes[i].y = o.y + pointsArr[i].y dataNodes.push(data_nodes[i]) nodeLen++ } // todo 网状,线条重复需要去重 // 连接各data节点与中心节点 dataNodes.forEach(element => { dataNodes.forEach(elements => { if (element.id != elements.id) dataLinks.push({ source: `${element.id}`, target: `${elements.id}` }) }) }) // var width = 200 * mockBusLen // var height = 400 return { dataNodes: dataNodes, dataLinks: dataLinks, width: starR, height: starR } } //基准圆点 var o = { x: 0, y: 0 } var dataNodes var dataLinks // 总线图预览 var bus = createBusNodeDatas(data_busNodes, o) console.log(bus); dataNodes = bus.dataNodes dataLinks = bus.dataLinks // 星型图预览 // o = { x: 1000, y: 1000 } // var star = createStarNodeDatas(data_busNodes, o,200) // dataNodes = star.dataNodes // dataLinks = star.dataLinks // 环型图预览 // o = { x: 2000, y: 2000 } // var star = createRingNodeDatas(data_busNodes, o,200) // dataNodes = star.dataNodes // dataLinks = star.dataLinks // 树型图预览 // o = { x: 0, y: 0 } // var tree = createTreeNodeDatas(params, o, 200) // console.log(tree); // dataNodes = tree.dataNodes // dataLinks = tree.dataLinks // 网状图预览 // o = { x: 0, y: 0 } // var net = createNetNodeDatas(params, o, 200) // dataNodes = net.dataNodes // dataLinks = net.dataLinks // dataNodes = bus.dataNodes.concat(star.dataNodes) // dataLinks = bus.dataLinks.concat(star.dataLinks) // 创建chart 相当于new一个对象 var chart = echarts.init(document.querySelector("div")); // 图表内容,样式 var option = { title: { text: '拓扑图', }, tooltip: {}, // animationDurationUpdate: 10, // animationEasingUpdate: 'quinticInOut', // animationEasing: 'elasticOut', // animationDelayUpdate: function (idx) { // return idx * 1; // }, // grid: { // show: false, // y: '0', // y2: '0', // x: '0', // x2: '0' // }, series: [ { type: 'graph', // layout: 'force', symbolSize: 30, // roam: true, focusNodeAdjacency: true, legend: { left: 'left', data: ['h1', 'h2', 'h3'] }, edgeSymbol: ['circle'], edgeSymbolSize: [1, 1], edgeLabel: { normal: { show: false }, emphasis: { textStyle: { fontSize: 16 } } }, // animationDelay: function (idx) { // return idx * 300; // }, force: { edgeLength: 100, // repulsion: 200, gravity: 0, // repulsion: 100 }, //节点信息 data: dataNodes, links: dataLinks, lineStyle: { curveness: 0 }, zoom: 0.8 } ], }; option.series[0].data = option.series[0].data; option.series[0].links = option.series[0].links; //配置图表的样式 chart.setOption(option); var doNode // 添加echarts 事件,'click'、'dblclick'、'mousedown'、'mousemove'、'mouseup'、'mouseover'、'mouseout'、'globalout'、'contextmenu' chart.on('mousedown', function (params) { console.warn(params, 'mousedown'); doNode = params.data // console.warn(params.data.entityDeviceId); }); chart.on('mousemove', function (params) { // console.log(params.event.offsetX, params.event.offsetY, 'moving'); // var offsetX = params.event.offsetX // var offsetX = params.event.offsetY // console.log(offsetX, offsetY); // console.log(dataNodes.filter(i => i.entityDeviceId === doNode.entityDeviceId)); // dataLinks }); chart.on('click', function (params) { console.log(params, 'click'); }); chart.on('mouseup', function (params) { console.log(params, 'mouseup'); }); chart.on('globalout', function (params) { // 离开图层 console.log(params, 'globalout'); }); var move = { offsetX: 0, offsetY: 0 } var isClick = 0 var canvas = document.querySelector('canvas') canvas.addEventListener('mousedown', function (e) { // console.log('mousedown', e); move.offsetX = e.offsetX move.offsetY = e.offsetY isClick = 1 }, false) canvas.addEventListener('mouseup', function (e) { console.log('mouseup', e); move.offsetX = 0 move.offsetY = 0 isClick = 0 }) canvas.addEventListener('mousemove', debounce(function (e) { // console.log(e.offsetX, move.offsetX); if (isClick) { var offsetX = e.offsetX - move.offsetX var offsetY = e.offsetY - move.offsetY console.log('xChange: ' + offsetX, 'yChange: ' + offsetY); } }, 12, true)) /** * 防反跳。fn函数在最后一次调用时刻的delay毫秒之后执行! * @param fn 执行函数 * @param delay 时间间隔 * @param isImmediate 为true,debounce会在delay时间间隔的开始时立即调用这个函数 * @returns {Function} */ function debounce(fn, delay, isImmediate) { var timer = null; //初始化timer,作为计时清除依据 return function () { var context = this; //获取函数所在作用域this var args = arguments; //取得传入参数 clearTimeout(timer); if (isImmediate && timer === null) { //时间间隔外立即执行 fn.apply(context, args); timer = 0; return; } timer = setTimeout(function () { fn.apply(context, args); timer = null; }, delay); } } </script> </body> </html>
有什么不同见解可以在评论区共同讨论
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
2021-06-16 vue-element-admin 学习笔记
2021-06-16 idea 配置 vue开发环境