렌더링 비교
아직 온라인에 지하철 노선도가 많아서 참고용으로 비교적 최근에 나온 지하철 노선도를 선택했습니다. 상하이에 열심히 일하러 왔을 때 사진에 빨간색과 녹색 십자가가 있었던 기억이 나네요... 나이는 말할 것도 없고 사진만 보시면 됩니다:
동생이 한 일을 살펴보겠습니다.
얼핏 보면 차이점을 알 수 없지만 이것은 렌더링이 아닙니다. , 초보자가 단 며칠 만에 만든 것인데, 많은 미화와 조정이 프로그램을 통해 자동으로 이루어지기 때문에 쉽지 않습니다. 더 중요한 것은 데드 맵이 아니라 순수한 벡터, 대화형, 동적 효과 및 왜곡 없는 스케일링 토폴로지 맵이라는 것입니다! 먼저 상호작용 효과에 대해 간략하게 살펴보고 코드 구현에 대해 자세히 이야기하겠습니다.
텍스트 프롬프트가 뜹니다
우선 사이트, 도로 구간, 아이콘 등에 마우스를 갖다 대면 텍스트 프롬프트가 뜹니다. 바이두는 비교적 기본적인 내용입니다. 비교적 간단한 팝업 콘텐츠를 담았습니다. 기본적인 소개나 관련팁, 주변정보를 추가하면... 광고만 추가하면 돈도 벌 수 있고... 아무튼 무엇이든 추가할 수 있는데, 그냥 setToolTip 명령어 뿐입니다.
사이트 아이콘이 변경됩니다
사이트 위에 마우스를 올리면 사이트 아이콘이 확대되는 효과가 아주 배려심이 깊네요. 홈에서는 발광 효과를 사용합니다.
구현 방법도 매우 간단합니다. 즉, 사이트 벡터 그래픽 등록 시 동적 판단이 추가됩니다. 다음 코드는 일반 사이트의 벡터 그래픽을 등록하는 데 사용됩니다.
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; } });
애니메이션 효과 드래그
위 그림에서 볼 수 있듯이 환승역 아이콘에 색상을 추가하는 것 외에도 , 회전 효과를 얻었습니다. 이것은 즉시 Baidu를 능가합니다. 코드를 살펴보겠습니다.
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에서도 매우 쉽습니다. 회전 속성을 동적으로 변경하기만 하면 됩니다.
그리고 사이트 클릭 후 더블클릭 시 선택 및 로딩 애니메이션 효과도 구현되어 칭찬할 만합니다!
눈부신 하이브리드 스케일링
왜곡 없는 스케일링은 벡터 그래픽의 타고난 장점이기도 합니다. 저도 이를 잘 익히고 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} };
介绍到这里就结束了,虽然是个小例子,实在是但美观性和实用性都还过得去,小弟花了心思去做,其实稍加改造就可以做出高铁图、公交图、运行图等应用。设想一下,如果能用在轨道交通列控中心大屏监控里,是多么炫酷。