
本文详解如何将原始经纬度数据聚合成频次统计,并据此动态设置 d3 绘制的 svg 圆圈半径,实现地理热点强度可视化。核心在于使用 d3 的 `d3.group()` 对坐标去重计数,并将频次映射为圆圈半径(如 `r = baseradius * count`)。
在 D3 与 Leaflet 协同绘制地理散点图时,若原始数据包含大量重复的经纬度组合(例如用户打卡、事件上报等场景),直接为每条记录渲染一个圆圈会导致视觉重叠、遮挡严重,且无法直观反映空间分布的密度差异。理想的解决方案是:先聚合数据,再按频次缩放圆圈大小。
✅ 正确做法:数据聚合 + 比例映射
D3 v7 提供了简洁高效的聚合工具 d3.group()。我们以经纬度数组 [lat, lng] 为键进行分组(注意需转为字符串以支持对象键比较),再统计每组出现次数:
// 假设原始 data 是 d3.csv 加载的数组,每项含 sub_district_lat / sub_district_long 字段
const grouped = d3.group(data, d =>
[d.sub_district_lat, d.sub_district_long].toString()
);
// 转为标准数组并构造新数据结构:{ cnt, sub_district_lat, sub_district_long }
const aggregatedData = Array.from(grouped, ([_, records]) => ({
cnt: records.length,
sub_district_lat: records[0].sub_district_lat,
sub_district_long: records[0].sub_district_long
}));随后,在绑定数据时,将圆圈半径 r 属性改为基于 cnt 动态计算:
.attr("r", d => Math.max(4, 8 * Math.sqrt(d.cnt))) // 推荐:用 sqrt 缓解极端值放大效应⚠️ 注意:*不建议直接使用 `12 d.cnt`**(如原答案所示)。当某位置出现 100 次时,半径达 1200px,极易覆盖整屏且丧失可比性。更科学的做法是:使用 Math.sqrt(d.cnt) 或 Math.cbrt(d.cnt) 压缩尺度;设置最小半径(如 Math.max(3, ...))确保低频点仍可见;可结合 d3.scaleLinear() 进行精细化映射(见下文进阶示例)。
? 完整集成代码(D3 v7 + Leaflet)
? 关键注意事项
- 版本兼容性:务必使用 D3 v7+(d3.group 在 v4 中不可用);Leaflet v1.x 与 SVG 图层配合稳定。
- 性能优化:数千条记录聚合后通常只剩数百个唯一坐标,大幅提升渲染效率。
- 视觉合理性:优先使用 scaleSqrt() 而非线性缩放,避免高频点“吞噬”周边区域。
- 坐标精度:若原始数据含小数位数过多(如 10 位),建议预处理四舍五入(如 +d.lat.toFixed(4)),防止微小浮点误差导致分组失败。
- 交互增强:可为圆圈添加 title 属性显示具体频次,或绑定 on("click", ...) 实现钻取分析。
通过以上方法,你不仅能清晰呈现地理热点分布,还能让可视化结果具备真实的统计意义和专业表现力。










