Echarts功能出色,文檔詳細(xì)(強(qiáng)烈推薦),如今已經(jīng)更新到了4.x版本。
本文要介紹的是echarts所支持的graph(關(guān)系圖、力導(dǎo)向圖)的功能拓展的小實(shí)踐。我們會(huì)利用其所提供的API增添三個(gè)功能。
1.選中節(jié)點(diǎn)
2.增刪節(jié)點(diǎn)
3.層級(jí)縮放
一、容器準(zhǔn)備與數(shù)據(jù)加載
這部分沒有太多要說的,無論是直接寫在html中還是使用框架,都需要為可視化的呈現(xiàn)準(zhǔn)備一個(gè)DOM容器,初始化后隨即渲染即可生成圖表。
var myChart = echarts.init(document.getElementById('main'))//初始化,如果你的容器id為main的<div></div>
var option={/*你的配置項(xiàng),含有重要的數(shù)據(jù)部分*/}
myChart.setOption(option)// 終了
option含有許多功能配置信息,包括坐標(biāo)軸,區(qū)域縮放,地理坐標(biāo)系等等組件,在本例中基本采用默認(rèn)配置,只需要關(guān)注配置項(xiàng)的series即可。
var option={series:[{type:'graph',name:'隨意',layout:'force',data:[],link:[]}]}
echarts中大部分配置都是共通的,我們選定圖表類型graph和布局force即可(layout有三種選擇,分別為:circular,none,force。circular是環(huán)形布局,所有點(diǎn)圍成了一個(gè)圈,none布局需要為每個(gè)數(shù)據(jù)點(diǎn)指定坐標(biāo),force就是老版本中采用力布局算法的圖)。相比其他圖表,graph除了數(shù)據(jù)data(可用別名nodes)之外,還需links(edges)這個(gè)數(shù)據(jù)屬性。
如果數(shù)據(jù)如下:
data:[{
name:'n1'
},{
name:'n2'
},{
name:'n3'
}],
links: [{
source: 'n1',
target: 'n2'
}, {
source: 'n2',
target: 'n3'
}]
則圖形生成為:

所以data描述了一個(gè)個(gè)單獨(dú)節(jié)點(diǎn)的信息,它當(dāng)然不止name這樣簡(jiǎn)陋,這里我們只關(guān)注了必須的項(xiàng),除了文檔中的諸多可選項(xiàng)value,category等之外還可以自定義屬性,值得注意的是它不允許出現(xiàn)重名。link則描述了節(jié)點(diǎn)的關(guān)系,如果不關(guān)注節(jié)點(diǎn)的指向或者說主次(可以為連線填上箭頭),source與target只要分別填上連線的兩端即可。
二、事件處理
Echarts通過on來添加事件處理函數(shù),在取得實(shí)例化的(前述)myChart后,事件監(jiān)聽如下:
myChart.on('click', function (params) {
console.log(params);
});
鼠標(biāo)事件包括'click'(單擊),'dblclick'(雙擊),'mousedown','mouseup','mouseover','mouseout','globalout','contextmenu(右鍵)'。
除去鼠標(biāo)事件之外,還支持圖表行為,詳見這里。
這個(gè)回調(diào)函數(shù):1.可以具名 2.可以指定上下文
它的參數(shù)params包含這些屬性:
{
// 當(dāng)前點(diǎn)擊的圖形元素所屬的組件名稱,
// 其值如 'series'、'markLine'、'markPoint'、'timeLine' 等。
componentType: string,
// 系列類型。值可能為:'line'、'bar'、'pie' 等。當(dāng) componentType 為 'series' 時(shí)有意義。
seriesType: string,
// 系列在傳入的 option.series 中的 index。當(dāng) componentType 為 'series' 時(shí)有意義。
seriesIndex: number,
// 系列名稱。當(dāng) componentType 為 'series' 時(shí)有意義。
seriesName: string,
// 數(shù)據(jù)名,類目名
name: string,
// 數(shù)據(jù)在傳入的 data 數(shù)組中的 index
dataIndex: number,
// 傳入的原始數(shù)據(jù)項(xiàng)
data: Object,
// sankey、graph 等圖表同時(shí)含有 nodeData 和 edgeData 兩種 data,
// dataType 的值會(huì)是 'node' 或者 'edge',表示當(dāng)前點(diǎn)擊在 node 還是 edge 上。
// 其他大部分圖表中只有一種 data,dataType 無意義。
dataType: string,
// 傳入的數(shù)據(jù)值
value: number|Array
// 數(shù)據(jù)圖形的顏色。當(dāng) componentType 為 'series' 時(shí)有意義。
color: string
}
比如params.name可以得到單擊(或雙擊事件)節(jié)點(diǎn)名,或者使用params.dataType判斷點(diǎn)擊到了節(jié)點(diǎn)還是連線。所以每當(dāng)鼠標(biāo)事件發(fā)生時(shí),我們便可以抽取出目標(biāo)節(jié)點(diǎn)的信息,從而在整個(gè)數(shù)據(jù)結(jié)構(gòu)中以此為判斷條件進(jìn)行變換??梢詤⒖?a target="_blank" rel="nofollow">官方小例子。
三、邏輯梳理與功能實(shí)現(xiàn)
(下文談?wù)摰淖痈腹?jié)點(diǎn)是以links中source、target為依據(jù)的,主要體現(xiàn)數(shù)據(jù)索引的結(jié)構(gòu),并且樹結(jié)構(gòu)才能直接應(yīng)用這些功能)
點(diǎn)擊高亮
這個(gè)比較簡(jiǎn)單,點(diǎn)擊節(jié)點(diǎn)后通過params得到目標(biāo)點(diǎn)的name后(其實(shí)data中的數(shù)據(jù)還含有index屬性,可以通過這個(gè)來處理),找到該點(diǎn)在data數(shù)組中的位置后改變相應(yīng)的配色或者亮度即可。對(duì)于改變配色的方案,只需要在規(guī)定好被選中的顏色基礎(chǔ)上傳遞該節(jié)點(diǎn)的name(或者能夠索引的信息)和category即可,因?yàn)樵龠x中其余點(diǎn)時(shí),我們通過維持前一個(gè)的索引和category來恢復(fù)顏色(默認(rèn)會(huì)根據(jù)category的不同分配調(diào)色板上的顏色)。
//偽代碼
if(preName===params.data.name)//do Nothing
else{
在option中找到preName所在項(xiàng)A
A.category=preCategory
在option中找到param.data.name所在項(xiàng)B
B.category=uniCategory//如果uniCategory所代表的數(shù)字沒有規(guī)定的話一般會(huì)顯示紅色
[ preName,preCategory]=[params.data.name,params.data.category]//傳遞當(dāng)前節(jié)點(diǎn)信息以供后續(xù)使用
}
//稍顯曲折的原因在于通過事件獲取的params參數(shù)無法修改,我們知曉了節(jié)點(diǎn)的基本信息,但修改它還需要去option中
//這一部分可以順便設(shè)置傳遞一些變量的功能,把選中節(jié)點(diǎn)的信息暴露給后續(xù)的增刪操作
實(shí)際上,我傾向于改變節(jié)點(diǎn)的模糊度(屬性opacity 0到1從不顯示到正常亮度)來實(shí)現(xiàn)選種效果,graph中series中名為focusNodeAdjacency的作用是鼠標(biāo)移到節(jié)點(diǎn)上的時(shí)候突出顯示節(jié)點(diǎn)以及節(jié)點(diǎn)的邊和鄰接節(jié)點(diǎn),只要我們把它改成單擊突出顯示節(jié)點(diǎn)即可。我希望你可以在源碼上修改,順便學(xué)習(xí)下自定義構(gòu)建方法,或者重新發(fā)布一個(gè)DIY的npm包。
需要修改的地方在echarts/lib/chart/graph/GraphView.js,這里貼上參考鏈接沒有提及的對(duì)鄰接點(diǎn)不作為的部分,各個(gè)Echarts版本可能有少許差異。GraphView.js
實(shí)際代碼參照源碼的方案,通過設(shè)置全局的opacity(series.itemStyle.opacity、series.lineStyle.opacity)和單點(diǎn)的opacity(data.itemStyle.opacity)做到了類似功能,點(diǎn)擊節(jié)點(diǎn)高亮,再點(diǎn)擊恢復(fù)。
增刪節(jié)點(diǎn)
//add
newNode=輸入信息
newSource.source=selectName
newSource.target=newNode.name
data.push(newNode)
links.push(newSource)
//delete
data.splice(selectNode.index,1)
找出待刪除節(jié)點(diǎn)的子節(jié)點(diǎn)有關(guān)的Clinks
Clinks.source=selectLinks.source
links.splice(selectLinks.index,1)
//以上push,splice操作同時(shí)作用于數(shù)據(jù)庫
這個(gè)需要添加兩個(gè)按鈕(或者直接用事件區(qū)分,根據(jù)你具體情況也可以加上增加節(jié)點(diǎn)的信息填框)。選擇(父)節(jié)點(diǎn)A之后,我們得到了需要增添或者刪除節(jié)點(diǎn)A的name。增加節(jié)點(diǎn)時(shí),通過data.push可以直接把新(子)節(jié)點(diǎn)加上去,然后在links數(shù)組中增加一項(xiàng)source為A,target為新節(jié)點(diǎn)名字的項(xiàng);刪除節(jié)點(diǎn)則有一些選擇,比如將A的子節(jié)點(diǎn)連同A全部刪除,或者只刪除A,然后將A的子節(jié)點(diǎn)過渡到A的父節(jié)點(diǎn)上:前者通過A.name在數(shù)組links中找出所有source為A的target,然后在data數(shù)組中找到所有name符合這些target和A.name的項(xiàng)刪除掉,如果不考慮重名的因素,links中的項(xiàng)可以不刪除。后者找到所有source為A.name對(duì)應(yīng)的target后,將source改為A對(duì)應(yīng)的source即可(links中target為A.name對(duì)應(yīng)的source)。
這部分可能會(huì)和其余功能有所沖突,需要注意是不是同時(shí)更新了數(shù)據(jù)庫和視圖的數(shù)據(jù)。具體細(xì)節(jié)有興趣可以自行查看源代碼。

層級(jí)縮放

這個(gè)指的是雙擊(或者其他)節(jié)點(diǎn)展開或者收起該節(jié)點(diǎn)的子節(jié)點(diǎn),可以參照Echarts中的另一圖表類型tree的功能模樣。在實(shí)現(xiàn)過程上和增刪的查找一樣,我們只需要維持兩個(gè)數(shù)據(jù)集合,一個(gè)是后臺(tái)傳過來的option,另一個(gè)是當(dāng)前容器內(nèi)的option。拿著目標(biāo)節(jié)點(diǎn)的信息在后臺(tái)option中查找相應(yīng)的子節(jié)點(diǎn)和連線信息,然后通過push、splice操作改變?nèi)萜髦械?em>option。
//偽代碼
//通過數(shù)據(jù)庫數(shù)據(jù)和視圖數(shù)據(jù)的對(duì)比實(shí)現(xiàn)
//如果splice數(shù)據(jù)時(shí)沒有順便刪除links數(shù)組那么數(shù)據(jù)庫與視圖的這部分是一樣的
1.找出數(shù)據(jù)庫中l(wèi)inks中所有滿足(edges.source===name)的項(xiàng)
2.if(edges.target in 當(dāng)前視圖){刪除該項(xiàng),第三步} //得出是否子節(jié)點(diǎn)的結(jié)論備用
3.name=node.target 返回第一步
遞歸結(jié)束
4.if(A has no child) {第五步} else end
5.找出數(shù)據(jù)庫在links數(shù)組edges.source===name的項(xiàng)
6.找出數(shù)據(jù)庫在data數(shù)組data.name===edges.target的項(xiàng)
7.if(data.name in 視圖){do nothing} else{push.(data)}//如果第二步?jīng)]有設(shè)計(jì)好,預(yù)防收起子節(jié)點(diǎn)之后增加節(jié)點(diǎn)的情形 (?ェ?。)
參考:文內(nèi)鏈接
代碼地址:https://github.com/southproject/echarts-demo
