之前写了一篇文章讲怎么在关系图里使用markLine作为底图,并且能够随着关系图的移动而移动
前文链接
最近来了新需求,如下
1.点击某个节点,只留下与之相关的节点,再次点击,恢复原样
2.鼠标移动节点上去与之相关的线条高亮
3.把底图的标识文字居中
4.添加一个下拉框筛选要展示的节点。
其实总结下来,第一个需求以及第二个需求和最后一个需求一部分是共通的,也就是其实都是对ehcarts
所展示的数据的操作,不同的地方在于:第一个和第二个需求只需要考虑数据就行,最后一个需求需要考虑底图也就是markLine
的移动以及某个区域内的节点数据没有了以后整体的移动,也就是每一次点击树状下拉框,都要重新计算每个节点和markLine
底图的位置。
本文章并非手把手
教会怎么做出图中的效果,只是讲解做这个项目中遇到的问题
和具体的解决思路
。
首先解决第一个需求,也就是点击某个节点,只展示这个节点相关的数据
解决思路:首先肯定是拿到点击事件,然后点击节点的时候,拿到这个节点相关的数据,根据与之相关的某个筛选条件,把有关的数据筛选出来,赋值给echarts
,数据就展现出来了。
第二点是再次点击的时候,恢复到点击之前的数据。
解决思路:数据隔离,本地保存一份展示数据的拷贝,然后点击的时候,使用lodash
的cloneDeep
方法拷贝本地的拷贝数据,点击的时候给点击的项计数+1,同时把本地数据的拷贝中的对应项的计数+1。当某个节点的计数为2的时候,清空所有计数。
也就是点击的时候,把点击节点的计数+1,根据这个节点的某个条件筛选出有关的其他节点。
再次点击的时候,如果有节点的计数为2,就把本地未经筛选的数据赋值回来,并且清空所有计数。
上代码讲解
//html 代码
<div id="room-bar-chart" echarts [options]="echartOptions"
(chartMouseOver)="mouseOver($event,'over')" (chartMouseOut)="mouseOver($event,'out')"
(chartInit)="InitChart($event)" (chartClick)="chartClick($event)"
(chartContextMenu)="chartDbClick($event,menu)" style="height: 650px;"></div>
//要看的其实就是chartClick事件
//ts
chartClick($event: any){
if($event.dataType==="edge") return //点到线的时候就不做操作
let series = _.cloneDeep(this.echartOptions.series[0]);//备份当前echarts图的series
let name = $event.name//拿到点击节点的名字
let relative: any[] = []//保存筛选出不论是source还是target与点击项有关的数据
edgeList.forEach((item,index,array)=>{//edgeList就是节点关系的数据
if(item.source===name||item.target===name){//如果目标节点或者起始节点是点击节点的name
relative.push(item)//将其加入到关系数组中保存
}
})
let seriesData:Array<any> = series.data//拿到现有echarts图的数据
let dataArray = []//保存 展示的数据里与relative有关的数据
for (let w = 0; w < seriesData.length; w++) {//循环本体的数据
let item = seriesData[w];
let index = w;
if (item.name == name) {//找到本体上点击的数据
item.count++
this.echartOptions.series[0].data[index].count++//数据改动到原始数据上
} else {//如果不是,把本体上其他节点的计数清0
this.echartOptions.series[0].data[index].count = 0//数据改动到原始数据上
}
if (this.echartOptions.series[0].data[index].count >= 2) {//如果同一个数据点击了两次
this.echartOptions.series[0].data.forEach((element: { count: number; }) => {//把所有数据的count计数清空
element.count = 0
});
this.setEchartsOption({ series: series })//重绘所有数据 重绘方法在上一篇文章里有。
return
}
for (let x = 0; x < relative.length; x++) {
let data = relative[x];
if (item.name == data.source || item.name == data.target) {
dataArray.push(item)
}
}
}
series.data = _.uniq(dataArray)//去重
this.setEchartsOption({series:series})
}
这样点击某个节点的时候就能够实现第一次点击保留相关节点,第二次点击恢复原样。
第二个需求,鼠标移动上去的时候,高亮与之相关的线
解决思路:大体同第一个需求,捕获鼠标移动到某个节点之上的事件,筛选出与节点相关的线的数据,然后改变线条的颜色就行,鼠标移出的时候,在把颜色改回原来的样子。
上代码讲解
//html 代码
<div id="room-bar-chart" echarts [options]="echartOptions"
(chartMouseOver)="mouseOver($event,'over')" (chartMouseOut)="mouseOver($event,'out')"
(chartInit)="InitChart($event)" (chartClick)="chartClick($event)"
(chartContextMenu)="chartDbClick($event,menu)" style="height: 650px;"></div>
//关注mouseOver事件
//ts
/** 鼠标移动到节点上高亮与之相关的线 */
mouseOver($event: any, type: string) {
if ($event.dataType === "node") {//node 代表是节点,如果是线的话不进行操作
//数据的变动是否需要同步到原始数据上是需要考量的,这里使用深拷贝复制一份,和直接使用原始数据效果相同。
let series = this.echartsInstance.getOption().series[0]
let name = $event.name
if (type === "over") {//由于移入移出我都用同一个方法,所以需要用参数判断是移入还是移出
// console.log("series",series)
series.links.forEach((item: { source: any; target: any; lineStyle: { curveness: any; color?: string; width?: number; opacity?: number; }; }) => {
if (item.source === name || item.target === name) {//如果有关
item.lineStyle = {
color: "#39adfa",
width: 1.5,
opacity: 1,
curveness: item.lineStyle ? item.lineStyle.curveness : 0
}
}
})
// console.log('鼠标移入series',series)
} else {
//如果单纯的数据赋值,比如不要下面的series.links的循环,外层的linkStyle样式是无法应用
//到内层的links.lineStyle上的,所以需要把内层的links.lineSyle上的样式都加上才行
// series = this.echartsInstance.getOption().series[0]
series.links.forEach((item: { source: any; target: any; lineStyle: { curveness: any; color?: string; opacity?: number; width?: number; }; }) => {
if (item.source === name || item.target === name) {
item.lineStyle = {
color: "#aaa",
curveness: item.lineStyle ? item.lineStyle.curveness : 0,
opacity: 0.7,
width: 0.8,
}
}
})
}
this.setEchartsOption({ series: series })
} else if ($event.dataType === "edge") {
}
}
第三个需求,把markLine
的文字居中
解决思路:一开始的想法就是对着官方的文档, 想尽办法想要通过移动markLine
的label
,结果官方文档里的markLine
的label
属性非常麻烦,position
为start
或者end
的时候x轴方向无法移动,而为insideStartTop
的时候,字体会旋转90°,且不支持rotate
属性改变字体旋转角度,非常的头疼,如下图
这个问题我想破脑袋尝试了各种方法都没有想到怎么旋转字体 甚至2020年就有人提过相关的求!
越想越气
求人求天不如求己,我看着关系图的markLine
,线条扭曲的仿佛在嘲笑我。。。
然后我突然灵光一闪!我为什么要纠结于非要label
对应显示的线条?我多摆几个markLine
专门用于显示label
,然后把markLine
的线隐藏起来不就好了
上代码
/**
* markLine的处理
* @param levelScale 纬度轴处理后的数据 包含scale
* @param rowInterval 纬度间距
* @param classScale 经度轴处理后的数据 包含scale
* @param columnInterval 经度间距
* @returns 包含data的一个对象
*/
markLineOperate(levelScale: string | any[], rowInterval: number, classScale: string | any[], columnInterval: number) {
let levelCalculation = -(levelScale[0].scale * rowInterval) + (rowInterval / 2);//纬度轴算法关键,原本的算法里面单纯的写死为负的纬度间距的一半
let classCoord = columnInterval / 2;
// let totalLevelCoord = -rowInterval / 2;
let totalLevelCoord = levelCalculation;
const markLine = [];
// 计算y轴最高点
for (let i = 0; i < levelScale.length; i++) {
totalLevelCoord += Number(levelScale[i].scale * rowInterval);
}
let fontColor = 'rgba(101,97,97,.65)'//label字体颜色
// 计算x轴个点
for (let i = 0; i < classScale.length; i++) {
let pre = classScale[i].scale * columnInterval;
classCoord += pre;
const list = [
{
lineStyle: {
color: '#4169E1'
},
coord: [classCoord, -rowInterval / 2],
y: 35,
},
{
coord: [classCoord, totalLevelCoord],
}
];
let list2 = [//只用来居中显示区域名字,具体思路就是既然markLine的label显示的位置不好搞,那直接多一条线专门用来显示label就行了
{
lineStyle: {
color: 'rgba(0,0,0,0)'
},
label: {
position: 'start',
color: fontColor,
},
name: classScale[i].chinaName,
coord: [classCoord - pre / 2, -rowInterval / 2],
y: 35,
},
{
coord: [classCoord - pre / 2, totalLevelCoord],
}
]
markLine.push(list);
markLine.push(list2)
}
// x轴虚线
let totalClassCoord = columnInterval / 2;
// let levelCoord = -rowInterval / 2;
// let levelCoord = rowInterval / 2;
let levelCoord = levelCalculation;
// 计算x轴最远点
for (let i = 0; i < classScale.length; i++) {
totalClassCoord += classScale[i].scale * columnInterval;
}
// 计算y轴各点
for (let i = 0; i < levelScale.length; i++) {
let pre = levelScale[i].scale * rowInterval;
levelCoord += pre;
const list = [
{
// name: levelScale[i].name,
lineStyle: {
color: '#ff3324'
},
// label: {
// position: 'insideStartTop',
// fontSize: 10,
// color: fontColor,
// // distance:[0,result],
// },
// name: levelScale[i].chinaName,
coord: [columnInterval / 2, levelCoord],
x: 35,
},
{
coord: [totalClassCoord, levelCoord],
}
];
let list2 = [
{
// name: levelScale[i].name,
lineStyle: {
color: 'rgba(0,0,0,0)'
// color: 'rgba(0,0,0,1)'
},
label: {
position: 'insideStartTop',
fontSize: 10,
color: fontColor,
distance: [0, -5],
},
name: levelScale[i].chinaName,
coord: [columnInterval / 2, levelCoord - pre / 2],
x: 35,
},
{
coord: [totalClassCoord, levelCoord - pre / 2],
}
]
markLine.push(list);
markLine.push(list2);
}
return markLine;
}
第四个需求 增加一个下拉框,决定显示哪些节点
解决思路:核心和第一个需求是一样的,也就是操作显示的数据,关键是做好数据隔离,也就是确定哪些地方的改动直接操作原始数据,哪些改动是用复制的数据,代码就不上了,难度不大,只是需要一些编程的时间。用的是antd的树状下拉框。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。