小程序之基于canvas绘制高铁线路图

来源:互联网 发布:易玩网络 编辑:程序博客网 时间:2024/04/28 18:30

前几天@天下雪 给了我一张高铁的路线图,问我能不能用canvas画出来,所以我就试了试,我的思路可能比较复杂;如果有更简单的思路可以留言回复;

关注微信公众号,获取源码和教程

 

下面说一下我的实现思路: 
1、首先是每个站点圆角矩形的绘制,一开始想着用canvas把圆角矩形绘制出来,但发现小程序暂时还没有绘制圆角的arcTo方法,所以用canvas绘制就相对比较麻烦,最后为了方便决定用图片代替; 
2、将整个路线图分为四个小图片,(1)站点圆角矩形(2)站点之间的直连线(3)站点之间右侧弯曲连线(4)站点之间左侧弯曲连线; 
3、通过观察分析,将绘制过程分为两步,(1)奇数行圆角矩形、连线的绘制点x坐标是从左至右递增,y坐标值是行数乘以某个固定值(2)偶数行圆角矩形、连线的绘制点x坐标是从左至右递减,y坐标值是行数乘以某个固定值 
4、奇数行,偶数行的圆角矩形的下标index+1是3的倍数的话,奇数行当前下标右侧绘制右弯曲连线图片,偶数行当前下标左侧绘制左弯曲连线图片; 
5、整个canvas绘制区域在不同手机上的适配 
6、具体的一些细节请参照代码注释 
7、开发工具上使用drawImage重复绘制同一张图片只显示第一次绘制的位置,暂时不知道什么原因,所以请在真机上测试; 
8、有什么不足之处,望大家多多指点!感激! 

wxml代码:

  1. <!--pages/Gline/index.wxml-->
  2. <viewclass="g-title">(G23)选择出发站点<textclass="chooseStation">{{chooseStation}}</text></view>
  3. <canvasbindtouchstart="touchS"canvas-id="map"style='width:{{canvWidth}}rpx;height:{{canvHeight}}px;background-color:#eee'/>

wxss代码:

  1. /* pages/Gline/index.wxss */
  2. page{ background-color: #eeeeee }
  3. .g-title{font-size: 36rpx;font-weight: 600;color: #768da4;padding: 36rpx 0;padding-left: 20rpx; background-color: #fff}
  4. .chooseStation{color: #32b16c}

js代码:

[javascript] view plain copy
  1. js代码:  
  2.   
  3. // pages/Gline/index.js  
  4. Page({  
  5.   data:{  
  6.     canvWidth:750,  
  7.     canvHeight:750,  
  8.     stations:['北京南','天津南','济南西','泰安','滕州东','徐州东','南京南','镇江南','苏州北','上海虹桥','北京南','天津南','济南西','泰安','滕州东','徐州东','南京南','镇江南','苏州北','上海虹桥','北京南','天津南','济南西','泰安','滕州东','徐州东','南京南','镇江南','苏州北','上海虹桥'],  
  9.     chooseStation:'',//页面显示选中的车站名字  
  10.     prevChooseIdx:null,//上一次选中车站的下标  
  11.     // stations:['北京南','天津南','济南西','泰安'],  
  12.   },  
  13.   onLoad:function(options){  
  14.     // 页面初始化 options为页面跳转所带来的参数  
  15.     // this.setData({canvHeight:502});  
  16.     const ctx = wx.createCanvasContext('map');//路线图绘制的画布上下文对象  
  17.     this.ctx = ctx;//将ctx对象绑定到当前页面中  
  18.     this.column = 3;//每行显示车站数量  
  19.     this.offsetTop = 30;//绘制起始坐标的top值,也就是距离canvas顶部的距离  
  20.     this.rect={//圆角矩形对象  
  21.       img_b:'/images/rect-b.png',//初始时图片  
  22.       img_g:'/images/rect-g.png',//选中时图片  
  23.       height:32,  
  24.       width:68  
  25.     }  
  26.     this.line = {//站与站之间的连线对象  
  27.       img:'/images/line.png',  
  28.       height:6,  
  29.       width:30  
  30.     },  
  31.     this.bendLine = {//站与站之间弯曲的连线  
  32.       img_l:'/images/line_l.png',//左侧连线  
  33.       img_r:'/images/line_r.png',//右侧连线  
  34.       height:70,  
  35.       width:20  
  36.     },  
  37.     this.rectArr=[];//记录所有车站的绘制起始点的坐标的数组  
  38.     this.oddRowIndexArr=[];//记录奇数行的车站的下标数组,如[0,1,2,6,.....]  
  39.     this.evenRowIndexArr=[];//记录偶数行的车站的下标数组,如[3,4,5,9,.....]  
  40.     this.initMap();  
  41.   },  
  42.   onReady:function(){  
  43.   },  
  44.   onShow:function(){  
  45.     // 页面显示  
  46.   },  
  47.   onHide:function(){  
  48.     // 页面隐藏  
  49.   },  
  50.   onUnload:function(){  
  51.     // 页面关闭  
  52.   },  
  53.   //对不同设备下图片大小的适配  
  54.   adaptiveScreenSize:function(o){  
  55.     let ww = this.data.winWidth;  
  56.     let zoom = ww/375;//375这里是按iPhone6的宽度做等比缩放  
  57.     this.setData({zoom:zoom});  
  58.     let rectW = o.width*zoom;  
  59.     let rectH = o.height*zoom;  
  60.     o.width = rectW;  
  61.     o.height = rectH;  
  62.   },  
  63.   //初始化路线图的方法  
  64.   initMap:function(){  
  65.     const that = this;  
  66.     wx.getSystemInfo({  
  67.       success: function(res){  
  68.         const ww = res.windowWidth;  
  69.         const pr = res.pixelRatio;  
  70.         that.setData({ winWidth:ww,pixelRatio:pr});//将设备的信息存入data中,供后面使用  
  71.         that.drawMap();  
  72.       }  
  73.     })  
  74.   },  
  75.   drawTxtAtPos:function(idx){  
  76.     const rectArr = this.rectArr;  
  77.     const w = this.rect.width;  
  78.     const h = this.rect.height;  
  79.     let txt = this.data.stations[idx];  
  80.     let len = txt.length;  
  81.     //当站点文本文字超过3个字,将缩小字号  
  82.     let fontSize = len>3?12:14;  
  83.     let x = rectArr[idx].x;  
  84.     let y = rectArr[idx].y;  
  85.           //计算文本在圆角矩形中的绘制点,使文字居中显示  
  86.     let txt_x = Math.floor((w - len*fontSize)/2)+x;  
  87.     let txt_y = Math.floor(h/2+fontSize/2)+y-2;//这里额外-2,文本才能更接近垂直居中  
  88.     this.ctx.setFontSize(fontSize);  
  89.     this.ctx.setFillStyle('#ffffff')  
  90.     this.ctx.fillText(txt, txt_x, txt_y);  
  91.   },  
  92.   //在下标为idx处绘制圆角矩形  
  93.   initRect:function(idx){  
  94.     const rectArr = this.rectArr;  
  95.     let x = rectArr[idx].x;  
  96.     let y = rectArr[idx].y;  
  97.     this.ctx.drawImage(this.rect.img_b,x, y, this.rect.width, this.rect.height);  
  98.   },  
  99.   //动态计算不同屏幕大小canvas的高度  
  100.   initCanvHeight:function(){  
  101.     let len = this.data.stations.length;  
  102.     let pr = this.data.pixelRatio;  
  103.     let z = this.data.zoom;  
  104.     let row = Math.ceil(len/this.column);  
  105.     let h = 0;  
  106.     if(row <= 1){  
  107.       console.log(this.rect.height);  
  108.       h = (this.offsetTop*2 + this.rect.height)*2;  
  109.     }else{  
  110.        h = this.offsetTop*2+(row-1)*(this.bendLine.height-this.line.height)+this.rect.height;  
  111.     }  
  112.    this.setData({canvHeight:h});  
  113.   },  
  114.   //绘制线路这逻辑比较乱,我是把路线分为奇数段和偶数段进行绘制  
  115.   drawLine:function(){  
  116.     const rectArr = this.rectArr;  
  117.     let x=0,y=0;   
  118.     if(rectArr.length==2){//首先当车站数量为2个的时候,只需绘制一条线段  
  119.         x = rectArr[0].x+this.rect.width;//计算绘制线段起始点的x坐标  
  120.         y = rectArr[0].y+Math.floor((this.rect.height-this.line.height)/2);//计算绘制线段起始点的y坐标  
  121.         this.ctx.drawImage(this.line.img, x, y, this.line.width, this.line.height);  
  122.     }else{  
  123.       const odd = this.oddRowIndexArr;  
  124.       const even = this.evenRowIndexArr;  
  125.       if(odd.length>0){  
  126.         for(let i=0;i<odd.length;i++){  
  127.           if((odd+1)!=rectArr.length){//判断当前下标绘制点后面是否还有绘制点  
  128.             x = rectArr[odd].x+this.rect.width;  
  129.             y = rectArr[odd].y+Math.floor((this.rect.height-this.line.height)/2);  
  130.             if((odd+1)%this.column!=0){//判断奇数行绘制点的下标如果不是3的整数倍将绘制一条直线,反之绘制右曲线  
  131.               this.ctx.drawImage(this.line.img, x, y, this.line.width, this.line.height);  
  132.             }else{  
  133.               this.ctx.drawImage(this.bendLine.img_r, x, y, this.bendLine.width, this.bendLine.height);  
  134.             }  
  135.           }  
  136.         }  
  137.       }  
  138.       //下面逻辑同奇数行的逻辑,不同的是绘制直线和弯曲线时x的坐标会有变化  
  139.       if(even.length>0){  
  140.         for(let i=0;i<even.length;i++){  
  141.           if((even+1)!=rectArr.length){  
  142.             y = rectArr[even].y+Math.floor((this.rect.height-this.line.height)/2);  
  143.             if((even+1)%this.column!=0){  
  144.               x = rectArr[even].x-this.line.width;//绘制直线时的计算公式  
  145.               this.ctx.drawImage(this.line.img, x, y, this.line.width, this.line.height);  
  146.             }else{  
  147.               x = rectArr[even].x-this.bendLine.width;//绘制弯曲线时的计算公式  
  148.               this.ctx.drawImage(this.bendLine.img_l, x, y, this.bendLine.width, this.bendLine.height);  
  149.             }  
  150.           }  
  151.         }  
  152.       }  
  153.     }  
  154.   },  
  155.   drawMap:function(){  
  156.     this.adaptiveScreenSize(this.rect);  
  157.     this.adaptiveScreenSize(this.line);  
  158.     this.adaptiveScreenSize(this.bendLine);  
  159.     this.initCanvHeight();  
  160.     this.createRectTopPoints();  
  161.     // setTimeout(()=>{  
  162.       const rectArr = this.rectArr;   
  163.       for(let i=0;i<rectArr.length;i++){  
  164.         this.initRect(i);  
  165.         this.drawTxtAtPos(i);  
  166.       }  
  167.       this.ctx.draw(true);  
  168.     // },500);  
  169.     this.drawLine();  
  170.     this.ctx.draw(true);  
  171.   },  
  172.   //计算后,每行的所有绘制点的起始坐标x值是一个固定数组  
  173.   //如:奇数行[10,20,30],偶数行:[30,20,10]  
  174.   getDisXArr:function(){  
  175.     let arr = [];  
  176.     let ww = this.data.winWidth;  
  177.     let disX = Math.floor((ww-(this.column*this.rect.width+(this.column-1)*this.line.width))/2);   
  178.     for(let i=0;i<this.column;i++){  
  179.       let x = disX+i%this.column*(this.rect.width+this.line.width);  
  180.       arr = x;  
  181.     }    
  182.     return arr;  
  183.   },  
  184.   //根据给出的车站数量,将每个车站的绘制顶点计算出来存入数组rectArr中  
  185.   createRectTopPoints:function(){  
  186.     let rectArr = [];  
  187.     let disXArr = this.getDisXArr();  
  188.     let disXArrRev = this.getDisXArr().reverse();  
  189.     let disY = this.offsetTop;//绘制初始点距离canvas顶部的高度  
  190.     let len = this.data.stations.length;  
  191.     let row = Math.ceil(len/this.column);//根据车站数量计算需要绘制的行数  
  192.     let n=0,x=0,y=0;  
  193.     for(let j = 1;j<=row;j++){  
  194.       for(let i=0;i<this.column;i++){  
  195.         ++n;  
  196.         if(n<=len){  
  197.           if(j%2!=0){  
  198.             this.oddRowIndexArr.push(n-1);  
  199.             //console.log("奇数行:"+n);  
  200.             x = disXArr;  
  201.           }else{  
  202.             this.evenRowIndexArr.push(n-1);  
  203.             //console.log("偶数行:"+n);  
  204.             x = disXArrRev;  
  205.           }  
  206.           y = disY + (j-1)*(this.bendLine.height-this.line.height);  
  207.           this.rectArr[n-1] = {x:x,y:y};  
  208.         }  
  209.       }  
  210.     }  
  211.   },  
  212.   //判断手指触摸点是否在圆角矩形中  
  213.   pointInRectPolygon : function (point, vs) {  
  214.     let x = point[0], y = point[1],inside = false;  
  215.     for (let i = 0, j = vs.length - 1; i < vs.length; j = i++) {  
  216.         let xi = vs[0], yi = vs[1];  
  217.         let xj = vs[j][0], yj = vs[j][1];  
  218.         let intersect = ((yi > y) != (yj > y))  
  219.             && (x < (xj - xi) * (y - yi) / (yj - yi) + xi);  
  220.         if (intersect) inside = !inside;  
  221.     }  
  222.     return inside;  
  223.   },  
  224.         //根据某个圆角矩形的绘制点和宽高,计算出圆角矩形4个顶点的坐标值  
  225.         //顺序为左上,右上,右下,左下,也就是顺时针方向  
  226.   getRectPolygon:function(x,y,w,h){  
  227.     let vs = new Array() ;  
  228.     vs[0] = [x,y];  
  229.     vs[1] = [x+w,y];  
  230.     vs[2] = [x+w,y+h];  
  231.     vs[3] = [x,y+h];  
  232.     return vs;  
  233.   } ,  
  234.   //点击车站调取的事件,事件中需要处理:  
  235.   //1、需要获取到当前点击的车站文本  
  236.   //2、判断是否有过选取,如果之前有选取,需要将之前选取过的区块颜色改为默认色  
  237.   //3、改变当前区块的颜色  
  238.   //4、记录当前点击的下标  
  239.   chooseStation:function(currIdx){  
  240.     let txt = this.data.stations[currIdx];  
  241.     let prevIdx = this.data.prevChooseIdx;  
  242.     if(prevIdx!=null){  
  243.       let x = this.rectArr[prevIdx].x;  
  244.       let y = this.rectArr[prevIdx].y;  
  245.       this.ctx.drawImage(this.rect.img_b,x, y, this.rect.width, this.rect.height);  
  246.       this.drawTxtAtPos(prevIdx);  
  247.     }  
  248.     let x = this.rectArr[currIdx].x;  
  249.     let y = this.rectArr[currIdx].y;  
  250.     this.ctx.drawImage(this.rect.img_g,x, y, this.rect.width, this.rect.height);  
  251.     this.drawTxtAtPos(currIdx);  
  252.     this.ctx.draw(true);  
  253.     this.setData({chooseStation:txt,prevChooseIdx:currIdx});  
  254.   },  
  255.   //点击事件  
  256.   touchS:function(e){  
  257.     console.log(e);  
  258.     let touch = e.changedTouches;//这里一定要用changedTouches,如果用touches,安卓机会有问题  
  259.     if(touch.length==1){  
  260.       let tapPoint = [touch[0].x,touch[0].y];  
  261.       let rectArr = this.rectArr;  
  262.       for(let i=0;i<rectArr.length;i++){  
  263.         let vs = this.getRectPolygon(rectArr.x,rectArr.y,this.rect.width,this.rect.height);  
  264.         let inside = this.pointInRectPolygon(tapPoint,vs);  
  265.         if(inside){  
  266.           this.chooseStation(i);  
  267.           break;  
  268.         }  
  269.       }  
  270.     }  
  271.   }  
  272. })  

真机测试图:

wxapp-Gline.zip 
ios系统2个站点 
ios多个站点 
安卓机多个站点

转载于http://www.wxapp-union.com/article-1419-1.html
0 0