行政区统计动画效果
这里我们来看看如何实现视频里的效果
region-statistics-animation demo
要求
- 数据一共有三个类别,省->市->县
- 每个类别的数据在不同的层级区间展示,显示区间有我们来定义
- 层级数据切换是展现动画,扩散和收缩也是难点所在
注意和聚合区分
一定要主要这个效果不是聚合,聚合是是按照点位的数据分布自动利用数学来生成对应的聚合点,这里的 效果就是简单直白的行政区统计效果,即一个行政区里不管有多少数据,数据也不管怎么分布都只有一个统计点, 只是切换数据时的动画和聚合插件一样而已
数据准备
这里我使用了江苏省的行政划分的数据 省->市->县这样的结构化数据,是树形的。
为啥要用树形结构呢?因为我们做动画效果时,节点的扩散和收缩效果,要知道这个节点从哪里派生出来, 也要知道收缩到哪个节点,即要知道一个节点的父节点,我们才好做这种扩散收缩动画
数据结构
如果你的数据结构不同,注意修改代码里必要的地方
数据创建
注意代码里有递归区创建Marker,并显示的携带每个Marker的父节点_parentMarker
.
主要方便做动画使用,快速的定位一个节点的父节点
function createMarker(c, info) {
const zoomRangle = info.zoomRangle;
const level = info.level;
const name = info.name;
const marker = new maptalks.Marker(c, {
center: c.copy(),
zIndex: level,
properties: {
name,
},
symbol: {
...symbol,
markerFill: getColor(level),
textName: name,
markerWidth: measureTextWidth(name, symbol.textSize) + 10,
},
});
marker.options.zoomRangle = zoomRangle;
return marker;
}
function getColor(level) {
if (level < 1) {
return "red";
}
if (level < 2) {
return "yellow";
}
if (level < 3) {
return "blue";
}
return "green";
}
function createMarkers(items) {
const markers = [];
function loop(item, level, parent) {
const { center, children, name } = item;
const zoomInfo = ZOOMDATCONFIG[level][1];
const { zoomRangle } = zoomInfo;
const info = {
zoomRangle,
level,
name,
};
const marker = createMarker(new maptalks.Coordinate(center), {
zoomRangle,
level,
name,
});
marker._parentMarker = parent;
markers.push(marker);
if (children) {
level++;
children.forEach((d) => {
loop(d, level, marker);
});
}
}
let level = 0;
items.forEach((item) => {
loop(item, level, null);
});
return markers;
}
fetch("./../assets/data/chinacity.json")
.then((res) => res.json())
.then((json) => {
const items = json.children.filter((d) => {
return d.name === "江苏省";
});
const points = createMarkers(items);
layer.addGeometry(points);
zoomChange();
map.on("zoomend", zoomChange);
});
分级展示数据
将所有的数据都添加到图层上了
不同的地图层级控制对应的类别数据的显示和隐藏
地图zoom切换事件里做类别数据的控制和动画
定义数据显示层级
zoomRangle
表示每个类别数据的显示zoom区间
const ZOOMDATCONFIG = [
[
"省",
{
zoomRangle: [0, 7],
offset: 6,
},
],
[
"市",
{
zoomRangle: [7.01, 11],
offset: 0.2,
},
],
[
"县",
{
zoomRangle: [11.01, 100],
offset: 0.05,
},
],
];
动画思路
为了区分扩散和收缩动画,我们用了preLevel
来表示的,如果当前层级>preLevel就是 扩散,否则就是收缩动画
WARNING
注意这个不是地图的zoom,而是数据分级展示的层级,省->市->县这个东西
关于父节点的查找,因为地图zoom切换可能很大,所以父节点不一定是当前节点的直接 父节点,可能是父节点的父节点的,那怎么确定是哪个呢?我们用preZoom
记录上一个地图 的层级,这时我们确定在上一个层级里哪些数据是处于显示状态,从这批数据查找即可
function findParent(marker, zoom) {
let parent = marker._parentMarker;
while (parent) {
const [minZoom, maxZoom] = parent.options.zoomRangle;
if (minZoom <= zoom && zoom <= zoom) {
return parent;
}
parent = parent._parentMarker;
}
}
maptalks里自带了 animate
方法,且可以直接对图形进行平移的,动画结束了控制对应的节点 显示于隐藏即可
marker.animate(
{
// animation translate distance
translate: offset,
},
{
duration: duration,
},
(frame) => {
// console.log(frame.state.playState);
if (frame.state.playState !== "running") {
// console.log(marker.getCoordinates().toArray());
marker.setCoordinates(c2.copy());
}
},
);
zoomChange 完整代码
function zoomChange() {
const zoom = map.getZoom();
const currentLevel = getCurrentLevel();
if (typeof preLevel === "number") {
if (currentLevel === preLevel) {
return;
}
const { showMarkers, hideMarkers } = typeMarkers();
//扩散动画
if (currentLevel > preLevel) {
const parents = showMarkers.map((marker) => {
//从上一个层级里查找父节点
return findParent(marker, preZoom);
});
showMarkers.forEach((marker, index) => {
const parent = parents[index];
if (parent && isInCurrentLevel(marker)) {
const c2 = marker.options.center;
const c1 = parent.getCoordinates();
const offset = c2.sub(c1).toArray();
marker.setCoordinates(c1.copy());
marker.show();
parent.hide();
marker.animate(
{
// animation translate distance
translate: offset,
},
{
duration: duration,
},
(frame) => {
// console.log(frame.state.playState);
if (frame.state.playState !== "running") {
// console.log(marker.getCoordinates().toArray());
marker.setCoordinates(c2.copy());
}
},
);
} else {
marker.show();
}
});
hideMarkers.forEach((marker) => {
if (parents.indexOf(marker) === -1) {
marker.hide();
}
});
}
//收缩动画
if (currentLevel < preLevel) {
const parents = hideMarkers.map((marker) => {
//从当前层级里查找父节点
return findParent(marker, zoom);
});
hideMarkers.forEach((marker, index) => {
const parent = parents[index];
if (parent && marker.isVisible()) {
const c2 = marker.options.center;
const c1 = parent.getCoordinates();
const offset = c1.sub(c2).toArray();
marker.setCoordinates(c2.copy());
marker.show();
marker.animate(
{
// animation translate distance
translate: offset,
},
{
duration: duration,
},
(frame) => {
if (frame.state.playState !== "running") {
marker.hide();
parent.show();
}
},
);
} else {
marker.hide();
}
});
showMarkers.forEach((marker) => {
if (parents.indexOf(marker) === -1) {
marker.show();
}
});
}
} else {
//第一次
const { showMarkers, hideMarkers } = typeMarkers();
hideMarkers.forEach((marker) => {
marker.hide();
});
showMarkers.forEach((marker) => {
marker.show();
});
}
preZoom = zoom;
preLevel = currentLevel;
}
说明
- 这里只用了三级的数据,如果你的数据是更多层级的,定义对应的
ZOOMDATCONFIG
对象即可