Loading

前端 | 使用 ECharts 绘制关系图

0 需求

做的项目需要画一个关系图,主要需求如下:

  • 需要展示6种对象之间的关系:数据机构 数据 合约 模型 计算机构 应用
  • 支持突出显示6种对象中的某一种的所有对象
  • 支持Top x子图功能。top x子图的定义:在6种对象中的每一种对象,取关系数最多的x个,将这至多6*x个对象绘制在一张图中
  • 任务仅为原型展示,无后端,可以面向数据编程

众所周知 ECharts 是一个功能强大的 JS 图表库,这次也使用 echarts 进行图表绘制。

最终效果大致如下图;感兴趣的话可以在 Codepen 预览动画效果,也可以亲自尝试各种参数的修改。

1 使用 ECharts

使用echarts想必大家都比较熟悉了,官网上也有很详细的教程,这里就简单介绍一下。

charts 库支持的图表类型有二三十种,基本涵盖了能想象到和完全没想过的各种图表绘制。同时echarts也支持各种常用的引入方式:从源码构建、npm安装、CDN引入等。

引入 JS 库后在网页中使用也很容易:

<!-- 为 ECharts 准备一个具备大小的 DOM -->
<div id="model-kg-graph" style="height: 800px"></div>
var graph_chart = echarts.init(document.getElementById("model-kg-graph"));
let option = {
    // 相应的配置项
}
graph_chart.setOption(option);

不管绘制什么图表,需要的代码都是如上几行,区别只在 option 配置项的不同。主要的配置项有这些:

let option = {
  // 图例,因为一张图中可能存在多个图表(例如柱状图+折线图),因此参数是数组
  // 数组中每一项对应一个系列,每项是一个包含类别名称数组的对象;
  // categories: ["数据机构", "数据", "合约", "模型", "计算机构", "应用"],
  legend: [
    {
      data: graph_data.categories,
    },
  ],    
  // 图表的配置;如果数组有多项几个图表将绘制在一起
  // 每种不同的图表都有大量专属的配置内容,以下是比较常见通用的几种
  series: {[
    type: "graph",	// 指定绘制的是什么图
    roam: true,		// 使用鼠标滚轮缩放、点击移动
    animation: false,	// 是否开启动画;默认是开启
    emphasis: {},	// 可以设置被选中的元素突出显示的特殊样式
  ]},
};

除此以外还有标题、坐标系(柱状图/折线图中很常用)等等常见配置,这次没用到就忽略了。使用时可以查阅echarts事无巨细的配置项文档

2 关系图 Graph

本节中提到的所有配置项均是在 option.series 内,下文不再特殊说明

图的基本构成

说回关系图。关系图其实就是平时常说的图,基本构成是节点和边。在配置项中对应的就是:

// 也可以叫 nodes
data: [
  { name: "数据机构1", symbolSize: 3.0 }, 
],

// 也可以叫 edges
links: [
  { source: "数据1", target:"数据机构3" }, 
],
  • data 中的每个对象对应一个节点,name 为唯一表示,不能重复,否则会报错 Cannot set property 'dataIndex' of undefined ,直接渲染失败;symbolSize 表示节点的大小,不设置会有默认值,根据连接数设置不同大小也是常见的做法。
  • links 中的每个对象对应一条边,对应 data 的 name 属性;如果找不到 name == source || name == target 的节点,那这条边就不会渲染。

分类

需求中提到有6类对象需要分别表示,也就是节点对应不同的颜色。相应的配置项:

// categories: ["数据机构", "数据", "合约", "模型", "计算机构", "应用"],

categories: graph_data.categories.map((c) => ({ name: c })),
data: [
  { name: "数据机构1", category: 0, symbolSize: 3.0 }, 
],
  • categories 虽然就是类别的数组,但数组的每项是一个对象,对象只有一个叫 name 的属性。
    • 如果不熟悉map语句,简单来说这个值就是 [ {name: "aaa"}, {name: "bbb"}, ... ]
    • 这里的名称需要和 legend (图例)里面的那个类别数组对应,否则会缺少对应的图例
  • 每个节点的数据里增加类别的索引,对应类别数组的下标。注意这里必须是一个 number,不能用字符串,否则整张图无法渲染(亲自踩过的抗)

图的布局

有了节点和边就可以构造出一张图了。echarts 提供了三种布局方式:

layout: "none" | "circular" | "force"
  • none:布局完全由每个节点中指定的 (x, y) 坐标决定;显然对于数据很多并且不确定的图来说并不现实

  • circular:环形布局;很有特点的布局方式,见下图

  • force:力引导布局;这种其实就是最常见的图的样子,可以根据参数自动渲染。文中使用的就是这种方式。

使用 force 布局之后还有一些细节设置可以选择:

force: {
  initLayout: "circular",
  repulsion: 1000,
  layoutAnimation: false,
},
  • initLayout:初始布局,之后会根据设置的力引导属性继续变化直到稳定。说实话指不指定好像样子区别也不大
  • repulsion:斥力大小,简而言之数值越大节点之间距离越远,反之节点距离越近
  • layoutAnimation:渲染动画,就是从初始位置直到稳定的动画过程。官方文档中的说法是”节点数据较多(>100)的时候不建议关闭,布局过程会造成浏览器假死。“但那个动画真的十分魔性,建议去文章开头的链接里亲自体验一下。比起看这个视觉污染动画我宁愿他假死。

强调和样式

图有一些可以自定义的样式配置,基本节点、边、标签等等的颜色形状位置都可以自定义。这里介绍几个我用到的:

focusNodeAdjacency: true,
legendHoverLink: true,
lineStyle: {
  color: "source",
  opacity: 0.2,
  curveness: 0.3,
},
  • focusNodeAdjacency:聚焦邻接节点,就是下图所示这种喜闻乐见的样式,很不戳。文档中说这个选项的默认值是true,但我用v4.9.0版本的库手动添加这一句之后才有效果,可能是v5中改了。

  • legendHoverLink:文档的说法是鼠标悬停在图例上节点高亮,但实际上至少v5.0.2还并不是这个效果;可能是库的bug,可以期待一下之后会不会改进。
  • lineStyle:顾名思义,线的样式:
    • curveness :线的曲率,不设置将为直线
    • opacity/color:透明度/颜色,可以用 source/target 指定为源/目标节点的颜色

其他配置项设置的是静态状态下图的样式,对于高亮状态的元素(例如鼠标悬停在节点/边上),还可以单独设置强调样式 emphasis。同样,基本所有元素的各种样式都能设置,以下是几个例子:

emphasis: {
  itemStyle: {
    shadowColor: "rgba(0, 0, 0, 0.4)",
    shadowBlur: 15,
  },
  lineStyle: {
    width: 3,
  },
  label: {
    textBorderColor: "rgba(255, 255, 255, 0.8)",
    textBorderWidth: 2,
  },
},

3 突出显示指定节点

有一项需求是:突出显示某个类别的节点。(按理说鼠标悬停图例应该是这个效果,但他并不能用)

不过echarts提供了API,可以对图执行动作(action)。调用方式如下:

graph_chart.dispatchAction({
  type: action,
  seriesIndex: 0,
  name: names,
});
  • type:指定动作,这里要用到的是 "highlight"/"downplay",高亮/取消高亮
  • seriesIndex/seriesName:用下标/名字指定操作的系列,数组指定多个
  • dataIndex/name:用data[]中的下标/名字指定要操作的数据,数组指定多个

据此可以写出函数:

nodesAction(action, category) {
  if (category !== "") {
    // 取到指定图option中的data[]
    let nodes = this.graph_chart.getOption().series[0].data;
      
    // 根据category下标,获取到对应类别所有name的数组
    let names = nodes
      .filter((node) => node.category == category)
      .map((node) => node.name);
      
    // 对指定的name[]执行指定操作
    this.graph_chart.dispatchAction({
      type: action,
      seriesIndex: 0,
      name: names,
    });
  }
}

项目使用的Vue框架,下拉选择框绑定了graphFocus属性,对这个数据添加一个侦听器:

watch: {
  graphFocus(n, o) {
    // n:新的值;o: 旧的值
    this.nodesAction("highlight", n);
    this.nodesAction("downplay", o);
  },
},

就能实现切换选项的同时高亮对应的节点:

如果你使用的echarts版本在5.0.0以上,还可以通过以下配置实现类似于focusNodeAdjacency的效果:

emphasis: {
    focus: "adjacency",
}

4 替换数据

最后一个需求是显示不同的子图。根据之前说的“如果两端节点不同时存在则边不会渲染”,我们只需要将data[]替换成子图的节点,就可以实现对应的子图。

按理说top节点应该有后端直接返回,所以如何获取节点的过程这里省略了。有了top节点之后,使用setOptionAPI更新新的配置项:

switchSubgraph(value) {
  let data = graph_data.nodes;
  switch (value) {
    case 1:
      data = this.top1Sub;
      break;
    case 3:
      data = this.top3Sub;
      break;
    case 5:
      data = this.top5Sub;
      break;
  }
  this.graph_chart.setOption({
    series: { data: data, zoom: 1 },	// zoom=1 重置缩放
  });
},

setOption 的默认更新方式是合并更新,意思是只更改传入的新option与原来不同的地方,其他部分保持不变;所以我们只需要将更换的data传进去,不需要复制整个原先的option。

如果不希望使用合并更新,可以手动传入第二个参数notMerge=true,就会将整个option替换为传入的新选项

如果打开了动画,还可以自动根据更新前后的差异展示适当的动画。

  • 但是动画这东西是个坑。用v4.9.0的时候,替换data后边的显示是乱的,需要手动缩放一下;换成v5.0.0之后,替换完data显示没问题,一缩放又坏了。设置了animation: false之后啥毛病都没了。
  • 可能这功能还不够完善,对于常用的柱状图折线图表现比较好,关系图这种不那么常用又复杂的东西就有各种神秘bug。期望日后的版本中可以改善。

替换节点后,得到了期望的Top子图,并且之前的高亮功能也可以正常使用。

结语

以上就是我这次使用ECharts关系图的过程中遇到的问题以及最终的解决方式,希望也可以帮到你。如果有相关的问题、或是文章中存在疏漏,欢迎在评论区留言讨论!

PS:第一次用CodePen,真的够呛,研究这玩意的时间快和写文差不多长了。眼看可以就如何使用CodePen再写一篇(跑

参考资料

ECharts官方文档

posted @ 2021-02-08 00:11  Skuld_yi  阅读(18765)  评论(4编辑  收藏  举报