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

    一.jar可以引进项目中,复制到路下后,要add as library,加载到工作空间中才能引入: 也jar包放在硬盘的项目目录外面,可以多个项目引入共用: 二.xml配置 1.aop全自动配置 2. ...

  2. Servlet3.0注解配置访问路径和urlParttern配置

    一.Servlet用注解配置访问路径 二.IDEA的tomcat相关配置 其中,第一点的配置文件,直接在IDEA的可视化操作界面修改就可以改掉配置文件中内容: 三.urlParttern配置 其中,* ...

  3. 推荐一款编程字体:Iosevka

    最近发现一款很好用的编程字体:Iosevka.它是一款现代化的编程字体集合,除了等宽.oO0 iIl1明显区分等基本特性外,还有很多非常现代的特性,比如: 多种风格:有非常多的字形可供选择,衬线/非衬 ...

  4. 马哈鱼数据血缘分析器分析case-when语句

    马哈鱼数据血缘分析器是一个分析数据血缘关系的平台,可以在线直接递交 SQL 语句进行分析,也可以选择连接指定数据库获取 metadata.从本地上传文件目录.或从指定 git 仓库获取脚本进行分析. ...

  5. mysql中通过sql语句查询指定数据表的字段信息

      mysql数据库在安装完成时,自动创建了information_schema.mysql.test这三个数据库.其中,information_schema记录了创建的所有数据库的相关信息,因此可以 ...

  6. 使用vsCode开发vue项目格式化通用配置

    {   "editor.tabSize": 2,   "editor.fontSize": 18,   "editor.wordWrap": ...

  7. Shell系列(1)- Shell概述

    Shell是什么 Shell是一个命令行解释器,它为用户提供了一个向Linux内核发送请求以便运行程序的界面系统级程序,用户可以用Shell来启动.挂起.停止甚至时编写一些程序 Shell还是一个功能 ...

  8. Linux系列(29) - rpm包命名规则(1)

    RPM包命名规则 例如包名:httpd-2.2.15-15.el6.centsos.1.i686.rpm 软件包名-httpd 软件版本-2.2.15 发布的次数-15 el6.centos适合的Li ...

  9. 定要过python二级 第11套

    1. 2.乃至好的代码片段与解决方法,我保存在了 H:盘中python中的:H:\python\python二级好的代码片段与错误解决 3.接着第一个点,为什么print(read(f))  把f 放 ...

  10. Ybt#452-序列合并【期望dp】

    正题 题目链接:https://www.ybtoj.com.cn/contest/113/problem/2 题目大意 一个空序列,每次往末尾加入一个\([1,m]\)中的随机一个数.如果末尾两个数相 ...