上一章说了如何制作一个线路图,当然上一章是手写的JSON数据,当然手写的json数据有非常多的好处,例如可以应对客户的各种BT需求,但是大多数情况下我们都是使用地铁公司现成的JSON文件,话不多说我们先看一下百度官方线路图。
就是这样的,今天我们就来完成它的大部分需求,以及地铁公司爸爸提出来的需求。
需求如下:
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数据。
每条线路对象都有一个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文件都写完了,这里的操作就方便了很多,主要就是下面几个人方法,
renderInventLine(); //渲染虚拟新路 renderAllStation(); //渲染所有的线路名称(右上角) renderBugLine(); //渲染问题路段 renderAllLine(); //渲染所有线路 renderAllPoint(); //渲染所有点 renderCurrentLine() //渲染当前选中的线路 renderCurrentPoint() //渲染当前选中的站点 zoomed() //缩放时执行的方法 getCenter() //获取虚拟线中心点的坐标 scale() //点击缩放按钮时执行的方法
下面是对应的方法体
svg.call(zoom); svg.call(zoom.transform, d3.zoomIdentity.translate((1 - baseScale) * transX, (1 - baseScale) * transY).scale(baseScale)); let pathArray = subway.getPathArray(); let pointArray = subway.getPointArray(); renderInventLine(); renderAllStation(); renderBugLine(); function renderInventLine() { let arr = subway.getInvent(); whole.selectAll('path') .data(arr) .enter() .append('path') .attr('d', d => d.path) .attr('class', d => d.lid) .attr('stroke', 'none') .attr('fill', 'none') } function renderAllLine() { for (let i = 0; i < pathArray.length; i++) { path.append('g') .selectAll('path') .data(pathArray[i].path) .enter() .append('path') .attr('d', d => d.path) .attr('lid', d => d.lid) .attr('id', d => d.id) .attr('class', 'lines origin') .attr('stroke', d => d.color) .attr('stroke-width', 7) .attr('stroke-linecap', 'round') .attr('fill', 'none') path.append('text') .attr('x', pathArray[i].lbx) .attr('y', pathArray[i].lby) .attr('dy', '1em') .attr('dx', '-0.3em') .attr('fill', pathArray[i].lc) .attr('lid', pathArray[i].lid) .attr('class', 'line-text origin') .attr('font-size', 14) .attr('font-weight', 'bold') .text(pathArray[i].lb) } } function renderAllPoint() { for (let i = 0; i < pointArray.length; i++) { for (let j = 0; j < pointArray[i].length; j++) { let item = pointArray[i][j]; let box = point.append('g'); if (item.ex) { box.append('image') .attr('href', './trans.png') .attr('class', 'points origin') .attr('id', item.sid) .attr('x', item.x - 8) .attr('y', item.y - 8) .attr('width', 16) .attr('height', 16) } else { box.append('circle') .attr('cx', item.x) .attr('cy', item.y) .attr('r', 5) .attr('class', 'points origin') .attr('id', item.sid) .attr('stroke', item.lc) .attr('stroke-width', 1.5) .attr('fill', '#ffffff') } box.append('text') .attr('x', item.x + item.rx) .attr('y', item.y + item.ry) .attr('dx', '0.3em') .attr('dy', '1.1em') .attr('font-size', 11) .attr('class', 'point-text origin') .attr('lid', item.lid) .attr('id', item.sid) .text(item.lb) } } } function renderCurrentLine(name) { let arr = subway.getCurrentPathArray(name); path.append('g') .attr('class', 'temp') .selectAll('path') .data(arr.path) .enter() .append('path') .attr('d', d => d.path) .attr('lid', d => d.lid) .attr('id', d => d.id) .attr('stroke', d => d.color) .attr('stroke-width', 7) .attr('stroke-linecap', 'round') .attr('fill', 'none') path.append('text') .attr('class', 'temp') .attr('x', arr.lbx) .attr('y', arr.lby) .attr('dy', '1em') .attr('dx', '-0.3em') .attr('fill', arr.lc) .attr('lid', arr.lid) .attr('font-size', 14) .attr('font-weight', 'bold') .text(arr.lb) } function renderCurrentPoint(name) { let arr = subway.getCurrentPointArray(name); for (let i = 0; i < arr.length; i++) { let item = arr[i]; let box = point.append('g').attr('class', 'temp'); if (item.ex) { box.append('image') .attr('href', './trans.png') .attr('x', item.x - 8) .attr('y', item.y - 8) .attr('width', 16) .attr('height', 16) .attr('id', item.sid) } else { box.append('circle') .attr('cx', item.x) .attr('cy', item.y) .attr('r', 5) .attr('id', item.sid) .attr('stroke', item.lc) .attr('stroke-width', 1.5) .attr('fill', '#ffffff') } box.append('text') .attr('class', 'temp') .attr('x', item.x + item.rx) .attr('y', item.y + item.ry) .attr('dx', '0.3em') .attr('dy', '1.1em') .attr('font-size', 11) .attr('lid', item.lid) .attr('id', item.sid) .text(item.lb) } } function renderBugLine(modal) { let bugLineArray = subway.getBugLineArray(modal); d3.selectAll('.origin').remove(); renderAllLine(); renderAllPoint(); bugLineArray.forEach(d => { console.log(d) d.lines.forEach(dd => { d3.selectAll(`path#${dd}`).attr('stroke', '#eee'); }) d.points.forEach(dd => { d3.selectAll(`circle#${dd}`).attr('stroke', '#ddd') d3.selectAll(`text#${dd}`).attr('fill', '#aaa') }) }) d3.selectAll('.points').on('click', function () { let id = d3.select(this).attr('id'); let bool = judgeBugPoint(bugLineArray, id); if (bool) { let x, y; if (d3.select(this).attr('href')) { x = parseFloat(d3.select(this).attr('x')) + 8; y = parseFloat(d3.select(this).attr('y')) + 8; } else { x = d3.select(this).attr('cx'); y = d3.select(this).attr('cy'); } let toolX = (x * currentScale + transX - ((1 - currentScale) * transX - currentX)) * deviceScale; let toolY = (y * currentScale + transY - ((1 - currentScale) * transY - currentY)) * deviceScale; let toolH = document.getElementById('tooltip').offsetHeight; let toolW = 110; if (toolY < 935 / 2) { tooltip.style('left', `${toolX - toolW}px`).style('top', `${toolY + 5}px`); } else { tooltip.style('left', `${toolX - toolW}px`).style('top', `${toolY - toolH - 5}px`); } } }); } function judgeBugPoint(arr, id) { if (!arr || !arr.length || !id) return false; let bugLine = arr.filter(d => { return d.points.indexOf(id) > -1 }); if (bugLine.length) { removeTooltip() tooltip.select('#tool-head').html(`<span>${id}</span><div class="deletes" onclick="removeTooltip()">×</div>`); bugLine.forEach(d => { let item = tooltip.select('#tool-body').append('div').attr('class', 'tool-item'); item.html(` <div class="tool-content"> <div style="color: #ffffff;border-bottom: 2px solid ${d.lc};"> <span style="background: ${d.lc};padding: 4px 6px;">${d.lb}</span> </div> <div> <div class="content-left">封路时间</div><div class="content-right">${d.duration}</div> </div> <div> <div class="content-left">封路原因</div><div class="content-right">${d.cause}</div> </div> <div> <div class="content-left">封路路段</div><div class="content-right">${d.start}-${d.end}</div> </div> </div> `) }) d3.select('#tooltip').style('display', 'block'); return true; } else { return false; } } function removeTooltip() { d3.selectAll('.tool-item').remove(); d3.select('#tooltip').style('display', 'none'); } function zoomed() { removeTooltip(); let {x, y, k} = d3.event.transform; currentScale = k; currentX = x; currentY = y; group.transition().duration(50).ease(d3.easeLinear).attr("transform", () => `translate(${x + transX * k}, ${y + transY * k}) scale(${k})`) } function getCenter(str) { if (!str) return null; let x, y; let tempArr = []; let tempX = []; let tempY = []; str.split(' ').forEach(d => { if (!isNaN(d)) { tempArr.push(d) } }) tempArr.forEach((d, i) => { if (i % 2 == 0) { tempX.push(parseFloat(d)) } else { tempY.push(parseFloat(d)) } }) x = (d3.min(tempX) + d3.max(tempX)) / 2; y = (d3.min(tempY) + d3.max(tempY)) / 2; return [x, y] } function renderAllStation() { let nameArray = subway.getLineNameArray(); let len = Math.ceil(nameArray.length / 5); let box = d3.select('#menu').append('div') .attr('class', 'name-box') for (let i = 0; i < len; i++) { let subwayCol = box.append('div') .attr('class', 'subway-col') let item = subwayCol.selectAll('div') .data(nameArray.slice(i * 5, (i + 1) * 5)) .enter() .append('div') .attr('id', d => d.lid) .attr('class', 'name-item') item.each(function (d) { d3.select(this).append('span').attr('class', 'p_mark').style('background', d.lc); d3.select(this).append('span').attr('class', 'p_name').text(d.lb); d3.select(this).on('click', d => { selected = true; d3.selectAll('.origin').style('opacity', 0.1); d3.selectAll('.temp').remove(); renderCurrentLine(d.lid); renderCurrentPoint(d.lid); let arr = getCenter(d3.select(`path.${d.lid}`).attr('d')); svg.call(zoom.transform, d3.zoomIdentity.translate((width / 2 - transX) - arr[0] - (arr[0] + transX) * (currentScale - 1), (height / 2 - transY) - arr[1] - (arr[1] + transY) * (currentScale - 1)).scale(currentScale)); }) }) } } function scale(type) { if (type && currentScale + scaleStep <= scaleExtent[1]) { svg.call(zoom.transform, d3.zoomIdentity.translate((1 - currentScale - scaleStep) * transX - ((1 - currentScale) * transX - currentX) * (currentScale + scaleStep) / currentScale, (1 - currentScale - scaleStep) * transY - ((1 - currentScale) * transY - currentY) * (currentScale + scaleStep) / currentScale).scale(currentScale + scaleStep)); } else if (!type && currentScale - scaleStep >= scaleExtent[0]) { svg.call(zoom.transform, d3.zoomIdentity.translate((1 - (currentScale - scaleStep)) * transX - ((1 - currentScale) * transX - currentX) * (currentScale - scaleStep) / currentScale, (1 - (currentScale - scaleStep)) * transY - ((1 - currentScale) * transY - currentY) * (currentScale - scaleStep) / currentScale).scale(currentScale - scaleStep)); } }
上面是大部分代码,想看全部的可以查看demo。
想查看demo或代码的朋友们,请移步至原文http://www.bettersmile.cn
总结
以上所述是小编给大家介绍的d3.js 地铁轨道交通项目实战,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对小牛知识库网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!
云边开源轻博是国内首个开源的轻博客平台,作者吸取轻博使用方面的优点,争取做最好用的开源轻博客客给每一位朋友使用,为站长搭建更为简单方便的交流平台。 地铁客开源轻博v1.8 Beta 更新说明 1、全站素材文件、JS文件、CSS的优化和压缩,极大的提高网站的加载速度; 2、界面UI的更新,多谢群里小胖的大力支持,为地铁客做出了精美的UI设计图,这次更新一部分,剩下的会继续努力的! 3、数据库的优
我有ubuntu服务器、Rails 5.0和apache Web服务器,上面还有很多站点。 当我添加新站点时,我收到一个错误“收到来自应用程序的不完整响应” 当我检查apache日志时,我看到消息: 我的配置/机密。yml包含默认值: 另外,我刚刚通过在文件中写入设置了环境变量SECRET_KEY_BASE /etc/profile 我看到than通过命令< code > echo$SECRET
D3(Data-Driven Documents 或 D3.js)是一个 JavaScript 库,用于使用 Web 标准将数据可视化。D3 帮助你使用SVG、 Canvas 和 HTML 将数据变为现实。D3 将强大的可视化和交互技术与数据驱动的DOM操作方法相结合,让你拥有现代浏览器的全部功能,并可以自由地为您的数据设计合适的可视化界面。
本文向大家介绍js实现交通灯效果,包括了js实现交通灯效果的使用技巧和注意事项,需要的朋友参考一下 主体结构 样式 js代码 利用定时器改变类名 以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持呐喊教程!
D3.js是一个JavaScript库,用于在浏览器中创建交互式可视化。 D3库允许我们在数据集的上下文中操纵网页的元素。 这些元素可以是HTML,SVG或Canvas元素,可以根据数据集的内容进行引入,删除或编辑。 它是一个用于操作DOM对象的库。 D3.js可以成为数据探索的宝贵帮助。 它使您可以控制数据的表示,并允许您添加数据交互性。 与其他库相比,D3.js是最重要的框架之一。 这是因为;
本文向大家介绍d3.js 使用D3 js创建SVG元素,包括了d3.js 使用D3 js创建SVG元素的使用技巧和注意事项,需要的朋友参考一下 示例 尽管D3并非专门用于处理SVG元素,但它已广泛用于创建和处理基于SVG的复杂数据可视化。D3提供了许多强大的方法,可帮助轻松创建各种几何SVG结构。 建议首先了解SVG规范的基本概念,然后使用大量的D3 js示例创建可视化效果。 D3 JS范例 SV