最近在项目中需要开发一个图表来显示人员的各种属性,类似于一种树形的结构进行显示数据。如果多个人员有同一个属性,那么需要将相同的属性进行连线,即关联起来。即形成一个关系图,由于我自身对echarts稍微熟悉一下,因此采用echarts3来完成此图表的开发。
注意:echarts的不同版本api有些稍微的不同。
完成效果:
需求:
1、点击父节点
|- 该父节点的子节点是没有显示的,那么显示它的子节点
|- 该父节点的子节点是显示的,那么隐藏它的子节点和子孙节点
2、对于父节点显示出它的分类名称,比如 用户信息(父节点)下有用户名、性别、生日、身份证(子节点)等
|- 父节点显示 用户信息
|- 子节点显示 用户名 : 名称
性别 : 名称
3、鼠标移动到父节点上,显示出它下方子节点的具体信息
预备知识:
1、节点的隐藏和实现:在 series[index].data[index]中存在category 值,如果它的值和series[index].categories中的角标没有对应起来,那么此节点是不显示的(即隐藏,将category的值改成负数,显示 改成整数,值要和categories的角标对应起来)
2、2个节点要连接起来,那么 links 中的 source,target 的值只需要和 data 中的 name 属性的值对应起来即可
3、需要了解一下echarts的富文本样式,用于格式化节点上显示的值
4、了解一点es5,es6的语法
图片解释:(下方的 lengedName 实际是data中的 legendName ,图片上写错了)
具体实现:
1、点击显示和隐藏节点
|- 找到点击节点的 open 的值(第一次点击是不存在的,点击完增加这个属性)
> true(存在,即点过一回)
* 从links中找到所有的子节点和子孙节点的 name(links中的target属性)的值,需要递归获取
* 从data中获取获取关联的节点
* 将节点的category 的值改成 负数
* 如果节点的nodeType === 1(即上面图片解释中的父节点), 那么需要将 open的值设置成false
* 将当前点击的节点的 open 属性改成 false
* 重新渲染echarts图表
* 此时图表的节点就折叠起来了
> false(即不存在或后续赋值为false)
* 从links中找到所有的子节点的 name(links中的target属性)的值
* 从data中获取获取关联的节点
* 将节点的category 的值改成 整数
* 将当前点击的节点的 open 属性改成 true
* 重新渲染echarts图表
* 此时图表的节点就展开了
/** * 绑定图表的点击事件 * @param chart */ function bindChartClickEvent(chart) { chart.on('click', function (params) { var category = params.data.category, nodeType = params.data.nodeType; if (category === 0 || nodeType === 1) { toggleShowNodes(chart, params); } }); } /** * 展开或关闭节点 * @param chart * @param params */ function toggleShowNodes(chart, params) { var open = !!params.data.open, options = chart.getOption(), seriesIndex = params.seriesIndex, srcLinkName = params.name, serieLinks = options.series[seriesIndex].links, serieData = options.series[seriesIndex].data, serieDataMap = new Map(), serieLinkArr = []; // 当前根节点是展开的,那么就需要关闭所有的根节点 if (open) { // 递归找到所有的link节点的target的值 findLinks(serieLinkArr, srcLinkName, serieLinks, true); if (serieLinkArr.length) { serieData.forEach(sd => serieDataMap.set(sd.name, sd)); for (var i = 0; i < serieLinkArr.length; i++) { if (serieDataMap.has(serieLinkArr[i])) { var currentData = serieDataMap.get(serieLinkArr[i]); currentData.category = -Math.abs(currentData.category); if (currentData.nodeType === 1) { currentData.open = false; } } } serieDataMap.get(srcLinkName).open = false; chart.setOption(options); } } else { // 当前根节点是关闭的,那么就需要展开第一层根节点 findLinks(serieLinkArr, srcLinkName, serieLinks, false); if (serieLinkArr.length) { serieData.forEach(sd => serieDataMap.set(sd.name, sd)); for (var j = 0; j < serieLinkArr.length; j++) { if (serieDataMap.has(serieLinkArr[j])) { var currentData = serieDataMap.get(serieLinkArr[j]); currentData.category = Math.abs(currentData.category); } } serieDataMap.get(srcLinkName).open = true; chart.setOption(options); } } } /** * 查找连接关系 * @param links 返回的节点放入此集合 * @param srcLinkName 源线的名称 * @param serieLinks 需要查找的集合 * @param deep 是否需要递归进行查找 */ function findLinks(links, srcLinkName, serieLinks, deep) { var targetLinks = []; serieLinks.filter(link => link.source === srcLinkName).forEach(link => { targetLinks.push(link.target); links.push(link.target) }); if (deep) { for (var i = 0; i < targetLinks.length; i++) { findLinks(links, targetLinks[i], serieLinks, deep); } } }
2、节点名称显示的格式化
富文本样式的使用 (series中label的设置)
"label": { "normal": { "show": true, "position": "top", "formatter": function (args) { if (args.data.nodeType === 1) { return "{prefixClassName|" + args.data.legendName + "}"; } else { return "{prefixClassName|" + args.data.legendName + " :}\r\n " + args.name; } }, "rich": { "prefixClassName": { color: "#FF9301", fontWeight: "bold" } } } }
3、鼠标移动到父节点上显示子节点的信息
找到当前节点关联的所有的子节点,通过links来进行查找,当前节点的name属性的值等于links中source中的值,那么target就是关联的子节点的name的值,遍历data数据,如果name属性的值等于target的值,就找到了关联节点的数据。
注意: 在显示的时候需要注意一下获取前面颜色的获取(当categories中的值过多时需要注意一下)
tooltip: { "formatter": function (arg) { var nodeType = arg.data.nodeType, srcName = arg.name, seriesIndex = arg.seriesIndex, options = echart.getOption(), serieData = options.series[seriesIndex].data, serieLinks = options.series[seriesIndex].links, colors = options.color, serieDataMap = new Map(), serieLinkArr = [], tips = ''; // 父节点,排除根节点 if (nodeType === 1) { serieLinks.filter(link => link.source === srcName).forEach(link => serieLinkArr.push(link.target)); if (serieLinkArr.length) { serieData.forEach(sd => serieDataMap.set(sd.name, sd)); for (var i = 0; i < serieLinkArr.length; i++) { if (serieDataMap.has(serieLinkArr[i])) { var currentData = serieDataMap.get(serieLinkArr[i]), color = getColor(colors, currentData.category); tips += '<span style="background-color: ' + color + ';width: 10px;height: 10px;border-radius: 50%;display: inline-block"></span> ' + currentData.legendName + " : " + currentData.name + ' <br />'; } } } return tips; } else { return ''; } } } /** * 获取颜色 * @param colors * @param index * @returns {*} */ function getColor(colors, index) { var length = colors.length, colorIndex = index; if (index >= length) { colorIndex = length - index; } return colors[colorIndex]; }
完成代码如下:(如需运行,下载附件中的即可,或自定导入echarts3的js文件)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>echart3 力引导布局实现节点的提示和折叠</title> <script src="echarts.common.min.js"></script> </head> <body> <div id="chart" style="width: 100%;height: 600px"></div> <script type="text/javascript"> var echart = echarts.init(document.getElementById('chart')); var options = { tooltip: { "formatter": function (arg) { var nodeType = arg.data.nodeType, srcName = arg.name, seriesIndex = arg.seriesIndex, options = echart.getOption(), serieData = options.series[seriesIndex].data, serieLinks = options.series[seriesIndex].links, colors = options.color, serieDataMap = new Map(), serieLinkArr = [], tips = ''; // 父节点,排除根节点 if (nodeType === 1) { serieLinks.filter(link => link.source === srcName).forEach(link => serieLinkArr.push(link.target)); if (serieLinkArr.length) { serieData.forEach(sd => serieDataMap.set(sd.name, sd)); for (var i = 0; i < serieLinkArr.length; i++) { if (serieDataMap.has(serieLinkArr[i])) { var currentData = serieDataMap.get(serieLinkArr[i]), color = getColor(colors, currentData.category); tips += '<span style="background-color: ' + color + ';width: 10px;height: 10px;border-radius: 50%;display: inline-block"></span> ' + currentData.legendName + " : " + currentData.name + ' <br />'; } } } return tips; } else { return ''; } } }, "series": [ { "itemStyle": { "normal": { "label": { "show": true }, "borderType": "solid", "borderColor": "rgba(182,215,0,0.5)", "borderWidth": 2, "opacity": 1 }, "emphasis": { "borderWidth": 5, "borderType": "solid", "borderColor": "#40f492" } }, "lineStyle": { "normal": { "color": "rgba(182,0,255,0.5)", "width": "3", "type": "dotted", "curveness": 0.1, "opacity": 1 } }, "label": { "normal": { "show": true, "position": "top", "formatter": function (args) { if (args.data.nodeType === 1) { return "{prefixClassName|" + args.data.legendName + "}"; } else { return "{prefixClassName|" + args.data.legendName + " :}\r\n " + args.name; } }, "rich": { "prefixClassName": { color: "#FF9301", fontWeight: "bold" } } } }, "layout": "force", "roam": true, "edgeSymbolSize": [ 8, 10 ], "edgeSymbol": [ "circle", "arrow" ], "focusNodeAdjacency": false, "force": { "repulsion": 300, "edgeLength": 50 }, "links": [ { "source": "3****************3", "target": "3****************3-bank-card" }, { "source": "3****************3-bank-card", "target": "工行卡:4077" }, { "source": "3****************3-bank-card", "target": "建行卡:4078" }, { "source": "3****************3", "target": "3****************3-basic-info" }, { "source": "3****************3-basic-info", "target": "张三" }, { "source": "3****************3", "target": "3****************3-contact" }, { "source": "3****************3-contact", "target": "145157****@qq.com" }, { "source": "3****************3-contact", "target": "14515783**" } ], "categories": [ { "name": "用户" }, { "name": "身份证" }, { "name": "姓名" }, { "name": "性别" }, { "name": "生日" }, { "name": "手机" }, { "name": "固定电话" }, { "name": "邮箱" }, { "name": "qq" }, { "name": "地址" }, { "name": "银行卡" }, { "name": "基本信息" }, { "name": "地址分类" }, { "name": "联系方式" }, { "name": "银行卡分类" } ], "name": "人员关系图", "type": "graph", "showSymbol": true, "yAxisIndex": 0, "z": 2, "data": [ { "name": "3****************3", "symbolSize": 40, "value": "3****************3", "category": 0, "draggable": true, "label": { "normal": { "show": true, "position": "inside" } }, "legendName": "用户", "nodeType": 0, "idCardNum": "3****************3" }, { "name": "3****************3-bank-card", "symbolSize": 40, "value": "银行卡分类", "category": -14, "draggable": true, "label": { "normal": { "show": true, "position": "inside" } }, "legendName": "银行卡分类", "nodeType": 1 }, { "name": "工行卡:4077", "symbolSize": 20, "value": "工行卡:4077", "category": -10, "draggable": true, "legendName": "银行卡", "nodeType": 0 }, { "name": "建行卡:4078", "symbolSize": 20, "value": "建行卡:4078", "category": -10, "draggable": true, "legendName": "银行卡", "nodeType": 0 }, { "name": "3****************3-basic-info", "symbolSize": 40, "value": "基本信息", "category": -11, "draggable": true, "label": { "normal": { "show": true, "position": "inside" } }, "legendName": "基本信息", "nodeType": 1 }, { "name": "张三", "symbolSize": 20, "value": "张三", "category": -2, "draggable": true, "legendName": "姓名", "nodeType": 0 }, { "name": "3****************3-contact", "symbolSize": 40, "value": "联系方式", "category": -13, "draggable": true, "label": { "normal": { "show": true, "position": "inside" } }, "legendName": "联系方式", "nodeType": 1 }, { "name": "145157****@qq.com", "symbolSize": 20, "value": "145157****@qq.com", "category": -7, "draggable": true, "legendName": "邮箱", "nodeType": 0 }, { "name": "14515783**", "symbolSize": 20, "value": "14515783**", "category": -8, "draggable": true, "legendName": "qq", "nodeType": 0 } ] } ], "legend": { "data": [ "用户", "身份证", "姓名", "性别", "生日", "手机", "固定电话", "邮箱", "qq", "地址", "银行卡", "基本信息", "地址分类", "联系方式", "银行卡分类" ] }, "title": [ { "left": "left", "text": "人员关系图" } ] }; echart.setOption(options); bindChartClickEvent(echart); /** * 获取颜色 * @param colors * @param index * @returns {*} */ function getColor(colors, index) { var length = colors.length, colorIndex = index; if (index >= length) { colorIndex = length - index; } return colors[colorIndex]; } /** * 绑定图表的点击事件 * @param chart */ function bindChartClickEvent(chart) { chart.on('click', function (params) { var category = params.data.category, nodeType = params.data.nodeType; if (category === 0 || nodeType === 1) { toggleShowNodes(chart, params); } }); } /** * 展开或关闭节点 * @param chart * @param params */ function toggleShowNodes(chart, params) { var open = !!params.data.open, options = chart.getOption(), seriesIndex = params.seriesIndex, srcLinkName = params.name, serieLinks = options.series[seriesIndex].links, serieData = options.series[seriesIndex].data, serieDataMap = new Map(), serieLinkArr = []; // 当前根节点是展开的,那么就需要关闭所有的根节点 if (open) { // 递归找到所有的link节点的target的值 findLinks(serieLinkArr, srcLinkName, serieLinks, true); if (serieLinkArr.length) { serieData.forEach(sd => serieDataMap.set(sd.name, sd)); for (var i = 0; i < serieLinkArr.length; i++) { if (serieDataMap.has(serieLinkArr[i])) { var currentData = serieDataMap.get(serieLinkArr[i]); currentData.category = -Math.abs(currentData.category); if (currentData.nodeType === 1) { currentData.open = false; } } } serieDataMap.get(srcLinkName).open = false; chart.setOption(options); } } else { // 当前根节点是关闭的,那么就需要展开第一层根节点 findLinks(serieLinkArr, srcLinkName, serieLinks, false); if (serieLinkArr.length) { serieData.forEach(sd => serieDataMap.set(sd.name, sd)); for (var j = 0; j < serieLinkArr.length; j++) { if (serieDataMap.has(serieLinkArr[j])) { var currentData = serieDataMap.get(serieLinkArr[j]); currentData.category = Math.abs(currentData.category); } } serieDataMap.get(srcLinkName).open = true; chart.setOption(options); } } } /** * 查找连接关系 * @param links 返回的节点放入此集合 * @param srcLinkName 源线的名称 * @param serieLinks 需要查找的集合 * @param deep 是否需要递归进行查找 */ function findLinks(links, srcLinkName, serieLinks, deep) { var targetLinks = []; serieLinks.filter(link => link.source === srcLinkName).forEach(link => { targetLinks.push(link.target); links.push(link.target) }); if (deep) { for (var i = 0; i < targetLinks.length; i++) { findLinks(links, targetLinks[i], serieLinks, deep); } } } </script> </body> </html>
相关推荐
echarts关系图(力引导)拖动节点不还原位置,在init创建时第三个参数对象添加myOpts_:{draggableFixed_:true}即可,如: var mychart = echarts.init(dom,null,{myOpts_:{draggableFixed_:true}});创建一个拖动不还原的...
2. `layout`: 指定节点的布局方式,可以是`'force'`(力引导布局)或其他布局算法。 3. `Roam`: 设置是否开启缩放和平移,`roam: true`允许用户自由拖动和缩放图表。 4. `topology.draggable`: 设置节点是否可拖动,...
**ECharts 拓扑图拖拽互不影响详解** ECharts 是一个由百度开发的开源JavaScript图表库,它提供了一套...通过学习和理解这个示例,开发者可以进一步扩展功能,比如添加节点连接线的拖拽,或者实现更复杂的布局算法。
ECharts Tree提供了`symbol`和`symbolSize`属性来控制节点的显示样式,我们可以利用这些属性来实现节点的收缩效果。例如,当节点收缩时,我们可以将`symbol`设置为"none",隐藏节点;当节点展开时,可以将其设置为...
在这个“echart 3D环形图 动画效果”的主题中,我们将探讨如何使用ECharts来创建具有3D视觉冲击力和动态效果的环形图。 3D环形图是一种特殊的饼图变体,通过三维视角展示数据的比例关系,使数据更具有立体感和层次...
3. **节点样式与连接线**:双向布局中,节点的位置和连接线的方向需要特别考虑。可以使用`symbolSize`设置节点大小,`lineStyle`调整连接线的样式。对于连接线,可能需要自定义`edgeShape`,例如使用`arc`或`...
力导向图(Force-directed Graph Drawing)是一种用于图形可视化的算法,它通过模拟物理系统中粒子间的相互作用力来布局图的节点和边,使得图形在视觉上清晰且美观。在这个项目中,MATLAB被用来实现力导向图算法,...
通过将节点替换为图片,我们可以创建更具吸引力和表现力的可视化,帮助用户更好地理解和探索复杂的数据网络。在实践中,不断调整和优化布局参数,以及添加交互性,可以使你的可视化作品更加引人入胜。
3. **布局算法**:ECharts提供了自动布局功能,如力导向布局,它模拟物理系统的引力和斥力来自动排列节点,使得图形更加美观且易于理解。也可以自定义布局算法,适应特殊的需求。 4. **交互功能**:关系图支持多种...
当鼠标移动到节点上时,会有节点描叙信息提示框。 当在节点处右键时,会弹出右键编辑菜单。 当鼠标单击节点以外区域时,会隐藏右键编辑菜单。 节点动作包含: 增加子节点,删除子节点,删除当前节点,编辑节点 非常...
本项目“基于echart.js实现的人员架构图,企业内部组织结构图展示图”就是利用ECharts这一强大的工具来呈现企业的组织结构。 ECharts提供了多种图表类型,包括柱状图、折线图、饼图、散点图等,同时也支持自定义...
6. **布局调整**:为了正确地显示折叠后的子项,可能还需要重写`QAbstractItemView`的`updateGeometries()`和`setFirstVisibleRow()`方法,以便在折叠节点时调整可见行的布局。 在实际项目中,你可能会遇到一些额外...
这个配置项允许我们定义数据、布局方式、节点样式、以及连接线的样式等。 要实现横向组织结构图,我们需要设置`layout`属性为`'horizontal'`。这将使树图的节点沿着水平方向排列。同时,为了使连接线呈现曲线,我们...
实现GridView父子节点异步折叠,通常需要以下几个关键步骤: 1. **数据模型**:首先,你需要有一个支持层级关系的数据模型,比如自引用的实体类或者包含ParentId字段的对象集合。 2. **数据绑定**:在后台代码中,...
echarts力导向图节点折叠-附件资源
力导向布局是一种常见的网络图绘制方法,它模拟了物理系统的力场,其中节点间的引力和斥力相互作用,使得整个图形达到稳定状态。在ECharts中,力导向布局常用于展现人物关系图,帮助用户理解复杂的关联结构。节点与...
3. **计算位置**:根据节点在当前层的位置和前一层节点的半径,计算出每个节点的x和y坐标。半径可以通过一个递增函数(如指数增长)来确保远离根节点的节点在视觉上不会太密集。 4. **连接节点**:通过线条连接父...
在数据结构中,折叠可能通过维护一个表示当前可视节点的栈或队列来实现。 折叠和展开操作对于树遍历也至关重要。在深度优先搜索(DFS)和广度优先搜索(BFS)等算法中,它们可以帮助控制搜索的范围。例如,在DFS中...
树状图布局算法是构建树状图的关键,它的目标是在二维平面上合理地安排节点的位置,使各个节点之间的连接线尽可能短,同时保持整体的美观性和可读性。原生JavaScript实现的这种算法可以避免对特定库的依赖,降低引入...