您现在的位置是:首页 >技术杂谈 >【vue】Echarts3D地图下钻网站首页技术杂谈

【vue】Echarts3D地图下钻

顽皮宝 2023-05-15 12:00:03
简介【vue】Echarts3D地图下钻

需求分析

1.gif
地图下钻是一个非常常见的功能需求,本篇文章会细致讲解如何在Vue3中使用Eharts-gl渲染出3D地图,并且实现地图下钻和返回上级地图的完整功能。
github项目demo地址:点击这里

注意此项目为vue3版本,vue2版本在仓库分支里
给个星星吧!!不定期更新此demo,一般只更新vue3的版本,2版本自行迁移即可。

demo依赖版本确认:

"axios": "^1.3.5",
"echarts": "^5.2.2",
"echarts-gl": "^2.0.9",
"vue": "^3.2.47"

我把需求拆分成两个主要功能模块:初始化地图更新地图功能构思:

  1. 初始化地图
  • 初始化DOM节点
  • initmap初始化地图 -> getMapJSON 获取地图JSON并且初始化地图配置项data数据 -> getOption获得地图总配置项 -> updateMap更新配置项
  • 监听点击事件(地图下钻)
  1. 更新地图
  • updateMap
  • 点击区域下钻省市区地图
  • backMap返回上级地图

updateMap函数其实就是echarts.setOption,把它抽离成单独函数是为了灵活更新配置项,当我们修改配置线例如请求接口返回新配置后,调用updateMap去更新地图

  • setIntervalOptionsRegionsMap 高亮区域动画

需求实现

功能介绍

点击区域:初始化地图 => 给地图添加点击事件 => 拿到用户点击的区域名称 => 保存用户点击的区域做历史记录 => 请求接口获取到该区域的JSON数据 => 重新渲染
点击返回图标: 遍历历史记录 => 弹出当前历史记录的地图信息 => 找到要返回上一级的地图信息 => 重新渲染地图
自动高亮展示区域setInterval定时器修改配置项 => 更新配置项 => 渲染新配置项

程序运行流程图

关于使用用户内存保存历史记录,还是请求接口保存的问题:

image.pngimage.png

总体功能

1.地图下钻和返回上级功能
2.地图自动高亮显示区块功能

总体代码

<template>
  <div class="investment-screen">
    <svg
      style="position: absolute; left: 20px; top: 20px; cursor: pointer"
      @click="backMap"
      t="1681180771137"
      class="icon"
      viewBox="0 0 1024 1024"
      version="1.1"
      xmlns="http://www.w3.org/2000/svg"
      p-id="3427"
      width="200"
      height="200"
    >
      <path
        d="M426.666667 384V213.333333l-298.666667 298.666667 298.666667 298.666667v-174.933334c213.333333 0 362.666667 68.266667 469.333333 217.6-42.666667-213.333333-170.666667-426.666667-469.333333-469.333333z"
        p-id="3428"
        fill="#ffffff"
      ></path>
    </svg>
    <div class="map-chart" id="mapEchart"></div>
  </div>
</template>

<script lang="ts" setup>
import * as echarts from "echarts";
import "echarts-gl"; //3D地图插件
import { onMounted, ref } from "vue";
import axios from "axios";
/**
 * 初始化地图
 */
// 定义echarts方法
const chartMap = async () => {
  // 初始化dom
  const myChart = echarts.init(
    <HTMLElement>document.getElementById("mapEchart")
  );
  // 初始化map
  initMap(myChart, "map", "100000");
  // 添加点击事件
  myChart.on("click", (e: any) => {
    clearInterval(regionsSetInterVal.value);
    console.log(e);
    const newName: string = e.name;
    if (e.value.level === "district") return alert("该地区已经无法下钻");
    // 添加历史记录
    historyMapData.value.push(e.value);
    // 初始化地图
    initMap(myChart, newName, e.value.adcode);
  });
  // 添加鼠标移入事件
  myChart.on("mouseover", (e: any) => {
    console.log("鼠标移入");
    clearInterval(regionsSetInterVal.value);
  });
  // 添加鼠标移出事件
  myChart.on("mouseout", (e: any) => {
    console.log("鼠标移出");
  });
  //让可视化地图跟随浏览器大小缩放
  window.addEventListener("resize", () => {
    myChart.resize();
  });
};
// 初始化图表
const initMap = async (
  chartDOM: echarts.ECharts,
  geoName: string,
  adcode: string
) => {
  // 清除echarts实例
  chartDOM.clear();
  // 请求map的json
  const mapData = await getMapJSON(adcode, geoName);
  // 图表配置项
  const option = getOption(geoName, mapData);
  // 渲染配置
  setIntervalOptionsRegionsMap(option, mapData, chartDOM);
  // updateMap(chartDOM, option);
};
/**
 * 地图配置项
 */
// 请求地图json数据,并过滤成地图data配置项
const getMapJSON = async (adcode: string = "100000", geoName: string) => {
  const res = await axios.get(
    `https://geo.datav.aliyun.com/areas_v2/bound/${adcode}_full.json`
  );
  // 重新注册地图
  echarts.registerMap(geoName, <any>res.data);
  // 过滤json数据
  const lightMap: any = {
    河南省: {
      show: true,
      formatter: (e: any) => {
        return ` ${e.name} `;
      },
      textStyle: {
        color: "#f8fbfb",
        fontSize: 18,
        padding: [20, 20],
        backgroundColor: {
          image: "./2.png",
        },
      },
    },
    郑州市: {
      show: true,
      formatter: (e: any) => {
        return ` ${e.name} `;
      },
      textStyle: {
        color: "#f8fbfb",
        fontSize: 18,
        padding: [20, 20],
        backgroundColor: {
          image: "./2.png",
        },
      },
    },
  };
  const mapData = res.data.features.map((item: any) => {
    console.log(item.properties.name);
    return {
      value: item.properties,
      name: item.properties.name,
      label: lightMap[item.properties.name],
    };
  });
  return mapData;
};
// 图表生成配置项
const getOption = (geoName: string, mapData: any) => {
  // 图表配置项
  const option = {
    geo3D: {
      zlevel: -100,
      show: true,
      type: "map3D",
      map: geoName, // 地图类型。echarts-gl 中使用的地图类型同 geo 组件相同
      regionHeight: 2,
      shading: "realistic",
      realisticMaterial: {
        detailTexture: "./1.jpeg",
        roughness: 0.2,
        metalness: 0,
      },
      // viewControl: {
      //   minAlpha: 70, // 上下旋转的最小 alpha 值。即视角能旋转到达最上面的角度。[ default: 5 ]
      //   maxAlpha: 90, // 上下旋转的最大 alpha 值。即视角能旋转到达最下面的角度。[ default: 90 ]
      //   minBeta: -360, // 左右旋转的最小 beta 值。即视角能旋转到达最左的角度。[ default: -80 ]
      //   maxBeta: 360, // 左右旋转的最大 beta 值。即视角能旋转到达最右的角度。[ default: 80 ]
      // },
      regions: [
        {
          name: mapData[0].name,
          // label: {
          //   show: true,
          //   textStyle: {
          //     color: "#fff", // 地图初始化区域字体颜色
          //     fontSize: 18,
          //   },
          // },
          itemStyle: {
            color: "#ff9900",
          },
        },
      ], //默认高亮区域
      emphasis: {
        label: { show: false },
        itemStyle: {
          color: "transparent",
        },
      },
    },
    series: [
      {
        zlevel: -10,
        regionHeight: 2,
        type: "map3D",
        map: geoName, // 地图类型。echarts-gl 中使用的地图类型同 geo 组件相同
        data: mapData, //这里比较重要:获得过滤后的data,这样点击事件时就能获得这个data的值
        label: {
          show: true, // 是否显示标签。
          textStyle: {
            color: "#fff", // 地图初始化区域字体颜色
            fontSize: 12,
          },
          formatter: (e: any) => {
            // console.log(e.name);
            return ` ${e.name} `;
          },
        },
        shading: "realistic",
        realisticMaterial: {
          detailTexture: "./4.jpeg",
          roughness: 0.2,
          metalness: 0,
        },
        // viewControl: {
        //   minAlpha: 70, // 上下旋转的最小 alpha 值。即视角能旋转到达最上面的角度。[ default: 5 ]
        //   maxAlpha: 90, // 上下旋转的最大 alpha 值。即视角能旋转到达最下面的角度。[ default: 90 ]
        //   minBeta: -360, // 左右旋转的最小 beta 值。即视角能旋转到达最左的角度。[ default: -80 ]
        //   maxBeta: 360, // 左右旋转的最大 beta 值。即视角能旋转到达最右的角度。[ default: 80 ]
        // },
        itemStyle: {
          borderWidth: 1.5,
          borderColor: "#5FB9DA",
          color: "transparent",
        },
        emphasis: {
          label: {
            show: true,
            textStyle: {
              color: "#f8fbfb",
              // borderColor: "#17E8F4",
              fontSize: 18,
              padding: [20, 20],
              backgroundColor: {
                image: "./2.png",
              },
            },
          },
          itemStyle: {
            color: "#18B6FE",
          },
        },
      },
    ],
  };
  return option;
};
/**
 * 更新地图功能
 */
// 更新图表配置项重新渲染
const updateMap = (chartDOM: echarts.ECharts, option: any) => {
  // 渲染配置
  chartDOM.setOption(option);
};
/**
 * 返回上级地图功能
 */
type HistoryData = {
  name: string;
  adcode: string | undefined;
};
// 地图下钻历史记录
const historyMapData = ref<HistoryData[]>([{ name: "map", adcode: "100000" }]);
// 返回上级地图
const backMap = () => {
  clearInterval(regionsSetInterVal.value);
  const myChart = echarts.init(
    <HTMLElement>document.getElementById("mapEchart")
  );
  // 去除当前的地图信息
  historyMapData.value.pop();
  const len = historyMapData.value.length;
  // 获取上一级的地图信息
  const newdata = historyMapData.value[len - 1];
  // 重新渲染地图
  initMap(myChart, newdata?.name || "map", newdata?.adcode || "100000");
};
/**
 * 高亮区块功能
 */
// 轮训 regions 地图名的下标
let regionsCount = ref<number>(0);
// 定时器接收容器
const regionsSetInterVal = ref();
// 循环定时器修改地图option高亮显示地图区域
const setIntervalOptionsRegionsMap = (
  option: any,
  mapData: any,
  chartDOM: echarts.ECharts
) => {
  regionsSetInterVal.value = setInterval(() => {
    option.geo3D.regions[0].name = mapData[regionsCount.value].name;
    updateMap(chartDOM, option);
    regionsCount.value++;
    if (regionsCount.value === mapData.length) regionsCount.value = 0;
  }, 1000);
};
/**
 * 生命周期
 */
onMounted(() => {
  // 挂载echart
  chartMap();
});
</script>

<style scoped>
.investment-screen {
  background-color: rgb(0, 0, 42);
  width: 100vw;
  height: 100vh;
  position: relative;
  display: flex;
  justify-content: center;
  align-items: center;
}
.map-chart {
  width: 80%;
  height: 80%;
  /* background-color: wheat; */
}
</style>

细节讲解

  • 地图配置getOption函数中,使用了双地图geo3Dmap3D两种类型,因为map3D更友好的支持点击事件,所以为了实现点击下钻的功能,我把map3D的视图层级往上提升了,并且透明化了map3D每一块区域的颜色

map3D单独配置时:

这么做就是为了完成:“鼠标移入相关地区区域时高亮展示红框立体区域内容”的需求

image.png
而展示给用户的3d地图是由geo3D渲染的:
image.png

  • geo3D配置详解:

三维图形的着色效果
realistic 真实感渲染
在使用自定义渲染时,材质贴图一定要防抖 pubic文件夹 中,否则echarts无法识别。应该是底层echarts打包的时候自己做的处理。

shading: "realistic",
      realisticMaterial: {
        detailTexture: "./1.jpeg",
        roughness: 0.2,
        metalness: 0,
      },
  • map3D配置详解:

配置鼠标移入时的图标,可以自定义更换 image: "./2.png"

emphasis: {
          label: {
            show: true,
            textStyle: {
              color: "#f8fbfb",
              // borderColor: "#17E8F4",
              fontSize: 18,
              padding: [20, 20],
              backgroundColor: {
                image: "./2.png",
              },
            },
          },
          itemStyle: {
            color: "#18B6FE",
          },
        },

总结

本文中地图下钻返回上一级地图的整体功能需求基本完善。

  • 对于新增功能待完善的:

高亮显示区域这个功能,会阻碍鼠标移入时的样式展示,所以每次鼠标移入时,都要清除regionsSetInterVal这个定时器,那么导致定时器清除动画不会播放。
解决方案:鼠标移出时应该重新开始这个计时器,并且要添加防抖,提升性能。(待完成)

  • 可拓展的新功能:

添加动态3d柱状图
添加动态3d散点图
添加动态3d折线图
。。。等等功能待开发

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。