d3.js 地铁轨道交通项目实战

上一章说了如何制作一个线路图,当然上一章是手写的JSON数据,当然手写的json数据有非常多的好处,例如可以应对客户的各种BT需求,但是大多数情况下我们都是使用地铁公司现成的JSON文件,话不多说我们先看一下百度官方线路图。

d3.js 地铁轨道交通项目实战

就是这样的,今天我们就来完成它的大部分需求,以及地铁公司爸爸提出来的需求。

需求如下:

1.按照不同颜色显示地铁各线路,显示对应站点。

2.用户可以点击手势缩放和平移(此项目为安卓开发)。

3.用户在线路menu里点击线路,对应线路平移值屏幕中心并高亮。

4.根据后台数据,渲染问题路段。

5.点击问题路段站点,显示问题详情。

大致需求就是这些,下面看看看代码

1.定义一些常量和变量

const dataset = subwayData; //线路图数据源 let subway = new Subway(dataset); //线路图的类文件 let baseScale = 2; //基础缩放倍率 let deviceScale = 1400 / 2640; //设备与画布宽度比率 let width = 2640; //画布宽 let height = 1760; //画布高 let transX = 1320 + 260; //地图X轴平移(将画布原点X轴平移) let transY = 580; //地图X轴平移(将画布原点Y轴平移) let scaleExtent = [0.8, 4]; //缩放倍率限制 let currentScale = 2; //当前缩放值 let currentX = 0; //当前画布X轴平移量 let currentY = 0; //当前画布Y轴平移量 let selected = false; //线路是否被选中(在右上角的线路菜单被选中) let scaleStep = 0.5; //点击缩放按钮缩放步长默认0.5倍 let tooltip = d3.select('#tooltip'); //提示框 let bugArray = []; //问题路段数组 let svg = d3.select('#sw').append('svg'); //画布 let group = svg.append('g').attr('transform', `translate(${transX}, ${transY}) scale(1)`);//定义组并平移 let whole = group.append('g').attr('class', 'whole-line') //虚拟线路(用于点击右上角响应线路可以定位当视野中心,方法不唯一) let path = group.append('g').attr('class', 'path'); //定义线路 let point = group.append('g').attr('class', 'point'); //定义站点 const zoom = d3.zoom().scaleExtent(scaleExtent).on("zoom", zoomed); //定义缩放事件

这就是我们需要使用的一些常量和变量。注意transX不是宽度的一半,是因为北京地铁线路网西线更密集。

2.读官方JSON

使用d3.js数据必不可少,然而官方的数据并不通俗易懂,我们先解读一下官方JSON数据。

d3.js 地铁轨道交通项目实战

每条线路对象都有一个l_xmlattr属性和一个p属性,l_xmlattr是整条线路的属性,p是站点数组,我们看一下站点中我们需要的属性。ex是否是中转站,lb是站名,sid是站的id,rx、ry是文字偏移量,st是是否为站点(因为有的点不是站点而是为了渲染贝塞尔曲线用的),x、y是站点坐标。

3.构造自己的类方法

官方给了我们数据,但是并不是我们能直接使用的,所以我们需要构造自己的方法类

class Subway { constructor(data) { this.data = data; this.bugLineArray = []; } getInvent() {} //获取虚拟线路数据 getPathArray() {} //获取路径数据 getPointArray() {} //获取站点数组 getCurrentPathArray() {} //获取被选中线路的路径数组 getCurrentPointArray() {} //获取被选中线路的站点数组 getLineNameArray() {} // 获取线路名称数组 getBugLineArray() {} //获取问题路段数组 }

下面是我们方法内容,里面的操作不是很优雅(大家将就看啦)

getInvent() { let lineArray = []; this.data.forEach(d => { let { loop, lc, lbx, lby, lb, lid} = d.l_xmlattr; let allPoints = d.p.slice(0); loop && allPoints.push(allPoints[0]); let path = this.formatPath(allPoints, 0, allPoints.length - 1); lineArray.push({ lid: lid, path: path, }) }) return lineArray; } getPathArray() { let pathArray = []; this.data.forEach(d => { let { loop, lc, lbx, lby, lb, lid} = d.l_xmlattr; let allPoints = d.p.slice(0); loop && allPoints.push(allPoints[0]) let allStations = []; allPoints.forEach((item, index) => item.p_xmlattr.st && allStations.push({...item.p_xmlattr, index})) let arr = []; for(let i = 0; i < allStations.length - 1; i++) { let path = this.formatPath(allPoints, allStations[i].index, allStations[i + 1].index); arr.push({ lid: lid, id: `${allStations[i].sid}_${allStations[i + 1].sid}`, path: path, color: lc.replace(/0x/, '#') }) } pathArray.push({ path: arr, lc: lc.replace(/0x/, '#'), lb,lbx,lby,lid }) }) return pathArray; } getPointArray() { let pointArray = []; let tempPointsArray = []; this.data.forEach(d => { let {lid,lc,lb} = d.l_xmlattr; let allPoints = d.p; let allStations = []; allPoints.forEach(item => { if(item.p_xmlattr.st && !item.p_xmlattr.ex) { allStations.push({...item.p_xmlattr, lid, pn: lb, lc: lc.replace(/0x/, '#')}) } else if (item.p_xmlattr.ex) { if(tempPointsArray.indexOf(item.p_xmlattr.sid) == -1) { allStations.push({...item.p_xmlattr, lid, pn: lb, lc: lc.replace(/0x/, '#')}) tempPointsArray.push(item.p_xmlattr.sid); } } }); pointArray.push(allStations); }) return pointArray; } getCurrentPathArray(name) { let d = this.data.filter(d => d.l_xmlattr.lid == name)[0]; let { loop, lc, lbx, lby, lb, lid} = d.l_xmlattr; let allPoints = d.p.slice(0); loop && allPoints.push(allPoints[0]) let allStations = []; allPoints.forEach((item, index) => item.p_xmlattr.st && allStations.push({...item.p_xmlattr, index})) let arr = []; for(let i = 0; i < allStations.length - 1; i++) { let path = this.formatPath(allPoints, allStations[i].index, allStations[i + 1].index); arr.push({ lid: lid, id: `${allStations[i].sid}_${allStations[i + 1].sid}`, path: path, color: lc.replace(/0x/, '#') }) } return { path: arr, lc: lc.replace(/0x/, '#'), lb,lbx,lby,lid } } getCurrentPointArray(name) { let d = this.data.filter(d => d.l_xmlattr.lid == name)[0]; let {lid,lc,lb} = d.l_xmlattr; let allPoints = d.p; let allStations = []; allPoints.forEach(item => { if(item.p_xmlattr.st && !item.p_xmlattr.ex) { allStations.push({...item.p_xmlattr, lid, pn: lb, lc: lc.replace(/0x/, '#')}) } else if (item.p_xmlattr.ex) { allStations.push({...item.p_xmlattr, lid, pn: lb, lc: lc.replace(/0x/, '#')}) } }); return allStations; } getLineNameArray() { let nameArray = this.data.map(d => { return { lb: d.l_xmlattr.lb, lid: d.l_xmlattr.lid, lc: d.l_xmlattr.lc.replace(/0x/, '#') } }) return nameArray; } getBugLineArray(arr) { if(!arr || !arr.length) return []; this.bugLineArray = []; arr.forEach(item => { let { start, end, cause, duration, lid, lb } = item; let lines = []; let points = []; let tempObj = this.data.filter(d => d.l_xmlattr.lid == lid)[0]; let loop = tempObj.l_xmlattr.loop; let lc = tempObj.l_xmlattr.lc; let allPoints = tempObj.p; let allStations = []; allPoints.forEach(item => { if(item.p_xmlattr.st) { allStations.push(item.p_xmlattr.sid) } }); loop && allStations.push(allStations[0]); for(let i=allStations.indexOf(start); i<=allStations.lastIndexOf(end); i++) { points.push(allStations[i]) } for(let i=allStations.indexOf(start); i<allStations.lastIndexOf(end); i++) { lines.push(`${allStations[i]}_${allStations[i+1]}`) } this.bugLineArray.push({cause,duration,lid,lb,lines,points,lc: lc.replace(/0x/, '#'),start: points[0],end:points[points.length - 1]}); }) return this.bugLineArray;


这种方法大家也不必看懂,知道传入了什么,输入了什么即可,这就是我们的方法类。

4.d3渲染画布并添加方法

这里是js的核心代码,既然class文件都写完了,这里的操作就方便了很多,主要就是下面几个人方法,

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/250b83fba320d954a6b70c0fe321e4fb.html