Aggregation Transition
Runnable deck.gl example: Aggregation Transition. 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
<!--
示例:deck.gl 聚合过渡
要点:两套不同半径的 HexagonLayer 交叉淡入淡出,实现聚合粒度的平滑切换
依赖:deck.gl 9.0 · 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;background:#0d1117}
body{display:flex;flex-direction:column;overflow:hidden;
font:13px/1.6 -apple-system,"Segoe UI","PingFang SC",sans-serif;color:#e6e8ec}
.bar{flex:0 0 auto;padding:8px 12px;background:#161b22;border-bottom:1px solid #252c36;
display:flex;gap:10px;align-items:center;font-size:12px;color:#8b93a1;flex-wrap:wrap;
position:relative;z-index:5}
.bar b{color:#58a6ff;font-weight:600}
.bar button{font:inherit;font-size:12px;padding:3px 10px;border-radius:5px;cursor:pointer;
border:1px solid #303845;background:#1d2531;color:#c8d2e0}
.bar button:hover{background:#253040}
.bar button.on{background:#2c4a7a;border-color:#4f8ad6;color:#dfe9ff}
.bar .sep{width:1px;height:18px;background:#2a313c;margin:0 4px}
.progress{position:relative;flex:0 0 120px;height:4px;background:#242c38;border-radius:2px;
overflow:hidden}
.progress i{position:absolute;left:0;top:0;bottom:0;width:0;background:#4f9ef8;
border-radius:2px}
#cv{flex:1 1 auto;min-height:0;width:100%}
</style>
</head>
<body>
<div class="bar">
<span>点数 <b id="n">—</b></span>
<span class="sep"></span>
<button data-level="fine" class="on">细网格 800 m</button>
<button data-level="coarse">粗网格 2.2 km</button>
<span class="progress"><i id="bar"></i></span>
<span>拖拽旋转 · 滚轮缩放</span>
</div>
<canvas id="cv"></canvas>
<script src="https://cdn.jsdelivr.net/npm/deck.gl@9.0.35/dist.min.js"></script>
<script>
(function () {
'use strict';
if (typeof deck === 'undefined') {
document.body.innerHTML =
'<div style="padding:24px;color:#ff8c74">deck.gl 加载失败,请检查能否访问 cdn.jsdelivr.net</div>';
return;
}
var hubs = [
[121.470, 31.232, 0.20],
[121.600, 31.100, 0.16],
[121.335, 31.300, 0.14],
[121.470, 31.360, 0.12],
[121.390, 31.080, 0.18]
];
var points = [];
for (var i = 0; i < 50000; i++) {
var h = hubs[i % hubs.length];
var r = Math.sqrt(Math.random()) * h[2] * 2.2;
var a = Math.random() * Math.PI * 2;
var plng = h[0] + Math.cos(a) * r;
var plat = h[1] + Math.sin(a) * r * 0.82;
/* 权重随与主中心距离平滑衰减,相邻单元的统计值才不会跳变 */
var dx = (plng - 121.47) / 0.22;
var dy = (plat - 31.23) / 0.14;
points.push({
lng: plng,
lat: plat,
w: 0.55 + 2.8 * Math.exp(-(dx * dx + dy * dy))
});
}
document.getElementById('n').textContent = points.length.toLocaleString();
var colorRange = [
[16, 30, 62, 165],
[30, 84, 140, 190],
[40, 148, 176, 205],
[132, 202, 150, 220],
[248, 206, 92, 235],
[238, 104, 50, 250]
];
/* blend = 0 细网格全显,1 粗网格全显 */
var blend = 0;
var from = 0;
var target = 0;
var animStart = 0;
var duration = 800;
var animating = false;
function hexLayer(id, radius, opacity, active) {
return new deck.HexagonLayer({
id: id,
data: points,
getPosition: function (d) { return [d.lng, d.lat]; },
radius: radius,
coverage: 0.9,
extruded: true,
elevationScale: 1,
getColorWeight: function (d) { return d.w; },
colorAggregation: 'MEAN',
getElevationWeight: function (d) { return d.w; },
elevationAggregation: 'SUM',
colorRange: colorRange,
upperPercentile: 100,
opacity: opacity,
/* 完全透明的图层仍会写深度缓冲,隐藏它才能避免遮住另一层 */
visible: opacity > 0.01,
material: { ambient: 0.5, diffuse: 0.72, shininess: 18 },
pickable: active
});
}
function buildLayers() {
return [
hexLayer('hex-coarse', 2200, blend, blend >= 0.5),
hexLayer('hex-fine', 800, 1 - blend, blend < 0.5)
];
}
var deckgl = new deck.Deck({
canvas: 'cv',
width: '100%',
height: '100%',
initialViewState: {
longitude: 121.46,
latitude: 31.21,
zoom: 10.3,
pitch: 42,
bearing: -8,
maxPitch: 68
},
controller: true,
layers: buildLayers(),
getTooltip: function (info) {
if (!info.object) return null;
var tag = info.layer && info.layer.id === 'hex-coarse' ? '粗网格' : '细网格';
return tag + ' · ' + info.object.count + ' 点 · 权重和 ' +
Math.round(info.object.elevationValue);
}
});
function easeInOut(t) {
return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
}
var progress = document.getElementById('bar');
function frame(now) {
if (animating) {
var t = Math.min(1, (now - animStart) / duration);
blend = from + (target - from) * easeInOut(t);
progress.style.width = Math.round(blend * 100) + '%';
deckgl.setProps({ layers: buildLayers() });
if (t >= 1) {
blend = target;
animating = false;
}
}
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
function setBlend(v, btn) {
from = blend;
target = v;
animStart = performance.now();
animating = true;
var buttons = document.querySelectorAll('[data-level]');
for (var i = 0; i < buttons.length; i++) {
buttons[i].classList.toggle('on', buttons[i] === btn);
}
}
var buttons = document.querySelectorAll('[data-level]');
for (var b = 0; b < buttons.length; b++) {
buttons[b].addEventListener('click', function (e) {
var level = e.currentTarget.getAttribute('data-level');
setBlend(level === 'fine' ? 0 : 1, e.currentTarget);
});
}
window.__deck = deckgl;
})();
</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.