Skip to content
Create a Time Slider

Create a Time Slider in Maptoolkit Maps JS

Filtering features by time requires storing a timestamp as a feature property in your GeoJSON source and applying a filter expression to the layer that compares each feature’s timestamp against the current slider value. As the user moves the slider, the layer filter updates to show only features within the selected range. Use this pattern to build temporal visualizations, replay historical events, or let users explore how data changes over time.

const API_KEY = 'YOUR_API_KEY';

    const events = [];
    const cities = [
        [11.39085, 47.27574], [16.3738, 48.2082], [13.0550, 47.8095],
        [15.4395, 47.0707], [11.576, 48.1374], [8.5417, 47.3769],
        [7.5886, 47.5596], [9.0, 48.5], [13.4, 52.5]
    ];
    for (let year = 2018; year <= 2024; year++) {
        for (let i = 0; i < 3; i++) {
            const city = cities[Math.floor(Math.random() * cities.length)];
            events.push({
                type: 'Feature',
                geometry: { type: 'Point', coordinates: [city[0] + (Math.random()-0.5) * 2, city[1] + (Math.random()-0.5) * 1] },
                properties: { year }
            });
        }
    }

    const map = new maptoolkit.Map({
        container: 'map',
        apiKey: API_KEY,
        style: `https://styles.maptoolkit.net/maptoolkit/maptoolkit.summer.json?api_key=${API_KEY}`,
        center: [12.0, 47.5],
        zoom: 5,
        attributionControl: { compact: false }
    });

    map.addControl(new maptoolkit.NavigationControl(), 'top-right');

    map.on('load', () => {
        map.addSource('events', { type: 'geojson', data: { type: 'FeatureCollection', features: events } });

        map.addLayer({
            id: 'events',
            type: 'circle',
            source: 'events',
            filter: ['==', ['get', 'year'], 2020],
            paint: { 'circle-radius': 10, 'circle-color': '#ee8a65', 'circle-opacity': 0.8 }
        });

        document.getElementById('slider').addEventListener('input', (e) => {
            const year = parseInt(e.target.value);
            document.getElementById('year-label').textContent = year;
            map.setFilter('events', ['==', ['get', 'year'], year]);
        });
    });
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Create a Time Slider - Maptoolkit Maps JS</title>
    <meta property="og:description" content="Filter map features by a time range using a slider control." />
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script src="https://unpkg.com/@maptoolkit/maps@11.0.0-beta.3/dist/maptoolkit.js"></script>
    <link rel="stylesheet" href="https://unpkg.com/@maptoolkit/maps@11.0.0-beta.3/dist/maptoolkit.css" />
    <style>
        html, body { width: 100%; height: 100%; margin: 0; padding: 0; }
        #map { width: 100%; height: 100%; }
        #slider-container {
            position: absolute;
            bottom: 30px;
            left: 50%;
            transform: translateX(-50%);
            background: white;
            padding: 12px 20px;
            border-radius: .4rem;
            font-family: sans-serif;
            font-size: 14px;
            text-align: center;
            min-width: 280px;
        }
        #slider { width: 100%; margin: 8px 0 4px; }
    </style>
</head>
<body>
<div id="map"></div>
<div id="slider-container">
    <strong>Year: <span id="year-label">2020</span></strong>
    <input type="range" id="slider" min="2018" max="2024" step="1" value="2020">
</div>
<script>
    const API_KEY = 'YOUR_API_KEY';

    const events = [];
    const cities = [
        [11.39085, 47.27574], [16.3738, 48.2082], [13.0550, 47.8095],
        [15.4395, 47.0707], [11.576, 48.1374], [8.5417, 47.3769],
        [7.5886, 47.5596], [9.0, 48.5], [13.4, 52.5]
    ];
    for (let year = 2018; year <= 2024; year++) {
        for (let i = 0; i < 3; i++) {
            const city = cities[Math.floor(Math.random() * cities.length)];
            events.push({
                type: 'Feature',
                geometry: { type: 'Point', coordinates: [city[0] + (Math.random()-0.5) * 2, city[1] + (Math.random()-0.5) * 1] },
                properties: { year }
            });
        }
    }

    const map = new maptoolkit.Map({
        container: 'map',
        apiKey: API_KEY,
        style: `https://styles.maptoolkit.net/maptoolkit/maptoolkit.summer.json?api_key=${API_KEY}`,
        center: [12.0, 47.5],
        zoom: 5,
        attributionControl: { compact: false }
    });

    map.addControl(new maptoolkit.NavigationControl(), 'top-right');

    map.on('load', () => {
        map.addSource('events', { type: 'geojson', data: { type: 'FeatureCollection', features: events } });

        map.addLayer({
            id: 'events',
            type: 'circle',
            source: 'events',
            filter: ['==', ['get', 'year'], 2020],
            paint: { 'circle-radius': 10, 'circle-color': '#ee8a65', 'circle-opacity': 0.8 }
        });

        document.getElementById('slider').addEventListener('input', (e) => {
            const year = parseInt(e.target.value);
            document.getElementById('year-label').textContent = year;
            map.setFilter('events', ['==', ['get', 'year'], year]);
        });
    });
</script>
</body>
</html>

Use the prompt below with any LLM to get the same result. Make sure the Maptoolkit MCP server is connected first — check out AI Integration & MCP to get started.

Use the Maptoolkit Connector. Create an interactive map with zoom level 5, centered around [12.0, 47.5]. Generate random point events across European cities for years 2018-2024 and add a time slider to filter them by year.

How it works

Filtering happens in the renderer, not in your data. The full set of features stays in the source and setFilter decides which of them draw, so moving the slider does not re-upload anything.

The filter is an expression comparing a feature property against the slider value, which is why every feature carries a year. A filter can only test properties that exist on the feature; a timestamp kept in a parallel array is invisible to it.

setFilter replaces the layer’s filter outright, so if the layer already had one for another purpose, the new call has to include both conditions.

Note that the example generates its features randomly at load, so the map differs on each reload. Real data would come from a source URL or a Connector.

For real timestamps rather than years, store an epoch number rather than a date string: expressions compare numbers reliably and string dates only sort correctly in strict ISO form.

Next steps

Playback is the obvious addition, stepping the filter on a timer so the sequence animates rather than waiting to be dragged, with a pause control.

A range is often more useful than a single value, showing everything between two dates rather than one year at a time, and it composes with a category filter through an all expression. And since the filter only hides features, pairing it with a count of what is currently visible tells the user whether an empty map means no data or a bad filter.