采用d3绘制地铁路线图如下图:
image.png
image.png
地铁线路图绘制先要获取数据源,可是获取百度地铁图数据。我获取的地图数据如下
image
想知道如何获取百度地铁数据参考另我的一篇文章:获取百度地铁线路图
首先安装d3,注意版本我使用的是 "d3": "^5.7.0", 之前安装过最新的版本,发现许多的使用方式发生了变化 ,后面降级使用5.7.0版本
在使用页面引入d3:
import * as d3 from 'd3'
接下来可直接使用d3了,先定义变量
let width = 540; //画布宽
let height = 300; //画布高
let transX = -10 //地图X轴平移(将画布原点X轴平移)
let transY = -10; //地图X轴平移(将画布原点Y轴平移)
let scaleExtent = [0.1, 100]; //缩放倍率限制
let currentScale = 0.2; //当前缩放值
let currentX = 0; //当前画布X轴平移量
let currentY = 0; //当前画布Y轴平移量
let scaleStep = 0.2; //点击缩放按钮缩放步长默认0.2倍
let svg = null;// 画布
var group = null;//定义组并平移
const zoom1 = d3.zoom().scaleExtent(scaleExtent).on("zoom", zoomed);//定义缩放事件
const screenWidth = document.body.clientWidth; //当前可视区域宽度
const [cardInfo, setCardInfo] = useState({
title: '',
show: false,
type: 0,
top: 0,
left: 0,
});
const getLines = () => {
dispatch({
type: '路径/getLines',
}).then((res) => {
cityLines = res;
svg = d3.select('#flowChart').append('svg')
.attr('width', width)
.attr('height', height);
group = svg.append("g")
.attr("transform", `translate(${transX}, ${transY}) scale(${currentScale})`);
//renderAllStation();//渲染所有线路图例
renderAllLine();//渲染所有线路
renderAllPoint();//渲染所有点
svg.call(zoom1);
});
}
//渲染所有线路
const renderAllLine = () => {
let path = group.append('g').attr('class', 'path');
for (let i = 0; i < cityLines.length; i++) {
path.append('g')
.selectAll('path')
.data([cityLines[i].stations])
.enter()
.append('path')
.attr("d", function (d) {
let path=formatPath(d);
return path;
})
.attr('lid', d => d.name)
.attr('id', d => d[0].id)
.attr('class', 'lines origin')
.attr('stroke', d => d[0].col)
.attr('stroke-width', 7)
.attr('stroke-linecap', 'round')
.attr('fill', 'none')
}
}
////渲染所有点
const renderAllPoint = () => {
let point = group.append('g').attr('class', 'point'); //定义站点
for (let i = 0; i < cityLines.length; i++) {
for (let j = 0; j < cityLines[i].stations.length; j++) {
let item = cityLines[i].stations[j];
let box = point.append('g')
.data([item])
.attr("class",item.name=='赤峰路'?'node node--doing':'point')
//box.on("click", handleTable) //也可定义click 事件
box.on("mouseover", handleTable)
box.on("mouseout", removeTooltip);
if (item.name=='赤峰路') { //这里给其中的一个站做个不一样的效果
box.append('image')
.attr('href', require('./breakdown.png'))
.attr('class', 'points origin')
.attr('id', item.id)
.attr('x', item.x - 10)
.attr('y', item.y - 40)
.attr('width', 20)
.attr('height', 20)
box.append("circle")
.attr("r", 5)
.attr('cx', item.x)
.attr('cy', item.y)
.attr("id", 'station-01');
} else {
box.append('circle')
.attr('cx', item.x)
.attr('cy', item.y)
.attr('r', 5)
.attr('class', 'points origin')
.attr('id', item.name)
.attr('stroke', item.col)
.attr('stroke-width', 1.5)
.attr('fill', '#ffffff')
}
box.append('text')
.attr('x', item.x -20)
.attr('y', item.y -20)
.attr('dx', '0.3em')
.attr('dy', '1.1em')
.attr('font-size', 11)
.attr('class', 'point-text origin')
.attr('lid', item.name)
.attr('id', item.id)
.attr('fill', '#ffffff')
.text(item.name)
}
}
}
//绘制 path
const formatPath = (allPoints) => {
let path = d3.path();
allPoints.forEach((item, index) => {
if (index == 0) {
path.moveTo(item.x, item.y);
}
else {
path.lineTo(item.x, item.y);
}
})
return path;
}
//线路点击事件
const handleTable = (d) => {
let cardObj = { ...cardInfo };
cardObj.show = true;
cardObj.top = d3.event.pageY - height-50;//d3.event.pageY可以获取当前鼠标XY 轴的值,具体top 和 left 的值 可根据自己需求定义
cardObj.left = d3.event.pageX - width*2;
cardObj.title = d.name;
setCardInfo(cardObj);
}
//清除弹框
const removeTooltip = () => {
let cardObj = { ...cardInfo };
cardObj.show = false;
setCardInfo(cardObj);
}
//缩放方法
function zoomed() {
let {x, y, k} = d3.event.transform;
currentScale = k;
currentX = x;
currentY = y;
group.transition().duration(50).ease(d3.easeLinear).attr("transform", () => `translate(${currentX + transX * k}, ${currentY + transY * k}) scale(${currentScale})`)
removeTooltip();
}
//计算弹框距离头部距离
const getCardTop = () => {
return cardInfo.top + 20 + "px";
}
//计算弹框距离左侧距离
const getCardLeft = () => {
let left = screenWidth * 0.95;
if (cardInfo.left + 240 + 50 > left) {
return cardInfo.left - 240 - 40 + "px";
}
return cardInfo.left + 40 + "px";
}
如需要渲染右上角线路图例
image.png
//渲染右上角线路
const renderAllStation = () => {
let nameArray = cityLines;
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.name)
.attr('class', 'name-item')
item.each(function (d) {
d3.select(this).append('span').attr('class', 'p_mark').style('background', d.stations[0].col);
d3.select(this).append('span').attr('class', 'p_name').text(d.name);
d3.select(this).on('click', d => {
/**在此处写入你的点击逻辑**/
})
})
}
}
下面是 html 定义
<div class="flow" >
<div className="flowChart" id="flowChart"></div>
<div id="menu" className="menu"></div>
{cardInfo.show &&
<div className="cardInfo" style={{ padding: '10px', top: getCardTop(), left: getCardLeft() }}>
<p className="cardInfoTitle">{cardInfo.title}</p>
</div>
}
</div>
部分css 定义
.flow .flowChart .point {
cursor: pointer;
}
.flow .flowChart .point circle:hover {
r:8;
stroke-width: 2.5;
}
.flow .flowChart .node {
cursor: pointer;
}
.flow .flowChart .node--doing circle {
stroke-width: 2px;
fill: #ff4a3b;
animation: bounce 3s infinite;
position: relative;
}
@keyframes bounce {
0% {
stroke: #d2d391;
stroke-width: 0;
fill: #c55a19;
r:5;
}
50% {
stroke: #faffaf;
stroke-width: 0.2em;
fill: #ff4a3b;
r:8;
}
to {
stroke: #d2d391;
stroke-width: 0;
fill: #c55a19;
r:5;
}
}
//图例样式
.name-box{
position: absolute;
z-index: 100;
right: 10px;
top: 10px;
background: #fff;
border: solid 1px #ccc;
opacity: .9;
padding: 5px;
border-radius: 3px;
-webkit-border-radius: 3px;
color: #666;
}
.subway-col{
display: table-cell;
vertical-align: top;
}
.name-item {
font-size: 14px;
cursor: pointer;
padding: 2px;
}
.p_mark {
position: relative;
bottom: 2px;
display: inline-block;
width: 8px;
height: 8px;
}
.p_name {
padding-left: 5px;
}