Echarts实现3D地图(多层geo)同步缩放
echarts 3D地图实现缩放
- 功能实现
- 静态3D地图实现
- 缩放功能实现
- 修改配置项使其可缩放
- 监听缩放事件
- 同步缩放效果
- 中心点偏移问题处理
- 性能优化
- 添加节流
- 最终展示
- 效果展示
- 完整代码
功能实现
静态3D地图实现
这里使用echarts-for-react组件来实现,使用echarts 配置项也通用
import ReactECharts from "echarts-for-react";
import * as echarts from 'echarts'const eCharts_react = useRef(null); // 地图实例
const [option, setOption] = useState({}); // 配置项
const [mapJson, setMapJson] = useState({features: []}) // geoJson
// 获取地图geoJson
const getGeoJson = (city) => {const url = `${httpUrl}/region/hlj/${city}_full.json`axios.get(url).then(response => {const geoJSON = response.datasetMapJson(geoJSON)}).catch(error => {noMapData() // 报错的一些处理console.error('Error fetching district data:', error);});
}
// 城市下钻要切换地图,所以这里需要根据父级的code来获取对应的geo数据
useEffect(() => {getGeoJson(mapCode)const chartIns = eCharts_react?.current.getEchartsInstance()setMyChart(chartIns)myChart && myChart.dispose();
}, [mapCode])useEffect(() => {// 处理地图geoJsonlet data = mapJson?.features?.map((item) => {return {name: item.properties.name,adCode: item.properties.adcode};});// map配置let exampleOption = {geo: [{type: "map",map: mapName, // 父级传递当前地图名称aspectScale: 1,zoom: 0.65,layoutCenter: ["50%", "50%"],layoutSize: "146%",label: {emphasis: {show: false,},},itemStyle: {normal: {borderColor: "#00E9FF",borderWidth: 2,shadowColor: "#8cd3ef",shadowOffsetY: 20,shadowBlur: 120,areaColor: "transparent",},}},// 重影{type: "map",map: mapName,zlevel: -1,aspectScale: 1,zoom: 0.65,layoutCenter: ["50%", "51%"],layoutSize: "146%",silent: true,itemStyle: {normal: {borderWidth: 1,borderColor: "rgba(58,149,253,0.8)",shadowColor: "rgba(172, 122, 255,0.5)",shadowOffsetY: 58,shadowBlur: 15,areaColor: "rgba(5,21,35,0.1)",},},},{type: "map",map: mapName,zlevel: -2,aspectScale: 1,zoom: 0.65,layoutCenter: ["50%", "52%"],layoutSize: "146%",silent: true,itemStyle: {normal: {borderWidth: 1,borderColor: "rgba(58,149,253,0.6)",shadowColor: "rgba(65, 214, 255,1)",shadowOffsetY: 5,shadowBlur: 15,areaColor: "transpercent",},},},{type: "map",map: mapName,zlevel: -3,aspectScale: 1,zoom: 0.65,layoutCenter: ["50%", "53%"],layoutSize: "146%",silent: true,itemStyle: {normal: {borderWidth: 1,borderColor: "rgba(58,149,253,0.4)",shadowColor: "rgba(58,149,253,1)",shadowOffsetY: 15,shadowBlur: 10,areaColor: "transpercent",},},},{type: "map",map: mapName,zlevel: -4,aspectScale: 1,zoom: 0.65,layoutCenter: ["50%", "54%"],layoutSize: "146%",silent: true,itemStyle: {normal: {borderWidth: 5,borderColor: "rgba(5,9,57,0.8)",shadowColor: "rgba(29, 111, 165,0.8)",shadowOffsetY: 15,shadowBlur: 10,areaColor: "rgba(5,21,35,0.1)",},},},],series: [{name: "丹东市数据",type: "map",map: mapName, // 自定义扩展图表类型aspectScale: 1,zoom: 0.65,showLegendSymbol: true,label: {normal: {show: true,textStyle: {color: "#fff", fontSize: "120%"},},},itemStyle: {normal: {areaColor: {type: "linear",x: 1200,y: 0,x2: 0,y2: 0,colorStops: [{offset: 0,color: "rgba(3,27,78,0.75)", // 0% 处的颜色},{offset: 1,color: "rgba(58,149,253,0.75)", // 50% 处的颜色},],global: true, // 缺省为 false},borderColor: "#fff",borderWidth: 0.2,},emphasis: {show: false,color: "#fff",areaColor: "rgba(0,254,233,0.6)",},},layoutCenter: ["50%", "50%"],layoutSize: "146%",markPoint: {symbol: "none",},data: data,roam:"scale"},],};setOption(exampleOption)// 注册地图echarts.registerMap(mapName, mapJson)}, [mapJson])return (<div style={{height: '100%', width: '100%'}}><ReactEChartsoption={option}style={{ height: '100%', width: '100%' }} ref={eCharts_react}/></div>)
效果
缩放功能实现
修改配置项使其可缩放
// 每一个geo以及series添加
roam:"scale"
// true 开启缩放和移动
// scale 开始缩放 move 开启移动
此时,地图的缩放功能已开启。
但是有个问题,光标在地图之外缩放时效果正常,
光标在地图上时,只有最上层的地图触发了缩放。
于是想,某一层缩放时,将他的缩放比例同步到其他层来实现效果。
监听缩放事件
根据百度尝试了多种监听,均未生效。
找到文档发现该组件的onEvents支持所有 ECharts 事件类型。
欣喜若狂,立即前往。查阅文档找到georoam事件监听,尝试了下,果然生效了!
const onEvents = {'click': handleClickMap, // 下钻点击事件// 'mapRoam': zoomMap// 'mousewheel': zoomMap// 'wheel': zoomMap// 'graphRoam': zoomMap'georoam': sameData}
return (<div style={{height: '100%', width: '100%'}}><ReactEChartsoption={option}onEvents={onEvents}style={{ height: '100%', width: '100%' }} ref={eCharts_react}/></div>
)
除了组件的onEvents事件监听,还尝试了echart实例的on事件监听,但是都没有生效。
如果有生效的,麻烦请留言告知,万分感激🙏
同步缩放效果
const sameData = (params) => {if(params.zoom){option.geo.map((item) => {item.zoom = item.zoom * params.zoom})option.series[0].zoom = option.series[0].zoom * params.zoomeCharts_react.current.getEchartsInstance().setOption(option)}}
同步缩放生效了,但是发现了新的问题。
光标在不同的位置进行缩放,不同层的中心点会出现一定程度的偏移。导致地图出现上图错位的效果。
中心点偏移问题处理
设置zoom时,将每一层的center设置为undefined
option.geo.map((item) => {item.zoom = item.zoom * params.zoomitem.center = undefined
})
option.series[0].zoom = option.series[0].zoom * params.zoom
option.series[0].center = undefined
eCharts_react.current.getEchartsInstance().setOption(option)
性能优化
添加节流
function throttle (fn, delay) {let last = 0;return function(...args) {const now = Date.now();if (now - last > delay) {fn.apply(this, args);last = now;}}
}
// 这个箭头函数要记得换成普通函数
function sameData(params) {if(params.zoom){option.geo.map((item) => {item.zoom = item.zoom * params.zoomitem.center = undefined})option.series[0].zoom = option.series[0].zoom * params.zoomoption.series[0].center = undefinedeCharts_react.current.getEchartsInstance().setOption(option)}
}
const onEvents = {'georoam': throttle(sameData, 100)
}
最终展示
效果展示
完整代码
import React, {useEffect, useRef, useState} from 'react'
import './index.less'
import ReactECharts from "echarts-for-react";
import * as echarts from 'echarts'
import axios from 'axios'
import {httpUrl} from '@/public/loginConfig'const CityMap: React.FC = ({mapCode, mapName, noMapData, underMap}:{}) => {const eCharts_react = useRef(null);const [myChart, setMyChart] = useState(null)const [option, setOption] = useState({});const [mapJson, setMapJson] = useState({features: []})const handleClickMap = (params) => {underMap(params)}const getGeoJson = (city) => {const url = `${httpUrl}/region/hlj/${city}_full.json` // 公司服务axios.get(url).then(response => {const geoJSON = response.datasetMapJson(geoJSON)}).catch(error => {noMapData()console.error('Error fetching district data:', error);});}function throttle (fn, delay) {let last = 0;return function(...args) {const now = Date.now();if (now - last > delay) {fn.apply(this, args);last = now;}}}function sameData(params) {if(params.zoom){option.geo.map((item) => {item.zoom = item.zoom * params.zoomitem.center = undefined})option.series[0].zoom = option.series[0].zoom * params.zoomoption.series[0].center = undefinedeCharts_react.current.getEchartsInstance().setOption(option)}}const registerMap = () => {echarts.registerMap(mapName, mapJson)}useEffect(() => {getGeoJson(mapCode)const chartIns = eCharts_react?.current.getEchartsInstance()setMyChart(chartIns)myChart&& myChart.dispose();}, [mapCode])useEffect(() => {let data = mapJson?.features?.map((item) => {return {name: item.properties.name,adCode: item.properties.adcode};});let exampleOption = {geo: [{type: "map",map: mapName,aspectScale: 1,zoom: 0.65,layoutCenter: ["50%", "50%"],layoutSize: "146%",roam:"scale",label: {emphasis: {show: false,},},itemStyle: {normal: {borderColor: "#00E9FF",borderWidth: 2,shadowColor: "#8cd3ef",shadowOffsetY: 20,shadowBlur: 120,areaColor: "transparent",},}},// 重影{type: "map",map: mapName,zlevel: -1,aspectScale: 1,zoom: 0.65,layoutCenter: ["50%", "51%"],layoutSize: "146%",roam:"scale",silent: true,itemStyle: {normal: {borderWidth: 1,borderColor: "rgba(58,149,253,0.8)",shadowColor: "rgba(172, 122, 255,0.5)",shadowOffsetY: 58,shadowBlur: 15,areaColor: "rgba(5,21,35,0.1)",},},},{type: "map",map: mapName,zlevel: -2,aspectScale: 1,zoom: 0.65,layoutCenter: ["50%", "52%"],layoutSize: "146%",roam:"scale",silent: true,itemStyle: {normal: {borderWidth: 1,borderColor: "rgba(58,149,253,0.6)",shadowColor: "rgba(65, 214, 255,1)",shadowOffsetY: 5,shadowBlur: 15,areaColor: "transpercent",},},},{type: "map",map: mapName,zlevel: -3,aspectScale: 1,zoom: 0.65,layoutCenter: ["50%", "53%"],layoutSize: "146%",roam:"scale",silent: true,itemStyle: {normal: {borderWidth: 1,borderColor: "rgba(58,149,253,0.4)",shadowColor: "rgba(58,149,253,1)",shadowOffsetY: 15,shadowBlur: 10,areaColor: "transpercent",},},},{type: "map",map: mapName,zlevel: -4,aspectScale: 1,zoom: 0.65,layoutCenter: ["50%", "54%"],layoutSize: "146%",roam:"scale",silent: true,itemStyle: {normal: {borderWidth: 5,borderColor: "rgba(5,9,57,0.8)",shadowColor: "rgba(29, 111, 165,0.8)",shadowOffsetY: 15,shadowBlur: 10,areaColor: "rgba(5,21,35,0.1)",},},},],series: [{name: "丹东市数据",type: "map",map: mapName, // 自定义扩展图表类型aspectScale: 1,zoom: 0.65, // 缩放showLegendSymbol: true,label: {normal: {show: true,textStyle: {color: "#fff", fontSize: "120%"},},},itemStyle: {normal: {areaColor: {type: "linear",x: 1200,y: 0,x2: 0,y2: 0,colorStops: [{offset: 0,color: "rgba(3,27,78,0.75)", // 0% 处的颜色},{offset: 1,color: "rgba(58,149,253,0.75)", // 50% 处的颜色},],global: true, // 缺省为 false},borderColor: "#fff",borderWidth: 0.2,},emphasis: {show: false,color: "#fff",areaColor: "rgba(0,254,233,0.6)",},},layoutCenter: ["50%", "50%"],layoutSize: "146%",markPoint: {symbol: "none",},data: data,roam:"scale"},],};setOption(exampleOption)registerMap()}, [mapJson])const onEvents = {'click': handleClickMap,'georoam': throttle(sameData, 100)}return (<div style={{height: '100%', width: '100%'}}><ReactEChartsoption={option}onEvents={onEvents}style={{ height: '100%', width: '100%' }} ref={eCharts_react}/></div>)
}
export default CityMap