d3.js繪制地鐵線路圖

采用d3繪制地鐵路線圖如下圖:


image.png

image.png

地鐵線路圖繪制先要獲取數(shù)據(jù)源创夜,可是獲取百度地鐵圖數(shù)據(jù)。我獲取的地圖數(shù)據(jù)如下

image

想知道如何獲取百度地鐵數(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";
    }

如需要渲染右上角線路圖例


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 => {
                    /**在此處寫入你的點(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;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末昌跌,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子照雁,更是在濱河造成了極大的恐慌避矢,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件囊榜,死亡現(xiàn)場(chǎng)離奇詭異审胸,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)卸勺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門砂沛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人曙求,你說(shuō)我怎么就攤上這事碍庵∮称螅” “怎么了?”我有些...
    開封第一講書人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵静浴,是天一觀的道長(zhǎng)堰氓。 經(jīng)常有香客問(wèn)我,道長(zhǎng)苹享,這世上最難降的妖魔是什么双絮? 我笑而不...
    開封第一講書人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮得问,結(jié)果婚禮上囤攀,老公的妹妹穿的比我還像新娘。我一直安慰自己宫纬,他們只是感情好焚挠,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著漓骚,像睡著了一般蝌衔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蝌蹂,一...
    開封第一講書人閱讀 50,050評(píng)論 1 291
  • 那天胚委,我揣著相機(jī)與錄音,去河邊找鬼叉信。 笑死亩冬,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的硼身。 我是一名探鬼主播硅急,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼佳遂!你這毒婦竟也來(lái)了营袜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤丑罪,失蹤者是張志新(化名)和其女友劉穎荚板,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體吩屹,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡跪另,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了煤搜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片免绿。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖擦盾,靈堂內(nèi)的尸體忽然破棺而出嘲驾,到底是詐尸還是另有隱情淌哟,我是刑警寧澤,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布辽故,位于F島的核電站徒仓,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏誊垢。R本人自食惡果不足惜掉弛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望彤枢。 院中可真熱鬧,春花似錦筒饰、人聲如沸缴啡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)业栅。三九已至,卻和暖如春谬晕,著一層夾襖步出監(jiān)牢的瞬間碘裕,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工攒钳, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留帮孔,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓不撑,卻偏偏與公主長(zhǎng)得像文兢,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子焕檬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351