高德API+Echarts 实现3D地图展示图表

2023-10-27

效果图在这里插入图片描述

前期准备(所需依赖)

  • echarts
  • @amap/amap-jsapi-loader
    npm i echarts @amap/amap-jsapi-loader

代码实现

3D地图

<!-- 地图容器 -->
<div id="mapContainer">

</div>
  • ⚠️您在2021年12月02日以后申请的key需要配合您的安全密钥一起使用。
...
mounted() {
	this.initMap()
},
methods: {
	/**
	 * @description: 初始化地图
	 */
	initMap() {
		// TODO 之后nginx配置
		// 安全密钥使用 https://lbs.amap.com/api/javascript-api-v2/guide/abc/jscode
		// 明文方式
		window._AMapSecurityConfig = {
		  securityJsCode: '「您申请的安全密钥」'
		}
		
		// JS API 结合 Vue 使用
		AMapLoader.load({
		      key:"", // 申请好的Web端开发者Key,首次调用 load 时必填
		      version:"1.4.15", // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
		      plugins:['Map3D', 'AMap.DistrictLayer', 'AMap.DistrictSearch'], // 需要使用的的插件列表,
		      // AMap.DistrictLayer 插件只适用于版本1.4.15,所以我这边的版本选择的1.4.15
		      // 插件列表 https://lbs.amap.com/api/javascript-api/guide/abc/plugins#plugins
		    }).then((AMap)=>{
		    	const _this = this
		    	
				// 初始化地图
				this.map = new AMap.Map('mapContainer', {
					zoom: 7, // 缩放等级
					viewMode: '3D', // 默认为‘2D’,可选’3D’
					pitch: 55, // 俯仰角度,默认0,[0,83],2D地图下无效 。
					pitchEnable: true, // 是否允许设置俯仰角度,3D视图下为true,2D视图下无效。
					skyColor: 'rgba(10, 35, 93, 1)' // 天空颜色
				})
		
				// 3D实例
		        const object3DLayer = new AMap.Object3DLayer({ zIndex: 1 })
		
				// 行政区域查询实例
		        const district = new AMap.DistrictSearch({
					zIndex: 130,
					level: 'province',
					extensions: 'all',
					subdistrict: 1
		        })
		
				// 查询行政区域
				district.search('山西省', function(status, result) {
					if (status === 'complete') {
						// 设置地图中心
						const center = result.districtList[0].center // 中心点
						_this.map.setCenter(center)
						
						// 设置掩模
						const bounds = result.districtList[0].boundaries // 行政区域边界坐标连线点数组
						const mask = []
						for (let i = 0; i < bounds.length; i += 1) {
						    mask.push([bounds[i]])
						}
						_this.map.setMask(mask, mask)
						
						// 设置地图‘高度’
						const wall = new AMap.Object3D.Wall({
						    path: bounds,
						    height: -100000,
						    color: 'rgba(69,184,255,0.5)'
						})
						wall.backOrFront = 'both'
						wall.transparent = true
						object3DLayer.add(wall)
						_this.map.add(object3DLayer)
						// 添加描边
						for (let i = 0; i < bounds.length; i += 1) {
						    new AMap.Polyline({
						        path: bounds[i],
						        strokeColor: '#4de4e5',
						        strokeWeight: 6,
						        map: _this.map
						    })
						}
						
						// 设置行政区划
						_this.initDistrictLayer(AMap, 12, result.districtList[0].adcode, 1)
						_this.disProvince.setMap(_this.map)
					}
				})
		    }).catch(e=>{
		      console.log(e);
		    })
	},
	/**
     * @description: 设置市级填充颜色
     * @param {string|number} adcode 区划编码
     * @return {string} 颜色rgb
     */
	getColorByAdcode(adcode) {
		const colors = {}
		if (!colors[adcode]) {
			const gb = Math.random() * 155 + 50
			colors[adcode] = 'rgba(' + gb + ',' + gb + ',255' + ',1)'
		}
		return colors[adcode]
	},
    /**
     * @description: 初始化行政区划图层
     * @param {*} AMap AMap实例
     * @param {number} zIndex 图层叠加顺序
     * @param {string|number} adcode 区划编码
     * @param {0|1|2} depth 0:省级 1:市级 2:县级
     */
    initDistrictLayer(AMap, zIndex, adcode, depth) {
		// 行政区划
		const _this = this
		this.disProvince = new AMap.DistrictLayer.Province({
		zIndex: zIndex,
		adcode: [adcode],
		depth: depth,
		// opacity: 0,
		rejectMapMask: true,
		styles: {
			'fill': function(properties) {
				// properties为可用于做样式映射的字段,包含
				// NAME_CHN:中文名称
				// adcode_pro
				// adcode_cit
				// adcode
				const adcode = properties.adcode
				return _this.getColorByAdcode(adcode)
				},
				'stroke-width': 100,
				// 'province-stroke': 'cornflowerblue',
				'city-stroke': 'rgba(138,228,251,1)' // 中国地级市边界
				// 'county-stroke': 'rgba(255,255,255,0.5)' // 中国区县边界
			}
		})
	}
},

<style lang="scss">
.map-container {
    width: 100%;
    height: 100%;
    #mapContainer {
        width: 100%;
        height: 100%;
        background-color: transparent !important;
    }
    .amap-layer {
        background-color: rgba(10, 35, 93, 1);
    }
}
</style>

在这里插入图片描述

Echarts图表

我这里是通过循环创建div容器来实现每个地级市的图表展示

  • initMap.vue
<template>
	...
	<div class="legend-container">
      <div class="legend-box legend-left" @click="projectShow = !projectShow">
        <div
          class="legend-icon"
          :style="{'background-image': projectShow ? 'linear-gradient(rgba(242, 212, 111, 1), rgba(242, 212, 111, 0.3))' : 'linear-gradient(rgba(75, 75, 75, 1), rgba(75, 75, 75, 1))'}"
        />
        <div class="legend-text">项目数</div>
      </div>
      <div class="legend-box" @click="discussShow = !discussShow">
        <div
          class="legend-icon"
          :style="{'background-image': discussShow ? 'linear-gradient(rgba(190, 237, 113, 1), rgba(190, 237, 113, 0.3))' : 'linear-gradient(rgba(75, 75, 75, 1), rgba(75, 75, 75, 1))'}"
        />
        <div class="legend-text">评价数</div>
      </div>
    </div>
	<div v-for="(item, index) in cityList" :key="index" style="position: absolute; z-index: 10">
      <ItemChart
        :id="index"
        ref="itemChart"
        :left="item.position.x"
        :top="item.position.y"
        :zoom="item.zoom"
        :legend-type="legendType"
        :legend-name="legendName"
        :obj-data="item.data"
        :max-y1="maxY1"
        :max-y2="maxY2"
      />
    </div>
</template>
<script>
import ItemChart from './components/ItemChart.vue'
export default{
	components: {
	    ItemChart
  	},
	data() {
		return {
			cityList: [], //图表所需要的属性
			cityDataList: [ //接口获取
	        {
	          'administrativeName': '运城市',
	          'projectNum': 10,
	          'discussNum': 5
	        },
	        {
	          'administrativeName': '大同市',
	          'projectNum': 14,
	          'discussNum': 7
	        },
	        {
	          'administrativeName': '晋中市',
	          'projectNum': 54,
	          'discussNum': 27
	        },
	        {
	          'administrativeName': '忻州市',
	          'projectNum': 15,
	          'discussNum': 6
	        },
	        {
	          'administrativeName': '临汾市',
	          'projectNum': 58,
	          'discussNum': 29
	        },
	        {
	          'administrativeName': '阳泉市',
	          'projectNum': 1,
	          'discussNum': 5
	        },
	        {
	          'administrativeName': '综改区',
	          'projectNum': 41,
	          'discussNum': 160
	        },
	        {
	          'administrativeName': '太原市',
	          'projectNum': 28,
	          'discussNum': 19
	        },
	        {
	          'administrativeName': '晋城市',
	          'projectNum': 30,
	          'discussNum': 11
	        },
	        {
	          'administrativeName': '朔州市',
	          'projectNum': 5,
	          'discussNum': 4
	        },
	        {
	          'administrativeName': '小店区',
	          'projectNum': 21,
	          'discussNum': 3
	        },
	        {
	          'administrativeName': '长治市',
	          'projectNum': 21,
	          'discussNum': 57
	        },
	        {
	          'administrativeName': '吕梁市',
	          'projectNum': 11,
	          'discussNum': 19
	        },
	        {
	          'administrativeName': '山西省',
	          'projectNum': 80,
	          'discussNum': 72
	        }
	      ],
	      maxY1: 80, //Y1轴的最大值
	      maxY2: 160,// Y2轴的最大值
	      
	      // 统一修改图表的展示与隐藏
	      legendType: '', // 图例操作类型
	      legendName: '', // 图例操作名称
	      projectShow: true,
	      discussShow: true
		}
	},
	methods: {
		initMap() {
			...
			district.search('山西省', function(status, result) {
            if (status === 'complete') {
              const districtList = result.districtList[0].districtList // 下级区域 这里指省下面的地级市
              const zoom = _this.map.getZoom() // 地图当前缩放比例
              districtList.map(value => {
                const position = _this.map.lngLatToContainer([value.center.lng, value.center.lat]).round()
                const data = _this.cityDataList.filter(item => item.administrativeName === value.name)
                _this.cityList.push({
                  position: position,
                  center: [value.center.lng, value.center.lat],
                  zoom: zoom,
                  data: data[0]
                })
              })
              ...
              // 每次缩放结束后重新计算 缩放比
              _this.map.on('zoomend', function() {
                const zoom = _this.map.getZoom()
                _this.cityList.map(value => {
                  _this.$set(value, 'zoom', zoom)
                })
              })
              // 每次移动后重新计算 像素
              _this.map.on('mapmove', function() {
                // console.log(_this.map.getCenter());
                _this.cityList.map(value => {
                  const position = _this.map.lngLatToContainer([value.center[0], value.center[1]]).round()
                  _this.$set(value, 'position', position)
                })
              })
              // 每次旋转后重新计算 像素
              _this.map.on('mousemove', function() {
                _this.cityList.map(value => {
                  const position = _this.map.lngLatToContainer([value.center[0], value.center[1]]).round()
                  _this.$set(value, 'position', position)
                })
              })
            })
		}
	}
}
</script>
<style scss="scss">
	...
	.legend-container {
        position: absolute;
        z-index: 10;
        display: flex;
        flex-direction: column;
        align-items: flex-start;
        left: 20px;
        top: 20px;
        .legend-box {
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            .legend-icon {
                width: 15px;
                height: 15px;
                border-radius: 100%;
                background-image: linear-gradient(rgb(75, 75, 75), rgb(75, 75, 75));
                margin-right: 5px;
            }
            .legend-text {
                font-size: 15px;
                color: transparent;
                background-image: linear-gradient(rgba(190, 237, 113, 1), rgba(190, 237, 113, 0.3));
                -webkit-background-clip: text;
            }
        }
        .legend-left {
            margin-bottom: 10px;
            .legend-text {
                background-image: linear-gradient(rgba(242, 212, 111, 1), rgba(242, 212, 111, 0.3));
            }
        }
    }
</style>
  • ItemChart.vue
<template>
  <div :id="`main${id}`" ref="chartBar" class="test-position" :style="{left: `${left - size/4}px`, top: `${top - size}px`,width: `${size * 0.5}px`, height: `${size}px`}" />
</template>
<script>
import * as echarts from 'echarts'
export default {
  props: {
    left: {
      type: Number,
      default: 10
    },
    top: {
      type: Number,
      default: 10
    },
    id: {
      type: Number,
      default: 0
    },
    zoom: {
      type: Number,
      default: 7
    },
    legendType: {
      type: String,
      default: ''
    },
    legendName: {
      type: String,
      default: ''
    },
    objData: {
      type: Object,
      default: function() {
        return {}
      }
    },
    maxY1: {
      type: Number,
      default: 0
    },
    maxY2: {
      type: Number,
      default: 0
    }
  },
  data() {
    return {
      myChart: null,
      size: 0
    }
  },
  watch: {
    zoom: {
      handler(value) {
        if (value >= 6 && value < 7) {
          this.size = value * 6
        } else if (value >= 7 && value < 8) {
          this.size = value * 10
        } else if (value >= 8 && value < 9) {
          this.size = value * 15
        } else if (value >= 9 && value <= 10) {
          this.size = value * 20
        }
      },
      immediate: true
    },
    legendType: {
      handler(value) {
        this.handleSelected(value, this.legendName)
      }
    },
    legendName: {
      handler(value) {
        this.handleSelected(this.legendType, value)
      }
    }
  },
  async mounted() {
    await this.initChart()
    const resizeObserver = new ResizeObserver(this.onResize)
    resizeObserver.observe(this.$refs.chartBar)
  },
  methods: {
    onResize(e) {
      // console.log(e);
      this.myChart.resize()
    },
    /**
     * @description: 初始化图表
     */
    initChart() {
      // 基于准备好的dom,初始化echarts实例
      this.myChart = echarts.init(document.getElementById(`main${this.id}`))
      // 绘制图表
      this.myChart.setOption({
        tooltip: {
          trigger: 'item'
        },
        legend: {
          show: false
        },
        grid: {
          width: '100%',
          height: '90%',
          top: '5%',
          left: '0%'
        },
        xAxis: {
          show: false,
          type: 'category',
          data: [this.objData.administrativeName]
        },
        yAxis: [
          {
            show: false,
            type: 'value',
            max: this.maxY1
          },
          {
            show: false,
            type: 'value',
            max: this.maxY2
          }
        ],
        series: [
          {
            name: '项目数',
            type: 'bar',
            data: [this.objData.projectNum],
            barWidth: '35%',
            yAxisIndex: 0,
            itemStyle: {
              // borderRadius: this.size * 0.1,
              color: {
                type: 'linear',
                x: 0,
                y: 0,
                x2: 0,
                y2: 1,
                colorStops: [{
                  offset: 0, color: 'rgba(242, 212, 111, 1)' // 0% 处的颜色
                }, {
                  offset: 1, color: 'rgba(242, 212, 111, 0.3)' // 100% 处的颜色
                }],
                global: false // 缺省为 false
              }
            },
            barGap: '50%'
          },
          {
            name: '评价数',
            type: 'bar',
            data: [this.objData.discussNum],
            barWidth: '35%',
            yAxisIndex: 1,
            itemStyle: {
              // borderRadius: this.size * 0.1,
              color: {
                type: 'linear',
                x: 0,
                y: 0,
                x2: 0,
                y2: 1,
                colorStops: [{
                  offset: 0, color: 'rgba(190, 237, 113, 1)' // 0% 处的颜色
                }, {
                  offset: 1, color: 'rgba(190, 237, 113, 0.3)' // 100% 处的颜色
                }],
                global: false // 缺省为 false
              }
            },
            barGap: '50%'
          }
        ]
      })
    },
    /**
     * @description: 图例触发的行为
     * @param {'legendSelect' | 'legendUnSelect'} type 行为类型
     * @param {string} name 图例名称
     */
    handleSelected(type, name) {
      this.myChart.dispatchAction({
        type: type,
        // 图例名称
        name: name
      })
    }
  }
}
</script>
<style lang="scss">
.test-position {
    position: absolute;
}
</style>

参考文献

JS API 使用
JS API 参考手册
ECHARTS 配置项手册
ECHARTS API 文档

资源地址

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

高德API+Echarts 实现3D地图展示图表 的相关文章

  • 关闭选项卡时要求确认[关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 当我在某些浏览器上关闭页面时 我希望出现一个消息框 并询问我是否真的要关闭页面 有两个按钮 如果我单击No那么这个标签就不会被关闭 我怎样
  • TypeError: props.render 不是一个函数(React hook 形式)

    我将方法作为我用react hook form制作的形式的道具传递 当从react hook form添加控制器时 它给了我 TypeError props render不是一个函数 我在网上找不到任何解决方案 因此感谢任何帮助 impor
  • 在 Vue.js 中从父组件执行子方法

    目前 我有一个 Vue js 组件 其中包含其他组件的列表 我知道使用 vue 的常见方式是将数据传递给孩子 并从孩子向父母发出事件 但是 在这种情况下 我想在子组件中的按钮出现时执行子组件中的方法 parent被点击 哪种方法最好 一种建
  • 解析“流”JSON

    我在浏览器中有一个网格 我想通过 JSON 将数据行发送到网格 但浏览器应该在接收到 JSON 时不断解析它 并在解析时将行添加到网格中 换句话说 在接收到整个 JSON 对象后 不应将行全部添加到网格中 应该在接收到行时将其添加到网格中
  • 如何重置使用 JavaScript 更改的 CSS 属性?

    我的导航按钮的宽度从 100px 增加到 150px 当鼠标悬停在 nav li hover width 150px 但是使用 javascript 我已经做到了 无论选择哪个选项 宽度都将继续为 150px 当选择每个选项时 它会使其他选
  • 使用 useReducers 调度函数发送多个操作?

    使用时是否可以通过调度函数发送多个动作useReducer挂钩反应 我尝试向它传递一组操作 但这会引发未处理的运行时异常 明确地说 通常会有一个初始状态对象和一个减速器 如下所示 const initialState message1 nu
  • Google App Engine:修改云运行环境

    我正在尝试部署一个使用自定义 Node js 服务器的 Next js 应用程序 我想将自定义构建变量注入应用程序 next config js const NODE ENV process env NODE ENV const envTy
  • 如何抑制窗口鼠标滚轮滚动...?

    我正在开发嵌入页面中的画布应用程序 我有它 因此您可以使用鼠标滚轮放大绘图 但不幸的是 这会滚动页面 因为它是文章的一部分 当我在 dom 元素上滚动鼠标滚轮时 是否可以阻止鼠标滚轮在窗口上滚动 附加鼠标滚轮 不是 Gecko DOMMou
  • Javascript正则表达式用于字母字符和空格? [关闭]

    这个问题不太可能对任何未来的访客有帮助 它只与一个较小的地理区域 一个特定的时间点或一个非常狭窄的情况相关 通常不适用于全世界的互联网受众 为了帮助使这个问题更广泛地适用 访问帮助中心 help reopen questions 我需要一个
  • 标签获取 href 值

    我有以下 html div class threeimages a img alt Australia src Images Services 20button tcm7 9688 gif a div class text h2 a hre
  • 使用 KnockoutJs 映射插件进行递归模板化

    我正在尝试使用以下方法在树上进行递归模板化ko映射 插入 http knockoutjs com documentation plugins mapping html 但我无法渲染它 除非我定义separate每个级别的模板 在以下情况下
  • 跟踪用户何时点击浏览器上的后退按钮

    是否可以检测用户何时单击浏览器的后退按钮 我有一个 Ajax 应用程序 如果我可以检测到用户何时单击后退按钮 我可以显示适当的数据 任何使用 PHP JavaScript 的解决方案都是优选的 任何语言的解决方案都可以 只需要我可以翻译成
  • 通过 CDN 使用 Dojo 时如何加载自定义 AMD 模块?

    我正在使用 google 的 CDN 并尝试使用他们的加载程序加载我自己的 AMD 模块 我知道我做错了什么 但我被困住了 有任何想法吗
  • Babel 7 Jest Core JS“TypeError:wks不是函数”

    将我的项目升级到 Babel 7 后 通过 Jest 运行测试会抛出以下错误 测试在 Babel 6 中运行没有任何问题 但在 Babel 7 中失败并出现以下错误 TypeError wks is not a function at Ob
  • 提交表单并重定向页面

    我在 SO 上看到了很多与此相关的其他问题 但没有一个对我有用 我正在尝试提交POST表单 然后将用户重定向到另一个页面 但我无法同时实现这两种情况 我可以获取重定向或帖子 但不能同时获取两者 这是我现在所拥有的
  • 条件在反应本机生产中失败,但在开发中有效

    我创建了一个反应本机应用程序 我需要通过它进行比较 如果属实 就会执行死刑 问题是 该条件适用于 React Native 开发模式 而不适用于 React Native 生产版本 我使用 firebase 作为数据库 也使用 redux
  • Safari 支持 JavaScript window.onerror 吗?

    我有一个附加到 window onerror 的函数 window onerror function errorMsg url line window alert asdf 这在 firefox chrome 和 IE 中工作正常 但在 s
  • 将 MQTTNet 服务器与 MQTT.js 客户端结合使用

    我已经启动了一个 MQTT 服务器 就像this https github com chkr1011 MQTTnet tree master例子 该代码托管在 ASP Net Core 2 0 应用程序中 但我尝试过控制台应用程序 但没有成
  • 如何将vue组件插入到contenteditable div中

    我想用 vue 创建简单的所见即所得编辑器 我发现只有一个在 vue js 上创建的真正的所见即所得编辑器 这里是 https quasar dev vue components editor 但我没有发现有插入图像的能力 其他 vue w
  • 如何在 pg-promise 中设置模式

    我正在搜索的文档pg 承诺 https github com vitaly t pg promise特别是在创建客户端时 但我无法找到设置连接中使用的默认架构的选项 它始终使用public架构 我该如何设置 通常 为数据库或角色设置默认架构

随机推荐