背景介绍
需要画一个江西省地图,可以下探到县区,在县区中可以进行打标记,以下为个人总结与学习Echarts文档
1.获取地图json
获取江西省地图json和地市json,从阿里云数据可视化平台中下载,保存在项目中(也可以通过接口去请求数据)
2.配置江西省地图option
const option = {
title: {
text: "“足迹”江西省", // 标题
subtext: "", // 副标题
show: true, // 是否显示标题
left: "center", // 标题x轴位置,可以为像素20,百分比20%,字符串'center'、'left'、'right'
top: 20, // 标题y轴位置 同left
textStyle: {
color: "#ccc", // 主标题文字的颜色。
},
},
geo: { // geo:地理坐标系组件
show: true, // 是否显示,默认为true
map: 'jiangxi', // registerMap注册的地图名称,与series-map中的map名需要一致
roam: true, // 是否开启缩放与平移,可以设置成 'scale' 或者 'move'。设置成 true 为都开启
aspectScale: 0.75, // 地图长宽比
zoom: 1.1, // 地图缩放比例
animationDurationUpdate: 0, // 缩放与平移后的动画的时长
itemStyle: { // 地图区域的多边形图形样式
areaColor: '#013C62',
shadowColor: '#182f68',
shadowOffsetX: 0,
shadowOffsetY: 25
},
emphasis: { // 高亮状态下的多边形和标签样式,比如鼠标悬浮样式变化
itemStyle: {
areaColor: '#2AB8FF',
borderWidth: 0,
color: 'green',
label: {
show: false
}
}
}
},
series: [
{
type: "map", // 地图:地图主要用于地理区域数据的可视化
map: "jiangxi", // registerMap注册的地图名称,与geo中的map名需要一致
roam: true, // 是否开启缩放与平移
animationDurationUpdate: 0, // 缩放与平移后的动画的时长
selectedMode: false, // 是否支持选中,支持布尔值和字符串,字符串取值可选'single'表示单选,或者'multiple'表示多选
label: { // 省、市、县名
normal: { // 正常时候显示的样式
show: true,
textStyle: {
color: '#fff'
}
},
emphasis: { // 高亮时候显示的样式(比如鼠标点击,鼠标悬浮)
textStyle: {
color: '#fff'
}
}
},
itemStyle: { // 整个地图的样式
normal: { // 正常情况下的样式
borderColor: '#2ab8ff',
borderWidth: 1.5,
areaColor: '#12235c'
},
emphasis: { // 高亮情况下的样式,也可以像geo那样配置
areaColor: '#2AB8FF',
borderWidth: 0,
color: 'green'
}
},
zoom: 1.1, // 缩放比例
},
],
}
3.绘制江西省地图
let jsonArr = [
{
name: '江西省',
alias: 'jiangxisheng',
json: jiangxisheng, // 自行下载json,并引入
},
{
name: '赣州市',
alias: 'ganzhoushi',
json: ganzhoushi,
coor: [114.940278, 25.85097]
},
{
name: '吉安市',
alias: 'jianshi',
json: jianshi,
coor: [114.986373, 27.111699]
},
{
name: '上饶市',
alias: 'shangraoshi',
json: shangraoshi,
coor: [117.971185, 28.44442]
},
{
name: '九江市',
alias: 'jiujiangshi',
json: jiujiangshi,
coor: [115.992811, 29.712034],
},
{
name: '抚州市',
alias: 'fuzhoushi',
json: fuzhoushi,
coor: [116.358351, 27.98385],
},
{
name: '宜春市',
alias: 'yichunshi',
json: yichunshi,
coor: [114.391136, 27.8043],
},
{
name: '南昌市',
alias: 'nanchangshi',
json: nanchangshi,
coor: [115.892151, 28.676493],
},
{
name: '景德镇市',
alias: 'jingdezhenshi',
json: jingdezhenshi,
coor: [117.214664, 29.29256],
},
{
name: '萍乡市',
alias: 'pingxiangshi',
json: pingxiangshi,
coor: [113.852186, 27.622946],
},
{
name: '鹰潭市',
alias: 'pingxiangshi',
json: yingtanshi,
coor: [117.033838, 28.238638],
},
{
name: '新余市',
alias: 'xinyushi',
json: xinyushi,
coor: [114.930835, 27.810834],
},
]
/**
* @params mapName 地图名(获取对应的json)mapData 标点数据
*/
function echartInit(mapName, mapData = []) {
const myChart = echarts.init(document.getElementById("main")) // 获取DOM元素
let mapInfo = {}
jsonArr.forEach(item => {
if (item.name === mapName) {
mapInfo = item
}
})
geoJson = mapInfo.json
echarts.registerMap("jiangxi", geoJson)
const option = [...] // 步骤2的option
myChart.setOption(option);
}
4.配置标记
// 在步骤2中的option.series添加配置
series=[
{
type: 'map',
...
},
// 1.增加散点气泡图1、2、3,
{
type: 'scatter', // 散点气泡图
coordinateSystem: 'geo', // 使用的坐标系为geo地理坐标系
symbol: '/images/example.png', // 使用的图标
symbolSize: [32, 41], // [width,height]
symbolOffset: [0, -20], // [top, left]
z: 9999, // css中的z-index
data: mapData, // 根据数据在哪里显示,数据格式为[{name:'宜丰县', value: ['114.767853', '28.448926']}]
},
// 2.增加散点气泡图4
{
type: 'scatter',
coordinateSystem: 'geo',
label: {
normal: {
show: true,
formatter: function (params) { // 自定义label
const value = params.data.county;
const text = `{tline|${value}}`;
return text;
},
rich: {
tline: { // 配置formatter中的tline属性
padding: [0, 0],
color: '#ABF8FF',
fontSize: 12,
},
},
},
},
symbol: '/images/tu4.png', // 配置一个背景图
symbolSize: [50, 25], // 图标大小
symbolOffset: [0, -60], // 图标位置
z: 999,
data: mapData,
},
// 3.增加带有涟漪特效动画的散点(气泡)图
{
type: 'effectScatter',
coordinateSystem: 'geo',
rippleEffect: {
scale: 10,
brushType: 'stroke',
},
showEffectOn: 'render', // 带有涟漪特效动画的散点(气泡)图
itemStyle: {
normal: {
shadowColor: '#0ff',
shadowBlur: 10,
shadowOffsetX: 0,
shadowOffsetY: 0,
color: function (params) { // 配置一个渐变色
return new echarts.graphic.LinearGradient(1, 0, 0, 0, [
{
offset: 0,
color: '#64fbc5',
},
{
offset: 1,
color: '#018ace',
},
]);
},
},
},
symbol: 'circle', // 标记的图形,'circle', 'rect', 'roundRect', 'triangle'等
symbolSize: [10, 5], // 宽度,高度
data: mapData
},
]
5.地图下探到县区
// 城市列表
const citys = '赣州市,吉安市,抚州市,萍乡市,南昌市,九江市,景德镇市,上饶市,鹰潭市,宜春市,新余市'
// 增加点击事件
myChart.on('click', function (e) {
if (e.componentSubType !== 'map') return // 点击的组件为地图
if (citys.includes(e.name)) { // 市级地区才进行下探地图
myChart.clear()
const mapData = [] // 实际应用中进行请求数据
echartInit(mapName, mapData)
}
})
6.标记与取消标记
// 城市列表
const citys = '赣州市,吉安市,抚州市,萍乡市,南昌市,九江市,景德镇市,上饶市,鹰潭市,宜春市,新余市'
// 增加点击事件
myChart.on('click', function (e) {
if (e.componentSubType !== 'map') return // 点击的组件为地图
if (citys.includes(e.name)) { // 市级地区才进行下探地图
myChart.clear()
const mapData = [] // 实际应用中进行请求数据
echartInit(mapName, mapData)
} else {
openMark() // 标记函数
}
})
7.配置graphic实现人员搜索
// 人员信息与显示
const persons = {
li: {
name: '李墨阳',
alias: 'li',
area: '赣州市,吉安市,抚州市,萍乡市',
show: true,
data: []
},
long: {
name: '龙藤虎',
alias: 'long',
area: '南昌市,九江市,景德镇市,上饶市',
show: true,
data: []
},
lan: {
name: '兰有斌',
alias: 'lan',
area: '鹰潭市,宜春市,新余市',
show: true,
data: []
},
}
const graphicChildren = []
for (const name in persons) {
if (Object.hasOwnProperty.call(persons, name)) {
const person = persons[name];
const imgGraphic = {
type: 'image',
left: 10,
top: 10 + graphicChildren.length / 2 * 32,
z: 100,
bounding: 'raw',
style: {
image: person.show ? `/images/${name}.png` : '/images/data-grey.png',
width: 20,
height: 26,
},
onclick: () => {
drawMapByPerson(name)
}
}
const textGraphic = {
type: 'text',
left: 42,
top: 15 + graphicChildren.length / 2 * 32,
z: 100,
style: {
fill: person.show ? '#ff8b5b' : '#ccc',
text: person.name,
font: '14px Microsoft YaHei'
},
onclick: () => {
drawMapByPerson(name)
}
}
graphicChildren.push(imgGraphic)
graphicChildren.push(textGraphic)
}
}
option. graphic: [{
type: 'group',
right: 200,
bottom: 50,
children: graphicChildren
}],
完整代码
<template>
<div class="box">
<div id="main">
</div>
<div class="back" @click="onClick" v-if="backBtn">
<el-tag type="primary">返回</el-tag>
</div>
</div>
</template>
<script setup>
/**
* getList 获取标记数据
* handleCancelMark 取消标记接口
* handleMark 标记接口
* jsonArr 江西省和所有地市json
*/
import { handleCancelMark, getList, handleMark } from "@/api/index.js";
import * as echarts from "echarts";
import { onMounted } from "vue";
import jsonArr from '../../json/config.js'
import { ElMessage, ElMessageBox } from 'element-plus'
// 地图json
let geoJson = {}
// 加载的地图名
let geoName = '江西省'
// 获取的所有数据
let geoData = []
// 当前选中市、县区
let cityValue = {}
let countyValue = {}
// 获取当前选中
function getValue(name, geoJson) {
const { features = [] } = geoJson
let map = {}
if (features && features.length > 0) {
features.forEach(item => {
if (item.properties.name === name) {
map = item.properties
}
})
}
const value = map.centroid.join(',')
return {
value,
name: map.name
}
}
// 城市列表
const citys = '赣州市,吉安市,抚州市,萍乡市,南昌市,九江市,景德镇市,上饶市,鹰潭市,宜春市,新余市'
// 市区足迹数据
let countyData = []
// 人员信息与显示
const persons = {
li: {
name: '李墨阳',
alias: 'li',
area: '赣州市,吉安市,抚州市,萍乡市',
show: true,
data: []
},
long: {
name: '龙藤虎',
alias: 'long',
area: '南昌市,九江市,景德镇市,上饶市',
show: true,
data: []
},
lan: {
name: '兰有斌',
alias: 'lan',
area: '鹰潭市,宜春市,新余市',
show: true,
data: []
},
}
// 地图初始化
const echartInit = (mapName, mapData = []) => {
let mapInfo = {}
jsonArr.forEach(item => {
if (item.name === mapName) {
mapInfo = item
}
})
geoJson = mapInfo.json
geoName = mapName
geoData = mapData
const symbolImg = 'image://' + new URL('/images/data-bg.png', import.meta.url).href
const myChart = echarts.init(document.getElementById("main"));
// 点击事件
myChart.off("click");
myChart.on('click', function (e) {
if (e.componentSubType !== 'map') return
if (citys.includes(e.name)) {
myChart.clear()
drawMap(e.name)
cityValue = getValue(e.name, geoJson)
} else {
countyValue = getValue(e.name, geoJson)
if (!(typeof e.value === 'string' && e.value)) {
if (countyData.some(item => item.county === countyValue.name)) {
unMark()
} else {
openMark()
}
}
}
})
// 滚轮事件
myChart.off("georoam");
myChart.on("georoam", function (e) {
const option = myChart.getOption();
if (e.zoom != null && e.zoom != undefined) {
//捕捉到缩放时
option.geo[0].zoom = option.series[0].zoom;
option.geo[0].center = option.series[0].center;
} else {
//捕捉到拖曳时
option.geo[0].center = option.series[0].center;
}
myChart.setOption(option);
});
echarts.registerMap("jiangxi", geoJson);
// 根据人员生成地图的graphic
const graphicChildren = []
for (const name in persons) {
if (Object.hasOwnProperty.call(persons, name)) {
const person = persons[name];
const imgGraphic = {
type: 'image',
left: 10,
top: 10 + graphicChildren.length / 2 * 32,
z: 100,
bounding: 'raw',
style: {
image: person.show ? `/images/${name}.png` : '/images/data-grey.png',
width: 20,
height: 26,
},
onclick: () => {
drawMapByPerson(name)
}
}
const textGraphic = {
type: 'text',
left: 42,
top: 15 + graphicChildren.length / 2 * 32,
z: 100,
style: {
fill: person.show ? '#ff8b5b' : '#ccc',
text: person.name,
font: '14px Microsoft YaHei'
},
onclick: () => {
drawMapByPerson(name)
}
}
graphicChildren.push(imgGraphic)
graphicChildren.push(textGraphic)
}
}
const option = {
title: {
text: "“足迹”" + mapInfo.name,
subtext: "",
left: "center",
top: 20,
textStyle: {
color: "#ccc",
},
},
geo: {
map: 'jiangxi',
aspectScale: 0.75, //长宽比
zoom: 1.1,
roam: true,
animationDurationUpdate: 0,
itemStyle: {
areaColor: '#013C62',
shadowColor: '#182f68',
shadowOffsetX: 0,
shadowOffsetY: 25
},
emphasis: {
itemStyle: {
areaColor: '#2AB8FF',
borderWidth: 0,
color: 'green',
label: {
show: false
}
}
}
},
graphic: [{
type: 'group',
right: 200,
bottom: 50,
children: graphicChildren
}],
series: [
{
type: "map",
map: "jiangxi",
roam: true,
animationDurationUpdate: 0,
selectedMode: false,
label: {
normal: {
show: true,
textStyle: {
color: '#fff'
}
},
emphasis: {
textStyle: {
color: '#fff'
}
}
},
itemStyle: {
borderColor: '#2ab8ff',
borderWidth: 1.5,
areaColor: '#12235c'
},
emphasis: {
itemStyle: {
areaColor: '#2AB8FF',
borderWidth: 0,
color: 'green'
}
},
zoom: 1.1,
data: mapData,
},
{
type: 'effectScatter',
coordinateSystem: 'geo',
rippleEffect: {
scale: 10,
brushType: 'stroke',
},
showEffectOn: 'render',
itemStyle: {
normal: {
shadowColor: '#0ff', // 阴影颜色
shadowBlur: 10, // 文字块的背景阴影长度
shadowOffsetX: 0, // 偏离中心X轴距离
shadowOffsetY: 0, // 偏离中心为之Y轴距离
color: function (params) {
const colorList = {
li: new echarts.graphic.LinearGradient(1, 0, 0, 0, [
{
offset: 0,
color: '#64fbc5',
},
{
offset: 1,
color: '#018ace',
},
]),
long: new echarts.graphic.LinearGradient(1, 0, 0, 0, [
{
offset: 0,
color: '#61c0f1',
},
{
offset: 1,
color: '#6f2eb6',
},
]),
lan: new echarts.graphic.LinearGradient(1, 0, 0, 0, [
{
offset: 0,
color: '#168e6d',
},
{
offset: 1,
color: '#c78d7b',
},
]),
}
return colorList[params.data.username];
},
},
},
symbol: 'circle',
symbolSize: [10, 5],
data: mapData,
},
{
type: 'scatter',
coordinateSystem: 'geo',
symbol: function (value, params) {
return params.data.img;
},
symbolSize: [32, 41],
symbolOffset: [0, -20],
z: 9999,
data: mapData,
},
{
type: 'scatter',
coordinateSystem: 'geo',
label: {
normal: {
show: true,
formatter: function (params) {
const value = params.data.county;
const text = `{tline|${value}}`;
return text;
},
rich: {
tline: {
padding: [0, 0],
color: '#ABF8FF',
fontSize: 12,
},
},
},
},
symbol: symbolImg,
symbolSize: [50, 25],
symbolOffset: [0, -60],
z: 999,
data: mapData,
},
],
};
myChart.setOption(option);
};
// 人员搜索
function drawMapByPerson(name) {
if (geoName !== '江西省') return
const person = persons[name]
person.show = !person.show
let data = []
for (const name in persons) {
if (Object.hasOwnProperty.call(persons, name)) {
const person = persons[name];
if (person.show) {
data = [...data, ...person.data]
}
}
}
echartInit('江西省', data);
}
// 地图下探
function drawMap(mapName) {
getList(mapName).then(res => {
let mapData = res.data || []
mapData = mapData.map(item => {
item.value = item.countyCoord.split(',')
const person = persons[item.username]
const url = `/images/${person.alias}.png`
item.img = 'image://' + new URL(url, import.meta.url)
return item
})
countyData = mapData
backBtn.value = true
// 重置人员显示
for (const name in persons) {
if (Object.hasOwnProperty.call(persons, name)) {
persons[name].show = true
}
}
echartInit(mapName, mapData);
})
}
// 打标记
function openMark() {
const title = `是否标记已到达${countyValue.name}?`
ElMessageBox.confirm(
title,
'标记',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'success',
}
)
.then(() => {
let person = {}
for (const key in persons) {
if (Object.hasOwnProperty.call(persons, key)) {
const element = persons[key];
if (element.area.includes(cityValue.name)) {
person = element
}
}
}
const data = {
username: person.alias,
city: cityValue.name,
cityCoord: cityValue.value,
county: countyValue.name,
countyCoord: countyValue.value
}
handleMark(data).then(res => {
drawMap(cityValue.name)
ElMessage({
type: 'success',
message: '成功标记!',
})
})
})
}
// 取消标记
function unMark() {
const title = `是否取消标记已到达${countyValue.name}?`
ElMessageBox.confirm(
title,
'取消标记',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(() => {
let person = {}
for (const key in persons) {
if (Object.hasOwnProperty.call(persons, key)) {
const element = persons[key];
if (element.area.includes(cityValue.name)) {
person = element
}
}
}
let id = null
geoData.forEach(item => {
if (item.countyCoord === countyValue.value) {
id = item.id
}
})
handleCancelMark(id).then(res => {
drawMap(cityValue.name)
ElMessage({
type: 'success',
message: '成功取消标记!',
})
})
})
}
// 县区返回
const backBtn = ref(false)
function onClick() {
getList('').then(res => {
for (const name in persons) {
if (Object.hasOwnProperty.call(persons, name)) {
const person = persons[name];
person.show = true;
person.data = []
}
}
let mapData = res.data || []
mapData = mapData.map(item => {
item.value = item.countyCoord.split(',')
const person = persons[item.username]
person.data.push(item)
const url = `/images/${person.alias}.png`
item.img = 'image://' + new URL(url, import.meta.url)
return item
})
let mapName = '江西省'
const myChart = echarts.init(document.getElementById("main"));
myChart.clear()
echartInit(mapName, mapData);
backBtn.value = false
})
}
onMounted(() => {
for (const name in persons) {
if (Object.hasOwnProperty.call(persons, name)) {
const person = persons[name];
person.show = true;
person.data = []
}
}
getList('').then(res => {
let mapData = res.data || []
mapData = mapData.map(item => {
item.value = item.countyCoord.split(',')
const person = persons[item.username]
person.data.push(item)
const url = `/images/${person.alias}.png`
item.img = 'image://' + new URL(url, import.meta.url)
return item
})
let mapName = '江西省'
console.log('mapData: ', mapData);
echartInit(mapName, mapData);
})
});
</script>
<style scoped>
.box {
width: 100vw;
height: 100vh;
padding: 0;
margin: 0;
background-color: #001650;
position: relative;
}
#main {
width: 100%;
height: 100vh;
margin: 0 auto;
position: absolute;
left: 0;
top: 0;
z-index: 11;
}
.back {
position: absolute;
left: 200px;
top: 50px;
z-index: 22;
color: #fff;
user-select: none;
cursor: pointer;
}
</style>
数据库设计
实现效果
资料
Echarts官方文档 echarts.apache.org/zh/option.h…
阿里云可视化平台 datav.aliyun.com/portal/scho…
可视化社区 www.makeapie.cn/echarts