热门标签 | HotTags
当前位置:  开发笔记 > 前端 > 正文

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

这篇文章主要介绍了d3.js地铁轨道交通项目实战,本文通过实例代码项目截图给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下

上一章说了如何制作一个线路图,当然上一章是手写的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  {
  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  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


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

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  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  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  {
  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(`${id}
×
`); bugLine.forEach(d => { let item = tooltip.select('#tool-body').append('div').attr('class', 'tool-item'); item.html(`
${d.lb}
封路时间
${d.duration}
封路原因
${d.cause}
封路路段
${d.start}-${d.end}
`) }) 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 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 地铁轨道交通项目实战,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!


推荐阅读
  • SpringMVC接收请求参数的方式总结
    本文总结了在SpringMVC开发中处理控制器参数的各种方式,包括处理使用@RequestParam注解的参数、MultipartFile类型参数和Simple类型参数的RequestParamMethodArgumentResolver,处理@RequestBody注解的参数的RequestResponseBodyMethodProcessor,以及PathVariableMapMethodArgumentResol等子类。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • 本文介绍了使用AJAX的POST请求实现数据修改功能的方法。通过ajax-post技术,可以实现在输入某个id后,通过ajax技术调用post.jsp修改具有该id记录的姓名的值。文章还提到了AJAX的概念和作用,以及使用async参数和open()方法的注意事项。同时强调了不推荐使用async=false的情况,并解释了JavaScript等待服务器响应的机制。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 本文讨论了Alink回归预测的不完善问题,指出目前主要针对Python做案例,对其他语言支持不足。同时介绍了pom.xml文件的基本结构和使用方法,以及Maven的相关知识。最后,对Alink回归预测的未来发展提出了期待。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • 本文介绍了在SpringBoot中集成thymeleaf前端模版的配置步骤,包括在application.properties配置文件中添加thymeleaf的配置信息,引入thymeleaf的jar包,以及创建PageController并添加index方法。 ... [详细]
  • 知识图谱——机器大脑中的知识库
    本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
  • 本文介绍了一些好用的搜索引擎的替代品,包括网盘搜索工具、百度网盘搜索引擎等。同时还介绍了一些笑话大全、GIF笑话图片、动态图等资源的搜索引擎。此外,还推荐了一些迅雷快传搜索和360云盘资源搜索的网盘搜索引擎。 ... [详细]
  • Java验证码——kaptcha的使用配置及样式
    本文介绍了如何使用kaptcha库来实现Java验证码的配置和样式设置,包括pom.xml的依赖配置和web.xml中servlet的配置。 ... [详细]
  • 本文介绍了Android中的assets目录和raw目录的共同点和区别,包括获取资源的方法、目录结构的限制以及列出资源的能力。同时,还解释了raw目录中资源文件生成的ID,并说明了这些目录的使用方法。 ... [详细]
  • HTML5网页模板怎么加百度统计?
    本文介绍了如何在HTML5网页模板中加入百度统计,并对模板文件、css样式表、js插件库等内容进行了说明。同时还解答了关于HTML5网页模板的使用方法、表单提交、域名和空间的问题,并介绍了如何使用Visual Studio 2010创建HTML5模板。此外,还提到了使用Jquery编写美好的HTML5前端框架模板的方法,以及制作企业HTML5网站模板和支持HTML5的CMS。 ... [详细]
  • 分享css中提升优先级属性!important的用法总结
    web前端|css教程css!importantweb前端-css教程本文分享css中提升优先级属性!important的用法总结微信门店展示源码,vscode如何管理站点,ubu ... [详细]
  • Netty源代码分析服务器端启动ServerBootstrap初始化
    本文主要分析了Netty源代码中服务器端启动的过程,包括ServerBootstrap的初始化和相关参数的设置。通过分析NioEventLoopGroup、NioServerSocketChannel、ChannelOption.SO_BACKLOG等关键组件和选项的作用,深入理解Netty服务器端的启动过程。同时,还介绍了LoggingHandler的作用和使用方法,帮助读者更好地理解Netty源代码。 ... [详细]
author-avatar
左手抽烟吹寂寞
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有