前一篇談及到了ECharts整合HT for Web的網絡拓撲圖應用,後來在ECharts的Demo中看到了有關空氣質量的相關報表應用,就想將百度地圖、ECharts和HT for Web三者結合起來也作一個相似空氣質量報告的報表+拓撲圖應用,因而有了下面的Demo:http://www.hightopo.com/demo/blog_baidu_20150928/ht-baidu.htmljavascript
在這個Demo中,將GraphView拓撲圖組件添加到百度地圖組件中,覆蓋在百度地圖組件之上,而且在百度地圖組件上和GraphView拓撲圖組件上分別添加事件監聽,相互同步經緯度和屏幕位置信息,從而來控制拓撲圖上的組件位置固定在地圖上,並在節點和節點之間的連線上加上了流動屬性。右下角的圖標框是採用HT for Web的Panel面板組件結合ECharts圖表組件完成的。html
接下來咱們來看看具體的代碼實現:java
1.百度地圖是如何與HT for Web組件結合的;node
map = new BMap.Map("map"); var view = graphView.getView(); view.className = 'graphView'; var mapDiv = document.getElementById('map'); mapDiv.firstChild.firstChild.appendChild(view);
首先須要在body中存在id爲map的div,再經過百度地圖的api來建立一個map地圖對象,而後建立GraphView拓撲圖組件,並獲取GraphView組件中的view,最後將view添加到id爲map的div的第二代孩子節點中。這時候問題就來了,爲何要將view添加到map的第二代孩子節點中呢,當你審查元素時你會發現這個div是百度地圖的遮罩層,將view添加到上面,會使view會是在地圖的頂層可見,不會被地圖所遮擋。api
2.百度地圖和GraphView的事件監聽;瀏覽器
map.addEventListener('moveend', function(e){ resetPosition(); }); map.addEventListener('dragend', function(e){ resetPosition(); }); map.addEventListener('zoomend', function(e){ resetPosition(); }); graphView.handleScroll = function(){}; graphView.handlePinch = function(){}; function resetPosition(e){ graphView.tx(0); graphView.ty(0); dataModel.each(function(data){ var lonLat, position; if(data instanceof ht.HtmlNode){ if(data.getId() != 'chartTotal') { position = data.getHost().getPosition(); position = {x: position.x + 168, y: position.y + 158}; data.setPosition(position.x, position.y); } } else if(data instanceof ht.Node){ lonLat = data.lonLat; position = map.pointToPixel(lonLat); data.setPosition(position.x,position.y); } }); }
首先監聽map的三個事件:moveend、 dragend、 zoomend,這三個事件作了同一件事--修改DataModel中全部data的position屬性,讓其在屏幕上的座標與地圖同步,而後將GraphView的Scroll和Pinch兩個事件的執行函數設置爲空函數,就是當監聽到Scroll或者Pinch事件時不作任何的處理,將這兩個事件交給map來處理。網絡
在resetPosition函數中,作的事情很簡單:遍歷DataModel中的data,根據它們各自在地圖上的經緯度來換算成屏幕座標,並將座標設置到相應的data中,從而達到GraphView中的節點可以固定在地圖上的效果。app
3.建立右下角的圖表組件:echarts
ht.Chart = function(option){ var self = this, view = self._view = document.createElement('div'); view.style.position = 'absolute'; view.style.setProperty('box-sizing', 'border-box', null); self._option = option; self._chart = echarts.init(self.getView()); if(option) self._chart.setOption(option); self._FIRST = true; }; ht.Default.def('ht.Chart', Object, { ms_v: 1, ms_fire: 1, ms_ac: ['chart', 'option', 'isFirst', 'view'], validateImpl: function(){ var self = this, chart = self._chart; chart.resize(); if(self._FIRST){ self._FIRST = false; chart.restore(); } }, setSize: function(w, h){ var view = this._view; view.style.width = w + 'px'; view.style.height = h + 'px'; } }); function createPanel(title, width, height){ chart = new ht.Chart(option); var c = chart.getChart(); c.on(echarts.config.EVENT.LEGEND_SELECTED, legendSelectedFun); var chartPanel = new ht.widget.Panel({ title: title, restoreToolTip: "Overview", width: width, contentHeight: height, narrowWhenCollapse: true, content: chart, expanded: true }); chartPanel.setPositionRelativeTo("rightBottom"); chartPanel.setPosition(0, 0); chartPanel.getView().style.margin = '10px'; document.body.appendChild(chartPanel.getView()); }
首先定義了ht.Chart類,並實現了validateImpl方法,方法中處理的邏輯也很簡單:在每次方法執行的時候調用圖表的reset方法從新設定圖標的展現大小,若是該方法是第一次執行的話,就調用圖表的restore方法將圖表還原爲最原始的狀態。會有這樣的設計是由於ht.Chart類中的view是動態建立的,在沒有添加到dom以前將一直存在於內存中,在內存中由於並無瀏覽器寬高信息,因此div的實際寬高均爲0,所以chart將option內容繪製在寬高爲0的div中,即便你resize了chart,若是沒用重置圖表狀態的話,圖表狀態將沒法在圖表上正常顯示。dom
接下來就是建立panel圖表組件了,這是HT for Web的Panel組件的基本用法,其中content屬性的值能夠是HT for Web的任何組件或div元素,若是是HT fro Web組件的話,該組件必須實現了validateImpl方法,由於在panel的屬性變化後將會調用content對應組件的validateImpl方法來從新佈局組件內容。
4.ECharts和GraphView拓撲圖組件的交互:
legendSelectedFun = function(param) { if(chart._legendSelect){ delete chart._legendSelect; return; } console.info(param); var id = nodeMap[param.target], dm = graphView.dm(), data = dm.getDataById(id), sm = dm.sm(), selection = sm.getSelection(); if(param.selected[param.target]) { sm.appendSelection([data]); if(selectionData.indexOf(param.target) < 0){ selectionData.push(param.target); } }else { sm.removeSelection([data]); var index = selectionData.indexOf(param.target); if(index >= 0){ selectionData.splice(index, 1); } } sm.setSelection(selection.toArray()); }; graphView.mi(function(e){ console.info(e.kind, e.data); var c = chart.getChart(), legend = c.component.legend, selectedMap = legend.getSelectedMap(); if(e.kind === 'endRectSelect'){ chart._legendSelect = true; for(var name in notes){ legend.setSelected(name, false); } notes = {}; graphView.dm().sm().each(function(data){ var note = data.s('note'); if(note) notes[note] = 1; }); for(var name in notes){ legend.setSelected(name, true); } } else if(e.kind === 'clickData'){ chart._legendSelect = true; var data = e.data; if(data instanceof ht.Node){ var note = data.s('note'); if(note){ var selected = legend.isSelected(note); if(selected){ graphView.dm().sm().removeSelection([data]); } legend.setSelected(note, !selected); } } } });
legendSelectedFun函數是EChart圖表的legend插件選中事件監聽,其中處理的邏輯是:當legend插件中的某個節點被選中了,也選中在GraphView拓撲圖中對應的節點,當取消選中是,也取消選中GraphView拓撲圖中對應的節點。
在GraphView中添加交互監聽,若是在GraphView中作了框選操做,在框選結束後,將本來legend插件上被選中的節點取消選中,而後再獲取被選中節點,並在legend插件上選中對應節點;當GraphView上的節點被選中,則根據legend插件中對應節點選中狀況來決定legend插件中的節點和graphView上的節點是否選中。
在GraphView交互中,我往chart實例中添加了_legendSelect變量,該變量的設定是爲了阻止在GraphView交互中修改legend插件的節點屬性後回調legendSelectedFun回調函數作修改GraphView中節點屬性操做。
今天就寫到這吧,但願這篇文章可以幫到那些有地圖、拓撲圖、圖表相結合需求的朋友,在設計上可能想法還不夠成熟,但願你們不吝賜教。