HTML5 Canvas的交互式地铁线路图实现代码
地图轻微内容有点多,要全部展现,字显得有点小了,但是不妨,可以依照需求放大缩小,字体和绘制的内容并不会失真,究竟都是用矢量绘制的~
界面生成
底层的 p 是通过 ht.graph.GraphView 组件生成的,然后就可以利用 HT for Web 供给好的办法,调取 canvas 画笔随意绘制就好,先来看看如何生成底层 p:
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); //将组件底层p增加到body中 style.left = '0';//默许组件是绝对定位,所以要设定位置 style.right = '0'; style.top = '0'; style.bottom = '0'; window.addEventListener('resize', function () { self.iv(); }, false); //窗口转变事件 }
此刻我就可以在这个 p 上乱涂乱画了~第一我猎取下载好的地铁线路图上的点,我将它们放在 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: [113.41548,23.10547]}); 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('A P 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 的底层 p 停止的 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('A P M');//设定提醒信息 else if(num === '60') polyline.setToolTip('G F'); else polyline.setToolTip('Line' + num);
然后我利用右下角的 form 表单,单击表单上的详细线路,或者双击拓扑图上任意一个“站点”或者线路,则拓扑图会自顺应到对应的部分,将被双击的部分展示到拓扑图的中央。
form 表单的声明部分我仿佛还没有说明。。。就是通过 new 一个 ht.widget.FomePane 类创立一个 form 表单组件,通过 form.getView() 猎取表单组件的底层 p,将这个 p 摆放在 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 来设定和猎取属性。这里的 width 和下面的 height 都是通过前面设定的 size 得到的 from: 0.5, //动画开端时的属性值 to: 1,//动画完毕时的属性值 next: "collapseWidth"//字符串类型,指定当前动画完成之后,要施行的下个动画,可将多个动画融合 }, 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, 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的交互式地铁线路图实现代码的具体内容,更多请关注百分百源码网其它相关文章!