Echarts-實現一個下鑽僞3d地圖

最近在大屏可視化項目中有個展現地圖的功能,要求:html

  1. 好看排第一
  2. 支持鑽入鑽出
  3. 能單獨展現省份、地市,由於客戶數據有的是省級,有的是市級

獲得這個需求後第一個想到的就是Echarts,因此打開Echarts官方案例查看,找到一個相似的地圖 demojquery

WechatIMG174.png 查看完整代碼看到他是請求一個路徑獲取數據,反問了下路徑看到他是一串帶座標的json數據 cdn.jsdelivr.net/gh/apache/e…git

WechatIMG175.png 到這一步我涉及到知識盲區了,因而百度了下geoJson,發現阿里雲dataV提供一個網站能獲取geoJson數據 datav.aliyun.com/tools/atlas…github

WechatIMG176.png

按照demo教程,嘗試實現:web

1. 先佈局一下並引入所需文件

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>echarts 3d map</title>
    <style>
      * {
        margin: 0;
        padding: 0;
      }

      .echarts-map {
        position: fixed;
        top: 0;
        left: 0;
        width: 100vw;
        height: 100vh;
        background: url("./background.png") no-repeat;
      }
    </style>
  </head>

  <body>
    <div class="echarts-map" id="3dMap"></div>
  </body>
  
  <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
  <script src="https://cdn.bootcdn.net/ajax/libs/echarts/5.1.2/echarts.min.js"></script>
</html>
複製代碼

WechatIMG177.png

2. 地圖渲染

let mapEcharts = null;

if (mapEcharts) {
    mapEcharts.dispose(); // 銷燬實例,實例銷燬後沒法再被使用。
}

// 初始化圖表
mapEcharts = echarts.init(document.getElementById("3dMap"));

// 數據請求
// 加載效果
mapEcharts.showLoading();

$.getJSON('https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json', jsonData => {
    // 註冊地圖
    echarts.registerMap('china', jsonData);

    // 配置
    let options = {
        series: [
            {
                name: "map",
                type: "map", // 地圖
                map: 'china', // 加載註冊的地圖
            },
        ],
    };

    mapEcharts.setOption(options); // 實例配置項與數據

    // 隱藏loading
    mapEcharts.hideLoading();
})
複製代碼

WechatIMG178.png 這裏能夠看到,地圖基本已經出來了,接下來就是美化地圖,在地圖上能看到海南諸島的數據,可是實際上海南諸島的數據已經在右下角,因此我整理了一份數據china.js放在本地,各位大佬能夠在demo中自行獲取。ajax

3. 美化地圖

添加地圖漸變顏色以及選中顏色apache

let options = {
    series: [
      {
        name: "map",
        type: "map", // 地圖
        map: mapName, // 加載註冊的地圖
        selectedMode: false, // 不讓單獨選中
        roam: true, // 開始鼠標事件,scale縮放、move移動
        // 圖形上的文本標籤
        label: {
          show: true,
          color: "#000a3c",
        },
        // 地圖樣式
        itemStyle: {
          // 區域樣式
          areaColor: {
            type: "radial",
            x: 0.5,
            y: 0.5,
            r: 3,
            colorStops: [
              {
                offset: 0,
                color: "rgba(223, 231, 242, 1)", // 0% 處的顏色
              },
              {
                offset: 1,
                color: "rgba(2, 99, 206, 1)", // 100% 處的顏色
              },
            ],
            globalCoord: false, // 缺省爲 false
          },
          borderWidth: 1, // 邊框大小
          borderColor: "rgba(104, 152, 190, 1)", // 邊框樣式
          shadowColor: "rgba(128, 217, 248, 1)", // 陰影顏色
          shadowOffsetX: -2, // 陰影水平方向上的偏移距離
          shadowOffsetY: 2, // 陰影垂直方向上的偏移距離
          shadowBlur: 10, // 文字塊的背景陰影長度
        },
        // 選中狀態下樣式
        emphasis: {
          label: {
            color: "#ffffff",
          },
          itemStyle: {
            areaColor: "#a5d4fe",
          },
        },
      },
    ],
};
複製代碼

WechatIMG179.png

4. 製做僞3d效果

let options = {
    geo: {
      map: mapName, //地圖類型。
      zoom: 1,
      roam: true,
      animation: false,
      itemStyle: {
        // 區域樣式
        areaColor: {
          type: "radial",
          x: 0.5,
          y: 0.5,
          r: 0.8,
          colorStops: [
            {
              offset: 0,
              color: "rgba(147, 235, 248, 1)", // 0% 處的顏色
            },
            {
              offset: 1,
              color: "rgba(2, 99, 206, 1)", // 100% 處的顏色
            },
          ],
          globalCoord: false, // 缺省爲 false
        },
        shadowColor: "#105781", //地圖區域的陰影顏色。
        shadowOffsetX: 0,
        shadowOffsetY: 10,
      },
    },
    series: [
      {
        name: "map",
        type: "map", // 地圖
        map: mapName, // 加載註冊的地圖
        selectedMode: false, // 不讓單獨選中
        roam: true, // 開始鼠標事件,scale縮放、move移動
        // 圖形上的文本標籤
        label: {
          show: true,
          color: "#000a3c",
        },
        // 地圖樣式
        itemStyle: {
          // 區域樣式
          areaColor: {
            type: "radial",
            x: 0.5,
            y: 0.5,
            r: 3,
            colorStops: [
              {
                offset: 0,
                color: "rgba(223, 231, 242, 1)", // 0% 處的顏色
              },
              {
                offset: 1,
                color: "rgba(2, 99, 206, 1)", // 100% 處的顏色
              },
            ],
            globalCoord: false, // 缺省爲 false
          },
          borderWidth: 1, // 邊框大小
          borderColor: "rgba(104, 152, 190, 1)", // 邊框樣式
          shadowColor: "rgba(128, 217, 248, 1)", // 陰影顏色
          shadowOffsetX: -2, // 陰影水平方向上的偏移距離
          shadowOffsetY: 2, // 陰影垂直方向上的偏移距離
          shadowBlur: 10, // 文字塊的背景陰影長度
        },
        // 選中狀態下樣式
        emphasis: {
          label: {
            color: "#ffffff",
          },
          itemStyle: {
            areaColor: "#a5d4fe",
          },
        },
      },
    ],
};
複製代碼

WechatIMG180.png 能夠看到如今已經有一個相似3d的陰影效果json

5. 實現下鑽效果

mapEcharts.on("click", (params) => {
    // 當雙擊事件發生時,清除單擊事件,僅響應雙擊事件
    clearTimeout(timeFn);
    timeFn = setTimeout(function () {
    if (
      allAreaCode.filter((item) => item.name.indexOf(params.name) > -1)[0]
    ) {
      let areaCode = allAreaCode.filter(
        (item) => item.name.indexOf(params.name) > -1
      )[0].code;
      loadMap(
        `https://geo.datav.aliyun.com/areas_v3/bound/${areaCode}_full.json`
      )
        .then((data) => {
          initMap(data, areaCode);
        })
        .catch(() => {
          loadMap(
            `https://geo.datav.aliyun.com/areas_v3/bound/${areaCode}.json`
          )
            .then((res) => {
              initMap(res, areaCode);
            })
            .catch(() => {});
        });

      historyList.push({
        code: areaCode,
        name: params.name,
      });

      let result = [];
      let obj = {};
      for (let i = 0; i < historyList.length; i++) {
        if (!obj[historyList[i].code]) {
          result.push(historyList[i]);
          obj[historyList[i].code] = true;
        }
      }
      historyList = result;
    }
    }, 250);
});
複製代碼

這裏的loadMap爲數據請求封裝,initMap爲渲染地圖,後面在所有代碼中會放出來。數組

這裏主要邏輯是監聽地圖點擊事件,經過篩選的到areaCode,經過areaCode拼接路徑請求數據,這裏的地址能在上面網站獲取,之因此分_full和普通的是由於,地圖數據有可能包含子區域數據,例如廣東省內包含廣州市、佛上市等......markdown

allAreaCode是我花不少時間蒐集全國地區省市區code數據,這份數據可能有部分錯誤,懂爬蟲的大佬能夠直接爬上面網站的數據,處理成個人數據格式就行

地圖渲染後把歷史記錄推動historyList中,後面鑽出用到。

5. 實現鑽出效果

mapEcharts.on("dblclick", (params) => {
    // 當雙擊事件發生時,清除單擊事件,僅響應雙擊事件
    clearTimeout(timeFn);
    if (historyList.length == 1) {
        alert("已經到達最上一級地圖了");
        return;
    }
    let map = historyList.pop();
    if (historyList[historyList.length - 1].code == "china") {
        initMap(china, "china", "中國");
    } else {
        loadMap(
          `https://geo.datav.aliyun.com/areas_v3/bound/${
            historyList[historyList.length - 1].code
          }_full.json`
        ).then((data) => {
            initMap(data, historyList[historyList.length - 1].code);
        }).catch(() => {
        loadMap(
          `https://geo.datav.aliyun.com/areas_v3/bound/${
            historyList[historyList.length - 1].code
          }.json`
        )
          .then((res) => {
            initMap(res, historyList[historyList.length - 1].code);
          })
          .catch(() => {});
      });
    }
});
複製代碼

這裏採用雙擊鑽出方式,雙擊時從歷史記錄中刪除最後一個,而且取刪除後數組的最後一個元素數據來渲染地圖

6. 同步map層與geo層的鼠標事件

若是開啓鼠標事件,會發現不管scale或者move,map層和geo層都是分開的,例如:

WechatIMG181.png 這種狀況咱們能夠經過事件監聽來同步他們的數據

mapEcharts.on("georoam", (params) => {
  let option = mapEcharts.getOption(); //得到option對象
  if (params.zoom != null && params.zoom != undefined) {
    //捕捉到縮放時
    option.geo[0].zoom = option.series[0].zoom; //下層geo的縮放等級跟着上層的geo一塊兒改變
    option.geo[0].center = option.series[0].center; //下層的geo的中心位置隨着上層geo一塊兒改變
  } else {
    //捕捉到拖曳時
    option.geo[0].center = option.series[0].center; //下層的geo的中心位置隨着上層geo一塊兒改變
  }
  mapEcharts.setOption(option); //設置option
});
複製代碼

7. 最終代碼與效果

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>echarts 3d map</title>
    <style>
      * {
        margin: 0;
        padding: 0;
      }

      .echarts-map {
        position: fixed;
        top: 0;
        left: 0;
        width: 100vw;
        height: 100vh;
        background: url("./background.png") no-repeat;
      }
    </style>
  </head>

  <body>
    <div class="echarts-map" id="3dMap"></div>
  </body>
  <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
  <script src="https://cdn.bootcdn.net/ajax/libs/echarts/5.1.2/echarts.min.js"></script>
  <script src="./china.js"></script>
  <script src="./geoAtlasJson.js"></script>
  <script>
    let mapEcharts = null;
    let historyList = [];
    let timeFn = null;

    if (mapEcharts) {
      mapEcharts.dispose(); // 銷燬實例,實例銷燬後沒法再被使用。
    }
    // 初始化圖表
    mapEcharts = echarts.init(document.getElementById("3dMap"));

    historyList.push({
      code: "china",
      name: "中國",
    });

    // 加載效果
    mapEcharts.showLoading();

    initMap(china, "china", "中國");

    mapEcharts.on("click", (params) => {
      // 當雙擊事件發生時,清除單擊事件,僅響應雙擊事件
      clearTimeout(timeFn);
      timeFn = setTimeout(function () {
        if (
          allAreaCode.filter((item) => item.name.indexOf(params.name) > -1)[0]
        ) {
          let areaCode = allAreaCode.filter(
            (item) => item.name.indexOf(params.name) > -1
          )[0].code;
          loadMap(
            `https://geo.datav.aliyun.com/areas_v3/bound/${areaCode}_full.json`
          )
            .then((data) => {
              initMap(data, areaCode);
            })
            .catch(() => {
              loadMap(
                `https://geo.datav.aliyun.com/areas_v3/bound/${areaCode}.json`
              )
                .then((res) => {
                  initMap(res, areaCode);
                })
                .catch(() => {});
            });

          historyList.push({
            code: areaCode,
            name: params.name,
          });

          let result = [];
          let obj = {};
          for (let i = 0; i < historyList.length; i++) {
            if (!obj[historyList[i].code]) {
              result.push(historyList[i]);
              obj[historyList[i].code] = true;
            }
          }
          historyList = result;
        }
      }, 250);
    });

    mapEcharts.on("dblclick", (params) => {
      // 當雙擊事件發生時,清除單擊事件,僅響應雙擊事件
      clearTimeout(timeFn);
      if (historyList.length == 1) {
        alert("已經到達最上一級地圖了");
        return;
      }
      let map = historyList.pop();
      console.log(historyList[historyList.length - 1])
      if (historyList[historyList.length - 1].code == "china") {
        initMap(china, "china", "中國");
      } else {
        loadMap(
          `https://geo.datav.aliyun.com/areas_v3/bound/${
            historyList[historyList.length - 1].code
          }_full.json`
        )
          .then((data) => {
            initMap(data, historyList[historyList.length - 1].code);
          })
          .catch(() => {
            loadMap(
              `https://geo.datav.aliyun.com/areas_v3/bound/${
                historyList[historyList.length - 1].code
              }.json`
            )
              .then((res) => {
                initMap(res, historyList[historyList.length - 1].code);
              })
              .catch(() => {});
          });
      }
    });

    mapEcharts.on("georoam", (params) => {
      let option = mapEcharts.getOption(); //得到option對象
      if (params.zoom != null && params.zoom != undefined) {
        //捕捉到縮放時
        option.geo[0].zoom = option.series[0].zoom; //下層geo的縮放等級跟着上層的geo一塊兒改變
        option.geo[0].center = option.series[0].center; //下層的geo的中心位置隨着上層geo一塊兒改變
      } else {
        //捕捉到拖曳時
        option.geo[0].center = option.series[0].center; //下層的geo的中心位置隨着上層geo一塊兒改變
      }
      mapEcharts.setOption(option); //設置option
    });

    // 地圖數據請求
    async function loadMap(url, pathName) {
      return await $.getJSON(url);
    }

    // 地圖初始化
    function initMap(mapData, mapName) {
      // 註冊地圖
      echarts.registerMap(mapName, mapData);

      // 配置項
      let options = {
        geo: {
          map: mapName, //地圖類型。
          zoom: 1,
          roam: true,
          animation: false,
          itemStyle: {
            // 區域樣式
            areaColor: {
              type: "radial",
              x: 0.5,
              y: 0.5,
              r: 0.8,
              colorStops: [
                {
                  offset: 0,
                  color: "rgba(147, 235, 248, 1)", // 0% 處的顏色
                },
                {
                  offset: 1,
                  color: "rgba(2, 99, 206, 1)", // 100% 處的顏色
                },
              ],
              globalCoord: false, // 缺省爲 false
            },
            shadowColor: "#105781", //地圖區域的陰影顏色。
            shadowOffsetX: 0,
            shadowOffsetY: 10,
          },
        },
        series: [
          {
            name: "map",
            type: "map", // 地圖
            map: mapName, // 加載註冊的地圖
            selectedMode: false, // 不讓單獨選中
            roam: true, // 開始鼠標事件,scale縮放、move移動
            // 圖形上的文本標籤
            label: {
              show: true,
              color: "#000a3c",
            },
            // 地圖樣式
            itemStyle: {
              // 區域樣式
              areaColor: {
                type: "radial",
                x: 0.5,
                y: 0.5,
                r: 3,
                colorStops: [
                  {
                    offset: 0,
                    color: "rgba(223, 231, 242, 1)", // 0% 處的顏色
                  },
                  {
                    offset: 1,
                    color: "rgba(2, 99, 206, 1)", // 100% 處的顏色
                  },
                ],
                globalCoord: false, // 缺省爲 false
              },
              borderWidth: 1, // 邊框大小
              borderColor: "rgba(104, 152, 190, 1)", // 邊框樣式
              shadowColor: "rgba(128, 217, 248, 1)", // 陰影顏色
              shadowOffsetX: -2, // 陰影水平方向上的偏移距離
              shadowOffsetY: 2, // 陰影垂直方向上的偏移距離
              shadowBlur: 10, // 文字塊的背景陰影長度
            },
            // 選中狀態下樣式
            emphasis: {
              label: {
                color: "#ffffff",
              },
              itemStyle: {
                areaColor: "#a5d4fe",
              },
            },
          },
        ],
      };
      mapEcharts.setOption(options); // 實例配置項與數據

      // 隱藏loading
      mapEcharts.hideLoading();
    }
  </script>
</html>
複製代碼

demo github地址:github.com/ljnMeow/ech…

效果圖.gif

8. 最後的話

在製做這份demo時實際上是花了挺多時間的,最開始是想經過echarts-gl直接實現一個3d地圖,但經過官方文檔來看echarts-glmap3d好像不支持漸變背景,因此就直接放棄了,其次就是蒐集省市區全部數據花費的時間,不得不感嘆咱們大華夏是真的地大物博。

最後但願這份教程對各位有幫助,以爲ok的話麻煩留個贊👍,或者github給點star🌟也行🙏。

祝各位大佬步步高昇🧨

相關文章
相關標籤/搜索