Create a marker clusterer

Open in CodeSandbox

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
        <script crossorigin src="https://cdn.jsdelivr.net/npm/@babel/standalone@7/babel.min.js"></script>

        <!-- To make the map appear, you must add your apikey -->
        <script src="https://js.api.mappable.world/v3/?apikey=<YOUR_APIKEY>&lang=en_US" type="text/javascript"></script>

        <script
            data-plugins="transform-modules-umd"
            data-presets="react, typescript"
            type="text/babel"
            src="./common.ts"
        ></script>
        <script
            data-plugins="transform-modules-umd"
            data-presets="react, typescript"
            type="text/babel"
            src="../variables.ts"
        ></script>
        <script data-plugins="transform-modules-umd" data-presets="typescript" type="text/babel">
            import type {LngLat} from '@mappable-world/mappable-types';
            import type {Feature} from '@mappable-world/mappable-clusterer';
            import {COMMON_LOCATION_PARAMS, getBounds, getRandomPoints, MARGIN} from './common';
            import {BOUNDS, LOCATION} from '../variables';
            
            window.map = null;
            
            main();
            async function main() {
                // Waiting for all api elements to be loaded
                await mappable.ready;
                interface ClustererChangeControlProps {
                    toggleClusterer: () => void;
                    changePointsCount: (count: number) => void;
                    updatePoints: () => void;
                }
            
                class ClustererChangeControl extends mappable.MMapComplexEntity<ClustererChangeControlProps> {
                    private _element: HTMLDivElement;
                    private _detachDom: () => void;
            
                    // Method for create a DOM control element
                    _createElement(props: ClustererChangeControlProps) {
                        const {toggleClusterer, changePointsCount, updatePoints} = props;
            
                        const clustererChange = document.createElement('div');
                        clustererChange.classList.add('clusterer-change');
            
                        const inputSection = document.createElement('div');
                        inputSection.classList.add('clusterer-change__section');
            
                        const inputLabel = document.createElement('div');
                        inputLabel.classList.add('clusterer-change__input__label');
                        inputLabel.textContent = 'Point count:';
                        inputSection.appendChild(inputLabel);
            
                        const inputField = document.createElement('input');
                        inputField.type = 'number';
                        inputField.classList.add('clusterer-change__input');
                        inputField.value = '100';
                        inputField.addEventListener('input', (e: Event) => {
                            const target = e.target as HTMLInputElement;
                            changePointsCount(Number(target.value));
                        });
                        inputSection.appendChild(inputField);
            
                        const btnSection = document.createElement('div');
                        btnSection.classList.add('clusterer-change__buttons');
            
                        const updatePointsBtn = document.createElement('button');
                        updatePointsBtn.type = 'button';
                        updatePointsBtn.classList.add('clusterer-change__btn');
                        updatePointsBtn.textContent = 'Update points';
                        updatePointsBtn.addEventListener('click', updatePoints);
                        btnSection.appendChild(updatePointsBtn);
            
                        const toggleClustererBtn = document.createElement('button');
                        toggleClustererBtn.type = 'button';
                        toggleClustererBtn.id = 'toggleBtn';
                        toggleClustererBtn.classList.add('clusterer-change__btn');
                        toggleClustererBtn.textContent = 'Disable cluster mode';
                        toggleClustererBtn.addEventListener('click', toggleClusterer);
                        btnSection.appendChild(toggleClustererBtn);
            
                        const dividerElement = document.createElement('hr');
                        dividerElement.classList.add('divider');
            
                        clustererChange.appendChild(inputSection);
                        clustererChange.appendChild(dividerElement);
                        clustererChange.appendChild(btnSection);
            
                        return clustererChange;
                    }
            
                    // Method for attaching the control to the map
                    _onAttach() {
                        this._element = this._createElement(this._props);
                        this._detachDom = mappable.useDomContext(this, this._element, this._element);
                    }
            
                    // Method for detaching control from the map
                    _onDetach() {
                        this._detachDom();
                        this._detachDom = null;
                        this._element = null;
                    }
                }
            
                const {MMap, MMapDefaultSchemeLayer, MMapDefaultFeaturesLayer, MMapMarker, MMapControls, MMapControl} = mappable;
            
                // Load the package with the cluster, extract the classes for creating clusterer objects and the clustering method
                const {MMapClusterer, clusterByGrid} = await mappable.import('@mappable-world/mappable-clusterer');
                const {MMapDefaultMarker} = await mappable.import('@mappable-world/mappable-default-ui-theme');
            
                // Declare number of points in the clusterer
                let pointsCount = 100;
                let points = getRandomPoints(pointsCount, BOUNDS);
                let markers = [];
            
                map = new MMap(document.getElementById('app'), {location: LOCATION, showScaleInCopyrights: true, margin: MARGIN}, [
                    new MMapDefaultSchemeLayer({}),
                    new MMapDefaultFeaturesLayer({})
                ]);
            
                /* We declare the function for rendering ordinary markers, we will submit it to the clusterer settings.
              Note that the function must return any Entity element. In the example, this is MMapDefaultMarker. */
                const marker = (feature: Feature) =>
                    new MMapDefaultMarker({
                        coordinates: feature.geometry.coordinates,
                        iconName: 'landmark'
                    });
            
                // As for ordinary markers, we declare a cluster rendering function that also returns an Entity element.
                const cluster = (coordinates: LngLat, features: Feature[]) =>
                    new MMapMarker(
                        {
                            coordinates,
                            onClick() {
                                const bounds = getBounds(features.map((feature: Feature) => feature.geometry.coordinates));
                                map.update({location: {bounds, ...COMMON_LOCATION_PARAMS}});
                            }
                        },
                        circle(features.length).cloneNode(true) as HTMLElement
                    );
            
                function circle(count: number) {
                    const circle = document.createElement('div');
                    circle.classList.add('circle');
                    circle.innerHTML = `
                              <div class="circle-content">
                                  <span class="circle-text">${count}</span>
                              </div>
                          `;
                    return circle;
                }
            
                /* We create a clusterer object and add it to the map object.
              As parameters, we pass the clustering method, an array of features, the functions for rendering markers and clusters.
              For the clustering method, we will pass the size of the grid division in pixels. */
                const clusterer = new MMapClusterer({
                    method: clusterByGrid({gridSize: 64}),
                    features: points,
                    marker,
                    cluster
                });
                map.addChild(clusterer);
            
                // Creating handler functions for changing the clusterer. We will use these functions in a custom control
                // THe handler function for changing the number of clusterer points
                function changePointsCount(count: number) {
                    pointsCount = count;
                }
                // The handler function for updating coordinates of clusterer points
                function updatePoints() {
                    points = getRandomPoints(pointsCount, map.bounds);
                    clusterer.update({features: points});
                    if (!clusterer.parent) {
                        markers.forEach((markerEl) => {
                            map.removeChild(markerEl);
                        });
                        markers = [];
                        points.forEach((feature) => {
                            const markerElement = marker(feature);
                            map.addChild(markerElement);
                            markers.push(markerElement);
                        });
                    }
                }
                // The handler function for attach/detach the clusterer
                function toggleClusterer() {
                    const button = document.getElementById('toggleBtn');
                    if (clusterer.parent) {
                        map.removeChild(clusterer);
                        points.forEach((feature) => {
                            const markerElement = marker(feature);
                            map.addChild(markerElement);
                            markers.push(markerElement);
                        });
                        button.innerText = 'Enable cluster mode';
                    } else {
                        map.addChild(clusterer);
                        markers.forEach((markerEl) => {
                            map.removeChild(markerEl);
                        });
                        markers = [];
                        button.innerText = 'Disable cluster mode';
                    }
                }
            
                // Creating and adding a custom clusterer change element to the map
                map.addChild(
                    new MMapControls({position: 'top right'}).addChild(
                        new MMapControl().addChild(new ClustererChangeControl({toggleClusterer, changePointsCount, updatePoints}))
                    )
                );
            }
        </script>

        <style> html, body, #app { width: 100%; height: 100%; margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; } .toolbar { position: absolute; z-index: 1000; top: 0; left: 0; display: flex; align-items: center; padding: 16px; } .toolbar a { padding: 16px; }  </style>
        <link rel="stylesheet" href="./common.css" />
        <link rel="stylesheet" href="../variables.css" />
    </head>
    <body>
        <div id="app"></div>
    </body>
</html>
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
        <script crossorigin src="https://cdn.jsdelivr.net/npm/react@17/umd/react.production.min.js"></script>
        <script crossorigin src="https://cdn.jsdelivr.net/npm/react-dom@17/umd/react-dom.production.min.js"></script>
        <script crossorigin src="https://cdn.jsdelivr.net/npm/@babel/standalone@7/babel.min.js"></script>

        <!-- To make the map appear, you must add your apikey -->
        <script src="https://js.api.mappable.world/v3/?apikey=<YOUR_APIKEY>&lang=en_US" type="text/javascript"></script>

        <script
            data-plugins="transform-modules-umd"
            data-presets="react, typescript"
            type="text/babel"
            src="./common.ts"
        ></script>
        <script
            data-plugins="transform-modules-umd"
            data-presets="react, typescript"
            type="text/babel"
            src="../variables.ts"
        ></script>
        <script data-plugins="transform-modules-umd" data-presets="react, typescript" type="text/babel">
            import type {Feature} from '@mappable-world/mappable-clusterer';
            import type {LngLat} from '@mappable-world/mappable-types';
            import type TReact from 'react';
            import {COMMON_LOCATION_PARAMS, getBounds, getRandomPoints, MARGIN} from './common';
            import {BOUNDS, LOCATION} from '../variables';
            
            window.map = null;
            
            main();
            
            async function main() {
                // For each object in the JS API, there is a React counterpart
                // To use the React version of the API, include the module @mappable-world/mappable-reactify
                const [mappableReact] = await Promise.all([mappable.import('@mappable-world/mappable-reactify'), mappable.ready]);
                const reactify = mappableReact.reactify.bindTo(React, ReactDOM);
                const {MMap, MMapDefaultSchemeLayer, MMapDefaultFeaturesLayer, MMapMarker, MMapControls, MMapControl} =
                    reactify.module(mappable);
            
                // Load the package with the cluster, extract the classes for creating clusterer objects and the clustering method
                const {MMapClusterer, clusterByGrid} = reactify.module(await mappable.import('@mappable-world/mappable-clusterer'));
                const {MMapDefaultMarker} = reactify.module(await mappable.import('@mappable-world/mappable-default-ui-theme'));
            
                const {useState, useMemo, useCallback} = React;
            
                function App() {
                    // We declare the initial states of the clusterer
                    // Clusterer visibility
                    const [isClusterer, setIsClusterer] = useState(true);
            
                    const [location, setLocation] = useState(LOCATION);
                    // Number of points in the clusterer
                    const [pointsCount, setPointsCount] = useState(100);
                    // Array with parameters for each clusterer point
                    const [points, setPoints] = useState(getRandomPoints(pointsCount, BOUNDS));
            
                    // We declare a render function. For the clustering method, we pass and store the size of one grid division in pixels
                    const gridSizedMethod = useMemo(() => clusterByGrid({gridSize: 64}), []);
            
                    const onClusterClick = useCallback(
                        (features: Feature[]) => {
                            const bounds = getBounds(features.map((feature: Feature) => feature.geometry.coordinates));
                            setLocation((prevLocation) => ({...prevLocation, bounds, ...COMMON_LOCATION_PARAMS}));
                        },
                        [location]
                    );
            
                    // We declare a function for rendering markers. Note that the function must return any Entity element. In the example, this is MMapDefaultMarker
                    const marker = (feature: Feature) => (
                        <MMapDefaultMarker iconName="landmark" coordinates={feature.geometry.coordinates} />
                    );
            
                    // We declare a cluster rendering function that also returns an Entity element. We will transfer the marker and cluster rendering functions to the clusterer settings
                    const cluster = (coordinates: LngLat, features: Feature[]) => (
                        <MMapMarker coordinates={coordinates}>
                            <div className="circle" onClick={() => onClusterClick(features)}>
                                <div className="circle-content">
                                    <span className="circle-text">{features.length}</span>
                                </div>
                            </div>
                        </MMapMarker>
                    );
            
                    // Creating handler functions for changing the clusterer. We will use these functions in a custom control
                    // THe handler function for changing the number of clusterer points
                    const changePointsCount = useCallback(
                        (event: TReact.ChangeEvent<HTMLInputElement>) => setPointsCount(Number(event.target.value)),
                        []
                    );
                    // The handler function for updating coordinates of clusterer points
                    const updatePoints = useCallback(() => setPoints(getRandomPoints(pointsCount, map.bounds)), [pointsCount]);
                    // The handler function for attach/detach the clusterer
                    const toggleClusterer = useCallback(() => setIsClusterer((prevValue) => !prevValue), []);
            
                    return (
                        // Initialize the map and pass initialization parameters
                        <MMap margin={MARGIN} location={location} showScaleInCopyrights={true} ref={(x) => (map = x)}>
                            {/* Add a map scheme layer */}
                            <MMapDefaultSchemeLayer />
                            {/* Add default feature layer */}
                            <MMapDefaultFeaturesLayer />
                            {/* In the clusterer props, we pass the previously declared functions for rendering markers and clusters,
                            the clustering method, and an array of features */}
                            {isClusterer ? (
                                <MMapClusterer marker={marker} cluster={cluster} method={gridSizedMethod} features={points} />
                            ) : (
                                points.map((feature) => marker(feature))
                            )}
            
                            {/* Add a custom clusterer change element to the map */}
                            <MMapControls position="top right">
                                <MMapControl>
                                    <div className="clusterer-change">
                                        <div className="clusterer-change__section">
                                            <div className="clusterer-change__input__label">Point count:</div>
                                            <input
                                                type="number"
                                                className="clusterer-change__input"
                                                value={pointsCount}
                                                onChange={changePointsCount}
                                            />
                                        </div>
                                        <hr className="divider" />
                                        <div className="clusterer-change__buttons">
                                            <button type="button" className="clusterer-change__btn" onClick={updatePoints}>
                                                Update points
                                            </button>
                                            <button type="button" className="clusterer-change__btn" onClick={toggleClusterer}>
                                                {isClusterer ? 'Disable cluster mode' : 'Enable cluster mode'}
                                            </button>
                                        </div>
                                    </div>
                                </MMapControl>
                            </MMapControls>
                        </MMap>
                    );
                }
            
                ReactDOM.render(
                    <React.StrictMode>
                        <App />
                    </React.StrictMode>,
                    document.getElementById('app')
                );
            }
        </script>

        <style> html, body, #app { width: 100%; height: 100%; margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; } .toolbar { position: absolute; z-index: 1000; top: 0; left: 0; display: flex; align-items: center; padding: 16px; } .toolbar a { padding: 16px; }  </style>
        <link rel="stylesheet" href="./common.css" />
        <link rel="stylesheet" href="../variables.css" />
    </head>
    <body>
        <div id="app"></div>
    </body>
</html>
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
        <script crossorigin src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.js"></script>
        <script crossorigin src="https://cdn.jsdelivr.net/npm/@babel/standalone@7/babel.min.js"></script>

        <script src="https://js.api.mappable.world/v3/?apikey=<YOUR_APIKEY>&lang=en_US" type="text/javascript"></script>

        <script
            data-plugins="transform-modules-umd"
            data-presets="react, typescript"
            type="text/babel"
            src="./common.ts"
        ></script>
        <script
            data-plugins="transform-modules-umd"
            data-presets="react, typescript"
            type="text/babel"
            src="../variables.ts"
        ></script>
        <script data-plugins="transform-modules-umd" data-presets="typescript" type="text/babel">
            import {getRandomPoints, MARGIN} from './common';
            import {BOUNDS, LOCATION} from '../variables';
            import {COMMON_LOCATION_PARAMS, getBounds} from '../../[objects]marker-custom-icon/common';
            import {Feature} from '@mappable-world/mappable-types';
            
            window.map = null;
            
            async function main() {
                // For each object in the JS API, there is a Vue counterpart
                // To use the Vue version of the API, include the module @mappable-world/mappable-vuefy
                const [mappableVue] = await Promise.all([mappable.import('@mappable-world/mappable-vuefy'), mappable.ready]);
                const vuefy = mappableVue.vuefy.bindTo(Vue);
                const {MMap, MMapDefaultSchemeLayer, MMapDefaultFeaturesLayer, MMapMarker, MMapControls, MMapControl} =
                    vuefy.module(mappable);
            
                const {MMapClusterer, clusterByGrid} = vuefy.module(await mappable.import('@mappable-world/mappable-clusterer'));
                const {MMapDefaultMarker} = vuefy.module(await mappable.import('@mappable-world/mappable-default-ui-theme'));
            
                const Marker = Vue.defineComponent({
                    props: ['feature'],
                    components: {
                        MMapDefaultMarker
                    },
                    template: `
                        <MMapDefaultMarker iconName="landmark" :coordinates="feature.geometry.coordinates" />
                    `
                });
            
                const Cluster = Vue.defineComponent({
                    props: {
                        coordinates: Array,
                        features: Array,
                        onClick: Function
                    },
                    components: {
                        MMapMarker
                    },
                    template: `
                  <MMapMarker
                    :coordinates="coordinates"
                    :onClick="onClick"
                  >
                    <div class="circle">
                      <div class="circle-content">
                        <span class="circle-text">{{ features.length }}</span>
                      </div>
                    </div>
                  </MMapMarker>
                `
                });
            
                const App = Vue.createApp({
                    components: {
                        MMap,
                        MMapDefaultSchemeLayer,
                        MMapDefaultFeaturesLayer,
                        MMapClusterer,
                        MMapControls,
                        MMapControl,
                        Cluster,
                        Marker
                    },
                    setup() {
                        const refMap = (ref) => {
                            window.map = ref?.entity;
                        };
                        const location = Vue.ref(LOCATION);
            
                        const isClusterer = Vue.ref(true);
                        const pointsCount = Vue.ref(100);
                        const points = Vue.ref(getRandomPoints(pointsCount.value, BOUNDS));
            
                        const changePointsCount = (event) => {
                            pointsCount.value = Number(event.target.value);
                        };
            
                        const updatePoints = () => {
                            points.value = getRandomPoints(pointsCount.value, map.bounds);
                        };
            
                        const toggleClusterer = () => {
                            isClusterer.value = !isClusterer.value;
                        };
                        const gridSizedMethod = clusterByGrid({gridSize: 64});
            
                        const onClusterClick = (features: Feature[]) => {
                            const bounds = getBounds(features.map((feature: Feature) => feature.geometry.coordinates));
                            location.value = {bounds, ...COMMON_LOCATION_PARAMS};
                        };
            
                        Vue.watch(pointsCount, () => {
                            updatePoints();
                        });
            
                        return {
                            MARGIN,
                            refMap,
                            location,
                            isClusterer,
                            points,
                            pointsCount,
                            changePointsCount,
                            updatePoints,
                            toggleClusterer,
                            onClusterClick,
                            Marker,
                            Cluster,
                            gridSizedMethod
                        };
                    },
                    template: `
                  <MMap :location="location" :showScaleInCopyrights="true" :ref="refMap" :margin="MARGIN">
                    <!-- Add a map scheme layer -->
                    <MMapDefaultSchemeLayer />
                    <!-- Add default features layer -->
                    <MMapDefaultFeaturesLayer />
                    <!-- In the clusterer props, we pass the previously declared functions for rendering markers and clusters,
                    the clustering method, and an array of features -->
            
                    <template v-if="isClusterer">
                      <MMapClusterer :method="gridSizedMethod" :features="points">
                        <template #marker="{feature}">
                          <Marker :feature="feature" />
                        </template>
                        <template #cluster="{coordinates, features}">
                          <Cluster :onClick="() => onClusterClick(features)" :coordinates="coordinates" :features="features" />
                        </template>
                      </MMapClusterer>
                    </template>
                    <template v-else>
                      <template v-for="feature in points">
                        <Marker :feature="feature" />
                      </template>
                    </template>
            
                    <!-- Add a custom clusterer change element to the map -->
                    <MMapControls position="top right">
                      <MMapControl>
                        <div class="clusterer-change">
                          <div class="clusterer-change__section">
                            <div class="clusterer-change__input__label">Point count:</div>
                            <input
                              type="number"
                              class="clusterer-change__input"
                              v-model="pointsCount"
                            />
                          </div>
                          <hr class="divider">
                          <div class="clusterer-change__buttons">
                            <button type="button" class="clusterer-change__btn" @click="updatePoints">
                              Update points
                            </button>
                            <button type="button" class="clusterer-change__btn" @click="toggleClusterer">
                              {{ isClusterer ? "Disable cluster mode" : "Enable cluster mode" }}
                            </button>
                          </div>
                        </div>
                      </MMapControl>
                    </MMapControls>
                  </MMap>
                `
                });
                App.mount('#app');
            }
            
            main();
        </script>

        <style> html, body, #app { width: 100%; height: 100%; margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; } .toolbar { position: absolute; z-index: 1000; top: 0; left: 0; display: flex; align-items: center; padding: 16px; } .toolbar a { padding: 16px; }  </style>
        <link rel="stylesheet" href="./common.css" />
        <link rel="stylesheet" href="../variables.css" />
    </head>
    <body>
        <div id="app"></div>
    </body>
</html>
import type {LngLatBounds, MMapLocationRequest} from '@mappable-world/mappable-types';

/* Rectangle bounded by bottom-left and top-right coordinates
Inside it, we generate the first bundle of clusterer points */
export const BOUNDS: LngLatBounds = [
    [55.2825, 25.2317],
    [55.4132, 25.1753]
];

export const LOCATION: MMapLocationRequest = {
    zoom: 11.6,
    center: [55.3948, 25.1947]
};
:root {
    --interact-action: #313133;
}
import type {LngLatBounds, LngLat, MMapLocationRequest, Margin} from '@mappable-world/mappable-types';
import type {Feature} from '@mappable-world/mappable-clusterer';

mappable.ready.then(() => {
    mappable.import.registerCdn('https://cdn.jsdelivr.net/npm/{package}', [
        '@mappable-world/mappable-default-ui-theme@0.0',
        '@mappable-world/mappable-clusterer@0.0'
    ]);
});

// Function for generating a pseudorandom number
const seed = (s: number) => () => {
    s = Math.sin(s) * 10000;
    return s - Math.floor(s);
};

const rnd = seed(10000); // () => Math.random()

// Generating random coordinates of a point [lng, lat] in a given boundary
const getRandomPointCoordinates = (bounds: LngLatBounds): LngLat => [
    bounds[0][0] + (bounds[1][0] - bounds[0][0]) * rnd(),
    bounds[1][1] + (bounds[0][1] - bounds[1][1]) * rnd()
];

// A function that creates an array with parameters for each clusterer random point
export const getRandomPoints = (count: number, bounds: LngLatBounds): Feature[] => {
    return Array.from({length: count}, (_, index) => ({
        type: 'Feature',
        id: index.toString(),
        geometry: {type: 'Point', coordinates: getRandomPointCoordinates(bounds)}
    }));
};

export const COMMON_LOCATION_PARAMS: Partial<MMapLocationRequest> = {easing: 'ease-in-out', duration: 2000};

export function getBounds(coordinates: number[][]): LngLatBounds {
    let minLat = Infinity,
        minLng = Infinity;
    let maxLat = -Infinity,
        maxLng = -Infinity;

    for (const coords of coordinates) {
        const lat = coords[1];
        const lng = coords[0];

        if (lat < minLat) minLat = lat;
        if (lat > maxLat) maxLat = lat;
        if (lng < minLng) minLng = lng;
        if (lng > maxLng) maxLng = lng;
    }

    return [
        [minLng, minLat],
        [maxLng, maxLat]
    ] as LngLatBounds;
}

export const MARGIN: Margin = [100, 100, 100, 100];
.clusterer-change {
    padding: 8px 16px;
    border-radius: 12px;

    display: flex;
    flex-direction: column;
    align-items: center;
}

.clusterer-change__section {
    display: flex;
    align-items: center;
    width: 100%;
    justify-content: space-between;
    gap: 10px;
    height: 48px;
}

.clusterer-change__buttons {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 8px 0;
}

.clusterer-change__input__label {
    font-size: 14px;
}

.clusterer-change__input {
    max-width: 60px;
    height: 32px;
    font-size: 16px;
    text-align: center;
    border: none;
    border-radius: 8px;
    background: rgba(92, 94, 102, 0.06);
    font-size: 16px;
    outline: none;
    transition: border-color 0.2s ease;
}

.clusterer-change__btn {
    border: none;
    cursor: pointer;

    width: 172px;
    padding: 8px 16px;

    color: #050d33;
    font-size: 14px;
    font-weight: 15;
    background-color: rgba(92, 94, 102, 0.06);
    border-radius: 8px;
    transition: background-color 0.2s;
}

.clusterer-change__btn:hover {
    background-color: rgba(92, 94, 102, 0.06);
}

.clusterer-change__btn:active {
    background-color: rgba(92, 94, 102, 0.06);
}

.circle {
    position: absolute;

    width: 40px;
    height: 40px;

    color: var(--interact-action);
    border-radius: 50%;
    background-color: rgba(255, 255, 255, 0.7);
    box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.2);
    transform: translate(-50%, -50%);
}

.circle:hover {
    color: #2e4ce5;
    cursor: pointer;
}

.circle-content {
    position: absolute;
    top: 50%;
    left: 50%;

    display: flex;
    justify-content: center;
    align-items: center;

    width: 90%;
    height: 90%;

    border-radius: 50%;
    background-color: currentColor;

    transform: translate3d(-50%, -50%, 0);
}

.circle-text {
    font-size: 16px;
    font-weight: 500;
    line-height: 20px;
    color: #fff;
}

.pin {
    transform: translate(-50%, -100%);
}

.divider {
    width: 100%;
    border: none;
    border-top: 1px solid rgba(92, 94, 102, 0.1);
    margin: 8px 0;
}