前兩天在echarts 上尋找靈感的時候,看到了很多有關地圖類似的例子,地圖定位等等,但是好像就是沒有地鐵線路圖,就自己花了一些時間搗鼓出來了這個交互式地鐵線路圖的Demo ,地鐵線路上的點是在網上隨便下載了一個,這篇文章記錄自己的一些收穫(畢竟我還是個菜鳥)以及代碼的實現,希望能夠幫到一些朋友。當然,如果有什麼意見的可以直接跟我說,大家一起交流才會進步。
效果圖
http://www.hightopo.com/demo/subway/index.html
地圖稍微內容有點多,要全部展示,字顯得有點小了,但是沒關係,可以按照需求放大縮小,字體和繪製的內容並不會失真,畢竟都是用向量繪製的~
介面生成底層的div 是透過ht.graph.GraphView 元件產生的,然後就可以利用HT for Web 提供好的方法,呼叫canvas 畫筆隨便繪製就好,先來看看怎麼產生底層div:
var dm = new ht.DataModel();//資料容器var gv = new ht.graph.GraphView(dm);//拓樸元件gv.addToDOM();//將拓樸圖元件加入到body中
addToDOM 函數宣告如下:
addToDOM = function(){ var self = this, view = self.getView(), style = view.style; document.body.appendChild(view); //將元件底層div加入body中style.left = '0 ';//預設元件是絕對定位,所以設定位置style.right = '0'; style.top = '0'; style.bottom = '0'; window.addEventListener('resize', function () { self.iv(); }, false); //視窗變更事件}
現在我就可以在這個div 上亂塗亂畫了~首先我獲取下載好的地鐵線路圖上的點,我將它們放在subway.js 中,這個js 文件全部都是下載的內容,我沒有做其他的改動,主要是將這些點根據線路來分分配添加到數組中,例如:
mark_Point13 = [];//線路數組內包含線路的起點和終點座標以及這條線路的名稱t_Point13 = [];//換成站點數組內包含線路中的換乘站點座標以及換成站點名稱n_Point13 = [];//小站點數組內包含線路中的小站點座標以及小站點名稱mark_Point13.push({ name: '十三號線', value: [113.4973,23.1095]}); mark_Point13.push({ name: '十三號線', value: [113.4155,23.1080]}); t_Point13.push({ name: '魚珠', value:4113. ]}); n_Point13.push({ name: '裕豐圍', value: [113.41548,23.10004]});
接下來來描繪地鐵線路,我聲明了一個數組lineNum,用來裝js 中所有的地鐵線路的編號,以及一個color 數組,用來裝所有的地鐵線的顏色,這些顏色的index 與lineNum 中地鐵線編號的index 是一一對應的:
var lineNum = ['1', '2', '3', '30', '4', '5', '6', '7', '8', '9', '13', '14 ', '32', '18', '21', '22', '60', '68'];var color = ['#f1cd44', '#0060a1', '#ed9b4f', '#ed9b4f', '#007e3a', '#cb0447', '#7a1a57', '#18472c', '#008193', '#83c39e', '#8a8c29', '#82352b', '#82352b', '#09a1e0', '#8a8c29', '#82352b', '#b6d300', '#09a1e0'];
接著遍歷lineNum,將lineNum 中的元素和顏色傳到createLine 函數中,根據這兩個參數來繪製地鐵線路以及配色,畢竟js 文件中的命名方式也是有規律的,哪一條線路,則命名後面一定會加上對應的數字,所以我們只需要將字串與這個編號結合即可獲得js 中對應的陣列了:
let lineName = 'Line' + num;let line = window[lineName];createLine 的定義也非常簡單,我的程式碼設定了不少的樣式,所以看起來有點多。建立一個ht.Polyline 管線,我們可以透過polyline.addPoint() 函數在這個變數中加入具體的點,透過setSegments 可以設定點的連接方式。 function createLine(num, color) {//繪製地圖線var polyline = new ht.Polyline();//多邊形管線polyline.setTag(num);//設定節點tag標籤,作為唯一標示if(num === '68') polyline.setToolTip('AP M');//設定提示訊息else if(num === '60') polyline.setToolTip('G F'); else polyline.setToolTip('Line' + num); if(color) { polyline.s({//s 為setStyle 的簡寫,設定樣式'shape.border.width': 0.4,//設定多邊形的邊框寬度'shape.border.color': color,//設定多邊形的邊框顏色'select.width': 0.2,//設定選取節點的邊框寬度'select.color': color//設定選取節點的邊框顏色}); } let lineName = 'Line' + num; let line = window[lineName]; for(let i = 0; i < line.length; i++) { for(let j = 0; j < line[i].coords.length; j++) { polyline.addPoint({x: line[i].coords[j][0]*300, y: -line[i].coords[j][1]* 300}); if(num === '68'){//APM線(有兩條,但是點是在同一個陣列中的) if(i === 0 && j === 0) { polyline.setSegments([1]); } else if(i === 1 && j === 0) { polyline.getSegments().push(1); } else { polyline.getSegments().push (2); } } } } polyline.setLayer('0');//將線設定在下層,點設定在上層top dm.add(polyline);//將管線加入資料容器中儲存,不然這個管線屬於遊離狀態,是不會顯示在拓樸圖上的return polyline;}
上面程式碼中加入地鐵線上的點有分為幾種情況,是因為js 中設定線的時候Line68 有一個跳躍點的現象,所以我們必須跳躍過去,篇幅有限Line68 數組具體的聲明自行看subway.js 。
這裡說明一點,如果用的是addPoint 函數,不設定segments 時,預設會加進的點用直線連接,segments 的定義如下:
1: moveTo,佔用1 個點信息,代表一個新路徑的起點
2: lineTo,佔用1 個點信息,代表從上次最後點連接到該點
3: quadraticCurveTo,佔用2 個點信息,第一個點作為曲線控制點,第二個點作為曲線結束點
4: bezierCurveTo,佔用3 個點信息,第一和第二個點作為曲線控制點,第三個點作為曲線結束點
5: closePath,不佔用點信息,代表本次路徑繪製結束,並閉合到路徑的起始點
所以我們要做跳躍的行為設定segments 為1 即可。
最後繪製這些地鐵線上的點,這個部分subway.js 中也分離出來了,命名以mark_Point、t_Point以及n_Point開頭,我在前面js 的展示部分有對這些數組進行解釋,大家動動中指劃上去看看。
我們在這些點的位置加入ht.Node 節點,當節點一加入到dm 資料容器中時,就會在拓樸圖上顯示,當然,前提是這個拓樸圖元件gv 設定的資料容器是這個dm。篇幅有限,添加地鐵線上的點的代碼部分我只展示添加換乘站點的點:
var tName = 't_Point' + num;var tP = window[tName];//大站點if(tP) {//有些線路沒有換乘站點for(let i = 0; i < tP.length; i++) { let node = createNode(tP[i].name, tP[i].value, color[index]);//在取得的線路上的點的座標位置加入節點node.s({//設定節點的樣式style 'label.scale': 0.05,//文字縮放,可以避免瀏覽器限制的最小字號問題'label.font': 'bold 12px arial, sans-serif'//設定文字的font }); node.setSize(0.6, 0.6);//設定節點大小。由於js中每個點之間的偏移量太小,所以我必須把節點設定小一些node.setImage('images/旋轉箭頭.json');//設定節點的圖片node.a('alarmColor1 ', 'rgb(150, 150, 150)');//attr屬性,可以在這裡面設定任何的東西,alarmColor1是在上面設定的image的json中綁定的屬性,具體參看HT for Web 向量手冊(http://www.hightopo. com/guide/guide/core/vector/ht-vector-guide.html#ref_binding) node.a('alarmColor2', 'rgb(150, 150, 150)');//同上node.a('tpNode', true);//這個屬性設定只是為了用來區分換乘站點和小站點的,後面會用上}}
所有的地鐵線路以及站點都添加完畢。但是!你可能會看不見自己繪製的圖,因為他們太小了,這個時候可以設定graphView 拓樸元件上的fitContent 函數,我們順便將拓樸圖上的所有東西都不可移動也設定一下:
gv.fitContent(false, 0.00001);//自適應大小,參數1為是否動畫,參數2為gv與邊框的padding值gv.setMovableFunc(function(){ return false;//設定gv上的節點不可移動});
這下你的地鐵線路圖就可以顯示啦~接下來看看互動。
互動首先是滑鼠移動事件,滑鼠滑過具體線路時,線路會變粗,懸停一會兒還能看到這條線路的編號;當滑鼠移動到換乘站點或小站點,站點對應的圖示都會變大並且變色,字體也會變大,滑鼠移開圖示變回原來的顏色並且字體變小。不同點在於滑鼠移動到轉乘站點時,換乘站點會旋轉。
滑鼠滑動事件,我直接基於gv 的底層div 進行的mousemove 事件,透過ht 封裝的getDataAt 函數傳入事件event 參數,取得事件下對應的節點,然後就可以隨意操作節點了:
gv.getView().addEventListener('mousemove', function(e) { var data = gv.getDataAt(e);//傳入邏輯座標點或是交互event事件參數,傳回目前點下的圖元if(name ) { originNode(name);//不管何時都要讓節點保持原來的大小} if (data instanceof ht.Polyline) {//判斷事件節點的型別dm.sm().ss(data);//選取管道name = ''; clearInterval(interval); } else if (data instanceof ht.Node) { if(data.getTag( ) !== name && data.a('tpNode')) {//若不是同一個節點,且mousemove的事件物件為ht.Node類型,那麼設定節點的旋轉interval = setInterval(function() { data.setRotation(data.getRotation() - Math.PI/16); //在自身旋轉的基礎上再旋轉}, 100); } if(data.a('npNode')) {//如果滑鼠移到小站點也要停止動畫clearInterval(interval); } expandNode(data, name);////自訂的放大節點函數,比較容易,我不黏程式碼了,可以去http://hightopo.com/ 看dm.sm().ss(data);//設定選取節點name = data.getTag();//作為上一個節點的儲存變量,可以透過這個值來取得節點} else {//其他任何情況則不選取任何內容並且清除換乘網站上的動畫dm.sm( ).ss(null); name = ''; clearInterval(interval); }});
滑鼠懸停在地鐵線路上時顯示具體線路信息,我是通過設置tooltip 來完成的(注意:要打開gv 的tooltip 開關):
gv.enableToolTip();//開啟tooltip 的開關if(num === '68') polyline.setToolTip('AP M');//設定提示資訊else if(num === '60') polyline. setToolTip('G F'); else polyline.setToolTip('Line' + num);
然後我利用右下角的form 表單,點擊表單上的具體線路,或者雙擊拓撲圖上任意一個站點或線路,則拓撲圖會自適應到對應的部分,將被雙擊的部分展現到拓撲圖的中央。
form 表單的聲明部分我好像還沒解釋。 。 。就是透過new 一個ht.widget.FomePane 類別建立一個form 表單元件,透過form.getView() 取得表單元件的底層div,將這個div 擺放在body 右下角,然後透過addRow 函數向form 表單中新增一行的表單項,可以在這一行中新增任意多個項,透過addRow函數的第二個參數(一個數組),對添加進的表單項進行寬度的設置,透過第三個參數設定這行的高度:
function createForm() {//建立右下角的form表單var form = new ht.widget.FormPane(); form.setWidth(200);//設定表單寬度form.setHeight(416);//設定表單高度let view = form.getView(); document.body.appendChild(view);//將表單加入body中view.style.zIndex = 1000; view.style.bottom = '10px';//ht組件幾乎都設定絕對路徑view.style.right = '10px'; view.style.background = 'rgba(211, 211, 211, 0.8)' ; names.forEach(function(nameString) { form.addRow([//在表單中新增一個行{//這一行中的第一個表單項目button: {//在表單中新增button按鈕icon: 'images/Line'+nameString.value+'.json' ,//設定按鈕的圖示background: '',//設定按鈕的背景borderColor: '',//設定按鈕的邊框顏色clickable: false//設定按鈕不可點選} }, {//第二個表單項目button: { label: nameString.name, labelFont: 'bold 14px arial, sans-serif', labelColor: '#fff', background: '', borderColor: '', onClicked: function( ) {//按鈕點選回呼事件gv.sm().ss(dm.getDataByTag(nameString.value));//設定選取按下的按鈕對應的線路gv.fitData(gv.sm().ld(), true, 5);//將選取的地鐵線路顯示在拓樸圖的中央} } } ], [0.1, 0.2], 23);//第二個參數是設定第一參數中的陣列的寬度,小於1是比例,大於1是實際寬度。
點擊網站顯示紅色標註,雙擊節點自適應放置到拓撲圖中央以及雙擊空白處將紅色標註隱藏的內容都是透過對拓樸元件gv 的事件監聽來控制的,非常清晰易懂,程式碼如下:
var node = createRedLight();//建立一個新的節點,顯示為紅燈的樣式gv.mi(function(e) {//ht 中拓樸元件中的事件監聽if(e.kind === 'clickData ' && (e.data.a('tpNode') || e.data.a('npNode')))) {//e.kind取得目前事件類型,e.data取得目前事件下的節點node.s('2d.visible', true);//設定node節點可見node.setPosition(e.data.getPosition() .x, e.data.getPosition().y);//設定node的座標為目前事件下節點的位置} else if(e.kind === 'doubleClickData') {//雙擊節點gv.fitData(e.data, false, 10);//將事件下的節點自適應到拓樸圖的中央,參數1為自適應的節點,參數2為是否動畫,參數3為gv與邊框的padding } else if(e.kind === 'doubleClickBackground') {//雙擊空白處node.s('2d.visible', false);//設定node節點看不見去查看HT for Web 樣式手冊(http://www.hightopo.com/guide/guide/core/theme/ht-theme-guide.html#ref_style) }});
注意s(style) 和a(attr) 定義是這樣的,s 是ht 預先定義的一些樣式屬性,而a 是我們使用者來自定義的屬性,一般是透過呼叫字串來呼叫結果的,這個字串對應的可以是常數也可以是函數,還是很靈活的。
最後也做了一個小小的部分,選取站點,則該站點的上方會顯示一個紅色的會呼吸的用來註明目前選取的站點。
呼吸的部分是利用ht 的setAnimation 函數來完成的,在用這個函數之前要先打開資料容器的動畫開關,然後再設定動畫:
dm.enableAnimation();//開啟資料容器的動畫開關function createRedLight() { var node = new ht.Node(); node.setImage('images/紅燈.json');//設定節點的圖片node .setSize(1, 1);//設定節點的大小node.setLayer('firstTop');//設定節點顯示在gv的最上層node.s('2d.visible', false);//節點不可見node.s( 'select.width', 0);//節點選取時的邊框為0,不可見node.s('2d.selectable', false);//設定這個屬性,則節點不可選取node.setAnimation({//設定動畫具體參考HT for Web 動畫手冊(http://www.hightopo.com/guide/guide/plugin/animation/ht- animation-guide.html) expandWidth: { property: width,//設定這個屬性,且未設定accessType,則預設透過setWidth/getWidth來設定和取得屬性。 /字串類型,指定目前動畫完成之後,要執行的下個動畫,可將多個動畫融合}, collapseWidth: { property: width, from: 1, to: 0.5, next: expandWidth }, expandHeight: { property: height, from: 0.5, to: 1, next: collapseHeight }, collapseHeight: { property: height, from: 1, to: 0.5, nextseHeight: { property: height, from: 1, to: 0.5, next: expandHeight }, start: expandWidth, expandHeight]//數組,用於指定要啟動的一個或多個動畫}); dm.add(node); return node;}
全部代碼結束!
總結這個Demo 花了我兩天時間完成,總覺得有點不甘心啊,但是有時候思維又轉不過彎來,花費了不少的時間,但是總的來說收穫還是很多的,我以前一直以為只要通過getPoints().push 來向多邊形中添加點就可以了,求助了大神之後,發現原來這個方法不僅繞彎路而且還會出現各種各樣的問題,比如getPoints 之前,一定要在多邊形中已經有points才可以,但是在很多情況下,初始化的points 並不好設置,而且會造成代碼很繁瑣,直接通過addPoint 方法,直接將點添加進多邊形變量中,並且還會默認將點通過直線的方式連接,也不用設定segments,多可愛的一個函數。
還有因為ht 預設縮放小是20,而我這個Demo 的間距又很小,導致縮放到最大地鐵線路圖顯示也很小,所以我在htconfig 中更改了ht 的預設zoomMax 屬性,記住,更改這個值一定要在所有的ht 呼叫之前,因為在htconfig 中設定的值在後面定義都是不可更改的。
以上所述是小編給大家介紹的基於HTML5 Canvas實現的互動式地鐵線圖,希望對大家有幫助,如果大家有任何疑問請給我留言,小編會及時回覆大家的。在此也非常感謝大家對VeVb武林網站的支持!