HTML5,不只是看上去很美(第四彈:可交互地鐵線路圖)
前言
最近特別忙,承蒙大伙關(guān)照,3D機(jī)房的項(xiàng)目一個(gè)接著一個(gè),領(lǐng)了一幫小弟,搞搞傳幫帶,烏飛兔走,轉(zhuǎn)眼已經(jīng)菊黃蟹肥……有個(gè)小弟很不錯(cuò),勤奮好學(xué),很快就把API都摸透了,國(guó)慶幾天自己折騰著做了個(gè)HTML5的魔都的地鐵線路圖,能拖能拽的,還和電子地圖做了交互。哥決定把小弟的成果納入“HTML5,不只是看上去很美”系列,以示鼓勵(lì)(P.S. 其實(shí)還挺有壓力的,后浪推前浪,新人趕舊人。我們這些老鳥也得注意,免得讓00后給搶了飯碗)
效果圖對(duì)比
網(wǎng)上的地鐵圖還是很多的,小弟選了這張比較新的做參考。想當(dāng)年哥來魔都打拼時(shí),圖上可就一紅一綠打個(gè)叉……暴露年齡的話不多說,看圖:
再來看看小弟做的:
我是一眼看不出區(qū)別,但這可不是一張效果圖,而是一個(gè)新手僅用了幾天做出來的東西,而且里面許多美化和調(diào)整是通過程序自動(dòng)完成的,這就不容易了。更重要的是,它并不是一張死圖,而是純矢量、可交互、有動(dòng)態(tài)效果、無失真縮放的拓?fù)鋱D!我們先簡(jiǎn)單看一下交互效果,后面可以詳細(xì)說說代碼的實(shí)現(xiàn)。
文本提示彈彈彈
首先,把鼠標(biāo)移到站點(diǎn)、路段、圖標(biāo)等位置,都會(huì)有文本提示彈出,這個(gè)比較基本,百度家的就有,小弟也就放了比較簡(jiǎn)單的彈出內(nèi)容。如果加上基本介紹啊、相關(guān)提示啊、周邊信息啊……要是加上廣告,就可以賺錢了……反正什么都可以加嘛,就是一個(gè)setToolTip命令而已。
站點(diǎn)圖標(biāo)變變變
當(dāng)鼠標(biāo)移到站點(diǎn)上時(shí),站點(diǎn)圖標(biāo)做了放大效果,這個(gè)效果很貼心,看了下百度家,用的是發(fā)光效果。
實(shí)現(xiàn)的方法也很簡(jiǎn)便,就是在注冊(cè)站點(diǎn)矢量圖形時(shí),加入了動(dòng)態(tài)判斷。以下注冊(cè)普通站點(diǎn)矢量圖形的代碼:
- 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;
- }
- });
動(dòng)畫效果拽拽拽
從上圖還可以看到,在換乘站圖標(biāo)中,除了增加了顏色,還實(shí)現(xiàn)了旋轉(zhuǎn)效果。這個(gè)就秒殺百度家了。來看代碼:
- twaver.Util.registerImage('rotateArrow', {
- w: 124,
- h: 124,
- v: [{
- shape: 'vector',
- name: 'doubleArrow',
- rotate: 360,
- animate: [{
- attr: 'rotate',
- to: 0,
- dur: 2000,
- reverse: false,
- repeat: Number.POSITIVE_INFINITY
- }]
- }]
- });
當(dāng)然這對(duì)于TWaver來說也很容易,只不過對(duì)rotate屬性進(jìn)行了動(dòng)態(tài)改變而已。
另外,在單擊和雙擊站點(diǎn)時(shí),還實(shí)現(xiàn)了selected和loading的動(dòng)畫效果,值得點(diǎn)贊!
混合縮放炫炫炫
無失真縮放是矢量圖的先天優(yōu)勢(shì),小弟也掌握得爐火純青,把TWaver的混合縮放模式用到極致,還有縮放比例控制、文字自動(dòng)隱藏等小功能,方便訂制。
代碼也不復(fù)雜:
- network.setZoomManager(new twaver.vector.MixedZoomManager(network));
- network.setMinZoom(0.2);
- network.setMaxZoom(3);
- network.setZoomVisibilityThresholds({
- label : 0.6,
- });
交互功能用起來
小弟很自豪地給我介紹這個(gè)功能:圖標(biāo)可以自由拖動(dòng),松開后會(huì)自動(dòng)彈回。哥問小弟這有什么用,他一本正經(jīng)地說:證明圖是活的!
好吧你贏了,雖然是個(gè)沒什么卵用的功能,但閑的蛋疼的時(shí)候可以隨便玩上幾十分鐘我也是信的。
連續(xù)單擊同一站點(diǎn)
連續(xù)單擊同一站點(diǎn)(注意不是雙擊),可以將經(jīng)過此站點(diǎn)的所有線路突出顯示出來。小弟說加入這個(gè)功能純粹因?yàn)楹?jiǎn)單易做,我……竟然表示非常理解,誰年輕時(shí)沒耍過這類輕松又討好的小招數(shù)呢?
雙擊站點(diǎn)
雙擊站點(diǎn),竟然彈出了本站周邊的電子地圖!知道引入他山之玉,看來小子可教啊。我發(fā)現(xiàn)他的定位方法,有的是用經(jīng)緯度,有的是關(guān)鍵詞查詢。小弟狡黠地說,開始是人工查每個(gè)站點(diǎn)經(jīng)緯度的,干了一段兒發(fā)現(xiàn)太麻煩,后來改路子了。馬大大說的,懶人改變世界,我服!
最后來八一八程序設(shè)計(jì)的思路吧,小弟是棵好苗子,能做出那么像樣的程序,必然是深思熟慮過的。不想再聽我啰嗦的朋友,也可以直接發(fā)郵件給我,tw-service@servasoft.com,來鑒賞下小弟的成果。
數(shù)據(jù)文件的整理
數(shù)據(jù)格式,選擇了JavaScript原生支持的json文件,直觀方便。數(shù)據(jù)結(jié)構(gòu),按照站點(diǎn)、線路、雜項(xiàng)三大塊來組織,結(jié)構(gòu)清晰,利于遍歷、查詢等操作。
- {
- "stations":{
- "l01s01":{ },
- …………
- }
- "lines":{
- "l01":{……},
- …………
- }
- "sundrys":{
- "railwaystationshanghai":{……},
- …………
- }
- }
命名比較規(guī)范,通過名字就可以看出基本信息(例如“l01s01”就是1號(hào)線第1個(gè)站點(diǎn)),甚至直接利用名字就可以進(jìn)行查詢和遍歷。
- "l01s01":{
- "id":"l01s01",
- "name":"莘莊",
- "loc":{"x":419,"y":1330},
- "label":"bottomright.bottomright",
- },
- …………
站點(diǎn)路線的創(chuàng)建
首先是讀取json文件的數(shù)據(jù)。
- function loadJSON(path,callback){
- var xhr = new XMLHttpRequest();
- xhr.onreadystatechange = function(){
- if (xhr.readyState === 4) {
- if (xhr.status === 200) {
- dataJson = JSON.parse(xhr.responseText);
- callback && callback();
- }
- }
- };
- xhr.open("GET", path, true);
- xhr.send();
- }
因?yàn)樽x取文件是一個(gè)異步的過程,所以要程序的展開都要放在文件讀取函數(shù)的內(nèi)部。
- function init(){
- loadJSON("shanghaiMetro.json", function(){
- initNetwork(dataJson);
- initNode(dataJson);
- });
- }
只要通過對(duì)站點(diǎn)進(jìn)行一次遍歷,車站的建立就完成了。
- for(staId in json.stations){
- var station = json.stations[staId];
- staNode = new twaver.Node({
- id: staId,
- name: station.name,
- image:'station',
- });
- staNode.s('label.color','rgba(99,99,99,1)');
- staNode.s('label.font','12px 微軟雅黑');
- staNode.s('label.position',station.label);
- staNode.setClient('location',station.loc);
- box.add(staNode);
- }
再對(duì)數(shù)據(jù)文件中的各條線路下的所有站點(diǎn)進(jìn)行遍歷,在站點(diǎn)間依次創(chuàng)建Link。
- for(lineId in json.lines) {
- ……
- for(staSn in line.stations) {
- ……
- var link = new twaver.Link(linkId,prevSta,staNode);
- link.s('link.color', line.color);
- link.s('link.width', linkWidth);
- link.setToolTip(line.name);
- box.add(link);
- }
- }
再對(duì)label位置進(jìn)行調(diào)整,否則站點(diǎn)名稱會(huì)顯示的很亂。小弟是通過在原始數(shù)據(jù)中手動(dòng)加入位置信息來實(shí)現(xiàn)的,稍顯笨了一點(diǎn),應(yīng)該可以通過程序自動(dòng)判斷站點(diǎn)周圍空間來進(jìn)行智能調(diào)整。
最后再加入圖標(biāo),一張?jiān)嫉牡罔F圖就呈現(xiàn)出來了。
路線拐點(diǎn)的添加
基本的示意功能已經(jīng)具備了,這里,小弟讓我很欣賞的一點(diǎn)是沒有就此停止,而是進(jìn)一步做了調(diào)整,使線路只保留了橫平豎直和正斜的走向,以達(dá)到整齊美觀的效果。可能看起來與參考圖稍稍有些不同,主要因?yàn)楦髀范位局惶砑恿艘粋€(gè)拐點(diǎn),這樣做既大大簡(jiǎn)化了程序,又基本保證了圖形的美觀度。想遠(yuǎn)一點(diǎn),做多一點(diǎn),是塊做產(chǎn)品的好料子。
當(dāng)然為了提高程序的靈活性,應(yīng)對(duì)必須添加兩個(gè)或以上拐點(diǎn)的情況,也使用了人工拐點(diǎn)的手段。不過這里人工拐點(diǎn)被設(shè)成一個(gè)隱形的節(jié)點(diǎn),可能利于智能拐點(diǎn)的判斷,但也有可能在路線操作時(shí)造成混亂。如何處理更好還可以進(jìn)一步推敲。
- 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;
- }
接點(diǎn)位置的調(diào)整
大家可以看到,并不是所有路段都直接連入站點(diǎn)中心,在許多情況下必須要進(jìn)行偏移。
- 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;
- }
小弟采取了虛擬節(jié)點(diǎn)的辦法,就是在站點(diǎn)的旁邊,添加一個(gè)Follower(但并不顯示出來),讓并行的不同線路連接到不同的Follower上。通過調(diào)整Follower的位置,來實(shí)現(xiàn)線路與站點(diǎn)連接點(diǎn)的控制。
- 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}
- };
介紹到這里就結(jié)束了,雖然是個(gè)小例子,實(shí)在是但美觀性和實(shí)用性都還過得去,小弟花了心思去做,其實(shí)稍加改造就可以做出高鐵圖、公交圖、運(yùn)行圖等應(yīng)用。設(shè)想一下,如果能用在軌道交通列控中心大屏監(jiān)控里,是多么炫酷。說到這,又想起了前兩天云棲大會(huì)上剛看到的杭州城市數(shù)據(jù)大腦,不知何時(shí),哥也能參與一把那樣的項(xiàng)目呢?可視化,哥的強(qiáng)項(xiàng)……
最后,想要看程序,或者想玩“地鐵拖拖樂”的各位,都可以給我留言和發(fā)郵件:tw-service@servasoft.com。





































