采用d3繪制地鐵路線圖如下圖:
地鐵線路圖繪制先要獲取數(shù)據(jù)源创夜,可是獲取百度地鐵圖數(shù)據(jù)。我獲取的地圖數(shù)據(jù)如下
想知道如何獲取百度地鐵數(shù)據(jù)參考另我的一篇文章:獲取百度地鐵線路圖
首先安裝d3乏冀,注意版本我使用的是 "d3": "^5.7.0", 之前安裝過(guò)最新的版本,發(fā)現(xiàn)許多的使用方式發(fā)生了變化 ,后面降級(jí)使用5.7.0版本
在使用頁(yè)面引入d3:
import * as d3 from 'd3'
接下來(lái)可直接使用d3了予借,先定義變量
let width = 540; //畫布寬
let height = 300; //畫布高
let transX = -10 //地圖X軸平移(將畫布原點(diǎn)X軸平移)
let transY = -10; //地圖X軸平移(將畫布原點(diǎn)Y軸平移)
let scaleExtent = [0.1, 100]; //縮放倍率限制
let currentScale = 0.2; //當(dāng)前縮放值
let currentX = 0; //當(dāng)前畫布X軸平移量
let currentY = 0; //當(dāng)前畫布Y軸平移量
let scaleStep = 0.2; //點(diǎn)擊縮放按鈕縮放步長(zhǎng)默認(rèn)0.2倍
let svg = null;// 畫布
var group = null;//定義組并平移
const zoom1 = d3.zoom().scaleExtent(scaleExtent).on("zoom", zoomed);//定義縮放事件
const screenWidth = document.body.clientWidth; //當(dāng)前可視區(qū)域?qū)挾?
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();//渲染所有點(diǎn)
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')
}
}
////渲染所有點(diǎn)
const renderAllPoint = () => {
let point = group.append('g').attr('class', 'point'); //定義站點(diǎn)
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=='赤峰路') { //這里給其中的一個(gè)站做個(gè)不一樣的效果
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;
}
//線路點(diǎn)擊事件
const handleTable = (d) => {
let cardObj = { ...cardInfo };
cardObj.show = true;
cardObj.top = d3.event.pageY - height-50;//d3.event.pageY可以獲取當(dāng)前鼠標(biāo)XY 軸的值轴或,具體top 和 left 的值 可根據(jù)自己需求定義
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();
}
//計(jì)算彈框距離頭部距離
const getCardTop = () => {
return cardInfo.top + 20 + "px";
}
//計(jì)算彈框距離左側(cè)距離
const getCardLeft = () => {
let left = screenWidth * 0.95;
if (cardInfo.left + 240 + 50 > left) {
return cardInfo.left - 240 - 40 + "px";
}
return cardInfo.left + 40 + "px";
}
如需要渲染右上角線路圖例
//渲染右上角線路
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 => {
/**在此處寫入你的點(diǎn)擊邏輯**/
})
})
}
}
下面是 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;
}