Skip to content

Leaflet 与 ECharts 结合示例

上次更新 2025年8月20日星期三 3:16:20 字数 0 字 时长 0 分钟

示例

TIP

本示例展示了如何在 Leaflet 地图上结合 ECharts 图表展示数据。主要实现了以下功能:

  • 在地图上添加自定义标记点
  • 点击标记点弹出 ECharts 饼图
  • 展示各区域人口年龄分布数据
  • 自定义标记点和弹窗样式

新标签页预览

代码实现

主要实现步骤:

  1. 引入 Leaflet 和 ECharts 库
  2. 初始化地图并添加底图
  3. 准备模拟数据
  4. 创建自定义标记点图标
  5. 为每个数据点创建标记和弹窗
  6. 在弹窗中初始化 ECharts 图表
  7. 处理弹窗打开和关闭事件

关键代码:

vue
<template>
  <div id="map" class="w-full h-96"></div>
</template>

<script setup>
import L from "leaflet";
import "leaflet/dist/leaflet.css";
import * as echarts from "echarts";
import { onMounted, ref } from "vue";

const map = ref(null);

onMounted(() => {
  // 初始化地图
  map.value = L.map("map", {
    center: [39.11967296689395, 117.18704223632814],
    zoom: 12,
    zoomControl: true,
  });

  // 添加底图
  const urlLayer =
    "http://t0.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=d32c6748c80f81a44acd8633cea41dfd";
  L.tileLayer(urlLayer).addTo(map.value);

  // 模拟数据
  const data = [
    {
      name: "和平区",
      location: [39.117, 117.215],
      population: [
        { value: 235, name: "0-18岁" },
        { value: 274, name: "19-35岁" },
        { value: 310, name: "36-60岁" },
        { value: 335, name: "60岁以上" },
      ],
    },
    {
      name: "河西区",
      location: [39.109, 117.223],
      population: [
        { value: 154, name: "0-18岁" },
        { value: 285, name: "19-35岁" },
        { value: 246, name: "36-60岁" },
        { value: 178, name: "60岁以上" },
      ],
    },
    {
      name: "南开区",
      location: [39.138, 117.15],
      population: [
        { value: 187, name: "0-18岁" },
        { value: 356, name: "19-35岁" },
        { value: 298, name: "36-60岁" },
        { value: 265, name: "60岁以上" },
      ],
    },
  ];

  // 创建自定义图标
  const createCustomIcon = (text) => {
    return L.divIcon({
      className: "custom-marker-icon",
      html: `<span>📊</span>`,
      iconSize: [32, 32],
      iconAnchor: [16, 16],
    });
  };

  // 创建自定义图表弹窗
  data.forEach((item) => {
    const marker = L.marker(item.location, {
      icon: createCustomIcon(item.name),
    }).addTo(map.value);

    const popupContent = document.createElement("div");
    popupContent.className = "chart-popup";

    const popup = L.popup({
      maxWidth: 320,
      minWidth: 320,
      maxHeight: 320,
      autoPan: true,
      closeButton: true,
      className: "custom-popup",
    }).setContent(popupContent);

    marker.bindPopup(popup);

    marker.on("popupopen", () => {
      const chart = echarts.init(popupContent);
      const option = {
        title: {
          text: `${item.name}人口年龄分布`,
          left: "center",
          top: 10,
          textStyle: {
            fontSize: 14,
          },
        },
        tooltip: {
          trigger: "item",
          formatter: "{a} <br/>{b}: {c} ({d}%)",
        },
        legend: {
          orient: "vertical",
          left: 10,
          top: "center",
        },
        series: [
          {
            name: "年龄分布",
            type: "pie",
            radius: ["40%", "70%"],
            avoidLabelOverlap: false,
            itemStyle: {
              borderRadius: 10,
              borderColor: "#fff",
              borderWidth: 2,
            },
            label: {
              show: false,
              position: "center",
            },
            emphasis: {
              label: {
                show: true,
                fontSize: "12",
                fontWeight: "bold",
              },
            },
            labelLine: {
              show: false,
            },
            data: item.population,
          },
        ],
      };
      chart.setOption(option);
    });

    marker.on("popupclose", () => {
      const chart = echarts.getInstanceByDom(popupContent);
      if (chart) {
        chart.dispose();
      }
    });
  });

  map.value.on("click", (e) => {
    console.log(e.latlng);
  });
});
</script>

<style scoped>
.chart-popup {
  width: 300px;
  height: 300px;
}

.custom-marker-icon {
  width: 32px !important;
  height: 32px !important;
  border-radius: 50%;
  background: linear-gradient(145deg, #2196f3, #1976d2);
  border: 2px solid #fff;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  font-size: 16px;
  transition: all 0.3s ease;
}

.custom-marker-icon:hover {
  transform: scale(1.1);
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4);
}
</style>
关注公众号