效果图:

html
<template>
<div style="width: 100%; height: 100%; background-color: #000">
<div style="width: 200px">
<el-select v-model="mapRegion.adcode" placeholder="请选择">
<el-option
v-for="item in mapRegionOptions"
:key="item.adcode"
:label="item.name"
:value="item.adcode"
>
</el-option>
</el-select>
</div>
<v-chart ref="vChartRef" :option="option"> </v-chart>
</div>
</template>
<script setup lang="ts">
import { watch, ref, nextTick, computed, reactive } from "vue";
import VChart from "vue-echarts";
import { use, registerMap } from "echarts/core";
import { EffectScatterChart, MapChart } from "echarts/charts";
import { CanvasRenderer } from "echarts/renderers";
import mapJsonWithoutHainanIsLands from "./mapWithoutHainanIsLands/mapWithoutHainanIsLands.json";
import {
DatasetComponent,
GridComponent,
TooltipComponent,
GeoComponent,
VisualMapComponent,
} from "echarts/components";
import cloneDeep from "lodash/cloneDeep";
import { setOption } from "./chart";
import mapChinaJson from "./mapGeojson/china.json";
use([
MapChart,
DatasetComponent,
CanvasRenderer,
GridComponent,
TooltipComponent,
GeoComponent,
EffectScatterChart,
VisualMapComponent,
]);
/** 下拉框 **/
const mapRegionOptions = ref([
{
adcode: "china",
name: "中国",
},
]);
const mapRegion = computed(() => {
return option.mapRegion;
});
const initMapRegionOptions = () => {
mapChinaJson.features.forEach((element: any) => {
if (element.properties.name) {
mapRegionOptions.value.push({ ...element.properties });
}
});
};
initMapRegionOptions();
/** 地图 **/
const vChartRef = ref<typeof VChart>();
//动态获取json注册地图
const getGeojson = (regionId: string) => {
return new Promise<boolean>((resolve) => {
import(`./mapGeojson/${regionId}.json`).then((data) => {
registerMap(regionId, { geoJSON: data.default as any, specialAreas: {} });
resolve(true);
});
});
};
// 数据源
const chartData = ref({
point: [
{
name: "北京",
value: [116.405285, 39.904989, 200],
},
{
name: "郑州",
value: [113.665412, 34.757975, 888],
},
{
name: "青海",
value: [101.778916, 36.623178, 666],
},
{
name: "宁夏回族自治区",
value: [106.278179, 38.46637, 66],
},
{
name: "哈尔滨市",
value: [126.642464, 45.756967, 101],
},
],
map: [
{
name: "北京市",
value: 666,
},
{
name: "河北省",
value: 98,
},
{
name: "江苏省",
value: 300,
},
{
name: "福建省",
value: 1199,
},
{
name: "山东省",
value: 86,
},
{
name: "河南省",
value: 850,
},
{
name: "湖北省",
value: 84,
},
{
name: "广西壮族自治区",
value: 81,
},
{
name: "海南省",
value: 900,
},
{
name: "青海省",
value: 800,
},
{
name: "新疆维吾尔自治区",
value: 7,
},
],
pieces: [
{
gte: 1000,
label: ">1000",
},
{
gte: 600,
lte: 999,
label: "600-999",
},
{
gte: 200,
lte: 599,
label: "200-599",
},
{
gte: 50,
lte: 199,
label: "49-199",
},
{
gte: 10,
lte: 49,
label: "10-49",
},
{
lte: 9,
label: "<9",
},
],
});
const seriesItem1 = reactive({
type: "effectScatter",
coordinateSystem: "geo",
symbolSize: 4,
legendHoverLink: true,
showEffectOn: "render",
rippleEffect: {
scale: 6,
color: "#FFFFFF",
brushType: "fill",
},
tooltip: {
show: true,
backgroundColor: "rgba(0,0,0,.6)",
borderColor: "rgba(147, 235, 248, .8)",
textStyle: {
color: "#FFF",
},
},
label: {
formatter: "{b}",
fontSize: 11,
offset: [0, 2],
position: "bottom",
textBorderColor: "#fff",
textShadowColor: "#000",
textShadowBlur: 10,
textBorderWidth: 0,
color: "#FFFFFF",
show: true,
},
itemStyle: {
color: "#FFFFFF",
borderColor: "rgba(225,255,255,2)",
borderWidth: 4,
shadowColor: "#E1FFFF",
shadowBlur: 10,
},
data: [],
});
const seriesItem2 = reactive({
name: "区域",
type: "map",
map: "china",
data: [],
selectedMode: false,
zoom: 1,
geoIndex: 1,
tooltip: {
show: true,
backgroundColor: "#00000060",
borderColor: "rgba(147, 235, 248, 0.8)",
textStyle: {
color: "#FFFFFF",
fontSize: 12,
},
},
label: {
show: false,
color: "#FFFFFF",
fontSize: 12,
},
emphasis: {
disabled: false,
label: {
color: "#FFFFFF",
fontSize: 12,
},
itemStyle: {
areaColor: "#389BB7",
shadowColor: "#389BB7",
borderWidth: 1,
},
},
itemStyle: {
borderColor: "#93EBF8",
borderWidth: 1,
areaColor: {
type: "radial",
x: 0.5,
y: 0.5,
r: 0.8,
colorStops: [
{
offset: 0,
color: "#93ebf800", // 0% 处的颜色
},
{
offset: 1,
color: "#93ebf820", // 100% 处的颜色
},
],
globalCoord: false,
},
shadowColor: "#80D9F842",
shadowOffsetX: -2,
shadowOffsetY: 2,
shadowBlur: 10,
},
});
const getSeries = (values: typeof chartData.value) => {
let series: any = [];
if (!values) return [];
// 不在这里修改 option,而是返回配置
let item1: any = cloneDeep(seriesItem1);
item1.data = values.point;
let item2: any = cloneDeep(seriesItem2);
item2.data = values.map;
series.push(item1, item2);
return series;
};
const updateSeries = () => {
option.series = getSeries(chartData.value);
// 如果需要更新 visualMap.pieces,也可以在这里处理
option.visualMap.pieces = chartData.value.pieces;
};
const option = reactive({
mapRegion: {
adcode: "china",
showHainanIsLands: true,
enter: false,
backSize: 20,
backColor: "#ffffff",
},
tooltip: {
show: true,
trigger: "item",
},
visualMap: {
show: true,
orient: "vertical",
pieces: [
{ gte: 1000, label: ">1000" },
{ gte: 600, lte: 999, label: "600-999" },
{ gte: 200, lte: 599, label: "200-599" },
{ gte: 50, lte: 199, label: "49-199" },
{ gte: 10, lte: 49, label: "10-49" },
{ lte: 9, label: "<9" },
],
inRange: {
color: ["#c3d7df", "#5cb3cc", "#8abcd1", "#66a9c9", "#2f90b9", "#1781b5"],
},
textStyle: {
color: "#fff",
},
},
geo: {
show: false,
type: "map",
roam: false,
map: "china",
selectedMode: false,
zoom: 1,
},
series: getSeries(chartData.value),
});
//异步时先注册空的 保证初始化不报错
registerMap(`${option.mapRegion.adcode}`, {
geoJSON: {} as any,
specialAreas: {},
});
// 进行更换初始化地图 如果为china 单独处理
const registerMapInitAsync = async () => {
await nextTick();
const adCode = `${option.mapRegion.adcode}`;
if (adCode !== "china") {
await getGeojson(adCode);
} else {
await hainanLandsHandle(option.mapRegion.showHainanIsLands);
}
vEchartsSetOption();
};
registerMapInitAsync();
// 手动触发渲染
const vEchartsSetOption = () => {
setOption(vChartRef.value, option);
};
// 处理海南群岛
const hainanLandsHandle = async (newData: boolean) => {
if (newData) {
await getGeojson("china");
} else {
registerMap("china", {
geoJSON: mapJsonWithoutHainanIsLands as any,
specialAreas: {},
});
}
};
// 切换地图
const checkOrMap = async (newData: string) => {
await getGeojson(newData);
option.geo.map = newData;
option.series.forEach((item: any) => {
if (item.type === "map") item.map = newData;
});
vEchartsSetOption();
};
//监听是否显示南海群岛
watch(
() => option.mapRegion.showHainanIsLands,
async (newData) => {
try {
await hainanLandsHandle(newData);
vEchartsSetOption();
} catch (error: any) {
throw new Error(error);
}
},
{
deep: false,
}
);
//监听地图展示区域发生变化
watch(
() => `${option.mapRegion.adcode}`,
async (newData) => {
try {
checkOrMap(newData);
} catch (error: any) {
throw Error(error);
}
},
{
deep: false,
}
);
// 监听 chartData 变化并更新图表
watch(
() => chartData.value,
async () => {
updateSeries();
await nextTick();
vEchartsSetOption();
},
{ deep: true }
);
</script>
TypeScript
import type VChart from 'vue-echarts'
// 定义包含chart属性的类型
type ChartInstance = {
chart: echarts.ECharts | null
} & (typeof VChart | undefined)
// 解决ts报错问题
type ChartOptionFun = <D>(instance: ChartInstance, data: D) => void
/**
* * 配置公共 setOption 方法
* @param instance
* @param data
*/
export const setOption: ChartOptionFun = <D>(instance: ChartInstance, data: D) => {
if (!instance?.chart) return
const option = instance.chart.getOption()
if (option) {
option.apiData = null
}
instance.chart.setOption(data)
}