🌏基于Proxy对象打造Echarts三级下钻地图

1,868 阅读5分钟

🌏 概论

相信每位开发大屏的小伙伴,都或多或少接触过这样一个需求:基于省、市、区三级下钻地图的可视化数据展示。地图上的数据展示对于大家来说基本都没有什么难度,但三级下钻地图的开发着实要花费一番心思。

为了避免重复造轮子,自己也前前后后开发、优化了好几版下钻地图的代码,但终究感觉不尽人意。直到最近利用Proxy的监听机制进行了重构,补上了一部分之前体验上的缺陷,才算是完成了一份较为满意的答卷。

下面就和大家分享下开发思路,欢迎交流~

🌐源码地址:三级下钻地图源码

🌐演示地址:三级下钻地图Demo

有帮助的话还请给个star⭐~

三级下钻地图.gif

🌏 基本思路

熟悉echarts Geo地图开发的小伙伴应该都清楚,Echarts每次地图的切换,其实都是重新注册一份静态的geo文件作为底图,而每张地图也都对应这一个唯一的编码:adcode

那我们便可以adcode作为地区切换功能的核心监听对象

(1)全局注册Watcher对象监听adcode变化;

(2)地图切换(下钻、返回)时触发Watcher对象中set钩子;

(3)set钩子中判断、更新adcode,同步获取geo资源;

(4)Echarts中注册新geo,完成地图切换。

🌏 核心代码

我比较习惯直接在注释里说明代码思路,这里就不多言了。

/*
 * @Author: AnZhou
 * @FileName: main.js
 * @Description: 入口文件
 */
import cityMapByName from '../assets/json/cityMapByNameSimple.json';
import cityMapByCode from '../assets/json/cityMapByCodeSimple.json';
import STATIC_DATA from '../data/staticData';
import mapOption from '../data/mapOption';
import getJson from '../request/getJson';
import judgeArea from '../utils/judgeArea';

let mapIns = null; //echarts实例

let mapDom = document.getElementById('chart-map'); //地图容器
let backBtnDom = document.getElementById('back-btn'); //返回父级按钮

let prevAdcode = null; //父级adcode
let areaInfo = {}; //当前区域的具体信息,详见../utils/judgeArea
/**
 * tips--start
 * 如下三项name在此demo中无用,但实际中很可能会遇到,故保留
 * 如果你的项目中不需要,可自行删除相应代码
/** tips--end */
let provName = null; //当前区域所在的省份全称,如无则为null
let cityName = null; //当前区域所在的省份全称,如无则为null
let districtName = null; //当前区域所在的省份全称,如无则为null

//核心监听对象
let watcher = new Proxy(
  {
    adcode: null, //当前区域的adcode
  },
  {
    get(target, key) {
      return target[key];
    },
    set(target, key, value) {
      target[key] = value;

      // 获取当前区域的具体信息
      areaInfo = judgeArea(watcher.adcode);

      //获取省市区三级全称(如需)
      getNames();

      mapController();

      backBtnController();

      return true;
    },
  }
);

//控制地图生成
function mapController() {
  /**
   * tips--start
   * 1. 仅当地图等级为国家级、省级、市级时可点击下钻
   * 2. 直辖市、澳门、香港、台湾较为特殊,只展示两级
  /** tips--end */
  if ([0, 1].includes(areaInfo.districtRank)) {
    //国家、省级时
    prevAdcode = STATIC_DATA.CHINA_ADCODE;
    pipeLine();
  } else if ([2, 3].includes(areaInfo.districtRank)) {
    //当前区域为市、区级时
    if (areaInfo.isSpecialArea) {
      //特殊地区的父级为中国
      prevAdcode = STATIC_DATA.CHINA_ADCODE;
    } else {
      //其余父级为所在省、自治区
      prevAdcode = areaInfo.provAdcode;
    }
    //仅点击市级可下钻
    areaInfo.districtRank == 2 && pipeLine();
  }
}

//控制返回按钮样式
function backBtnController() {
  backBtnDom.style.display = areaInfo.districtRank == 0 ? 'none' : 'inline-flex';
}

// 通过cityMapByCode中的json查询相应地区的省、市、区名称
function getNames() {
  /**
   * tips--start
   * 如果你的项目中不需要省市县全称,请可自行删除以下代码
   * 并删除顶部引用的cityMapByCode静态json
   */
  /** tips--end */
  [provName, cityName, districtName] = [
    areaInfo.provAdcode ? cityMapByCode[areaInfo.provAdcode] : '',
    areaInfo.cityAdcode ? cityMapByCode[areaInfo.cityAdcode] : '',
    areaInfo.districtAdcode ? cityMapByCode[areaInfo.districtAdcode] : '',
  ];
}

//注册生成echarts地图、绑定点击事件
function initMapChart({ mapDom, mapJson, mapOption }) {
  if (!mapIns) {
    mapIns = echarts.init(mapDom);

    mapIns.off('click');

    mapIns.on('click', async params => {
      watcher.adcode = cityMapByName[params.name];
    });
  }

  echarts.registerMap('统计地图', mapJson);

  mapIns.setOption(mapOption);
}

//总线
async function pipeLine() {
  //异步获取geojson
  const mapJson = await getJson(watcher.adcode);

  initMapChart({ mapDom, mapJson, mapOption });
}

//设置容器样式
mapDom.style.width = '100%';
mapDom.style.height = window.innerHeight - 56 * 2 + 'px';
//绑定返回事件
backBtnDom.addEventListener('click', () => {
  watcher.adcode = prevAdcode;
});

//初始化地图
watcher.adcode = '100000';
/*
 * @Author: AnZhou
 * @FileName: judgeArea.js
 * @Description: 地区信息计算函数
 */
import STATIC_DATA from '../data/staticData';

let [provAdcode, cityAdcode, districtAdcode, districtRank, isSpecialArea] = [
  ...Array(4).fill(null),
  false,
];

function judgeArea(adcode) {
  /*
   *  根据国家标准《中华人民共和国行政区划代码》
   *  标准号GB/T 2260-2007
   *  adcode代码由六位组成
   *  前两位表省、自治区级
   *  中两位表市州级
   *  后两位表区县级
   */

  adcode = adcode + '';

  let result = null;
  if (adcode === '100000') {
    //展示地图为中国
    result = [...Array(3).fill(null), 0, false];
  } else if (/^\d{2}0{4}$/.test(adcode)) {
    //展示地图为省、自治区级
    if (STATIC_DATA.SPECIAL_AREA.includes(adcode)) {
      //省级-特殊地区
      result = [adcode, adcode, null, 1, true];
    } else {
      //省
      result = [adcode, null, null, 1, false];
    }
  } else if (/^\d{4}0{2}$/.test(adcode)) {
    //展示地图为市州级
    result = [adcode.substring(0, 2) + '0000', adcode, null, 2, isSpecialArea];
  } else {
    //展示地图(选中区域为)区县级
    result = [
      adcode.substring(0, 2) + '0000',
      adcode.substring(0, 4) + '00',
      adcode,
      3,
      isSpecialArea,
    ];
  }

  [provAdcode, cityAdcode, districtAdcode, districtRank, isSpecialArea] = result;

  return {
    provAdcode, //省级adcode
    cityAdcode, //市级adcode
    districtAdcode, //区级adcode
    districtRank, //当前地图等级:0-国 1-省 2-市 3-区
    isSpecialArea, //是否特殊地区
  };
}

export default judgeArea;

🌏 其余代码

受限于篇幅非核心代码就不贴上来了,感兴趣可以去Github翻看。

🌏 额外说明

🚩 1. Geojson文件从哪来?

(1)阿里DataV的geojson开放平台。(本demo是获取其所有静态文件后,存入了git仓库(点击查看),然后再利用jsdelivrCDN返回);

(2)巧妙利用高德开放平台的行政区域查询API(这个方法我没试过,大家自己了解下)。

🚩 2. 为什么我的地图切换时没有过渡效果?

Echarts4.3以上版本都没有地图切换的过渡效果,如果想用过渡效果务必使用4.3以下版本(当然也不要太老)。

另补一句,过渡效果有望在5.3版本回归,详见issue

🚩 3. 有没有Vue/React版本?

目前没有,不过迁移相信对大家来说不是问题。

🚩 4. 为什么我修改了源码后页面上看不到效果?

本Demo中使用了Rollup,每次更改源码后必须重新打包才可。至于为什么不能热更新,因为rollup的热更新插件安装老是报错,就没有加这个功能。反正正式项目中基本都不会用Rollup,你说对吧😀。

🚩 4. 这代码不行,我有更好的!

欢迎交流🥳🥳~