最近在项目中需要开发一个图表来显示人员的各种属性,类似于一种树形的结构进行显示数据。如果多个人员有同一个属性,那么需要将相同的属性进行连线,即关联起来。即形成一个关系图,由于我自身对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>

echart3 力引导布局实现节点的提示和折叠的更多相关文章

  1. Echarts关系图-力引导布局

    需要做一个树形图,可以查看各个人员的关系. 可伸缩的力引导图-失败 刚开始,打算做一个可展开和伸缩的,搜索时候发现CSDN有一篇美美哒程序媛写的Echarts Force力导向图实现节点可折叠. 这里 ...

  2. echarts3关系图:力引导布局, 固定某些节点

    在数组里设置 fixed: true,<a href='http://echarts.baidu.com/option.html#series-graph.data.fixed'>官方文档 ...

  3. Echarts3 关系图-力导向布局图

    因为项目需要,要求实现类似力导图效果的图,我就瞄上了echarts. 注意事项1:由于我的项目要部署到内网,所以js文件要在本地,网上大多力导图都是echarts2的,而其又依赖zrender基础库, ...

  4. echarts拓扑图(graph,力导向布局图)

    echarts连接:https://gallery.echartsjs.com/editor.html?c=xCLEj67T3H 讲解:https://www.cnblogs.com/koala201 ...

  5. Xamarin Android布局文件没有智能提示

    Xamarin Android布局文件没有智能提示 在Visual Studio 2015中,Android项目的Main.axml文件没有智能提示,不便于布局文件的编写.解决办法:(1)从Xamar ...

  6. Android studio3.1的XML布局文件没有自动提示不全代码功能

    将studio从2.3升级到3.1,打开后发现布局文件没有代码提示 尝试了网上一些解决方法,但发现并不是平时所说的省电模式开关的问题,也尝试了删除idea和iml文件后rebuild的方法,无效 然后 ...

  7. D3力布图绘制--节点自己连自己的实现

    案例分析 先看下实现的效果图 实现方法 本篇是在之前写的博文 D3力布图绘制--节点间的多条关系连接线的方法 基础上加修改的,这里放上修改的代码,其他的一样 // DATA var nodes = [ ...

  8. ECharts之force力导向布局图——数据源说明及后端API约定

    Echarts ? 关于 Echarts 请移步这里 force 力导向图 实现方式,如: function require_EC () { require( [ 'echarts', //载入for ...

  9. D3力布图绘制--节点跑掉,单曲线弯曲问题记录

    D3力布图绘制中遇到的交互问题,频繁操作数据后,会出现节点跑掉和单曲线弯曲的问题 问题描述 在id指向都正常的情况下出现以下2种状况: 单曲线弯曲 节点跑掉 经排查,是数据重复导致的问题 线条也是一样 ...

随机推荐

  1. sticky -- position定位属性sticky值之粘性定位;

    sticky简述 sticky 是css定为新增的属性:可以说是相对定位relative和固定定位fixed的结合: 它主要用在对scroll事件的监听上,简单说在滑动过程中,某个元素的距离其父元素的 ...

  2. 在springboot pom文件配置过程,`spring-boot-maven-plugin`配置出错的问题解决及配置过程出现问题的一些思考

    在springboot pom文件配置过程,spring-boot-maven-plugin配置出错的问题解决及配置过程出现问题的一些思考 解决方法一: 也是最简单的方法,可能是maven没有来得及导 ...

  3. awk的执行方式

    https://blog.csdn.net/fengyuanye/article/details/82858863 awk执行有三种形式: 1.直接以命令行来执行,        语法形式为:awk  ...

  4. Ansible快速实战指南----多机自动化执行命令、部署神器

                                      1.需求: 需要在多台主机上,发送文件.执行命令,进行快速部署 2.ansible 远程复制文件 例子:在当前节点(20.88.14 ...

  5. DebugView端游日志查看工具

    端游日志工具 端游开发的同学可以通过DebugView - Windows Sysinternals | Microsoft Docs来查看游戏打印的log,它允许你监控本地系统上的debug pri ...

  6. 动态规划精讲(一)A单串

    单串 单串 dp[i] 线性动态规划最简单的一类问题,输入是一个串,状态一般定义为 dp[i] := 考虑[0..i]上,原问题的解,其中 i 位置的处理,根据不同的问题,主要有两种方式: 第一种是 ...

  7. CodeForce-785B Anton and Classes(简单贪心)

    Anton and Classes Anton likes to play chess. Also he likes to do programming. No wonder that he deci ...

  8. PHP 流行的框架

    Aura Laravel Symphony Yii Zend php components Packagist 最好的组件: Awesome PHP https://www.yiiframework. ...

  9. PHP 一个树为另一棵树的子结构 [TO BE CONTINUED]

    输入两棵二叉树A,B,判断B是不是A的子结构.(ps:我们约定空树不是任意一个树的子结构) <?php class TreeNode { private $val; private $left; ...

  10. Fillder抓包配置

    Faillder设置,完成以下设置后重启Fillder Fillder工具配置 设置端口 端口设置 (根据公司限制使用范围内的端口) 设置是否远程连接 勾选Decrypt HTTPS traffic ...