d3.js绘制地铁线路图

采用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;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。