Skip to content
目录

行政区统计动画效果

这里我们来看看如何实现视频里的效果

region-statistics-animation demo

要求

  • 数据一共有三个类别,省->市->县
  • 每个类别的数据在不同的层级区间展示,显示区间有我们来定义
  • 层级数据切换是展现动画,扩散和收缩也是难点所在

注意和聚合区分

一定要主要这个效果不是聚合,聚合是是按照点位的数据分布自动利用数学来生成对应的聚合点,这里的 效果就是简单直白的行政区统计效果,即一个行政区里不管有多少数据,数据也不管怎么分布都只有一个统计点, 只是切换数据时的动画和聚合插件一样而已

数据准备

这里我使用了江苏省的行政划分的数据 省->市->县这样的结构化数据,是树形的。

为啥要用树形结构呢?因为我们做动画效果时,节点的扩散和收缩效果,要知道这个节点从哪里派生出来, 也要知道收缩到哪个节点,即要知道一个节点的父节点,我们才好做这种扩散收缩动画

数据结构

如果你的数据结构不同,注意修改代码里必要的地方

)3%5B(_B2%5DN(MNGQ%60XYJE)K7L.png

数据创建

注意代码里有递归区创建Marker,并显示的携带每个Marker的父节点_parentMarker.

主要方便做动画使用,快速的定位一个节点的父节点

js
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区间

js
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记录上一个地图 的层级,这时我们确定在上一个层级里哪些数据是处于显示状态,从这批数据查找即可

js
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方法,且可以直接对图形进行平移的,动画结束了控制对应的节点 显示于隐藏即可

js
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 完整代码

js
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对象即可

maptalks教程 document auto generated by mdpress and vitepress