前言html
最近特別忙,承蒙大夥關照,3D機房的項目一個接着一個,領了一幫小弟,搞搞傳幫帶,烏飛兔走,轉眼已經菊黃蟹肥……有個小弟很不錯,勤奮好學,很快就把API都摸透了,本身折騰着作了個HTML5的魔都的地鐵線路圖,能拖能拽的,還和電子地圖作了交互。哥決定把小弟的成果歸入「HTML5大數據可視化效果」系列,以示鼓勵(P.S. 其實還挺有壓力的,後浪推前浪,新人趕舊人。咱們這些老鳥也得注意,省得讓00後給搶了飯碗)json
效果圖對比數據結構
網上的地鐵圖仍是不少的,小弟選了這張比較新的作參考。想當年哥來魔都打拼時,圖上可就一紅一綠打個叉……暴露年齡的話很少說,看圖:異步
再來看看小弟作的:函數
我是一眼看不出區別,但這可不是一張效果圖,而是一個新手僅用了幾天作出來的東西,並且裏面許多美化和調整是經過程序自動完成的,這就不容易了。更重要的是,它並非一張死圖,而是純矢量、可交互、有動態效果、無失真縮放的拓撲圖!咱們先簡單看一下交互效果,後面能夠詳細說說代碼的實現。大數據
文本提示彈彈彈動畫
首先,把鼠標移到站點、路段、圖標等位置,都會有文本提示彈出,這個比較基本,百度家的就有,小弟也就放了比較簡單的彈出內容。若是加上基本介紹啊、相關提示啊、周邊信息啊……要是加上廣告,就能夠賺錢了……反正什麼均可以加嘛,就是一個setToolTip命令而已。spa
站點圖標變變變設計
當鼠標移到站點上時,站點圖標作了放大效果,這個效果很貼心,看了下百度家,用的是發光效果。3d
實現的方法也很簡便,就是在註冊站點矢量圖形時,加入了動態判斷。如下注冊普通站點矢量圖形的代碼:
twaver.Util.registerImage('station',{ w: linkWidth*1.6, h: linkWidth*1.6, v: function (data, view) { var result = []; if(data.getClient('focus')){ result.push({ shape: 'circle', r: linkWidth*0.7, lineColor: data.getClient('lineColor'), lineWidth: linkWidth*0.2, fill: 'white', }); result.push({ shape: 'circle', r: linkWidth*0.2, fill: data.getClient('lineColor'), }); }else{ result.push({ shape: 'circle', r: linkWidth*0.6, lineColor: data.getClient('lineColor'), lineWidth: linkWidth*0.2, fill: 'white', }); } return result; } });
動畫效果拽拽拽
從上圖還能夠看到,在換乘站圖標中,除了增長了顏色,還實現了旋轉效果。這個就秒殺百度家了。 來看代碼:
1. twaver.Util.registerImage('rotateArrow', { 2. w: 124, 3. h: 124, 4. v: [{ 5. shape: 'vector', 6. name: 'doubleArrow', 7. rotate: 360, 8. animate: [{ 9. attr: 'rotate', 10. to: 0, 11. dur: 2000, 12. reverse: false, 13. repeat: Number.POSITIVE_INFINITY 14. }] 15. }] 16. });
固然這對於TWaver來講也很容易,只不過對rotate屬性進行了動態改變而已。 另外,在單擊和雙擊站點時,還實現了selected和loading的動畫效果,值得點贊!
混合縮放炫炫炫
無失真縮放是矢量圖的先天優點,小弟也掌握得爐火純青,把TWaver的混合縮放模式用到極致,還有縮放比例控制、文字自動隱藏等小功能,方便訂製。
代碼也不復雜:
1. network.setZoomManager(new twaver.vector.MixedZoomManager(network)); 2. network.setMinZoom(0.2); 3. network.setMaxZoom(3); 4. network.setZoomVisibilityThresholds({ 5. label : 0.6, 6. });
交互功能用起來
小弟很自豪地給我介紹這個功能:圖標能夠自由拖動,鬆開後會自動彈回。哥問小弟這有什麼用,他一本正經地說:證實圖是活的!
好吧你贏了,雖然是個沒什麼卵用的功能,但閒的蛋疼的時候能夠隨便玩上幾十分鐘我也是信的。
連續單擊同一站點
連續單擊同一站點(注意不是雙擊),能夠將通過此站點的全部線路突出顯示出來。小弟說加入這個功能純粹由於簡單易作,我……居然表示很是理解,誰年輕時沒耍過這類輕鬆又討好的小招數呢?
雙擊站點
雙擊站點,居然彈出了本站周邊的電子地圖!知道引入他山之玉,看來小子可教啊。我發現他的定位方法,有的是用經緯度,有的是關鍵詞查詢。小弟狡黠地說,開始是人工查每一個站點經緯度的,幹了一段兒發現太麻煩,後來改路子了。馬大大說的,懶人改變世界,我服!
最後來八一八程序設計的思路吧,小弟是棵好苗子,能作出那麼像樣的程序,必然是深思熟慮過的。不想再聽我囉嗦的朋友,也能夠直接發郵件給我,tw-service@servasoft.com,來鑑賞下小弟的成果。
數據文件的整理
數據格式,選擇了JavaScript原生支持的json文件,直觀方便。 數據結構,按照站點、線路、雜項三大塊來組織,結構清晰,利於遍歷、查詢等操做。
1. { 2. "stations":{ 3. "l01s01":{ }, 4. ………… 5. } 6. "lines":{ 7. "l01":{……}, 8. ………… 9. } 10. "sundrys":{ 11. "railwaystationshanghai":{……}, 12. ………… 13. } 14. }
命名比較規範,經過名字就能夠看出基本信息(例如「l01s01」就是1號線第1個站點),甚至直接利用名字就能夠進行查詢和遍歷。
1. "l01s01":{ 2. "id":"l01s01", 3. "name":"莘莊", 4. "loc":{"x":419,"y":1330}, 5. "label":"bottomright.bottomright", 6. }, 7. …………
站點路線的建立
首先是讀取json文件的數據。
1. function loadJSON(path,callback){ 2. var xhr = new XMLHttpRequest(); 3. xhr.onreadystatechange = function(){ 4. if (xhr.readyState === 4) { 5. if (xhr.status === 200) { 6. dataJson = JSON.parse(xhr.responseText); 7. callback && callback(); 8. } 9. } 10. }; 11. xhr.open("GET", path, true); 12. xhr.send(); 13. }
由於讀取文件是一個異步的過程,因此要程序的展開都要放在文件讀取函數的內部。
1. function init(){ 2. loadJSON("shanghaiMetro.json", function(){ 3. initNetwork(dataJson); 4. initNode(dataJson); 5. }); 6. }
只要經過對站點進行一次遍歷,車站的創建就完成了。
1. for(staId in json.stations){ 2. var station = json.stations[staId]; 3. staNode = new twaver.Node({ 4. id: staId, 5. name: station.name, 6. image:'station', 7. }); 8. staNode.s('label.color','rgba(99,99,99,1)'); 9. staNode.s('label.font','12px 微軟雅黑'); 10. staNode.s('label.position',station.label); 11. staNode.setClient('location',station.loc); 12. box.add(staNode); 13. }
再對數據文件中的各條線路下的全部站點進行遍歷,在站點間依次建立Link。
1. for(lineId in json.lines) { 2. …… 3. for(staSn in line.stations) { 4. …… 5. var link = new twaver.Link(linkId,prevSta,staNode); 6. link.s('link.color', line.color); 7. link.s('link.width', linkWidth); 8. link.setToolTip(line.name); 9. box.add(link); 10. } 11. }
再對label位置進行調整,不然站點名稱會顯示的很亂。小弟是經過在原始數據中手動加入位置信息來實現的,稍顯笨了一點,應該能夠經過程序自動判斷站點周圍空間來進行智能調整。 最後再加入圖標,一張原始的地鐵圖就呈現出來了。
路線拐點的添加
基本的示意功能已經具有了,這裏,小弟讓我很欣賞的一點是沒有就此中止,而是進一步作了調整,使線路只保留了橫平豎直和正斜的走向,以達到整齊美觀的效果。可能看起來與參考圖稍稍有些不一樣,主要由於各路段基本只添加了一個拐點,這樣作既大大簡化了程序,又基本保證了圖形的美觀度。想遠一點,作多一點,是塊作產品的好料子。
固然爲了提升程序的靈活性,應對必須添加兩個或以上拐點的狀況,也使用了人工拐點的手段。不過這裏人工拐點被設成一個隱形的節點,可能利於智能拐點的判斷,但也有可能在路線操做時形成混亂。如何處理更好還能夠進一步推敲。
var createTurnSta = function(line, staSn){ staTurn = new twaver.Node(staSn); staTurn.setImage(); staTurn.setClient('lineColor',line.color); staTurn.setClient('lines',[line.id]); var loc = line.stations[staSn]; staTurn.setClient('location',loc); box.add(staTurn); return staTurn; }
接點位置的調整
你們能夠看到,並非全部路段都直接連入站點中心,在許多狀況下必需要進行偏移。
var createFollowSta = function(json, line, staNode, staId){ staFollow = new twaver.Follower(staId); staFollow.setImage(); staFollow.setClient('lineColor',line.color); staFollow.setClient('lines',[line.id]); staFollow.setHost(staNode); var az = azimuth[staId.substr(6,2)]; var loc0 = json.stations[staId.substr(0,6)].loc; var loc = {x:loc0.x+az.x, y:loc0.y+az.y}; staFollow.setClient('location',loc); box.add(staFollow); return staFollow; }
小弟採起了虛擬節點的辦法,就是在站點的旁邊,添加一個Follower(但並不顯示出來),讓並行的不一樣線路鏈接到不一樣的Follower上。經過調整Follower的位置,來實現線路與站點鏈接點的控制。
var azimuth = { bb: {x: 0, y: linkWidth*zoom/2}, tt: {x: 0, y: -linkWidth*zoom/2}, rr: {x: linkWidth*zoom/2, y: 0}, ll: {x: -linkWidth/2, y: 0}, br: {x: linkWidth*zoom*0.7/2, y: linkWidth*zoom*0.7/2}, bl: {x: -linkWidth*zoom*0.7/2, y: linkWidth*zoom*0.7/2}, tr: {x: linkWidth*zoom*0.7/2, y: -linkWidth*zoom*0.7/2}, tl: {x: -linkWidth*zoom*0.7/2, y: -linkWidth*zoom*0.7/2}, BB: {x: 0, y: linkWidth*zoom}, TT: {x: 0, y: -linkWidth*zoom}, RR: {x: linkWidth*zoom, y: 0}, LL: {x: -linkWidth, y: 0}, BR: {x: linkWidth*zoom*0.7, y: linkWidth*zoom*0.7}, BL: {x: -linkWidth*zoom*0.7, y: linkWidth*zoom*0.7}, TR: {x: linkWidth*zoom*0.7, y: -linkWidth*zoom*0.7}, TL: {x: -linkWidth*zoom*0.7, y: -linkWidth*zoom*0.7} };
介紹到這裏就結束了,雖然是個小例子,實在是但美觀性和實用性都還過得去,小弟花了心思去作,其實稍加改造就能夠作出高鐵圖、公交圖、運行圖等應用。設想一下,若是能用在軌道交通列控中心大屏監控裏,是多麼炫酷。說到這,又想起了前段時間雲棲大會上剛看到的杭州城市數據大腦,不知什麼時候,哥也能參與一把那樣的項目呢?可視化,哥的強項…… 最後,想要看程序,或者想玩「地鐵拖拖樂」的各位,均可以給我留言和發郵件:tw-service@servasoft.com。