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.
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.