vue实现3d地图的展示与切换
引入echarts依赖
npm install echarts
npm install echarts-gl
定义一个使用3d地图的组件
<template>
<div class="echarts"/>
</template>
<script>
import {watchEffect} from 'vue'
import * as echarts from 'echarts'
import debounce from 'lodash/debounce'
import {addListener, removeListener} from 'resize-detector'
const INIT_TRIGGERS = ['theme', 'initOptions', 'autoResize']
const REWATCH_TRIGGERS = ['manualUpdate', 'watchShallow']
export default {
props: {
option: {
type: Object,
default: () => {
},
},
initOptions: {
type: Object,
default: () => {
},
},
group: {
type: String,
default: '',
},
autoResize: {
type: Boolean,
default: true,
},
watchShallow: {
type: Boolean,
default: false,
},
manualUpdate: {
type: Boolean,
default: false,
},
proCity: {
type: String,
default: "",
},
level: {
type: String,
default: "province",
}
},
setup() {
watchEffect(() => {
})
return {
lastArea: 0,
}
},
watch: {
proCity() {
this.initOptionsWatcher()
this.refresh()
}
},
created() {
this.initOptionsWatcher()
INIT_TRIGGERS.forEach((prop) => {
this.$watch(
prop,
() => {
this.refresh()
},
{deep: true}
)
})
REWATCH_TRIGGERS.forEach((prop) => {
this.$watch(prop, () => {
this.initOptionsWatcher()
this.refresh()
})
})
},
mounted() {
if (this.option) {
this.init()
}
},
activated() {
if (this.autoResize) {
this.chart && this.chart.resize()
}
},
unmounted() {
if (this.chart) {
this.destroy()
}
},
methods: {
mergeOptions(option, notMerge, lazyUpdate) {
if (this.manualUpdate) {
this.manualOptions = option
}
if (!this.chart) {
this.init(option)
} else {
this.delegateMethod('setOption', option, notMerge, lazyUpdate)
}
},
appendData(params) {
this.delegateMethod('appendData', params)
},
resize(option) {
this.delegateMethod('resize', option)
},
dispatchAction(payload) {
this.delegateMethod('dispatchAction', payload)
},
convertToPixel(finder, value) {
return this.delegateMethod('convertToPixel', finder, value)
},
convertFromPixel(finder, value) {
return this.delegateMethod('convertFromPixel', finder, value)
},
containPixel(finder, value) {
return this.delegateMethod('containPixel', finder, value)
},
showLoading(type, option) {
this.delegateMethod('showLoading', type, option)
},
hideLoading() {
this.delegateMethod('hideLoading')
},
getDataURL(option) {
return this.delegateMethod('getDataURL', option)
},
getConnectedDataURL(option) {
return this.delegateMethod('getConnectedDataURL', option)
},
clear() {
this.delegateMethod('clear')
},
dispose() {
this.delegateMethod('dispose')
},
delegateMethod(name, ...args) {
if (!this.chart) {
this.init()
}
return this.chart[name](...args)
},
delegateGet(methodName) {
if (!this.chart) {
this.init()
}
return this.chart[methodName]()
},
getArea() {
return this.$el.offsetWidth * this.$el.offsetHeight
},
init(option) {
if (this.chart) {
return
}
const chart = echarts.init(this.$el, this.initOptions)
chart.showLoading();
if (this.group) {
chart.group = this.group
}
let geoJson = require(`@/geo/${this.level}/${this.proCity}.json`)
echarts.registerMap('china', geoJson);
chart.hideLoading();
chart.setOption(option || this.manualOptions || this.option || {}, true)
if (this.autoResize) {
this.lastArea = this.getArea()
this.__resizeHandler = debounce(
() => {
if (this.lastArea === 0) {
this.mergeOptions({}, true)
this.resize()
this.mergeOptions(this.option || this.manualOptions || {}, true)
} else {
this.resize()
}
this.lastArea = this.getArea()
},
100,
{leading: true}
)
addListener(this.$el, this.__resizeHandler)
}
Object.defineProperties(this, {
width: {
configurable: true,
get: () => {
return this.delegateGet('getWidth')
},
},
height: {
configurable: true,
get: () => {
return this.delegateGet('getHeight')
},
},
isDisposed: {
configurable: true,
get: () => {
return !!this.delegateGet('isDisposed')
},
},
computedOptions: {
configurable: true,
get: () => {
return this.delegateGet('getOption')
},
},
})
this.chart = chart
},
initOptionsWatcher() {
if (this.__unwatchOptions) {
this.__unwatchOptions()
this.__unwatchOptions = null
}
if (!this.manualUpdate) {
this.__unwatchOptions = this.$watch(
'option',
(val, oldVal) => {
if (!this.chart && val) {
this.init()
} else {
this.chart.setOption(val, val !== oldVal)
}
},
{deep: !this.watchShallow}
)
}
},
destroy() {
if (this.autoResize) {
removeListener(this.$el, this.__resizeHandler)
}
this.dispose()
this.chart = null
},
refresh() {
if (this.chart) {
this.destroy()
this.init()
}
},
},
connect(group) {
if (typeof group !== 'string') {
group = group.map((chart) => chart.chart)
}
echarts.connect(group)
},
disconnect(group) {
echarts.disConnect(group)
},
getMap(mapName) {
return echarts.getMap(mapName)
},
registerMap(mapName, geoJSON, specialAreas) {
echarts.registerMap(mapName, geoJSON, specialAreas)
},
graphic: echarts.graphic,
}
</script>
<style lang="scss">
</style>
找到相关的地图选择器,如阿里的
引用站外地址
下载地图边界数据
datav
或者使用我个人的cdn上的数据,前缀为https://cdn.allbs.cn/pluginsSrc/geo/
示例,默认前缀+上方显示的几个json文件则为世界或者全国的边界数据。前缀+province+当前省的地址码则为省份相关的边界数据。前缀+city+当前市的地址码则为市级相关的边界数据。
-
全国地图边界数据,则url为
https://cdn.allbs.cn/pluginsSrc/geo/china.json
-
江苏省数据url则为
https://cdn.allbs.cn/pluginsSrc/geo/province/320000.json
-
南京市数据url则为
https://cdn.allbs.cn/pluginsSrc/geo/city/320100.json
下载地图相关的json数据,province中保存的是省一级的数据,city中保存的是市一级的数据
实际使用(下面的两种示例不是根据省市区分的,只是两种写法,里面包含一些echarts-gl的用法的区别)
省份
<template>
<geo-chart
class="jp-chart e-h-600"
:init-options="initOptions"
:option="mapOption"
ref="mapChartRef"
:pro-city="proCity"
:level="level"
/>
</template>
<script>
import GeoChart from "@/extra/GeoChart";
export default {
name: "index",
props: {
proCity: {
type: String,
default: ''
},
proCityName: {
type: String,
default: ''
}
},
data() {
return {
pieChartOption: {},
hxtUseChartOption: {},
phChartOption: {},
mapOption: {},
level: 'province',
geoJson: {},
dqData: [
{'name': '南京市', 'value': 66},
{'name': '镇江市', 'value': 77},
{'name': '宿迁市', 'value': 22},
{'name': '盐城市', 'value': 45},
{'name': '苏州市', 'value': 50},
]
}
},
components: {GeoChart},
mounted: function () {
},
watch: {
proCity() {
this.initMapGeo();
}
},
created() {
this.initMapGeo();
},
computed: {
},
methods: {
initMapGeo() {
let geoJson = require(`@/geo/${this.level}/${this.proCity}.json`)
const provinceCenter = new Map()
geoJson.features.forEach((province) => {
provinceCenter.set(province.properties.name, province.properties.cp)
})
const barData = this.dqData.map((item) => {
return {
name: item.name,
value: [...provinceCenter.get(item.name), item.value]
}
})
this.mapOption = {
title: {
text: this.proCityName,
x: 'left',
top: "20",
textStyle: {
color: '#000',
fontSize: 24
}
},
tooltip: {
show: true,
formatter: (params) => {
return "" + params.name + "接入情况<br/>" + "企业数: " + params.value[2] + "家<br/>";
},
},
geo3D: {
map: 'china',
itemStyle: {
color: '#4887f6',
areaColor: '#48D9FF',
opacity: 1,
borderWidth: 0.8,
borderColor: '#ffffff'
},
label: { // 标签的相关设置
show: true, // (地图上的城市名称)是否显示标签 [ default: false ]
textStyle: { // 标签的字体样式
color: '#fff', // 地图初始化区域字体颜色
fontSize: 12, // 字体大小
opacity: 1, // 字体透明度
backgroundColor: 'rgba(0,23,11,0)', // 字体背景色
fontWeight: 'bold'
},
},
light: { //光照阴影
main: {
color: '#fff', //光照颜色
intensity: 1.2, //光照强度
//shadowQuality: 'high', //阴影亮度
shadow: true, //是否显示阴影
alpha: 35,
beta: 10
},
ambient: {
intensity: 0.3
}
},
tooltip: { //提示框组件。
alwaysShowContent: true,
hoverAnimation: true,
trigger: 'item', //触发类型 散点图
enterable: true, //鼠标是否可进入提示框
transitionDuration: 1, //提示框移动动画过渡时间
triggerOn: 'click',
borderWidth: '1px',
borderRadius: '4',
borderColor: 'rgb(255,0,0)',
textStyle: {
color: 'rgb(255,0,0)'
},
padding: [5, 10]
},
},
series: {
type: 'bar3D',
coordinateSystem: 'geo3D',
itemStyle: {
color: '#ff0202'
},
// 倒角尺寸
bevelSize: 0.5,
bevelSmoothness: 20,
data: barData,
minHeight: 0.2,
barSize: 2,
emphasis: {
label: {
show: true,
formatter: (param) => {
return param.name + ' : ' + param.value[2] + '家'
},
distance: 1,
textStyle: {
fontWeight: 'bold',
fontSize: 18,
color: '#ff0202'
}
},
},
animation: true,
animationDurationUpdate: 2000
}
}
},
}
}
</script>
市
<template>
<vab-chart
class="jp-chart e-h-350"
:init-options="initOptions"
:option="phChartOption"
ref="phChartRef"
theme="vab-echarts-theme"
/>
</template>
<script>
import GeoChart from "@/extra/GeoChart";
export default {
name: "index",
props: {
proCity: {
type: String,
default: ''
},
proCityName: {
type: String,
default: ''
}
},
data() {
return {
initOptions: {
renderer: 'svg',
},
mapOption: {},
level: 'city',
}
},
components: {GeoChart},
mounted: function () {
},
created() {
this.initMapGeo();
},
computed: {},
watch: {
proCity() {
this.initMapGeo();
}
},
methods: {
initMapGeo() {
this.mapOption = {
// backgroundColor: "#220392",
title: {
text: this.proCityName,
x: 'left',
top: "20",
textStyle: {
color: '#000',
fontSize: 24
}
},
tooltip: {
show: true,
},
geo3D: {
map: 'china',
// roam: true,
itemStyle: {
color: '#4887f6',
areaColor: '#48D9FF',
opacity: 1,
borderWidth: 0.8,
borderColor: '#ffffff'
},
label: { // 标签的相关设置
show: true, // (地图上的城市名称)是否显示标签 [ default: false ]
textStyle: { // 标签的字体样式
color: '#ffffff', // 地图初始化区域字体颜色
fontSize: 12, // 字体大小
opacity: 1, // 字体透明度
backgroundColor: 'rgba(0,23,11,0)', // 字体背景色
fontWeight: 'bold'
},
},
//shading: 'lambert',
light: { //光照阴影
main: {
color: '#fff', //光照颜色
intensity: 1.2, //光照强度
//shadowQuality: 'high', //阴影亮度
shadow: true, //是否显示阴影
alpha: 35,
beta: 10
},
ambient: {
intensity: 0.3
}
},
},
series: [
{
//配置路径
type: 'lines3D',
coordinateSystem: 'geo3D',
polyline: 'true',
blendMode: 'lighter',
zlevel: 102,
effect: {
show: true,
trailWidth: 3,
trailOpacity: 0.5,
trailLength: 0.2,
constantSpeed: 5
},
label: { // 标签的相关设置
show: true, // (地图上的城市名称)是否显示标签 [ default: false ]
//distance: 50, // 标签距离图形的距离,在三维的散点图中这个距离是屏幕空间的像素值,其它图中这个距离是相对的三维距离
//formatter:, // 标签内容格式器
textStyle: { // 标签的字体样式
color: '#000', // 地图初始化区域字体颜色
fontSize: 8, // 字体大小
opacity: 1, // 字体透明度
backgroundColor: 'rgba(0,23,11,0)' // 字体背景色
},
},
lineStyle: {
color: '#FFB728',
opacity: 0.8,
width: 1.5
},
data: [
{
coords: [[113.149649, 22.617641], [112.88089, 22.583612]],
// 数据值
value: 100,
// 数据名
name: '测试一',
// 线条样式
lineStyle: {},
}, {
coords: [[112.316858, 22.186088], [112.88089, 22.583612]],
// 数据值
value: 100,
// 数据名
name: '测试二',
// 线条样式
lineStyle: {}
}
]
},
]
}
}
}
}
</script>
<style scoped>
</style>
效果
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 ALLBS!
评论