echarts绘制地图
cRack_cLick 人气:0echarts地图制作
离线地图下载地址https://datav.aliyun.com/tools/atlas/index.html
echarts文档地址https://echarts.apache.org/zh/option.html
基于VUE编写,其他框架请自行转换,大同小异
基础配置
先让地图内容出来,npm安装步骤省略,请参考官方文档,创建的div必须设置宽度和高度,关于图表的宽高自适应,参考我的另一篇文章
<template> <div class="content"> <div ref="charts" style="width: 1000px; height: 800px"></div> </div> </template> <script> import * as echarts from "echarts"; import zhongguo from "@/assets/mapJson/data-city.json" export default { created () { this.$nextTick(() => { this.initCharts(); }) }, methods: { initCharts() { const charts = echarts.init(this.$refs["charts"]); const option = { // 背景颜色 backgroundColor: "#404a59", // 提示浮窗样式 tooltip: { show: true, trigger: "item", alwaysShowContent: false, backgroundColor: "#0C121C", borderColor: "rgba(0, 0, 0, 0.16);", hideDelay: 100, triggerOn: "mousemove", enterable: true, textStyle: { color: "#DADADA", fontSize: "12", width: 20, height: 30, overflow: "break", }, showDelay: 100 }, // 地图配置 geo: { map: "china", label: { // 通常状态下的样式 normal: { show: true, textStyle: { color: "#fff", }, }, // 鼠标放上去的样式 emphasis: { textStyle: { color: "#fff", }, }, }, // 地图区域的样式设置 itemStyle: { normal: { borderColor: "rgba(147, 235, 248, 1)", borderWidth: 1, areaColor: { type: "radial", x: 0.5, y: 0.5, r: 0.8, colorStops: [ { offset: 0, color: "rgba(147, 235, 248, 0)", // 0% 处的颜色 }, { offset: 1, color: "rgba(147, 235, 248, .2)", // 100% 处的颜色 }, ], globalCoord: false, // 缺省为 false }, shadowColor: "rgba(128, 217, 248, 1)", shadowOffsetX: -2, shadowOffsetY: 2, shadowBlur: 10, }, // 鼠标放上去高亮的样式 emphasis: { areaColor: "#389BB7", borderWidth: 0, }, }, }, }; // 地图注册,第一个参数的名字必须和option.geo.map一致 echarts.registerMap("china",zhongguo) charts.setOption(option); }, }, }; </script>
这是最基础的配置,外加了一些我自己写的样式,使地图美观一些,如果你完全的复制,并且china.json
文件也引入了,那么你会看到如下的内容
这其中比较有意思的是,如果你注册地图时,还有option.geo.map
的名字用的是china,南海诸岛会如上图以缩略图展示,但是以此之外来命名地图,则不会展示缩略图。
再次声明,如果二者名字不一致,将会导致异常,致使地图无法显示
数据渲染
实际开发中,往往需要将后台的数据渲染到地图里,我们在option里添加series属性,以下是我的两个示例,仅做参考:
series: [ { type: "scatter", coordinateSystem: "geo", symbol: "pin", legendHoverLink: true, symbolSize: [60, 60], // 这里渲染标志里的内容以及样式 label: { show: true, formatter(value) { return value.data.value[2]; }, color: "#fff", }, // 标志的样式 itemStyle: { normal: { color: "rgba(255,0,0,.7)", shadowBlur: 2, shadowColor: "D8BC37", }, }, // 数据格式,其中name,value是必要的,value的前两个值是数据点的经纬度,其他的数据格式可以自定义 // 至于如何展示,完全是靠上面的formatter来自己定义的 data: [ { name: "西藏", value: [91.23, 29.5, 2333] }, { name: "黑龙江", value: [128.03, 47.01, 1007] }, ], showEffectOn: "render", rippleEffect: { brushType: "stroke", }, hoverAnimation: true, zlevel: 1, }, // { // type: "effectScatter", // coordinateSystem: "geo", // effectType: "ripple", // showEffectOn: "render", // rippleEffect: { // period: 10, // scale: 10, // brushType: "fill", // }, // hoverAnimation: true, // itemStyle: { // normal: { // color: "rgba(255, 235, 59, .7)", // shadowBlur: 10, // shadowColor: "#333", // }, // }, // zlevel: 1, // data: [ // { name: "西藏", value: [91.23, 29.5, 2333] }, // { name: "黑龙江", value: [128.03, 47.01, 1007] }, // ], // }, ],
两种渲染方式如下:
使用备注的部分时,需要在option.tooltip
里添加formatter属性,我写的如下:
const option = { // ... tooltip: { // ... formatter(params) { return `地区:${params.name}</br>数值:${params.value[2]}`; } } }
更多的方式还要自己多试验,这是个费时且需要耐心的活,你甚至可以将柱状图放上去。有更花里胡哨的效果,也请分享给我。
嵌入文字
使用option.graphic
可以实现简单的水印效果
const option = { // ... graphic:{ // 水印类型 type: 'text', // 相对于容器的位置 left:'10%', top: '10%', // 样式设置 style: { // 文本内容 text: "create by cRack_cLick", // 字体粗细、大小、字体 font: 'bolder 1.5rem "Microsoft YaHei", sans-serif', // 字体颜色 fill: "#fff" } } }
效果如下:
利用graphic的type=“group”,还可以组合出一些有意思的效果(抄官方文档的效果):
graphic: { type: "group", rotation: Math.PI / 4, bounding: "raw", left: 110, top: 110, z: 100, children: [ { type: "rect", left: "center", top: "center", z: 100, shape: { width: 400, height: 50, }, style: { fill: "rgba(0,0,0,0.3)", }, }, { type: "text", left: "center", top: "center", z: 100, style: { fill: "#ddd", text: "create by cRack_cLick", font: 'bolder 1.5rem "Microsoft YaHei", sans-serif', }, }, ], },
地图下钻
往往还有一种需求,在我们点击一个省的时候,需要切换到这个省的详细地图,甚至还可以下钻到市、县等等。
为了试下点击下钻,我们需要先了解echarts中的点击事件,文档参考
以目前的功能来说,我们暂时不需要加入其它的业务逻辑以及省级的数据渲染,仅仅只做地图的切换,所以点击事件里我们需要实现获取点击的省份名称,然后根据省份名称,来选择地图的JSON文件,最后重新渲染echarts图表,下面是我的简单示例:
// 新增加北京的地图JSON文件 import beijing from "@/assets/mapJson/data-beijing.json"; // ... initCharts(){ const charts = echarts.init(this.$refs["charts"]); // ... // 注意这里是echarts的实例对象,而不是echarts组件本身。 charts.on('click', ({name}) => { if (name === "北京") { // 修改option的配置,可以继续自定义 option.geo.zoom = 0.8 // 就像上面提到的,这里必须要和注册地图时的名字一致 option.geo.map = "beijing" // 注册地图 echarts.registerMap("beijing", beijing) // 重新渲染 charts.setOption(option, true) } }) }
需要注意的是,在重新setOption
的时候,我们加入了第二个参数,按照官方文档的说法:
参数:
调用方式:
chart.setOption(option, notMerge, lazyUpdate);
或者
chart.setOption(option, { notMerge: ..., lazyUpdate: ..., silent: ... });
- option
图表的配置项和数据。
- notMerge
可选,是否不跟之前设置的 option 进行合并,默认为 false,即合并。
- lazyUpdate
可选,在设置完 option 后是否不立即更新图表,默认为 false,即立即更新。
- silent
可选,阻止调用 setOption 时抛出事件,默认为 false,即抛出事件。
第二个从参数设置为true来让图表重新渲染,而不合并配置,当然,这一点具体需要看你显示开发的需求,我在这里仅是为了演示。绝不是偷懒
另外在echarts v3.x的版本里,切换地图默认是有过渡动画的,而v4.x和v5.x的版本里则没有过渡动画,如果知道怎么加上的,可以私信我。
上面虽然可以实现地图切换,但很显然开发中这么写要被打死。下钻三十多个地图要写三十多个if,显然是一种不理智的开发方式。一种方式我们可以通过axios或者ajax异步请求,但是这样需要你在生产环境和运维协商好,否则会导致请求不到JSON文件。
下面是我在前端写的一个简单的工具方法,仅供参考:
import zhongguo from "@/assets/mapJson/data-city.json"; import neimenggu from "@/assets/mapJson/data-neimenggu.json"; import beijing from "@/assets/mapJson/data-beijing.json"; // ... const mapDict = { "北京": "beijing", "内蒙古": "neimenggu", // ... } const mapData = { beijing, neimenggu, // ... } export function getMap(mapName) { const cityName = mapDict[mapName] if(cityName){ return [cityName, mapData[cityName]] } return ['china', zhongguo] }
需要建立两个字典,一个是汉字和拼音的对照映射,一个是拼音和JSON文件的映射,这个可灵活配置,并非唯一。
优化一下上面的的代码:
// 删除地图json文件的引用,修改为上面的工具方法 import { getMap } from "./maputil"; methods: { initCharts() { const charts = echarts.init(this.$refs["charts"]); const option = { // ... }; // 不传name默认会返回中国地图 const [mapName, mapJson] = getMap(); option.geo.map = mapName; // 地图注册,第一个参数的名字必须和option.geo.map一致 echarts.registerMap(mapName, mapJson); charts.setOption(option); charts.on("click", ({ name }) => { // 这里和上面一样,其实还可以再优化一下。为了方便阅读,这里不再封装。 const [mapName, mapJson] = getMap(name); option.geo.zoom = 0.8; option.geo.map = mapName; echarts.registerMap(mapName, mapJson); charts.setOption(option, true); }); } }
效果如下:
结合水印制作级联效果
现在的地图可以下钻了,但是似乎操作起来还有些别扭。
我们现在想要的效果是:我们需要每下钻一层,水印部分就会加上当前地区的名称。点击水印地区的名称,就会跳转到当前地区的地图,我们要来改造一下echarts示例的click事件。
首先option.graphic
的默认值修改为中国地图,这里为了方便阅读,仅使用text格式演示:
// ... graphic: [ { type: "text", left: "10%", top: "10%", style: { text: "中国", font: 'bolder 1.5rem "Microsoft YaHei", sans-serif', fill: "#fff", }, }, ],
以数组的形势编写后,思路就明显了,只要在click事件的时候,将下钻地图的信息push进来,并且为了防止重合,稍微移动一下定位即可,我的示例如下:
charts.on("click", ({ name }) => { const [mapName, mapJson] = getMap(name); option.geo.zoom = 0.8; option.geo.map = mapName; // 为了重新定位,这里使用了length const idx = option.graphic.length + 1; option.graphic.push({ type: "text", left: `${idx * 10}%`, top: "10%", style: { text: name, font: 'bolder 1.5rem "Microsoft YaHei", sans-serif', fill: "#fff", }, }); echarts.registerMap(mapName, mapJson); charts.setOption(option, true); });
点击后效果如下:
现在还有问题,就是点击地区名字没有响应,所以我们还要为option.graphic
子元素加上click事件
这个click事件功能也类似,获取地图名称,获取地图数据,重新渲染。但是这个click事件需要注意,比如我点击了北京,那么在数组里是需要将密云区的元素删除掉的,同理,点击中国,则后面的元素都要删除。在这里我就不把相同的部分抽离出来了:
// 防止graph里频繁添加click事件,在添加click事件之前先全部清空掉。 charts.off() charts.on("click", ({name}) => { // 如果option.graphic里已经有了城市名称,则不进行任何操作,防止频繁点击 const index = option.graphic.findIndex(i => i.style.text === name); if (!name || index !== -1) return const [mapName, mapJson] = getMap(name); option.geo.zoom = 0.8; option.geo.map = mapName; // 为了重新定位,这里使用了length const idx = option.graphic.length + 1; option.graphic.push({ type: "text", left: `${idx * 10}%`, top: "10%", style: { text: name, font: 'bolder 1.5rem "Microsoft YaHei", sans-serif', fill: "#fff", }, onclick: () => { // 利用函数的作用域,可以直接拿上面的name来用 const [grahpName, graphJson] = getMap(name); const index = option.graphic.findIndex(i => i.style.text === name); // 点击元素之后的所有元素全部删除 option.graphic.splice(index + 1); // 很多操作重复了,你可以将公共部分抽离出来 option.geo.map = mapName; echarts.registerMap(grahpName, graphJson); charts.setOption(option, true); }, }); echarts.registerMap(mapName, mapJson); charts.setOption(option, true); });
这里会有个坑,在给graph
添加click事件后,点击时会同时触发我们上面charts.on
的click事件,想了很久也没有找到好一点的方式来解决这个事件冲突,最后只好判断了一下name
是否为空来暂时解决。如果有更好的办法,也请留言。最终效果如下:
至此绘制地图已经完毕,更多是依靠自己的业务需求来进行更灵活的配置和渲染,它的API没有什么太复杂的,只是我们缺少了一点耐心去实验。
visualMap
首先来看效果
增加visualMap来让地图的数据渲染更有层次感,实现起来也很简单,只需要在option里增加visualMap配置即可:
const option = { // ... visualMap: { // 是否展示左下角,即是是false,也仅是不显示,不影响数据的映射 show: true, // 上下端文字 text: ["高", "低"], // 最小值和最大值,必须指定 min: 0, max: 6000, // 位置 left: "10%", bottom: "10%", // 是否展示滑块 calculable: true, // 指定映射的数据,对应的是option.series,这里根据自己的实际需要进行配置 seriesIndex: [0], // 从下到上的颜色 inRange: { color: ['#00467F', '#A5CC82'], }, //字体颜色 textStyle: { color: "#fff", map: "china", }, } }
如果你的代码是跟着我从上面一直写下来的,那么此时你应该发现只是定位的图标变了,相应的地图区域并未变色,所以我们还要把地图的数据映射上去,所以在option.series
里再加一个元素,使其type=“map”,内容与geo一致即可,但是要多加data属性,渲染的数据和定位图标一致。并将seriesIndex的索引做好映射,即可实现。
const option = { // ... visualMap: { // 是否展示左下角,即是是false,也仅是不显示,不影响数据的映射 show: true, // 上下端文字 text: ["高", "低"], // 最小值和最大值,必须指定 min: 0, max: 6000, // 位置 left: "10%", bottom: "10%", // 是否展示滑块 calculable: true, // 指定映射的数据,对应的是option.series,这里根据自己的实际需要进行配置 seriesIndex: [0], // 从下到上的颜色 inRange: { color: ['#00467F', '#A5CC82'], }, //字体颜色 textStyle: { color: "#fff", map: "china", }, } }
如果你的代码是跟着我从上面一直写下来的,那么此时你应该发现只是定位的图标变了,相应的地图区域并未变色,所以我们还要把地图的数据映射上去,所以在option.series
里再加一个元素,使其type=“map”,内容与geo一致即可,但是要多加data属性,渲染的数据和定位图标一致。并将seriesIndex的索引做好映射,即可实现。
如果出现了缩放重影,说明生成了两个地图组件,需要在新的series里加上geoIndex属性,值是geo里的索引,这样就只会共享一个组件,不会出现缩放重影的问题了
以下为源文档:
默认情况下,map series 会自己生成内部专用的 geo 组件。但是也可以用这个 geoIndex 指定一个 geo组件。这样的话,map 和 其他 series(例如散点图)就可以共享一个 geo组件了。并且,geo组件的颜色也可以被这个 map series 控制,从而用 visualMap来更改。
当设定了 geoIndex 后,series-map.map
属性,以及 series-map.itemStyle
等样式配置不再起作用,而是采用 geo中的相应属性。
总结
加载全部内容