Cluster Stats
Runnable Turf.js example: Cluster Stats. Open the live demo, read the complete source, and copy the file into your project. No build step or API key.
single HTML file · CDN dependencies onlyinterface may contain Chinese labels
Source
<!--
示例:分类聚合统计
核心逻辑:clusterEach 按类别分组,统计数量与均值并写回界面
依赖:Turf.js 7 · jsDelivr CDN
-->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta name="robots" content="noindex,follow">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>分类聚合统计</title>
<style>
html,body{height:100%;margin:0}
body{display:flex;flex-direction:column;overflow:hidden;
font:13px/1.6 -apple-system,"Segoe UI","PingFang SC",sans-serif;color:#16181d;background:#fff}
.bar{flex:0 0 auto;display:flex;gap:12px;align-items:center;padding:8px 12px;
background:#f6f7f9;border-bottom:1px solid #e3e6ea;font-size:12px;color:#58606e;flex-wrap:wrap}
.bar button{font:inherit;font-size:12px;padding:4px 10px;border-radius:4px;cursor:pointer;
border:1px solid #ccd1d9;background:#fff;color:#16181d}
.bar button:hover{background:#eef0f3}
.bar input[type=range]{width:150px}
.bar b{color:#1f5fd0;font-weight:600}
.hint{color:#8a929f}
svg{flex:1 1 auto;min-height:0;width:100%;display:block;background:#fbfcfd}
.out{flex:0 0 auto;display:flex;gap:20px;flex-wrap:wrap;padding:8px 12px;
border-top:1px solid #e3e6ea;
font-family:ui-monospace,Consolas,monospace;font-size:11.5px;color:#3a4250}
.out b{font-weight:600;color:#1f5fd0}
</style>
</head>
<body>
<div class="bar"><span>600 个点按类别分组</span><span class="hint">颜色对应三类 · 均值来自 v 字段</span></div>
<svg id="stage" viewBox="0 0 640 320" preserveAspectRatio="xMidYMid meet"></svg>
<div class="out">
<span>A 组 <b id="ca">—</b></span>
<span>B 组 <b id="cb">—</b></span>
<span>C 组 <b id="cc">—</b></span>
<span>总计 <b id="ct">—</b></span>
</div>
<script src="https://cdn.jsdelivr.net/npm/@turf/turf@7/turf.min.js"></script>
<script>
(function () {
'use strict';
if (typeof turf === 'undefined') {
document.body.innerHTML =
'<div style="padding:24px;color:#b42318">Turf.js 加载失败,请检查能否访问 cdn.jsdelivr.net</div>';
return;
}
function makeProjection(BOX, W, H, PAD) {
var kx = Math.cos(((BOX[1] + BOX[3]) / 2) * Math.PI / 180);
var x0 = BOX[0] * kx, x1 = BOX[2] * kx;
var y0 = BOX[1], y1 = BOX[3];
var s = Math.min((W - PAD * 2) / (x1 - x0), (H - PAD * 2) / (y1 - y0));
var ox = (W - (x1 - x0) * s) / 2;
var oy = (H - (y1 - y0) * s) / 2;
var to = function (c) {
return [(c[0] * kx - x0) * s + ox, (y1 - c[1]) * s + oy];
};
to.inverse = function (px, py) {
return [((px - ox) / s + x0) / kx, y1 - (py - oy) / s];
};
return to;
}
function linePath(pr, coords) {
var d = '';
for (var i = 0; i < coords.length; i++) {
var p = pr(coords[i]);
d += (i ? 'L' : 'M') + p[0].toFixed(1) + ' ' + p[1].toFixed(1);
}
return d;
}
function ringPath(pr, ring) { return linePath(pr, ring) + 'Z'; }
function polyPath(pr, rings) {
var d = '';
for (var i = 0; i < rings.length; i++) d += ringPath(pr, rings[i]);
return d;
}
var BOX = [116.28, 39.82, 116.52, 39.96];
var pr = makeProjection(BOX, 640, 320, 22);
var stage = document.getElementById('stage');
var COLORS = { A: '#1f5fd0', B: '#0f8a7a', C: '#c2410c' };
var feats = [];
var cats = ['A', 'B', 'C'];
for (var i = 0; i < 600; i++) {
var cat = cats[i % 3];
feats.push(turf.point(
[116.29 + Math.random() * 0.22, 39.83 + Math.random() * 0.12],
{ cat: cat, v: cat === 'A' ? 40 + Math.random() * 20 :
(cat === 'B' ? 60 + Math.random() * 30 : 25 + Math.random() * 15) }
));
}
var fc = turf.featureCollection(feats);
var stats = {};
turf.clusterEach(fc, 'cat', function (cluster, cat) {
var sum = 0;
turf.featureEach(cluster, function (f) {
sum += f.properties.v;
});
stats[cat] = {
count: cluster.features.length,
mean: sum / cluster.features.length
};
});
var parts = ['<rect x="0" y="0" width="640" height="320" fill="none" stroke="#e3e6ea"/>'];
turf.featureEach(fc, function (f) {
var c = pr(f.geometry.coordinates);
parts.push('<circle cx="' + c[0].toFixed(1) + '" cy="' + c[1].toFixed(1) +
'" r="3" fill="' + COLORS[f.properties.cat] + '" fill-opacity="0.72"/>');
});
var lx = 24;
cats.forEach(function (cat) {
parts.push('<rect x="' + lx + '" y="288" width="12" height="12" fill="' + COLORS[cat] + '"/>');
parts.push('<text x="' + (lx + 18) + '" y="299" font-size="12" fill="#3a4250">' +
cat + ' 组</text>');
lx += 84;
});
stage.innerHTML = parts.join('');
document.getElementById('ca').textContent =
stats.A.count + ' 个 · 均值 ' + stats.A.mean.toFixed(1);
document.getElementById('cb').textContent =
stats.B.count + ' 个 · 均值 ' + stats.B.mean.toFixed(1);
document.getElementById('cc').textContent =
stats.C.count + ' 个 · 均值 ' + stats.C.mean.toFixed(1);
document.getElementById('ct').textContent = feats.length;
})();
</script>
</body>
</html>
Language: the code and this page are in English; the full tutorial and API notes for this example are in Chinese. The demo itself is the authoritative version — it runs exactly as shown.