svg实现地铁线路图

3,505 阅读10分钟

简介

最近学习了svg,想着使用svg实现地铁线路图

insta.gif 其中黄色是1号线,蓝色是2号线,橙色是3号线

实现:react+svg+数据结构-图。

考虑范围包括了每站时间,但未包括了换站时间。考虑到换站时间可以加到每2个交换站的路程里

功能

功能:选择2个地铁站,标出最短路程。

求最少换站路线,暂未做

实现思路

  1. 简化问题,先将所有地铁站分2类,交换站和非交换站。那么交换站可以充当图中的。那么从a=>b, 变成a=>交换站=>交换站=>b的问题,需要写死的是非交换站(a,b)能到达的交换站(下面的adjcent数组), 其中a=>交换站 和b=>交换站 相对静止,但是我这里也考虑到了非交换站到交换站需要的时间(time)

地铁线路图

image.png

image.png

  1. 首先根据每条地铁图数据绘制出地铁线路图,并添加上点击事件,这里要处理好地铁线路图的数据,数据需要相对准确,因为后面需要计算出最短路径。

image.png

image.png

  1. 求最短距离,使用的是Floyd最短路算法(全局/多源最短路)。 其中原理:计算a->b的最短路径,遍历所有,查找是否有最捷径路径 a->x x->b
for(k=1;k<=n;k++) 
    for(i=1;i<=n;i++) 
        for(j=1;j<=n;j++) 
        if(e[i][j]>e[i][k]+e[k][j]) // i->j  i->k  k->j
            e[i][j]=e[i][k]+e[k][j]; 

然而拿到最短路程后,但是并未拿到路程,拿到的是比如,a点到所有点的最短路程。你们可以思考一下如果获取最短路径。

大概长这样

image.png

  1. 求最短路径 使用一个对象,存储每次找到较短路径。 changeRodePath[${is}to${js}] = [ [is, ks], [ks, js], ]
  function getAllPointShortest(n, e) {
    let changeRodePath = {};
    for (let k = 0; k < n; k++) {
      for (let i = 0; i < n; i++) {
        for (let j = 0; j < n; j++) {
          if (e[i][j] > e[i][k] + e[k][j]) {
            e[i][j] = e[i][k] + e[k][j];
            console.log("-------------------------");
            const is = changeStation[i];
            const ks = changeStation[k];
            const js = changeStation[j];
            changeRodePath[`${is}to${js}`] = [
              [is, ks],
              [ks, js],
            ];
            console.log(changeStation[i], changeStation[j]);
            console.log(changeStation[i], changeStation[k]);
            console.log(changeStation[k], changeStation[j]);
                // 2_2 2_5
                 //2_2 1_2
                   //1_2 2_5
          }
        }
      }
    }
    setChangeRodePath(changeRodePath);
    return e;
  }

当选中2个站时,先取出adjacent,然后求出最短路程,

         let path = {};
        adjacent0.forEach((p0,i1) => {
          adjacent1.forEach((p1,i2) => {
            const index0 = changeStation.indexOf(p0);
            const index1 = changeStation.indexOf(p1);
            let t=time0[i1]+time1[i2]
            if ((rodePath[index0][index1]+t) < minPath) {
              minPath = rodePath[index0][index1];
              path = { p0, p1};
            }
          });
        });

具体多少不重要,重要的是通过 let pathm = changeRodePath[${path.p0}to${path.p1}],递归查找是否有更短的捷径,因为,2_1 =>3_9 的路径是:2_1 =>1_3=>1_5=>1_8,所以不一定有捷径a->c c—b, 可能是 a->c c->b, 然后发现有更短路径,c->d d->b,那么a-b 路程就变成了a->c->d->b。回到正题,递归之后就能取到最短路径了,然后通过2个交换点取得路径。

没有就更简单了

5.取对应的line,去渲染,这里分2类,交换站之间的路径(最短路径),头和尾。然后分别渲染polyline(使用对应line 的颜色)

function getPl(item, attr, listen) {
    return (
      <g {...attr} {...listen}>  
        <polyline   //绘制line
          {...item}
          fill="none"
          color={item.colorH}
          strokeWidth="5"
          strokeLinecap="round"
          strokeLinejoin="round"
        />
        {item.usePointn.map((point) => { // line 上的站
          return (
            <use
              x={point.x}
              onClick={() => choosePoint(point)}
              y={point.y}
              fill={point.color}
              href="#point"
            ></use>
          );
        })}
      </g>
    );
  }

代码准备

// 上图所示,数据随便造,需要合理时间,不然得到的路程奇奇怪怪的

代码部分

html

  <div style={{ width: "80vw", height: "100vh" }}>
        <svg
          id="passWay"
          viewBox="0 0 800 600"
          xmlns="http://www.w3.org/2000/svg"
        >
          <defs>
            <g id="point">
              <circle r="4"></circle>
              <circle r="3" fill="#fff"></circle>
            </g>          
          </defs>
        // 所有地铁线路图
          {polyline.map((item) => {
            return getPl(
              item,
              {},
              {
                onMouseEnter: (e) => onMouseEnterShow(e, item),
                onMouseOut: () => {
                  clearTimeout(t1.current);
                  t1.current = null;
                },
              }
            );
          })}
          // mask
          { choosePoints.length==2 && (
            <rect
              x="0"
              y="0"
              width={"100%"}
              height={"100%"}
              fillOpacity={0.9}
              fill="white"
            ></rect>
          )}
              // 最短路程
          {choosePoints && choosePoints.length==2 && showReduLine.map(line=>{
            return  getPl(line, {}, {})
          })
         }
        </svg>
      </div>

通过line 获取 polyline

  function getLineP(line) {
    const usePointn = [];
    let path = "";
    line.points.forEach((item, index) => {
      const { x, y, isStart, isChange, isEnd } = item;

      usePointn.push({ ...item, color: line.color });
      if (index == 0) {
        path = `${x},${y} `;
      } else {
        path += `${x},${y} `;
      }
    });
    const polylinen = {
      usePointn,
      stroke: line.color,
      ...line,
      pointStation: line.points,
      points: path,
    };
    return polylinen;
  }

选出2站绘制路程

  function comfirPath(point0, point1, p0, p1, pathm) {
   
    let pShow0= getLines(point0,p0)
    let pShow1= getLines(point1,p1)
    let pathsCenter=[]
    if (pathm) {
      function recursion(pathm){
         pathm.map(([p0,p1])=>{
          let pathn = changeRodePath[`${p0}to${p1}`];
          if(pathn){
            recursion(pathn)
          }else{
            // 中间的line 不用按顺序
            pathsCenter.push(getChangeStationLine(p0,p1))
          }
       })
      }
      recursion(pathm)
    }else{
      pathsCenter=[getChangeStationLine(p0,p1)]
    }
    const pyAll= [pShow0,pShow1,...pathsCenter].map(line=>{
      const py= getLineP({
        points:line,
      })
      py.stroke=line.color
      return py
    })
    setShowReduLine(pyAll); // 绘制
  }

完整代码

import { useEffect, useRef, useState } from "react";
import "./App.css";
import { lineWidth, lines, changeStation, e } from "./dataEm";

function App() {
  const [polyline, setPolyline] = useState([]);
  const [showHover, setShowHover] = useState(false);
  const [showReduLine, setShowReduLine] = useState([]);
  const [choosePoints, setChoosePoints] = useState([]);
  useEffect(() => {
    drawPath(lines);
  }, []);
  const [changeRodePath, setChangeRodePath] = useState({});
  function getAllPointShortest(n, e) {
    let changeRodePath = {};
    for (let k = 0; k < n; k++) {
      for (let i = 0; i < n; i++) {
        for (let j = 0; j < n; j++) {
          if (e[i][j] > e[i][k] + e[k][j]) {
            e[i][j] = e[i][k] + e[k][j];
            console.log("-------------------------");
            const is = changeStation[i];
            const ks = changeStation[k];
            const js = changeStation[j];
            changeRodePath[`${is}to${js}`] = [
              [is, ks],
              [ks, js],
            ];
            console.log(changeStation[i], changeStation[j]);
            console.log(changeStation[i], changeStation[k]);
            console.log(changeStation[k], changeStation[j]);
          }
        }
      }
    }
    setChangeRodePath(changeRodePath);
    console.log(e,'e');
    
    return e;
  }
  const [rodePath, setRodePath] = useState([]);
  useEffect(() => {
    const ne = getAllPointShortest(
      changeStation.length,
      JSON.parse(JSON.stringify(e)),
      e
    );
    setRodePath(ne);
  }, []);
  function getLineP(line) {
    const usePointn = [];
    let path = "";
    line.points.forEach((item, index) => {
      const { x, y, isStart, isChange, isEnd } = item;

      usePointn.push({ ...item, color: line.color });
      if (index == 0) {
        path = `${x},${y} `;
      } else {
        path += `${x},${y} `;
      }
    });
    const polylinen = {
      usePointn,
      stroke: line.color,
      ...line,
      pointStation: line.points,
      points: path,
    };
    return polylinen;
  }
  function drawPath(lines) {
    const points = [];
    const edges = [];
    const polylinen = lines.map(getLineP);
    setPolyline(polylinen);
  }
  function drawChangeStation({ x, y }) {}
  const t1 = useRef(-1);
  function onMouseEnterShow(e, item) {
    t1.current = setTimeout(() => {
      setShowHover(item);
    }, 100);
  }
  function returnStart(){
    setChoosePoints([])
    setShowReduLine([])
  }
  function choosePoint(point) {
    console.log(point, "point");
    let n =[...choosePoints]
    n.push(point);
    setChoosePoints(n)
    const point0 = n[0];
    if (n.length == 2) {
        let pl0=point0.adjacent.length==0
        let pl1=point.adjacent.length==0
        const adjacent0 = pl0? [point0.id]:point0.adjacent
        const time0 = pl0? [0]:point0.time
        const adjacent1 = pl1? [point.id]:point.adjacent
        const time1 = pl1? [0]:point.time
        let minPath = Infinity;
        let path = {};
        console.log(adjacent0, adjacent1, "adjacent0");
        adjacent0.forEach((p0,i1) => {
          adjacent1.forEach((p1,i2) => {
            const index0 = changeStation.indexOf(p0);
            const index1 = changeStation.indexOf(p1);
            let t=time0[i1]+time1[i2]
            if ((rodePath[index0][index1]+t) < minPath) {
              minPath = rodePath[index0][index1]+t
              path = {
                p0,
                p1,
              };
            }
          });
        });
        console.log(changeRodePath, "changeRodePath");
        let pathm = changeRodePath[`${path.p0}to${path.p1}`];

        console.log(minPath, path, "minPath");
        comfirPath(point0, point, path.p0, path.p1, pathm);
        return;
      }
  }
  // 根据2点 获取截断的line, 按顺序返回
  function getLineOrder(l0,index0,index0c){
    let pShow0=[]
    if(index0>index0c){
      [index0,index0c]=[index0c,index0]
    }
    pShow0=l0.points.slice(index0,index0c+1)
    pShow0.color=l0.colorH
    return pShow0
  }
  //根据point0和交换站id,获取截断的line, 按顺序返回  
  function getLines(point0,p0){
    let l0 = lines[point0.parentId-1]
    let index0 =getPointInLineIndex(l0, point0.id)
    let index0c =getPointInLineIndex(l0, p0)
    return getLineOrder(l0,index0,index0c)
  }
  //id获取交换站
  function getChangeStationByStationId(id){
    let changeStation=null
    lines.find(line=>{
      return line.points.find((item,index)=>{
        if(item.id==id){
          changeStation=item
          return true
        }
      })
    })
      return changeStation
  }
  // 获取2个交换站的 line 
 function getChangeStationLine(p0,p1){

  let sameParentId=getSameParentId(getChangeStationByStationId(p0), getChangeStationByStationId(p1))
  const line=lines[sameParentId-1]
  let index0= line.points.findIndex(item=>item.id==p0)
  let index1= line.points.findIndex(item=>item.id==p1)
 return  getLineOrder(line,index0,index1)
 }
 // 站1 站2 交换站1 交换站2
  function comfirPath(point0, point1, p0, p1, pathm) {
    // 换站点一样,是同一条线
    if(p0==p1 && point0.parentId == point1.parentId){
      let l0 = lines[point0.parentId-1]
      let index0 =getPointInLineIndex(l0, point0.id)
      let index1 =getPointInLineIndex(l0, point1.id)
      let indexChangeStation =getPointInLineIndex(l0, p0)
      
      // 获取index  判断是否在同一侧
      if((index0<indexChangeStation && index1<indexChangeStation)|| (index0>indexChangeStation && index1>indexChangeStation)){
        const showLine = getLines(point0,point1.id)
        setShowLineByLinePath([showLine])
        return
      }
    }
    let pShow0= getLines(point0,p0)
    let pShow1= getLines(point1,p1)
    let pathsCenter=[]
    if (pathm) {
      function recursion(pathm){
         pathm.map(([p0,p1])=>{
          let pathn = changeRodePath[`${p0}to${p1}`];
          if(pathn){
            recursion(pathn)
          }else{
            // 中间的line 不用按顺序
            pathsCenter.push(getChangeStationLine(p0,p1))
          }
       })
      }
      recursion(pathm)
    }else{
      pathsCenter=[getChangeStationLine(p0,p1)]
    }
    // const pyAll= [pShow0,pShow1,...pathsCenter].map(line=>{
    //   const py= getLineP({
    //     points:line,
    //   })
    //   py.stroke=line.color
    //   return py
    // })
    // setShowReduLine(pyAll);
    setShowLineByLinePath([pShow0,pShow1,...pathsCenter])
  }
  function setShowLineByLinePath(pyAll){
   const showReduLine=pyAll.map(line=>{
      const py= getLineP({
        points:line,
      })
      py.stroke=line.color
      return py
    })
    setShowReduLine(showReduLine);
  }
  function getPointInLineIndex(line, id) {
    return line.points.findIndex((item) => id == item.id);
  }
  function getSameParentId({parentId,parentId2}, {parentId:p,parentId2:p2}) {
   let parentResult=[parentId,parentId2].find(item=>{
       return [p,p2].some(item2=>{
          return item==item2
        })
    })
    return parentResult
  }
  function getPl(item, attr, listen) {
    return (
      <g {...attr} {...listen}>
        <polyline
          {...item}
          fill="none"
          color={item.colorH}
          strokeWidth="5"
          strokeLinecap="round"
          strokeLinejoin="round"
        />
        {item.usePointn&&item.usePointn.map((point) => {
          return (
            <use
              x={point.x}
              onClick={() => choosePoint(point)}
              y={point.y}
              fill={point.color}
              href="#point"
            ></use>
          );
        })}
      </g>
    );
  }
  function showLine(line) {
    const p = polyline.find((item) => item.id == line.id);
    setShowHover(p);
  }
  return (
    <div style={{ display: "flex" }}>
      <div style={{ width: "80vw", height: "100vh" }}>
        <svg
          id="passWay"
          viewBox="0 0 800 600"
          xmlns="http://www.w3.org/2000/svg"
        >
          <defs>
            <g id="point">
              <circle r="4"></circle>
              <circle r="3" fill="#fff"></circle>
            </g>
            <symbol id="change" viewBox="0 0 1024 1024">
              <path
                d="M784.673811 364.080302a207.891321 207.891321 0 0 0-147.958339-61.304755H417.173736v-41.984c0-13.389283-9.177358-18.412679-20.518642-11.148075l-101.047547 65.226868c-11.283321 7.322566-11.186717 19.030943 0.231849 26.102339l100.622491 62.309434c11.341283 7.071396 20.711849 1.874113 20.711849-11.53449V349.898868h219.599698c89.33917 0 162.023849 72.742642 162.023849 162.023849 0 14.819019-1.951396 29.464151-5.873509 43.51034-3.497057 12.577811 3.864151 25.542038 16.441962 28.981132 2.105962 0.676226 4.211925 0.966038 6.317887 0.966037 10.336604 0 19.803774-6.878189 22.721207-17.272754 5.023396-18.219472 7.515774-37.11517 7.515774-56.146114a208.084528 208.084528 0 0 0-61.246793-147.881056zM745.703849 682.988679l-100.564528-62.309434c-11.437887-7.071396-20.808453-1.912755-20.808453 11.534491v41.752151H387.32317c-89.33917 0-162.023849-72.684679-162.023849-162.023849a161.521509 161.521509 0 0 1 25.831849-87.79351 23.687245 23.687245 0 0 0-6.974793-32.671396 23.648604 23.648604 0 0 0-32.613434 7.032755 208.664151 208.664151 0 0 0-33.424905 113.470792c0 55.894943 21.75517 108.408755 61.266113 147.919698a207.717434 207.717434 0 0 0 147.95834 61.304755h237.046339v41.984c0 13.447245 9.235321 18.451321 20.518642 11.148076l101.047547-65.284831c11.264-7.264604 11.167396-18.972981-0.25117-26.063698zM512 28.981132c-266.76166 0-483.018868 216.257208-483.018868 483.018868s216.257208 483.018868 483.018868 483.018868 483.018868-216.257208 483.018868-483.018868-216.257208-483.018868-483.018868-483.018868z m0 917.735849C271.920302 946.716981 77.283019 752.079698 77.283019 512S271.920302 77.283019 512 77.283019 946.716981 271.920302 946.716981 512 752.079698 946.716981 512 946.716981z"
                fill="#838383"
                p-id="10791"
                data-spm-anchor-id="a313x.search_index.0.i5.5b9b3a81AsGYCG"
                className="selected"
              ></path>
            </symbol>
          </defs>

          {polyline.map((item) => {
            return getPl(
              item,
              {},
              {
                onMouseEnter: (e) => onMouseEnterShow(e, item),
                onMouseOut: () => {
                  clearTimeout(t1.current);
                  t1.current = null;
                },
              }
            );
          })}
          { choosePoints.length==2 && (
            <rect
              x="0"
              y="0"
              width={"100%"}
              height={"100%"}
              fillOpacity={0.9}
              fill="white"
            ></rect>
          )}
          {choosePoints && choosePoints.length==2 && showReduLine.map(line=>{
            return  getPl(line, {}, {})
          })
         }
        </svg>
      </div>
      <div style={{ flex: 1, minWidth: "100px" }}>
        {lines.map((item) => {
          return <div onClick={() => showLine(item)}>{item.lineName}号线</div>;
        })}
        <div>
          <input type="radio"/>时间最短
          <input type="radio"/>换站最少
        </div>
        <div onClick={returnStart}>重选</div>
      </div>
    </div>
  );
}

export default App;

dataEm文件

export const pace = 10;
export const lineWidth = 8;
export const pointR = 4;
export const pointSR = 3;
export const changeStation = ["1_2", "1_3", "1_5", "1_8", "2_2", "2_5"];
export const changeStationPoint = ["1_2", "1_3", "1_5", "1_8", "2_2", "2_5"];
export const e = [
  [0, 4, 1000, 1000, 2, 16],
  [4, 0, 7, 1000, 1, 10],
  [1000, 7, 0, 13, 1000, 6],
  [1000, 1000, 13, 0, 1000, 12],
  [2, 1, 1000, 1000, 0, 1000],
  [16, 10, 6, 12, 1000, 0],
];
export const lines = [
  {
    lineName: 1,
    color: "rgba(250, 228, 28,.8)",
    colorH: "rgba(250, 228, 28,1)",
    id: 1,
    points: [ {
        x: 45,
        y: 5,
        isStart: true,
        parentId: 1,
        station: "1_0",
        id: "1_0",
        adjacent: ["1_2"],
        time: [8],
      },
      {
        x: 5,
        y: 5,
        isStart: true,
        parentId: 1,
        station: "1_1",
        id: "1_1",
        adjacent: ["1_2"],
        time: [3],
      },
      {
        x: 50,
        y: 50,
        isChange: true,
        changeStationTime: 2,
        parentId: 1,
        parentId2: 2,
        id: "1_2",
        station: "1_2",
        adjacent: [],
      },
      {
        x: 100,
        y: 100,
        isChange: true,
        changeStationTime: 2,
        parentId: 1,
        parentId2: 3,
        id: "1_3",
        station: "1_3",
        adjacent: [],
      },
      {
        x: 100,
        y: 150,
        parentId: 1,
        id: "1_4",
        station: "1_4",
        adjacent: ["1_3", "1_5"],
        time: [3, 4],
      },
      {
        x: 150,
        y: 200,
        isChange: true,
        changeStationTime: 4,
        parentId: 1,
        parentId2: 2,
        station: "1_5",
        id: "1_5",
        adjacent: [],
      },
      {
        x: 130,
        y: 300,
        parentId: 1,
        station: "1_6",
        id: "1_6",
        adjacent: ["1_5", "1_8"],
        time: [6, 7],
      },
      {
        x: 120,
        y: 350,
        parentId: 1,
        station: "1_7",
        id: "1_7",
        adjacent: ["1_8", "1_5"],
        time: [2, 11],
      },
      {
        x: 90,
        y: 380,
        parentId: 1,
        parentId2: 3,
        isChange: true,
        changeStationTime: 5,
        station: "1_8",
        id: "1_8",
        adjacent: [],
      },
      {
        x: 80,
        y: 420,
        parentId: 1,
        station: "1_9",
        id: "1_9",
        adjacent: ["1_8"],
        time: [4],
      },
      {
        x: 40,
        y: 440,
        parentId: 1,
        station: "1_10",
        id: "1_10",
        adjacent: ["1_8"],
        time: [6],
      },
    ],
  },
  {
    lineName: 2,
    color: "rgba(25, 117, 209,.8)",
    colorH: "rgba(25, 117, 209,1)",
    id: 2,
    points: [
      {
        x: 200,
        y: 105,
        isStart: true,
        parentId: 2,

        station: "2_1",
        id: "2_1",
        adjacent: ["2_2"],
        time: [3],
      },
      {
        x: 130,
        y: 80,
        isChange: true,
        changeStationTime: 4,
        parentId: 2,
        parentId2: 3,

        station: "2_2",
        id: "2_2",
        adjacent: [],
      },
      {
        x: 50,
        y: 50,
        isChange: true,
        changeStationTime: 2,
        parentId: 2,
        parentId2: 1,

        station: "1_2",
        id: "1_2",
        adjacent: [],
      },
      {
        x: 10,
        y: 150,
        parentId: 2,

        station: "2_4",
        id: "2_4",
        adjacent: ["1_2", "2_5"],
        time: [8, 8],
      },
      {
        x: 50,
        y: 250,
        isChange: true,
        changeStationTime: 7,
        parentId: 2,
        parentId2: 3,

        station: "2_5",
        id: "2_5",
        adjacent: [],
      },
      {
        x: 100,
        y: 220,
        parentId: 2,

        station: "2_6",
        id: "2_6",
        adjacent: ["2_5", "1_5"],
        time: [3, 3],
      },
      {
        x: 150,
        y: 200,
        isChange: true,
        changeStationTime: 4,
        parentId: 2,
        parentId2: 1,

        station: "1_5",
        id: "1_5",
        adjacent: [],
      },
      {
        x: 250,
        y: 200,
        parentId: 2,
        isEnd: true,

        station: "2_8",
        id: "2_8",
        adjacent: ["1_5"],
        time: [6],
      },
    ],
  },
  {
    lineName: 3,
    color: "rgba(236, 117, 70,.8)",
    colorH: "rgba(236, 117, 70,1)",
    id: 3,
    points: [
      {
        x: 200,
        y: 10,
        isStart: true,
        parentId: 3,

        station: "3_1",
        id: "3_1",
        adjacent: ["2_2"],
        time: [6],
      },
      {
        x: 130,
        y: 80,
        parentId: 3,
        parentId2: 1,
        isChange: true,
        changeStationTime: 4,

        station: "2_2",
        id: "2_2",
        adjacent: [],
      },
      {
        x: 100,
        y: 100,
        isChange: true,
        changeStationTime: 2,
        parentId: 3,
        parentId2: 1,

        station: "1_3",
        id: "1_3",
        adjacent: [],
      },
      {
        x: 60,
        y: 180,
        parentId: 3,

        station: "3_4",
        id: "3_4",
        adjacent: ["1_3", "2_5"],
        time: [4, 6],
      },
      {
        x: 50,
        y: 250,
        isChange: true,
        changeStationTime: 7,
        parentId: 3,
        parentId2: 2,

        station: "2_5",
        id: "2_5",
        adjacent: [],
      },
      {
        x: 50,
        y: 280,
        parentId: 3,

        station: "3_6",
        id: "3_6",
        adjacent: ["1_8", "2_5"],
        time: [10, 2],
      },
      {
        x: 80,
        y: 300,
        parentId: 3,

        station: "3_7",
        id: "3_7",
        adjacent: ["1_8", "2_5"],
        time: [4, 8],
      },
      {
        x: 85,
        y: 340,
        parentId: 3,

        station: "3_8",
        id: "3_8",
        adjacent: ["1_8", "2_5"],
        time: [2, 10],
      },
      {
        x: 90,
        y: 380,
        parentId: 3,
        parentId2: 1,
        isChange: true,

        station: "1_8",
        id: "1_8",
        adjacent: [],
      },
      {
        x: 120,
        y: 380,
        parentId: 3,

        station: "3_10",
        id: "3_10",
        adjacent: ["1_8"],
        time: [2],
      },
      {
        x: 150,
        y: 380,
        isEnd: true,
        parentId: 3,

        station: "3_11",
        id: "3_11",
        adjacent: ["1_8"],
        time: [4],
      },
    ],
  },
  // 3:'#ec7546',
  // 4:'#19a319',
  // 5:"#ff1919",
  // 6:'#96356c',
  // 7:'#52ac8b',
  // 8:'#19aad1',
  // 9:'#a3cd67',
  // 13:'#b0c055',
  // 14:'#803036',
  // 18:"#1967bb",
  // 21:"#22307a",
  // 22:'#8c443d',
];

参考: 1.# [数据结构拾遗]图的最短路径算法