vue 里怎么使用 echarts 实现地图自动轮播功能、自定义 tooltip 悬浮位置提示、自定义 label 标签位置样式?

简介: vue 里怎么使用 echarts 实现地图自动轮播功能、自定义 tooltip 悬浮位置提示、自定义 label 标签位置样式?

要实现的功能


比如:我们要实现白云地图24镇街的常住人口统计展示,然后需要我们实现 1s 自动轮播一次地区,自定义标签样式,自定义悬浮样式。


image.gif



准备工作


1、安装依赖

npm i echarts -s


7c53d80b88724eca897b0ef97143fcc8.png


2、准备 24 镇街的 geoJson 数据

关于怎么获取 24 镇街的 geoJson 数据,请参考我之前的一篇博客:怎么获取echarts需要的geoJson数据去渲染地图:以广州市白云区24镇街为例



3、准备一份配置 24 镇街的文件

我们新建文件 440111.config.js,在里面添加一些配置参数用于数据展示的方便,比如我配置了几个:

/**
 * 1)4个镇:江高镇、人和镇、太和镇、钟落潭镇;
 * 2)20个街:龙归街、大源街、三元里街、松洲街、景泰街、同德街、黄石街、棠景街、新市街、同和街、京溪街、永平街、嘉禾街、均禾街、石井街、金沙街、云城街、鹤龙街、白云湖街、石门街;
 * */ 
export const BAIYUN_CONFIG = [
  '江高镇',
  '人和镇',
  '太和镇',
  '钟落潭镇',
  '龙归街',
  '大源街',
  '三元里街',
  '松洲街',
  '景泰街',
  '同德街',
  '黄石街',
  '棠景街',
  '新市街',
  '同和街',
  '京溪街',
  '永平街',
  '嘉禾街',
  '均禾街',
  '石井街',
  '金沙街',
  '云城街',
  '鹤龙街',
  '白云湖街',
  '石门街'
];
/**
 * SUCCESS_STYLE:绿色样式:'江高镇','太和镇','景泰街','黄石街','棠景街','京溪街','永平街','均禾街','石井街','金沙街';
 * WARNING_STYLE:橙色样式:'人和镇','嘉禾街','三元里街','石门街','新市街','大源街','松洲街';
 * ERROR_STYLE:红色样式:'钟落潭镇','同德街','云城街','鹤龙街','白云湖街','龙归街','同和街';
 * */ 
export const SUCCESS_STYLE = ['江高镇','太和镇','景泰街','黄石街','棠景街','京溪街','永平街','均禾街','石井街','金沙街'];
export const WARNING_STYLE = ['人和镇','嘉禾街','三元里街','石门街','新市街','大源街','松洲街'];
export const ERROR_STYLE = ['钟落潭镇','同德街','云城街','鹤龙街','白云湖街','龙归街','同和街'];
// 测试数据
export const MapData = [
{
  id: "jianggao",
  name: "江高镇",
  value: 883945
},{
  id: "renhe",
  name: "人和镇",
  value: 4567992
},{
  id: "taihe",
  name: "太和镇",
  value: 4567323
},{
  id: "zhongluotan",
  name: "钟落潭镇",
  value: 497863
},{
  id: "longgui",
  name: "龙归街",
  value: 3486257
},{
  id: "dayuan",
  name: "大源街",
  value: 897435
},{
  id: "sanyuanli",
  name: "三元里街",
  value: 46809544
},{
  id: "songzhou",
  name: "松洲街",
  value: 123403
},{
  id: "jingtai",
  name: "景泰街",
  value: 342256677
},{
  id: "tongde",
  name: "同德街",
  value: 234677
},{
  id: "huangshi",
  name: "黄石街",
  value: 976542
},{
  id: "tangjing",
  name: "棠景街",
  value: 33456
},{
  id: "xinshi",
  name: "新市街",
  value: 3455602
},{
  id: "tonghe",
  name: "同和街",
  value: 487654
},{
  id: "jingxi",
  name: "京溪街",
  value: 3876735
},{
  id: "yongping",
  name: "永平街",
  value: 6677544
},{
  id: "jiahe",
  name: "嘉禾街",
  value: 34526784
},{
  id: "junhe",
  name: "均禾街",
  value: 8756534
},{
  id: "shijing",
  name: "石井街",
  value: 220232
},{
  id: "jinsha",
  name: "金沙街",
  value: 3352256
},{
  id: "yuncheng",
  name: "云城街",
  value: 335677
},{
  id: "helong",
  name: "鹤龙街",
  value: 334225
},{
  id: "baiyunhu",
  name: "白云湖街",
  value: 34556
},{
  id: "shimen",
  name: "石门街",
  value: 1354667
}
];



4、准备工具方法添加千分位

新建文件 utils.js,添加千分位函数:

// 数字加千位分隔符
export function numToThsSprtr (num) {
  let res = num.toString().replace(/\d+/, function (n) { // 先提取整数部分
    return n.replace(/(\d)(?=(\d{3})+$)/g, function ($1) {
      return $1 + ',';
    });
  })
  return res;
}



代码实现


1、自定义标签

我们配置标签的相应样式,我们利用 formatter,以及 rich 去处理,具体可以参考:https://echarts.apache.org/zh/option.html#series-map.label.formatter,backgroundColor 可以使用背景图片。


label: {
  show: true,
  color: "#fff",
  textAlign: "center",
  // {a}:系列名。{b}:数据名。{c}:数据值。
  formatter: (params) => {
    let richName = "";
    if (SUCCESS_STYLE.includes(params.name)) {
      richName = "success";
    } else if (WARNING_STYLE.includes(params.name)) {
      richName = "warning";
    } else if (ERROR_STYLE.includes(params.name)) {
      richName = "error";
    }
    return `{${richName}|${params.name}}`;
  },
  rich: {
    success: {
      fontSize: "0.0729rem",
      padding: [4, 7],
      borderWidth: 2, // 图形描边的宽度。
      borderColor: "#fff", // 边框颜色
      backgroundColor: 'green', // 背景色
    },
    warning: {
      fontSize: "0.0729rem",
      padding: [4, 7],
      borderWidth: 2, // 图形描边的宽度。
      borderColor: "#fff", // 边框颜色
      backgroundColor: 'orange', // 背景色
    },
    error: {
      fontSize: "0.0729rem",
      padding: [4, 7],
      borderWidth: 2, // 图形描边的宽度。
      borderColor: "#fff", // 边框颜色
      backgroundColor: 'red', // 背景色
    },
  },
},



怎么处理标签名重叠问题?

如果大家出现下面这种标签名重叠的问题,那应该怎么去处理?

275ee9d944cb468aad73eb7a048b2b3d.png


这里我们可以参考 github 上面 echarts 的问题 4379 进行相应的处理:中国地图,省份名称重叠 #4379

问题的描述:


24ca3e10951340c6a66a30c273db5ded.png



两种方式处理重叠

第一种:在 geoJson 数据中添加 cp 属性数据。


"properties": {
  "name": "白云湖街",
  "cp": [113.23196411132812, 23.24386977767157]
}


69ee654c44194a569ad3442c93e04b81.png



第二种:echarts.getMap(‘china’) 后修改已经加载的地图的数据。

var chinaMapInfoObj = document.getElementById(‘mianid‘’);
var chinaMap = echarts.init(chinaMapInfoObj);
var chinaEchartsObj = echarts.getMap('china');
var geoJSONChina = chinaEchartsObj.geoJson;
var allDefProvince = geoJSONChina.features;
for(var i=0,len=allDefProvince.length; i<len; i++){
       var sglProvinceProperties = allDefProvince[i].properties;
       var sglProvinceName = sglProvinceProperties.name;
       switch(sglProvinceName){
              case '新疆':
                       sglProvinceProperties.cp[0]=87.617733;
                       sglProvinceProperties.cp[1]=41.792818;
                       break;
               case '青海'://def:101.778916,36.623178
                        sglProvinceProperties.cp[0]=97.617733;
                        sglProvinceProperties.cp[1]=36.623178;
                       break;
                 case '江苏'://def:118.767413,32.041544
                        sglProvinceProperties.cp[0]=119.767413;
                        sglProvinceProperties.cp[1]=33.041544;
                        break;
        }
}
option.echarts.registerMap('china', geoJSONChina, {});


怎么确定中心点的坐标


可以去查看这个我写的这一篇博客里的确定边界汇聚点部分,里面有介绍怎么处理。怎么获取 echarts 需要的 geoJson 数据去渲染地图:以广州市白云区24镇街为例


大致就是把矩形左上方的点放到新的位置中心,就可以得到大概的坐标。


f16c99f95a1443ee89372133a45208a5.png


得到坐标之后,可能有点不太精确,可以自己合适的调整数据,达到自己想要的位置就行。

比如:现在 松洲街同德街,挡住了,需要把它进行移动,


cecf10caea4b494b9deae30275a9f94a.png


这里我们采用第一种方式,修改 geojson 数据,我们给松洲街添加 cp 坐标数据,我们发现就不会被遮住了,其他也是同样的原理

"properties": {
  "name": "松洲街",
  "cp": [113.21036999511719, 23.15851026498019]
}


d4f9f733f159418a9c2fd08c165128b1.png


2、自定义悬浮提示


这里我们主要需要处理的就是 position 跟样式 formatter。这里的位置需要计算一下 (point:鼠标位置) 跟(contentSize:dom 的尺寸),找到合适的显示位置。具体的可以参考:


tooltip: {
  show: true,
  trigger: "item",
  // point:鼠标位置 contentSize:dom 的尺寸
  position: (point, params, dom, rect, size) => {
    return [point[0] - 20, point[1] - size.contentSize[1] - 15];
  },
  extraCssText: "box-shadow: none", // 额外样式
  formatter: (param) => {
    let data = `
      <div class="map-tooltips">
        <div class="name">${param.name}常住人口</div>
        <div class="value">
         <span class="num">${numToThsSprtr(param.value || 0)}</span>
         <span class="unit">人</span>
        </div>
      </div>
    `;
    return data;
  },
},


3、自动轮播功能


我们可以通过 dispatchAction 实现轮播高亮提示效果。这里需要注意的就是轮播到最后一个时的状态处理。这里就不具体展开逻辑,看完整代码就行,有注释。


具体的配置参考:https://echarts.apache.org/zh/api.html#action

高亮指定的数据图形。

// 如果要高亮系列:
dispatchAction({
    type: 'highlight',
    // 用 index 或 id 或 name 来指定系列。
    // 可以使用数组指定多个系列。
    seriesIndex?: number | number[],
    seriesId?: string | string[],
    seriesName?: string | string[],
    // 数据项的 index,如果不指定也可以通过 name 属性根据名称指定数据项
    dataIndex?: number | number[],
    // 可选,数据项名称,在有 dataIndex 的时候忽略
    name?: string | string[],
});


取消高亮指定的数据图形。

// 如果要取消高亮系列:
dispatchAction({
    type: 'downplay',
    // 用 index 或 id 或 name 来指定系列。
    // 可以使用数组指定多个系列。
    seriesIndex?: number | number[],
    seriesId?: string | string[],
    seriesName?: string | string[],
    // 数据项的 index,如果不指定也可以通过 name 属性根据名称指定数据项
    dataIndex?: number | number[],
    // 可选,数据项名称,在有 dataIndex 的时候忽略
    name?: string | string[],
})



指定系列中的数据图形,根据 tooltip 的配置项显示提示框。

dispatchAction({
    type: 'showTip',
    // 系列的 index,在 tooltip 的 trigger 为 axis 的时候可选。
    seriesIndex?: number,
    // 数据项的 index,如果不指定也可以通过 name 属性根据名称指定数据项
    dataIndex?: number,
    // 可选,数据项名称,在有 dataIndex 的时候忽略
    name?: string,,
    // 本次显示 tooltip 的位置。只在本次 action 中生效。
    // 缺省则使用 option 中定义的 tooltip 位置。
    position: number[] | string | Function,
})


隐藏提示框。

dispatchAction({
    type: 'hideTip'
})


4、大屏自适应方案

这个可以参考我的另外一篇:使用 sass + rem + flexible.js 实现大屏自适应


e85568df9aab4fe8bb860e00aebe54a8.png



完整代码

<template>
<div ref="geoTwoDimensionalMapChart" class="geo-two-dimensional-map-chart"></div>
</template>
<script>
import * as echarts from "echarts";
// 白云区 geojson 数据
import baiyunGeoJson from "@/assets/mapData/440111.json";
// 24 镇街数据
import {
  BAIYUN_CONFIG,
  SUCCESS_STYLE,
  WARNING_STYLE,
  ERROR_STYLE,
  MapData
} from "@/assets/mapData/440111.config.js";
// 工具方法
import {
  numToThsSprtr
} from "@/utils/utils.js";
export default {
  name: "geoTwoDimensionalMapChart",
  data() {
    return {
      myChart: null,
      interval: 1000, // 时间间隔毫秒数
      index: 0, // 播放所在下标
      timer: null,
      option: {
        series: [{
          type: "map",
          map: "白云区",
          data: [],
          layoutCenter: ["50%", "50%"], // 属性定义地图中心在屏幕中的位置
          layoutSize: "99%", // 定义地图的大小
          label: {
            show: true,
            color: "#fff",
            textAlign: "center",
            // {a}:系列名。{b}:数据名。{c}:数据值。
            formatter: (params) => {
              let richName = "";
              if (SUCCESS_STYLE.includes(params.name)) {
                richName = "success";
              } else if (WARNING_STYLE.includes(params.name)) {
                richName = "warning";
              } else if (ERROR_STYLE.includes(params.name)) {
                richName = "error";
              }
              return `{${richName}|${params.name}}`;
            },
            rich: {
              success: {
                fontSize: "0.0729rem",
                padding: [4, 7],
                borderWidth: 2, // 图形描边的宽度。
                borderColor: "#fff", // 边框颜色
                backgroundColor: 'green', // 背景色
              },
              warning: {
                fontSize: "0.0729rem",
                padding: [4, 7],
                borderWidth: 2, // 图形描边的宽度。
                borderColor: "#fff", // 边框颜色
                backgroundColor: 'orange', // 背景色
              },
              error: {
                fontSize: "0.0729rem",
                padding: [4, 7],
                borderWidth: 2, // 图形描边的宽度。
                borderColor: "#fff", // 边框颜色
                backgroundColor: 'red', // 背景色
              },
            },
          },
          itemStyle: {
            borderWidth: 2, // 图形描边的宽度。
            borderColor: "#ddd", // 图形描边的颜色。
            areaColor: '#ccc',
            shadowColor: '#eee',
            shadowBlur: 10,
            shadowOffsetX: 0,
            shadowOffsetY: 2
          },
          // 点击选择样式
          select: {
            label: {
              color: "#fff",
            },
            itemStyle: {
              borderWidth: 2, // 图形描边的宽度。
              borderColor: "#ddd", // 图形描边的颜色。
              areaColor: '#ccc',
            }
          },
          emphasis: {
            // 当鼠标放上
            label: {
              color: "#fff",
            },
            itemStyle: {
              borderWidth: 2,
              borderColor: "#000",
              areaColor: 'blue',
            },
          },
        }],
        tooltip: {
          show: true,
          trigger: "item",
          // point:鼠标位置 contentSize:dom 的尺寸
          position: (point, params, dom, rect, size) => {
            return [point[0] - 20, point[1] - size.contentSize[1] - 15];
          },
          extraCssText: "box-shadow: none", // 额外样式
          formatter: (param) => {
            let data = `
              <div class="map-tooltips">
                <div class="name">${param.name}常住人口</div>
                <div class="value">
                 <span class="num">${numToThsSprtr(param.value || 0)}</span>
                 <span class="unit">人</span>
                </div>
              </div>
            `;
            return data;
          },
        },
      },
    };
  },
  mounted() {
    // 初始化渲染
    this.initRender();
    // 设置轮播
    this.setIntervalMyChart();
    // resize 事件监听
    window.addEventListener("resize", this.handleResize);
  },
  destroyed() {
    clearInterval(this.timer);
    window.removeEventListener("resize", this.handleResize);
  },
  methods: {
    // 初始化渲染
    initRender() {
      // 注册地图名字和数据
      echarts.registerMap("白云区", baiyunGeoJson);
      let chartDom = this.$refs.geoTwoDimensionalMapChart;
      this.myChart = echarts.init(chartDom);
      this.option.series[0].data = MapData;
      this.myChart.setOption(this.option);
      // 激活高亮跟提示
      console.log('initRender', this.index);
      this.dispatchActionChart("highlight", this.index);
      this.dispatchActionChart("showTip", this.index);
      // 鼠标划入
      this.mouseEvents();
    },
    // 鼠标划入
    mouseEvents() {
      this.myChart.on("mouseover", () => {
        // 停止定时器,清除之前的高亮
        clearInterval(this.timer);
        this.dispatchActionChart("downplay", this.index);
      });
      // 鼠标划出重新定时器开始
      this.myChart.on("mouseout", () => {
        clearInterval(this.timer);
        // 启动轮播
        this.setIntervalMyChart();
      });
    },
    // 设置轮播
    setIntervalMyChart() {
      const dataLength = BAIYUN_CONFIG.length;
      // 每隔 interval 进行一次切换
      this.timer = setInterval(() => {
        // 清除高亮跟提示
        this.dispatchActionChart("downplay", this.index);
        this.dispatchActionChart("hideTip", this.index);
        // 索引增加
        this.index++;
        // 激活高亮跟提示
        this.dispatchActionChart("highlight", this.index);
        this.dispatchActionChart("showTip", this.index);
        if (this.index === dataLength - 1) {
          // 需要对最后一个进行清除,对第一个进行激活
          let tempTimer = setTimeout(() => {
            this.dispatchActionChart("downplay", dataLength - 1);
            this.dispatchActionChart("highlight", this.index);
            clearTimeout(tempTimer);
          }, this.interval);
          this.index = -1;
        }
      }, this.interval);
    },
    /**
     * @description 控制高亮跟提示
     * @param {String} type (highlight:高亮(反:downplay);showTip:显示提示(反:hideTip))
     * @param {Number} dataIndex 数据项的 index
     * */ 
    dispatchActionChart(type, dataIndex) {
      this.myChart.dispatchAction({
        type: type,
        seriesIndex: 0,
        dataIndex: dataIndex,
      });
    },
    // resize 事件
    handleResize() {
      this.myChart.resize();
    },
  },
};
</script>
<style lang="scss" scoped>
@import "@/assets/scss/utils.scss";
.geo-two-dimensional-map-chart {
  width: 100%;
  height: p2r(730);
  ::v-deep .map-tooltips {
    min-width: p2r(160);
    position: relative;
    text-align: center;
    padding-top: p2r(10);
    .name {
      font-size: p2r(16);
      font-weight: 400;
      color: green;
      line-height: p2r(22);
      margin-bottom: p2r(8);
    }
    .value {
      color: green;
      .num {
        font-size: p2r(26);
        font-weight: bold;
        line-height: p2r(26);
      }
      .unit {
        font-size: p2r(14);
      }
    }
  }
}
</style>



参考资料


   echarts 图表行为 action

   echarts 标签内容格式器series-map.label.formatter

   echarts 提示框浮层的位置

   echarts 提示框浮层内容格式器,支持字符串模板和回调函数两种形式

   中国地图,省份名称重叠 #4379

   怎么获取 echarts 需要的 geoJson 数据去渲染地图:以广州市白云区24镇街为例

   使用 sass + rem + flexible.js 实现大屏自适应



目录
相关文章
|
6月前
Echarts 热力图自定义开发
Echarts 热力图自定义开发
234 0
|
6月前
|
数据可视化
echarts图表坐标轴数据标签添加下划线
echarts图表坐标轴数据标签添加下划线
197 0
|
17天前
|
JavaScript API
Echarts中单独为每个legend图例设置样式-根据数据正负显示不同样式
通过上述方法,我们便能够在ECharts中根据数据的正负为每个图例项设置不同的样式,增强了图表的可读性和表现力。这种方法虽然略显间接,但不失为一种灵活的解决方案。
30 2
Vue3,setup的使用需要搭配return进行使用,Vue3中带setup的script的标签和不带能不能合并到一起,export default不能放到setup里会报错,script
Vue3,setup的使用需要搭配return进行使用,Vue3中带setup的script的标签和不带能不能合并到一起,export default不能放到setup里会报错,script
|
6月前
|
JavaScript
echarts_自定义graph关系图
echarts_自定义graph关系图
101 0
|
4月前
|
JavaScript 定位技术
【天地图】vue 天地图 T is not defined
【天地图】vue 天地图 T is not defined
161 1
|
5月前
|
JavaScript 定位技术
BIGEMAP地图VUE中引入
BIGEMAP地图VUE中引入
BIGEMAP地图VUE中引入
|
5月前
|
JavaScript 前端开发 数据可视化
ECharts 雷达图案例001-自定义节点动画
使用ECharts创建自定义雷达图,通过JavaScript动态更新高亮和交互反馈,增强用户体验。关键步骤包括:开启动画效果,数据更新时保持图表状态,鼠标悬浮时动态高亮指标,优化动画性能。案例展示了ECharts在数据可视化中的灵活性和表现力。[查看完整案例](https://download.csdn.net/download/No_Name_Cao_Ni_Mei/89454380)。
166 0
 ECharts 雷达图案例001-自定义节点动画
|
4月前
|
JavaScript
|
4月前
echarts 自动轮播
echarts 自动轮播
95 0