d3.js 绘制北京市地铁线路状况图(部分)
地铁线路图的可视化一直都是路网公司的重点,今天来和大家一起绘制线路图。先上图。
点击线路按钮,显示相应的线路。点击线路图下面的站间按钮(图上未显示),上报站间故障。
首先就是制作json文件,这个文件包括两部分,一部分是站点信息,另一部分就是线路信息,由于时间问题,我只写了5条线路(10号线站点太tm多了);
然后就是构造类文件,不多说;
之后就是主要的操作线路图的逻辑,还画了一个天安门。
好了废话不多说,下面上代码
1.主要代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>$Title$</title>
<style>
* {
margin: 0;
padding: 0;
}
body {
cursor: pointer;
}
#subway {
margin: 10px;
overflow: scroll;
}
.list {
display: inline-block;
width: 100vw;
height: 800px;
margin-left: 10px;
border: 1px solid #000;
vertical-align: top;
}
#chartId {
width: 100vw;
height: 100vh;
/*border: 1px solid #000;*/
display: inline-block;
}
.svg-container {
display: inline-block;
position: relative;
width: 100%;
height: 100%;
/*padding-bottom: 100%; !* aspect ratio *!*/
vertical-align: top;
overflow: hidden;
}
.svg-content-responsive {
display: inline-block;
position: absolute;
top: 10px;
left: 0;
}
</style>
<script src="./d3.v5.min.js"></script>
</head>
<body>
<div id="chartId"></div>
<script src="./subway.js"></script>
<script src="./class.js"></script>
<script> var svg = d3.select("div#chartId")
.append('div')
.classed("svg-container", true)
.append("svg")
.attr("preserveAspectRatio", "xMinYMin meet")
.attr("viewBox", "0 0 1200 800")
.classed("svg-content-responsive", true); const chartId = document.getElementById('chartId');
const widthStr = chartId.currentStyle ? chartId.currentStyle.width : window.getComputedStyle(chartId,null).width ;
const width = widthStr.substring(0, widthStr.length - 2);
const svgScale = width / 1200;
const group = svg.append('g');
const lines = group.append('g');
const points = group.append('g');
const texts = group.append('g');
const buttons = group.append('g');
const tianAnMen = group.append('g');
const originalLineData = subwayLines;
const originalPointData = subwayPoints;
const subway = new Subway();
const zoom = d3.zoom().on("zoom", zoomed);
svg.call(zoom)
function zoomed () {
group.attr('transform-origin', `${d3.event.sourceEvent.offsetX}, ${d3.event.sourceEvent.offsetY}`)
group.attr("transform", d => `translate(${d3.event.transform.x}, ${d3.event.transform.y}) scale(${d3.event.transform.k})`)
}
let copyLineData = originalLineData.slice(0);
let copyPointData = originalPointData.slice(0);
let bugLines = [];
let shadow = svg.append('defs').append('filter').attr('id', 'shadow')//.attr('height', '125%').attr('width', '110%');
shadow.append('feGaussianBlur').attr('in', 'SourceGraphic').attr('stdDeviation', 1).attr('result', 'blur'); const rendering = (lineData,pointData) => {
lines.selectAll('g').remove();
points.selectAll('g').remove();
texts.selectAll('g').remove();
for (let i=0; i<lineData.length; i++) {
lines.append('g')
.attr('class',`.${lineData[i].name}`)
.selectAll('path')
.data(lineData[i].lines)
.enter()
.append('path')
.attr('class', d => d.state_name)
.attr('class', d => d.state_name)
.attr('d', d => d.state_path)
.attr('stroke', d => {
if (bugLines.indexOf(d.state_name) > -1) {
return '#000';
} else {
return d.state_color;
}
})
.attr('stroke-width', d => {
if (bugLines.indexOf(d.state_name) > -1) {
return 8;
} else {
return 4;
}
})
.attr('fill', 'none')
.on('click', function (e) {
if (bugLines.indexOf(this.getAttribute('class')) > -1) {
let arr = e.state_name.split('_')
renderTooltip(d3.event.offsetX,d3.event.offsetY, `${subway.getChinaBypoint(arr[0])}到${subway.getChinaBypoint(arr[1])}一线有道路施工。`)
}
})
.attr('opacity', 0)
.transition()
.duration(500)
.attr('opacity', 1) points.append('g')
.attr('class',`.${pointData[i].name}`)
.selectAll('circle')
.data(pointData[i].points)
.enter()
.append('circle')
.attr('class', d => d.state_name)
.attr('cx', d => d.point_x)
.attr('cy', d => d.point_y)
.attr('r', d => {
if (d.pivot == 1) {
return 5
} else {
return 3
}
})
.attr('fill', '#fff')
.attr('stroke', '#000')
.attr('opacity', 0)
.transition()
.duration(500)
.attr('opacity', 1) texts.append('g')
.attr('class',`.${pointData[i].name}`)
.selectAll('text')
.data(pointData[i].points)
.enter()
.append('text')
.attr('x', d => d.point_x + d.dx)
.attr('y', d => d.point_y + d.dy)
.attr('font-size', '8px')
.attr('text-anchor', 'middle')
.text(d => d.state_nickname)
.attr('opacity', 0)
.transition()
.duration(500)
.attr('opacity', 1)
}
} const initButton = () => {
const button_item = buttons.selectAll('g')
.data(copyLineData)
.enter()
.append('g')
.attr('class', d => d.name)
.attr('transform', (d, i) => {
return `translate(20, ${i * 35 + 55})`
}) button_item.append('rect')
.attr('fill', d => d.line_color)
.attr('x', 0)
.attr('y', 0)
.attr('width', 90)
.attr('height', 24)
.style('filter', 'url(#shadow)')
.on('click', function () {
subwayLines = copyLineData.filter(i => this.parentNode.getAttribute('class') == i.name);
subwayPoints = copyPointData.filter(i => this.parentNode.getAttribute('class') == i.name);
rendering(subwayLines,subwayPoints);
group.select('.tooltip').remove();
}) button_item.append('text')
.attr('x', 45)
.attr('y', 0)
.attr('fill', '#fff')
.text(d => `地铁${d.nick_name}`)
.attr('text-anchor', 'middle')
.attr('dy', '1.3em')
.attr('font-size', '12')
.on('click', function () {
let lines = copyLineData.filter(i => this.parentNode.getAttribute('class') == i.name);
let points = copyPointData.filter(i => this.parentNode.getAttribute('class') == i.name);
rendering(lines,points);
group.select('.tooltip').remove();
}) const all_button= buttons.append('g')
.attr('transform', `translate(20, 20)`); all_button.append('rect')
.attr('fill', '#888')
.attr('x', 0)
.attr('y', 0)
.attr('width', 90)
.attr('height', 24)
.style('filter', 'url(#shadow)')
.on('click', function () {
rendering(copyLineData,copyPointData);
}) all_button.append('text')
.attr('x', 45)
.attr('y', 0)
.attr('fill', '#fff')
.text('全部线路')
.attr('text-anchor', 'middle')
.attr('dy', '1.3em')
.attr('font-size', '12')
.on('click', function () {
rendering(copyLineData,copyPointData);
})
} const setBugLine = line_name => {
bugLines.push(line_name);
rendering(copyLineData,copyPointData);
} const transformContent = content => {
let arr = [];
let count = 0;
let cont = '';
let every = 24;
for(let i=0; i<content.length - 1; i++) {
//已经满every
if (count == every) {
count = 0;
cont = cont + '&&';
if (/^[\u4e00-\u9fa5]*$/.test(content[i])) {
count += 2;
cont = cont + content[i]
} else {
count += 1;
cont = cont + content[i]
}
}
//已经满every - 1且接下来为中文字符
else if (count == (every - 1) && /^[\u4e00-\u9fa5]*$/.test(content[i])) { }
//中文字符
else if (/^[\u4e00-\u9fa5]*$/.test(content[i])){
count += 2;
cont = cont + content[i]
}
//非中文字符
else {
count += 1;
cont = cont + content[i]
}
}
return cont.split('&&')
} const renderTooltip = (x,y,c) => {
let content = transformContent(c); group.select('.tooltip').remove();
let tooltip = group.append('g').attr('class', 'tooltip');
tooltip.append('path')
.attr('d', 'M 10,0 H 190 Q 200 0 200 10 V 130 Q 200 140 190 140 H 110 L 100,150 L 90,140 H 10 Q 0 140 0 130 V 10 Q 0 0 10 0 Z').attr('fill', '#141514').attr('stroke', '#141514').attr('stroke-width', '2');
tooltip.append('g').attr('class', 'delete').attr('width', 20).attr('height', 20)
.on('click', () => {
group.select('.tooltip').remove();
})
tooltip.select('.delete')
.append('path')
.attr('d', 'M 190, 0 A 10 10 0 1 1 180 10 H 200 A 10 10 0 1 1 190 0 V 20')
.attr('transform-origin', '190 10')
.attr('transform', 'translate(-2, 2) rotate(45)')
.attr('stroke', '#F97D7E')
.attr('stroke-width', 2) tooltip.append('text')
.attr('x', 100)
.attr('y', 20)
.attr('fill', '#F97D7E')
.attr('text-anchor', 'middle')
.attr('font-size', '16')
.text('公告') let texts = tooltip.append('text')
.attr('fill', '#F97D7E')
.attr('x', 7)
.attr('y', 40)
.attr('font-size', '14') content.forEach((d, i) => {
texts.append('tspan').attr('x', 6).attr('y', i * 20 + 44).text(d)
})
tooltip.attr('transform-origin', `${100} ${170}`).attr('transform', `translate(${x / svgScale - 100},${y / svgScale - 170}) rotate(-90)`).transition().duration(1000).ease(d3.easeElasticOut) .attr('transform-origin', `${100} ${170}`).attr('transform', `translate(${x / svgScale - 100},${y / svgScale- 170}) rotate(0)`)
} const initTianAnMen = () => {
console.log( 2086 / 4)
console.log( 1566 / 4) // 389V 389 H 541 V 188 H
tianAnMen.append('path').attr('d', 'M 505,399 H 518 V 398 H 519 V 399 H 523 V 397 H 524 V 399 H 528 V 396 H 529 V 399 H 534 V 397 H 535 V 399 H 538 V 398 H 539 V 399 H 552 L 550,393 H 543 V 395 H 536 L 537,393 H 529 V 395 H 528 V 393 H 520 L 520,395 H 514 V 393 H 507 Z').attr('fill', '#EE1B22')
tianAnMen.append('path').attr('d', 'M 517,393 H 519 V 392 H 528 V 391 H 529 V 392 H 538 V 393 H 540 V 391 H 541 V 390 H 540 V 389 H 541 V 388 H 539 L 537,385 H 536 L 535 386 H 521 L 520,385 H 519 L 518,388 H 516 V 389 H 517 V 390 H 516 V 391 H 517 Z').attr('fill', '#EE1B22')
tianAnMen.append('path').attr('d', 'M 519,391 H 520 V 390 H 519 Z').attr('fill', '#ffffff')
tianAnMen.append('path').attr('d', 'M 521.5,391 H 522.5 V 390 H 521.5 Z').attr('fill', '#ffffff')
tianAnMen.append('path').attr('d', 'M 524,391 H 525 V 390 H 524 Z').attr('fill', '#ffffff')
tianAnMen.append('path').attr('d', 'M 526.5,391 H 527.5 V 390 H 526.5 Z').attr('fill', '#ffffff')
tianAnMen.append('path').attr('d', 'M 529.5,391 H 530.5 V 390 H 529.5 Z').attr('fill', '#ffffff')
tianAnMen.append('path').attr('d', 'M 532,391 H 533 V 390 H 532 Z').attr('fill', '#ffffff')
tianAnMen.append('path').attr('d', 'M 534.5,391 H 535.5 V 390 H 534.5 Z').attr('fill', '#ffffff')
tianAnMen.append('path').attr('d', 'M 537,391 H 538 V 390 H 537 Z').attr('fill', '#ffffff')
} rendering(copyLineData,copyPointData);
initButton();
initTianAnMen(); window.onload = function () {
let box = document.getElementById('mm');
let str = '';
for(let i=0; i<5; i++) {
let nick_name = subway.getLines(i).nick_name;
let arr = subway.getLines(i).lines;
str = str + `<p>${nick_name}</p>`
arr.forEach((item, index)=>{
let points = item.state_name.split('_');
str = str + `<button onclick="setBugLine('${item.state_name}')">${subway.getChinaBypoint(points[0])}>${subway.getChinaBypoint(points[1])}</button>`
})
}
box.innerHTML = str;
} </script>
<div class="list">
<p>设置故障线路</p>
<div id="mm"> </div>
<p>点击黑色,显示封路原因</p>
</div>
</body>
</html>
2.Class类
class Subway {
constructor() {
this.lines = subwayLines;
this.points = subwayPoints;
}
getLines(index) {
return this.lines[index];
}
getChinaBypoint(element) {
let str = '';
this.points.forEach((item) => {
item.points.forEach((items) => {
if (items.state_name == element) {
str = items.state_nickname;
}
})
})
return str;
}
}
3. 地铁线路数据js文件(纯手工造数据)
此数据包含站点数据和线路数据,由于数据过长就不粘贴了,想看的朋友可以到原文预览或者下载demo
d3.js 绘制北京市地铁线路状况图(部分)的更多相关文章
- 利用d3.js绘制雷达图
利用d3,js将数据可视化,能够做到数据与代码的分离.方便以后改动数据. 这次利用d3.js绘制了一个五维的雷达图.即将多个对象的五种属性在一张图上对照. 数据写入data.csv.数据类型写入typ ...
- [js]d3.js绘制拓扑树
echart也支持拓扑树了 所需的json数据格式: children嵌套 vis.js也支持绘制拓扑树 数据格式: nodes: {id, label, title} edges: {from, t ...
- 用 D3.js 画一个手机专利关系图, 看看苹果,三星,微软间的专利纠葛
前言 本文灵感来源于Mike Bostock 的一个 demo 页面 原 demo 基于 D3.js v3 开发, 笔者将其使用 D3.js v5 进行重写, 并改为使用 ES6 语法. 源码: gi ...
- 利用d3.js绘制中国地图
d3.js是一个比較强的数据可视化js工具. 利用它画了一幅中国地图,例如以下图所看到的: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc3ZhcDE=/ ...
- D3.js+Es6+webpack构建人物关系图(力导向图)
功能列表:1. 增加下载SVG转PNG功能,图片尺寸超出可视区域也能够下载全部显示出来2. 增加图谱放大缩小平移功能3. 增加图谱初始化加载时自动缩放功能4. 增加导出excel功能,配合后台工具类达 ...
- d3.js 绘制极坐标图(polar plot)
0.引言 在极坐标系中,任意位置可由一个夹角和一段相对原点(极点)的距离表示.也就是说,我们可以用 (angle,r) 来表示极坐标系中的点. 1.数据 假设我们有如下数据集[ [10, 0.2], ...
- D3.js绘制平行坐标图
参照:https://syntagmatic.github.io/parallel-coordinates/ 和 https://github.com/syntagmatic/parallel-coo ...
- D3.js系列——布局:饼状图和力导向图
一.饼状图 在布局的应用中,最简单的就是饼状图. 1.数据 有如下数据,需要可视化: , , , , ]; 这样的值是不能直接绘图的.例如绘制饼状图的一个部分,需要知道一段弧的起始角度和终止角度,这些 ...
- D3.js绘制 颜色:RGB、HSL和插值 (V3版本)
颜色和插值 计算机中的颜色,常用的标准有RGB和HSL. RGB:色彩模式是通过对红(Red).绿(Green).蓝(Blue)三个颜色通道相互叠加来得到额各式各样的颜色.三个通道的值得范围都 ...
随机推荐
- 杭电多校第十场 hdu6432 Cyclic 打表找规律
Cyclic Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 524288/524288 K (Java/Others)Total Su ...
- ssh-agent代理的简单用法
前言 在ansible的官方文档中,提到了强烈推荐用ssh-agent来管理密钥 究竟ssh-agent是什么,它有什么用法呢,下面来一探究竟. ssh-agent是什么?用处是什么? ssh-age ...
- Java日志框架SLF4J和log4j以及logback的联系和区别
1.SLF4J(Simple logging Facade for Java) 意思为简单日志门面,它是把不同的日志系统的实现进行了具体的抽象化,只提供了统一的日志使用接口,使用时只需要按照其提供的接 ...
- Linux 中 /proc/meminfo 的含义
做嵌入式开发对内存泄露很敏感,而对泄露的位置更加关注.本文记录一下从网上搜集的/proc/meminfo各参数的含义.还不完整,待补完. 本文地址:https://segmentfault.com/a ...
- Redis的那些最常见面试问题(转)
Redis的那些最常见面试问题 1.什么是redis? Redis 是一个基于内存的高性能key-value数据库. 2.Reids的特点 Redis本质上是一个Key-Value类型 ...
- Build a pile of Cubes
version_1: def find_nb(m): # your code ii = 1 total = 0 while total < m: total = sum(each**3 for ...
- 做一个logitic分类之鸢尾花数据集的分类
做一个logitic分类之鸢尾花数据集的分类 Iris 鸢尾花数据集是一个经典数据集,在统计学习和机器学习领域都经常被用作示例.数据集内包含 3 类共 150 条记录,每类各 50 个数据,每条记录都 ...
- 降低 80% 的读写响应延迟!我们测评了 etcd 3.4 新特性(内含读写发展史)
作者 | 陈洁(墨封) 阿里云开发工程师 导读:etcd 作为 K8s 集群中的存储组件,读写性能方面会受到很多压力,而 etcd 3.4 中的新特性将有效缓解压力,本文将从 etcd 数据读写机制 ...
- 认识HTML和CSS
1.认识HTML标记 HTML的全称是Hyper text markup language,超文本标记语言,用于定义文档的内容结构.在HTML中,所有的标记都是成对出现的. <html>标 ...
- 【赶快收藏】Hystrix实战,优雅提升系统的鲁棒性
背景 最近接手了一个系统,其功能都是查询.查询分了两种方式,一种是公司集团提供的查询能力,支持全国各个省份的查询,但是业务高峰期时服务响应比较慢:另外一种是各省的分公司都分别提供了对应的查询能力,但是 ...