WishMeLz

生活其实很有趣

Echarts地图 两种方式:geo、series

唯一的区别就是配置项不一样,以及其他坐标计算差异

地图数据来源:https://datav.aliyun.com/portal/school/atlas/area_generator

在线代码:https://codesandbox.io/p/live/99e7cbfd-803a-421a-902f-82a613082f54

2025-05-13T06:56:46.png

初始化差异

## series
echarts.registerMap('china', mapData);
## geo
echarts.registerMap('China', mapData);

高亮方法 highlightCity

dispatchAction

坐标转换:updateCardPosition

注意getCityCenter的差别

完整代码 series

<template>
    <div class="commonData">
      <div id="map"></div>
  
      <div class="city-card" :style="{
        left: cardPosition.left,
        top: cardPosition.top,
        visibility: cardPosition.visible ? 'visible' : 'hidden'
      }">
        <div class='info'>
          <div class='sign top'>
            <div class='col'>
              <p>用户总数</p>
              <span>2,283</span>
            </div>
            <div class='col'>
              <p>在线用户</p>
              <span>824</span>
            </div>
          </div>
          <div class='sign bom'>
            <div class='col'>
              <span>今日入职</span>
              <span class="num">254</span>
            </div>
            <div class='col'>
              <span>今日离职</span>
              <span class="num">12</span>
            </div>
          </div>
        </div>
  
      </div>
    </div>
  </template>
  
  <script setup>
  import * as echarts from 'echarts';
  import axios from 'axios';
  import { onMounted, ref, onUnmounted, reactive } from 'vue';
  // 高亮的城市列表
  const citiesToHighlight = [
    '北京市',
    '上海市',
    '广东省',
    '江苏省',
    '浙江省',
  ];
  
  // 当前高亮的城市索引
  let currentCityIndex = 0;
  const highlightInterval = 2000; // 2秒切换一次
  let highlightTimer = null;
  let myChart = null;
  
  const cityListData = ref([]);
  
  const cardPosition = reactive({
    left: '0px',
    top: '0px',
    visible: false
  });
  
  const getHtmlDom = (str) => {
    let htmlElement = document.querySelector(str);
    htmlElement.innerHTML = ''
    htmlElement.removeAttribute('_echarts_instance_')
    htmlElement.removeAttribute('style')
  
    return htmlElement
  }
  
  
  onMounted(() => {
    axios.get('https://d.xzxo.cn/i/100000_full.json').then(res => {
      cityListData.value = res.data
      initChart(res.data);
    });
  });
  
  const getCityCenter = (cityName) => {
    if (!cityListData.value || !cityListData.value.features) {
      console.error('地图数据未加载');
      return null;
    }
  
    for (let i = 0; i < cityListData.value.features.length; i++) {
      const item = cityListData.value.features[i];
      if (item.properties && item.properties.name === cityName) {
        // 优先使用 center,如果没有则计算 bbox 的中心
        if (item.properties.center) {
          return item.properties.center;
        } else if (item.properties.bbox) {
          const bbox = item.properties.bbox;
          return [
            (bbox[0] + bbox[2]) / 2,
            (bbox[1] + bbox[3]) / 2
          ];
        }
      }
    }
  
    console.warn(`找不到城市 ${cityName} 的坐标`);
    return null;
  }
  
  onUnmounted(() => {
    if (highlightTimer) {
      clearInterval(highlightTimer);
    }
    if (myChart) {
      myChart.dispose();
    }
  });
  
  const initChart = (mapData) => {
    let html = getHtmlDom('#map');
    // 注册地图数据
    echarts.registerMap('china', mapData);
  
    // 初始化图表
    myChart = echarts.init(html);
  
    // 配置项
    const option = {
      backgroundColor: 'transparent', // 透明背景
      series: [{
        type: 'map',
        map: 'china',
        roam: false, // 禁用缩放和平移
        zoom: 1.5, // 适当放大
        center: [104, 35], // 地图中心位置
        itemStyle: {
          areaColor: '#0D56AA90', // 默认区域颜色-深蓝半透明
          borderColor: '#8CD1FF', // 边界线颜色-蓝色
          borderWidth: 1.1, // 边界线宽度
          shadowColor: 'rgba(0, 0, 0, 0.5)',
        },
        emphasis: {
          itemStyle: {
            areaColor: {
              type: 'linear',
              x: 0,
              y: 0,
              x2: 0,
              y2: 1, // 垂直渐变
              colorStops: [{
                offset: 0,
                color: '#33AFFF' // 渐变起始颜色
              }, {
                offset: 1,
                color: '#3D75D6' // 渐变结束颜色
              }]
            },
            borderWidth: 1.5,
            shadowColor: 'rgba(51, 175, 255, 0.7)',
            shadowBlur: 10
          },
          label: {
            show: false // 不显示区域名称
          }
        },
        label: {
          show: false // 默认不显示标签
        },
        // 南海诸岛样式
        // regions: [{
        //   name: '南海诸岛',
        //   itemStyle: {
        //     areaColor: '#0D56AA90',
        //     borderColor: '#8CD1FF',
        //   }
        // }],
        data: [
        ]
      }]
    };
  
    myChart.setOption(option);
  
    // 立即执行一次高亮
    highlightCity();
  
    // 设置定时轮播高亮
    highlightTimer = setInterval(highlightCity, highlightInterval);
  
    // 响应窗口大小变化
    window.addEventListener('resize', () => {
      myChart.resize();
    });
  };
  
  // 高亮当前城市并移动到下一个
  const highlightCity = () => {
    if (!myChart || citiesToHighlight.length === 0) return;
  
    // 获取前一个高亮的城市索引
    const previousIndex = (currentCityIndex - 1 + citiesToHighlight.length) % citiesToHighlight.length;
    const previousCity = citiesToHighlight[previousIndex];
  
    // 先取消前一个城市的高亮
    myChart.dispatchAction({
      type: 'downplay',
      seriesIndex: 0,
      name: previousCity
    });
  
    // 当前要高亮的城市
    const cityToHighlight = citiesToHighlight[currentCityIndex];
  
    // 高亮当前城市
    myChart.dispatchAction({
      type: 'highlight',
      seriesIndex: 0,
      name: cityToHighlight
    });
  
    // 更新卡片位置
    updateCardPosition(cityToHighlight);
  
    console.log(`当前高亮: ${cityToHighlight}`);
  
    // 更新索引,指向下一个城市
    currentCityIndex = (currentCityIndex + 1) % citiesToHighlight.length;
  
  };
  
  const updateCardPosition = (cityName) => {
    const coords = getCityCenter(cityName);
  
    if (!coords || !myChart) {
      cardPosition.visible = false;
      return;
    }
  
    console.log(`城市 ${cityName} 的地理坐标:`, coords);
  
    // 将地理坐标转换为屏幕像素坐标
    const pixelPos = convertGeoToPixel(coords);
  
    // 设置卡片位置,配合左右偏移
    cardPosition.left = `${pixelPos.x - 240}px`; // 卡片宽度一半左右
    cardPosition.top = `${pixelPos.y - 160}px`;  // 根据卡片高度调整
    cardPosition.visible = true;
  
    console.log(`城市 ${cityName} 的像素坐标: x=${pixelPos.x}, y=${pixelPos.y}`);
  
  }
  
  // 将地理坐标转换为屏幕像素坐标
  const convertGeoToPixel = (geoCoord) => {
    if (!myChart) return { x: 0, y: 0 };
  
    // 使用 series 而不是 geo 来转换坐标
    const pixelCoord = myChart.convertToPixel('series', geoCoord);
  
    // 确保返回有效的坐标
    if (Array.isArray(pixelCoord) && pixelCoord.length >= 2) {
      return { x: pixelCoord[0], y: pixelCoord[1] };
    } else {
      console.error('坐标转换失败:', pixelCoord);
      return { x: 0, y: 0 };
    }
  };
  
  
  </script>
  
  <style scoped lang="scss">
  .commonData {
    width: 100vw;
    height: 100vh;
    box-sizing: border-box;
    min-width: 100vw;
    height: 100vh;
    background-color: #000;
    display: flex;
    flex-direction: column;
  }
  
  p {
    margin: 0;
  }
  
  #map {
    width: 100%;
    height: 100%;
  }
  
  
  .city-card {
    position: fixed;
    color: #fff;
    transition: all .3s;
  
    p {
      margin: 0;
    }
  
    .info {
      width: 260px;
      background: linear-gradient(180deg, #040239 0%, rgba(0, 20, 102, 0.7) 100%);
      box-shadow: inset 0px 0px 10px 0px #51B4FC;
      border-radius: 12px;
      // border: 2px solid;
      border-image: linear-gradient(360deg, rgba(83.00000265240669, 182.00000435113907, 254.00000005960464, 1), rgba(30.00000011175871, 113.00000086426735, 210.00000268220901, 0.14000000059604645)) 2 2;
      padding: 10px;
      box-sizing: border-box;
  
  
      .sign {
        background: rgba(0, 18, 35, 0.7);
        border-radius: 8px 8px 8px 8px;
        border: 1px solid;
        border-radius: 12px;
        padding: 10px;
        border-image: linear-gradient(180deg, rgba(24.514065384864807, 94.71159741282463, 221.60714864730835, 0), rgba(24.514065384864807, 94.71159741282463, 221.60714864730835, 1)) 1 1;
  
        display: flex;
  
        .col {
          flex: 1;
        }
  
      }
  
      .sign:last-child {
        margin-top: 8px;
      }
  
      .top {
        p {
          font-size: 14px;
          color: #FFFFFF60;
        }
  
        span {
          font-weight: 700;
          font-size: 24px;
          color: #00F9DE;
        }
      }
  
      .bom {
        span {
          font-weight: 400;
          font-size: 14px;
          color: #FFFFFF60;
        }
  
        .num {
          font-weight: 700;
          font-size: 18px;
          color: #FFFFFF;
          margin-left: 10px;
        }
      }
  
    }
  }
  </style>

完整代码 geo

<template>
    <div class="commonData">
  
      <div  id="map"></div>
  
      <div class="city-card" :style="{
        left: cardPosition.left,
        top: cardPosition.top,
        visibility: cardPosition.visible ? 'visible' : 'hidden'
      }">
        <div class='info'>
          <div class='sign top'>
            <div class='col'>
              <p>用户总数</p>
              <span>2,283</span>
            </div>
            <div class='col'>
              <p>在线用户</p>
              <span>824</span>
            </div>
          </div>
          <div class='sign bom'>
            <div class='col'>
              <span>今日入职</span>
              <span class="num">254</span>
            </div>
            <div class='col'>
              <span>今日离职</span>
              <span class="num">12</span>
            </div>
          </div>
        </div>
      </div>
  
  
    </div>
  </template>
  
  <script setup>
  import * as echarts from 'echarts';
  import axios from 'axios';
  import { onMounted, ref, onUnmounted, reactive } from 'vue';
  
  // 设置需要轮流高亮的城市列表
  const citiesToHighlight = [
    '北京市',
    '上海市',
    '广东省',
    '江苏省',
    '浙江省',
    // 可以添加更多城市
  ];
  
  let currentCityIndex = 0;
  const highlightInterval = 2000; // 2秒切换一次
  let highlightTimer = null;
  let myChart = null;
  const cityListData = ref([]);
  const cardPosition = reactive({
    left: '0px',
    top: '0px',
    visible: false
  });
  
  const getHtmlDom = (str) => {
    let htmlElement = document.querySelector(str);
    htmlElement.innerHTML = ''
    htmlElement.removeAttribute('_echarts_instance_')
    htmlElement.removeAttribute('style')
  
    return htmlElement
  }
  
  
  onMounted(() => {
    axios.get('https://d.xzxo.cn/i/100000_full.json').then(res => {
      cityListData.value = res.data
      initChart(res.data);
    });
  });
  
  
  
  const getCityCenter = (cityName) => {
    for (let i = 0; i < cityListData.value.features.length; i++) {
      let item = cityListData.value.features[i].properties
      if (item.name == cityName) {
        return item.center
      }
    }
  }
  
  // 组件卸载时清除定时器
  onUnmounted(() => {
    if (highlightTimer) {
      clearInterval(highlightTimer);
    }
    if (myChart) {
      myChart.dispose();
    }
  });
  
  const initChart = (mapData) => {
    let html = getHtmlDom('#map');
    echarts.registerMap('China', mapData);
  
    myChart = echarts.init(html);
  
    // 配置项
    const option = {
      backgroundColor: 'transparent', // 透明背景
      geo: {
        map: 'China',
        roam: false, // 禁用缩放和平移,以保持与设计一致
        zoom: 1.5, // 适当放大
        center: [104, 35], // 地图中心位置
        itemStyle: {
          areaColor: '#0D56AA90', // 默认区域颜色-深蓝半透明
          borderColor: '#8CD1FF', // 边界线颜色-蓝色
          borderWidth: 1.1, // 边界线宽度
          shadowColor: 'rgba(0, 0, 0, 0.5)',
        },
        emphasis: {
          itemStyle: {
            areaColor: {
              type: 'linear',
              x: 0,
              y: 0,
              x2: 0,
              y2: 1, // 垂直渐变,对应CSS中的180deg
              colorStops: [{
                offset: 0,
                color: '#33AFFF' // 渐变起始颜色
              }, {
                offset: 1,
                color: '#3D75D6' // 渐变结束颜色
              }]
            },
            borderWidth: 1.5,
            shadowColor: 'rgba(51, 175, 255, 0.7)',
            shadowBlur: 10
          },
          label: {
            show: false // 不显示区域名称
          }
        },
      },
    };
  
    myChart.setOption(option);
  
    // 立即执行一次高亮
    highlightCity();
  
    // 设置定时轮播高亮
    highlightTimer = setInterval(highlightCity, highlightInterval);
  
    // 响应窗口大小变化
    window.addEventListener('resize', () => {
      myChart.resize();
    });
  };
  
  // 高亮当前城市并移动到下一个
  const highlightCity = () => {
    if (!myChart || citiesToHighlight.length === 0) return;
    const previousIndex = (currentCityIndex - 1 + citiesToHighlight.length) % citiesToHighlight.length;
    const cityToHighlight2 = citiesToHighlight[previousIndex];
    // 先取消所有高亮
    myChart.dispatchAction({
      type: 'geoUnSelect',
      name: cityToHighlight2
    });
  
    // 当前要高亮的城市
    const cityToHighlight = citiesToHighlight[currentCityIndex];
  
  
  
    // 高亮当前城市
    myChart.dispatchAction({
      type: 'geoSelect',
      name: cityToHighlight
    });
  
    updateCardPosition(cityToHighlight)
  
    console.log(`当前高亮: ${cityToHighlight}`);
  
    // 更新索引,指向下一个城市
    currentCityIndex = (currentCityIndex + 1) % citiesToHighlight.length;
  };
  
  const updateCardPosition = (cityName) => {
    const coords = getCityCenter(cityName);
  
    if (!coords || !myChart) {
      cardPosition.visible = false;
      return;
    }
  
    console.log(coords);
  
    // 将地理坐标转换为屏幕像素坐标
    const pixelPos = convertGeoToPixel(coords);
  
    // 设置卡片位置 (偏移一点以便不遮挡城市标记)
    cardPosition.left = `${pixelPos.x - 320}px`;
    cardPosition.top = `${pixelPos.y - 60}px`;
    cardPosition.visible = true;
  
    console.log(`城市 ${cityName} 的像素坐标: x=${pixelPos.x}, y=${pixelPos.y}`);
  }
  
  // 将地理坐标转换为屏幕像素坐标
  const convertGeoToPixel = (geoCoord) => {
    if (!myChart) return { x: 0, y: 0 };
  
    // 使用ECharts内置方法将地理坐标转为像素坐标
    const pixelCoord = myChart.convertToPixel('geo', geoCoord);
  
    // 确保返回有效的坐标
    if (Array.isArray(pixelCoord)) {
      return { x: pixelCoord[0], y: pixelCoord[1] };
    } else {
      return { x: 0, y: 0 };
    }
  };
  
  
  </script>
  
  <style scoped lang="scss">
  .commonData {
    width: 100vw;
    height: 100vh;
    box-sizing: border-box;
    min-width: 100vw;
    height: 100vh;
    background-color: #000;
    display: flex;
    flex-direction: column;
  }
  
  p {
    margin: 0;
  }
   
  #map {
      width: 100%;
      height: 100%;
    }
  
  
  .city-card {
    position: fixed;
    color: #fff;
    transition: all .3s;
  
    p {
      margin: 0;
    }
  
    .info {
      width: 260px;
      background: linear-gradient(180deg, #040239 0%, rgba(0, 20, 102, 0.7) 100%);
      box-shadow: inset 0px 0px 10px 0px #51B4FC;
      border-radius: 12px;
      // border: 2px solid;
      border-image: linear-gradient(360deg, rgba(83.00000265240669, 182.00000435113907, 254.00000005960464, 1), rgba(30.00000011175871, 113.00000086426735, 210.00000268220901, 0.14000000059604645)) 2 2;
      padding: 10px;
      box-sizing: border-box;
  
  
      .sign {
        background: rgba(0, 18, 35, 0.7);
        border-radius: 8px 8px 8px 8px;
        border: 1px solid;
        border-radius: 12px;
        padding: 10px;
        border-image: linear-gradient(180deg, rgba(24.514065384864807, 94.71159741282463, 221.60714864730835, 0), rgba(24.514065384864807, 94.71159741282463, 221.60714864730835, 1)) 1 1;
  
        display: flex;
  
        .col {
          flex: 1;
        }
  
      }
  
      .sign:last-child {
        margin-top: 8px;
      }
  
      .top {
        p {
          font-size: 14px;
          color: #FFFFFF60;
        }
  
        span {
          font-weight: 700;
          font-size: 24px;
          color: #00F9DE;
        }
      }
  
      .bom {
        span {
          font-weight: 400;
          font-size: 14px;
          color: #FFFFFF60;
        }
  
        .num {
          font-weight: 700;
          font-size: 18px;
          color: #FFFFFF;
          margin-left: 10px;
        }
      }
  
    }
  }
  </style>