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

    文章目录 1. 序言 2. quick_inI1_outR1()流程图 3. 快速模式消息②数据包格式 4. 源码分析 4.1 quick_inI1_outR1() 4.2 quick_inI1_ou ...

  2. Redis——set,hash与列表

    一.List列表 基于Linked List实现 元素是字符串类型 列表头尾增删快,中间增删慢,增删元素是常态 元素可以重复出现 最多包含2^32-1元素 列表的索引 从左至右,从0开始 从右至左,从 ...

  3. POJ3625Building Roads

    Building Roads Description Farmer John had just acquired several new farms! He wants to connect the ...

  4. PHP中使用PDO操作事务的一些小测试

    关于事务的问题,我们就不多解释了,以后在学习 MySQL 的相关内容时再深入的了解.今天我们主要是对 PDO 中操作事务的一些小测试,或许能发现一些比较好玩的内容. 在 MyISAM 上使用事务会怎么 ...

  5. PHP的变量赋值

    这个标题估计很多人会不屑一顾,变量赋值?excuse me?我们学开发的第一课就会了好不好.但是,就是这样基础的东西,反而会让很多人蒙圈,比如,值和引用的关系.今天,我们就来具体讲讲. 首先,定义变量 ...

  6. 字体图标Icon Font

    字体图标Icon Font 前段时间研究怎样做字体图标,在网上查找诸多资料,诸多尝试,找到一套可以自己制作自己独立控制的制作流程,公司按照这套流程形成一套自己公司图标,本人目前所在公司已经在使用没有发 ...

  7. 鸿蒙内核源码分析(源码结构篇) | 内核每个文件的含义 | 百篇博客分析OpenHarmony源码 | v18.04

    百篇博客系列篇.本篇为: v18.xx 鸿蒙内核源码分析(源码结构篇) | 内核每个文件的含义 | 51.c.h .o 前因后果相关篇为: v08.xx 鸿蒙内核源码分析(总目录) | 百万汉字注解 ...

  8. P2350-[HAOI2012]外星人【线性筛】

    正题 题目链接:https://www.luogu.com.cn/problem/P2350 题目大意 给出\(N\)质因数分解之后的结果,求每次\(N=\varphi(N)\),多少次后\(N=1\ ...

  9. Unity Event Trigger 事件响应(二维,三维)添加组件

    EventTrigger 上主要的方法有PointerEnter.PointerExit.PointerDown.PointerUp.PointerClick............都会显示在面板上面 ...

  10. amber模拟kcl水溶液

    最近刚开始学习amber软件,看网上的教程勉强知道怎么操作这个amber了.就暂时跑了个分子动力学,其他的啥也没处理.先把我的操作过程记录下来吧,免得日后忘记. 一.构建kcl.pdb结构 利用Gau ...