百度地圖、ECharts整合HT for Web網路拓撲圖應用

圖撲軟體發表於2015-03-23

前一篇談及到了ECharts整合HT for Web網路拓撲圖應用,後來在EChartsDemo中看到了有關空氣質量的相關報表應用,就想將百度地圖、EChartsHT for Web三者結合起來也做一個類似空氣質量報告的報表+拓撲圖應用,於是有了下面的Demo


在這個Demo中,將GraphView拓撲圖元件新增到百度地圖元件中,覆蓋在百度地圖元件之上,並且在百度地圖元件上和GraphView拓撲圖元件上分別新增事件監聽,相互同步經緯度和螢幕位置資訊,從而來控制拓撲圖上的元件位置固定在地圖上,並在節點和節點之間的連線上加上了流動屬性。右下角的圖示框是採用HT for WebPanel皮膚元件結合ECharts圖表元件完成的。

接下來我們來看看具體的程式碼實現:

 

1.百度地圖是如何與HT for Web元件結合的;

 

map = new BMap.Map("map");
var view = graphView.getView();
view.className = 'graphView';
var mapDiv = document.getElementById('map');
mapDiv.firstChild.firstChild.appendChild(view);

 

首先需要在body中存在idmapdiv,再通過百度地圖的api來建立一個map地圖物件,然後建立GraphView拓撲圖元件,並獲取GraphView元件中的view,最後將view新增到idmapdiv的第二代孩子節點中。這時候問題就來了,為什麼要將view新增到map的第二代孩子節點中呢,當你審查元素時你會發現這個div是百度地圖的遮罩層,將view新增到上面,會使view會是在地圖的頂層可見,不會被地圖所遮擋。

 

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中所有dataposition屬性,讓其在螢幕上的座標與地圖同步,然後將GraphViewScrollPinch兩個事件的執行函式設定為空函式,就是當監聽到Scroll或者Pinch事件時不做任何的處理,將這兩個事件交給map來處理。

 

resetPosition函式中,做的事情很簡單:遍歷DataModel中的data,根據它們各自在地圖上的經緯度來換算成螢幕座標,並將座標設定到相應的data中,從而達到GraphView中的節點能夠固定在地圖上的效果。



 


3.建立右下角的圖表元件:

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,因此chartoption內容繪製在寬高為0div中,即使你resizechart,如果沒用重置圖表狀態的話,圖表狀態將無法在圖表上正常顯示。

接下來就是建立panel圖表元件了,這是HT for WebPanel元件的基本用法,其中content屬性的值可以是HT for Web的任何元件或div元素,如果是HT fro Web元件的話,該元件必須實現了validateImpl方法,因為在panel的屬性變化後將會呼叫content對應元件的validateImpl方法來重新佈局元件內容。

 

 

4.EChartsGraphView拓撲圖元件的互動:

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中節點屬性操作。



今天就寫到這吧,希望這篇文章能夠幫到那些有地圖、拓撲圖、圖表相結合需求的朋友,在設計上可能想法還不夠成熟,希望大家不吝賜教。

相關文章