Echarts功能出色,文档详细(强烈推荐),如今已经更新到了4.x版本。
本文要介绍的是echarts所支持的graph(关系图、力导向图)的功能拓展的小实践。我们会利用其所提供的API增添三个功能。
1.选中节点
2.增删节点
3.层级缩放
一、容器准备与数据加载
这部分没有太多要说的,无论是直接写在html中还是使用框架,都需要为可视化的呈现准备一个DOM容器,初始化后随即渲染即可生成图表。
var myChart = echarts.init(document.getElementById('main'))//初始化,如果你的容器id为main的<div></div>
var option={/*你的配置项,含有重要的数据部分*/}
myChart.setOption(option)// 終了
option含有许多功能配置信息,包括坐标轴,区域缩放,地理坐标系等等组件,在本例中基本采用默认配置,只需要关注配置项的series即可。
var option={series:[{type:'graph',name:'随意',layout:'force',data:[],link:[]}]}
echarts中大部分配置都是共通的,我们选定图表类型graph和布局force即可(layout有三种选择,分别为:circular,none,force。circular是环形布局,所有点围成了一个圈,none布局需要为每个数据点指定坐标,force就是老版本中采用力布局算法的图)。相比其他图表,graph除了数据data(可用别名nodes)之外,还需links(edges)这个数据属性。
如果数据如下:
data:[{
name:'n1'
},{
name:'n2'
},{
name:'n3'
}],
links: [{
source: 'n1',
target: 'n2'
}, {
source: 'n2',
target: 'n3'
}]
则图形生成为:
所以data描述了一个个单独节点的信息,它当然不止name这样简陋,这里我们只关注了必须的项,除了文档中的诸多可选项value,category等之外还可以自定义属性,值得注意的是它不允许出现重名。link则描述了节点的关系,如果不关注节点的指向或者说主次(可以为连线填上箭头),source与target只要分别填上连线的两端即可。
二、事件处理
Echarts通过on来添加事件处理函数,在取得实例化的(前述)myChart后,事件监听如下:
myChart.on('click', function (params) {
console.log(params);
});
鼠标事件包括'click'(单击),'dblclick'(双击),'mousedown','mouseup','mouseover','mouseout','globalout','contextmenu(右键)'。
除去鼠标事件之外,还支持图表行为,详见这里。
这个回调函数:1.可以具名 2.可以指定上下文
它的参数params包含这些属性:
{
// 当前点击的图形元素所属的组件名称,
// 其值如 'series'、'markLine'、'markPoint'、'timeLine' 等。
componentType: string,
// 系列类型。值可能为:'line'、'bar'、'pie' 等。当 componentType 为 'series' 时有意义。
seriesType: string,
// 系列在传入的 option.series 中的 index。当 componentType 为 'series' 时有意义。
seriesIndex: number,
// 系列名称。当 componentType 为 'series' 时有意义。
seriesName: string,
// 数据名,类目名
name: string,
// 数据在传入的 data 数组中的 index
dataIndex: number,
// 传入的原始数据项
data: Object,
// sankey、graph 等图表同时含有 nodeData 和 edgeData 两种 data,
// dataType 的值会是 'node' 或者 'edge',表示当前点击在 node 还是 edge 上。
// 其他大部分图表中只有一种 data,dataType 无意义。
dataType: string,
// 传入的数据值
value: number|Array
// 数据图形的颜色。当 componentType 为 'series' 时有意义。
color: string
}
比如params.name可以得到单击(或双击事件)节点名,或者使用params.dataType判断点击到了节点还是连线。所以每当鼠标事件发生时,我们便可以抽取出目标节点的信息,从而在整个数据结构中以此为判断条件进行变换。可以参考官方小例子。
三、逻辑梳理与功能实现
(下文谈论的子父节点是以links中source、target为依据的,主要体现数据索引的结构,并且树结构才能直接应用这些功能)
点击高亮
这个比较简单,点击节点后通过params得到目标点的name后(其实data中的数据还含有index属性,可以通过这个来处理),找到该点在data数组中的位置后改变相应的配色或者亮度即可。对于改变配色的方案,只需要在规定好被选中的颜色基础上传递该节点的name(或者能够索引的信息)和category即可,因为再选中其余点时,我们通过维持前一个的索引和category来恢复颜色(默认会根据category的不同分配调色板上的颜色)。
//伪代码
if(preName===params.data.name)//do Nothing
else{
在option中找到preName所在项A
A.category=preCategory
在option中找到param.data.name所在项B
B.category=uniCategory//如果uniCategory所代表的数字没有规定的话一般会显示红色
[ preName,preCategory]=[params.data.name,params.data.category]//传递当前节点信息以供后续使用
}
//稍显曲折的原因在于通过事件获取的params参数无法修改,我们知晓了节点的基本信息,但修改它还需要去option中
//这一部分可以顺便设置传递一些变量的功能,把选中节点的信息暴露给后续的增删操作
实际上,我倾向于改变节点的模糊度(属性opacity 0到1从不显示到正常亮度)来实现选种效果,graph中series中名为focusNodeAdjacency的作用是鼠标移到节点上的时候突出显示节点以及节点的边和邻接节点,只要我们把它改成单击突出显示节点即可。我希望你可以在源码上修改,顺便学习下自定义构建方法,或者重新发布一个DIY的npm包。
需要修改的地方在echarts/lib/chart/graph/GraphView.js,这里贴上参考链接没有提及的对邻接点不作为的部分,各个Echarts版本可能有少许差异。
实际代码参照源码的方案,通过设置全局的opacity(series.itemStyle.opacity、series.lineStyle.opacity)和单点的opacity(data.itemStyle.opacity)做到了类似功能,点击节点高亮,再点击恢复。
增删节点
//add
newNode=输入信息
newSource.source=selectName
newSource.target=newNode.name
data.push(newNode)
links.push(newSource)
//delete
data.splice(selectNode.index,1)
找出待删除节点的子节点有关的Clinks
Clinks.source=selectLinks.source
links.splice(selectLinks.index,1)
//以上push,splice操作同时作用于数据库
这个需要添加两个按钮(或者直接用事件区分,根据你具体情况也可以加上增加节点的信息填框)。选择(父)节点A之后,我们得到了需要增添或者删除节点A的name。增加节点时,通过data.push可以直接把新(子)节点加上去,然后在links数组中增加一项source为A,target为新节点名字的项;删除节点则有一些选择,比如将A的子节点连同A全部删除,或者只删除A,然后将A的子节点过渡到A的父节点上:前者通过A.name在数组links中找出所有source为A的target,然后在data数组中找到所有name符合这些target和A.name的项删除掉,如果不考虑重名的因素,links中的项可以不删除。后者找到所有source为A.name对应的target后,将source改为A对应的source即可(links中target为A.name对应的source)。
这部分可能会和其余功能有所冲突,需要注意是不是同时更新了数据库和视图的数据。具体细节有兴趣可以自行查看源代码。
层级缩放
这个指的是双击(或者其他)节点展开或者收起该节点的子节点,可以参照Echarts中的另一图表类型tree的功能模样。在实现过程上和增删的查找一样,我们只需要维持两个数据集合,一个是后台传过来的option,另一个是当前容器内的option。拿着目标节点的信息在后台option中查找相应的子节点和连线信息,然后通过push、splice操作改变容器中的option。
//伪代码
//通过数据库数据和视图数据的对比实现
//如果splice数据时没有顺便删除links数组那么数据库与视图的这部分是一样的
1.找出数据库中links中所有满足(edges.source===name)的项
2.if(edges.target in 当前视图){删除该项,第三步} //得出是否子节点的结论备用
3.name=node.target 返回第一步
递归结束
4.if(A has no child) {第五步} else end
5.找出数据库在links数组edges.source===name的项
6.找出数据库在data数组data.name===edges.target的项
7.if(data.name in 视图){do nothing} else{push.(data)}//如果第二步没有设计好,预防收起子节点之后增加节点的情形 (・ェ・。)
参考:文内链接
代码地址:https://github.com/southproject/echarts-demo