| name | arcgis-time-animation |
| description | Work with temporal data using TimeSlider, TimeExtent, and time-aware layers. Use for animating data over time, filtering by date ranges, and visualizing temporal patterns. |
ArcGIS Time Animation
Use this skill for working with temporal data, time-aware layers, and time animation controls.
TimeExtent
Represents a time range with start and end dates.
import TimeExtent from "@arcgis/core/TimeExtent.js";
// Create time extent
const timeExtent = new TimeExtent({
start: new Date("2024-01-01"),
end: new Date("2024-12-31")
});
// Apply to view
view.timeExtent = timeExtent;
TimeExtent Properties
const timeExtent = new TimeExtent({
start: new Date("2024-01-01T00:00:00Z"),
end: new Date("2024-06-30T23:59:59Z")
});
console.log(timeExtent.start); // Date object
console.log(timeExtent.end); // Date object
// Check if instant (start === end)
if (timeExtent.start.getTime() === timeExtent.end.getTime()) {
console.log("This is a time instant");
}
Null TimeExtent
// No time filter (show all data)
view.timeExtent = null;
// Instant in time
const instant = new TimeExtent({
start: new Date("2024-06-15"),
end: new Date("2024-06-15")
});
TimeInterval
Represents a duration of time.
import TimeInterval from "@arcgis/core/TimeInterval.js";
const interval = new TimeInterval({
value: 1,
unit: "months" // milliseconds, seconds, minutes, hours, days, weeks, months, years
});
TimeInterval Units
millisecondssecondsminuteshoursdaysweeksmonthsyearsdecadescenturies
TimeSlider
Interactive widget for controlling temporal display.
TimeSlider Component
<arcgis-map item-id="YOUR_WEBMAP_ID">
<arcgis-time-slider
slot="bottom-left"
mode="time-window"
loop
time-visible>
</arcgis-time-slider>
</arcgis-map>
<script type="module">
const map = document.querySelector("arcgis-map");
const timeSlider = document.querySelector("arcgis-time-slider");
await map.viewOnReady();
// Configure from layer
const layer = map.view.map.layers.find(l => l.timeInfo);
if (layer) {
await layer.load();
timeSlider.fullTimeExtent = layer.timeInfo.fullTimeExtent;
timeSlider.stops = { interval: layer.timeInfo.interval };
}
</script>
TimeSlider Widget (Core API)
import TimeSlider from "@arcgis/core/widgets/TimeSlider.js";
const timeSlider = new TimeSlider({
container: "timeSliderDiv",
view: view,
fullTimeExtent: {
start: new Date("2020-01-01"),
end: new Date("2024-12-31")
},
timeExtent: {
start: new Date("2024-01-01"),
end: new Date("2024-03-31")
}
});
TimeSlider with Playback
const timeSlider = new TimeSlider({
container: "timeSliderDiv",
view: view,
mode: "time-window", // instant, time-window, cumulative-from-start, cumulative-from-end
fullTimeExtent: {
start: new Date("2020-01-01"),
end: new Date("2024-12-31")
},
playRate: 1000, // Milliseconds between steps
loop: true,
stops: {
interval: {
value: 1,
unit: "months"
}
}
});
// Start/stop playback
timeSlider.play();
timeSlider.stop();
TimeSlider Modes
// Instant - single point in time
const instantSlider = new TimeSlider({
mode: "instant",
fullTimeExtent: { start, end }
});
// Time Window - range with start and end
const windowSlider = new TimeSlider({
mode: "time-window",
fullTimeExtent: { start, end }
});
// Cumulative from Start - everything from start to current
const cumulativeStart = new TimeSlider({
mode: "cumulative-from-start",
fullTimeExtent: { start, end }
});
// Cumulative from End - everything from current to end
const cumulativeEnd = new TimeSlider({
mode: "cumulative-from-end",
fullTimeExtent: { start, end }
});
Custom Stops
// Interval-based stops
const timeSlider = new TimeSlider({
stops: {
interval: {
value: 1,
unit: "weeks"
}
}
});
// Specific dates
const timeSlider = new TimeSlider({
stops: {
dates: [
new Date("2024-01-01"),
new Date("2024-04-01"),
new Date("2024-07-01"),
new Date("2024-10-01")
]
}
});
// Number of stops (evenly distributed)
const timeSlider = new TimeSlider({
stops: {
count: 12 // 12 evenly spaced stops
}
});
TimeSlider Events
// Watch for time extent changes
timeSlider.watch("timeExtent", (timeExtent) => {
console.log("New time extent:", timeExtent.start, "to", timeExtent.end);
updateCharts(timeExtent);
});
// Watch for playback state
timeSlider.watch("viewModel.state", (state) => {
console.log("State:", state); // ready, playing, disabled
});
TimeSlider Configuration
const timeSlider = new TimeSlider({
container: "timeSliderDiv",
view: view,
fullTimeExtent: {
start: new Date("2020-01-01"),
end: new Date("2024-12-31")
},
// Display options
layout: "auto", // auto, compact, wide
tickConfigs: [{
mode: "position",
values: [
new Date("2021-01-01"),
new Date("2022-01-01"),
new Date("2023-01-01"),
new Date("2024-01-01")
],
labelsVisible: true,
labelFormatFunction: (value) => value.getFullYear().toString()
}],
// Behavior
playRate: 2000,
loop: true,
// Labels
labelFormatFunction: (value, type, element, layout) => {
const date = new Date(value);
if (type === "min" || type === "max") {
return date.toLocaleDateString();
}
return date.toLocaleDateString("en-US", { month: "short", year: "numeric" });
}
});
Time-Aware Layers
FeatureLayer with Time
const featureLayer = new FeatureLayer({
url: "https://services.arcgis.com/.../FeatureServer/0",
timeInfo: {
startField: "event_date", // Field containing date
endField: "end_date", // Optional end date field
interval: {
value: 1,
unit: "days"
}
}
});
// Check if layer supports time
if (featureLayer.timeInfo) {
console.log("Time field:", featureLayer.timeInfo.startField);
console.log("Full extent:", featureLayer.timeInfo.fullTimeExtent);
}
TimeInfo Properties
const timeInfo = {
startField: "start_time", // Required: start date field
endField: "end_time", // Optional: end date field (for duration)
fullTimeExtent: { // Data's full time range
start: new Date("2020-01-01"),
end: new Date("2024-12-31")
},
interval: { // Suggested animation interval
value: 1,
unit: "months"
},
useTime: true // Enable time filtering
};
ImageryLayer with Time
const imageryLayer = new ImageryLayer({
url: "https://services.arcgis.com/.../ImageServer",
timeInfo: {
startField: "acquisition_date"
}
});
// Multidimensional imagery
const multidimLayer = new ImageryLayer({
url: "https://services.arcgis.com/.../ImageServer",
multidimensionalDefinition: [{
variableName: "temperature",
dimensionName: "StdTime",
values: [1609459200000] // Epoch milliseconds
}]
});
MapImageLayer with Time
const mapImageLayer = new MapImageLayer({
url: "https://services.arcgis.com/.../MapServer",
timeInfo: {
startField: "date_field"
}
});
StreamLayer with Time
const streamLayer = new StreamLayer({
url: "wss://services.arcgis.com/.../StreamServer",
timeInfo: {
trackIdField: "vehicle_id",
startField: "timestamp"
},
purgeOptions: {
displayCount: 1000,
age: 5 // Minutes to keep
}
});
Initializing TimeSlider from Layer
// Auto-configure from time-aware layer
await featureLayer.load();
const timeSlider = new TimeSlider({
container: "timeSliderDiv",
view: view
});
// Initialize from layer's time info
TimeSlider.getPropertiesFromWebMap(webMap).then((properties) => {
timeSlider.set(properties);
});
// Or manually from layer
if (featureLayer.timeInfo) {
timeSlider.fullTimeExtent = featureLayer.timeInfo.fullTimeExtent;
timeSlider.stops = {
interval: featureLayer.timeInfo.interval
};
}
TimeZoneLabel
Display time zone information.
TimeZoneLabel Component
<arcgis-map>
<arcgis-time-zone-label slot="bottom-left"></arcgis-time-zone-label>
</arcgis-map>
TimeZoneLabel Widget (Core API) - Deprecated
DEPRECATED since 4.33: Use the
arcgis-time-zone-labelcomponent shown above instead. For information on widget deprecation, see Esri's move to web components.
// DEPRECATED - Use arcgis-time-zone-label component instead
import TimeZoneLabel from "@arcgis/core/widgets/TimeZoneLabel.js";
const timeZoneLabel = new TimeZoneLabel({
view: view
});
view.ui.add(timeZoneLabel, "bottom-left");
Filtering by Time
Client-Side Filter
// Apply time filter to view
view.timeExtent = new TimeExtent({
start: new Date("2024-01-01"),
end: new Date("2024-06-30")
});
// Filter specific layer
layerView.filter = {
timeExtent: new TimeExtent({
start: new Date("2024-03-01"),
end: new Date("2024-03-31")
})
};
Query with Time
const query = featureLayer.createQuery();
query.timeExtent = new TimeExtent({
start: new Date("2024-01-01"),
end: new Date("2024-12-31")
});
query.where = "status = 'active'";
query.returnGeometry = true;
const results = await featureLayer.queryFeatures(query);
Animation Patterns
Manual Animation Loop
async function animateOverTime(layer, startDate, endDate, intervalDays) {
const current = new Date(startDate);
while (current <= endDate) {
const nextDate = new Date(current);
nextDate.setDate(nextDate.getDate() + intervalDays);
view.timeExtent = new TimeExtent({
start: current,
end: nextDate
});
await new Promise(resolve => setTimeout(resolve, 500));
current.setDate(current.getDate() + intervalDays);
}
}
// Usage
animateOverTime(
featureLayer,
new Date("2024-01-01"),
new Date("2024-12-31"),
7 // Weekly intervals
);
TimeSlider with Statistics
// Update statistics panel as time changes
timeSlider.watch("timeExtent", async (timeExtent) => {
const query = featureLayer.createQuery();
query.timeExtent = timeExtent;
query.outStatistics = [{
statisticType: "count",
onStatisticField: "OBJECTID",
outStatisticFieldName: "count"
}, {
statisticType: "sum",
onStatisticField: "value",
outStatisticFieldName: "total"
}];
const result = await featureLayer.queryFeatures(query);
const stats = result.features[0].attributes;
document.getElementById("count").textContent = stats.count;
document.getElementById("total").textContent = stats.total;
});
Synchronized TimeSliders
// Sync multiple time sliders
const mainSlider = new TimeSlider({
container: "mainSliderDiv",
view: mainView
});
const compareSlider = new TimeSlider({
container: "compareSliderDiv",
view: compareView
});
// Sync them
mainSlider.watch("timeExtent", (timeExtent) => {
compareSlider.timeExtent = timeExtent;
});
Historical Data Visualization
Track Lines
// Show movement paths with time
const featureLayer = new FeatureLayer({
url: "https://.../tracks/FeatureServer/0",
timeInfo: {
startField: "timestamp",
trackIdField: "vehicle_id"
},
renderer: {
type: "simple",
symbol: {
type: "simple-line",
color: "blue",
width: 2
}
}
});
Age-Based Styling
// Style features based on age
const featureLayer = new FeatureLayer({
url: "...",
renderer: {
type: "simple",
symbol: { type: "simple-marker", size: 8 },
visualVariables: [{
type: "color",
field: "timestamp",
stops: [
{ value: Date.now() - 86400000, color: "red" }, // 1 day old
{ value: Date.now() - 604800000, color: "yellow" }, // 1 week old
{ value: Date.now() - 2592000000, color: "gray" } // 30 days old
]
}]
}
});
Common Patterns
Full TimeSlider Setup
async function setupTimeSlider(view, layer) {
await layer.load();
if (!layer.timeInfo) {
console.warn("Layer is not time-aware");
return null;
}
const timeSlider = new TimeSlider({
container: "timeSliderDiv",
view: view,
fullTimeExtent: layer.timeInfo.fullTimeExtent,
mode: "time-window",
playRate: 1000,
loop: true,
stops: {
interval: layer.timeInfo.interval || { value: 1, unit: "months" }
}
});
// Update UI on time change
timeSlider.watch("timeExtent", (extent) => {
document.getElementById("currentTime").textContent =
`${extent.start.toLocaleDateString()} - ${extent.end.toLocaleDateString()}`;
});
return timeSlider;
}
Time-Aware Query
async function queryByTimeRange(layer, startDate, endDate) {
const query = layer.createQuery();
query.timeExtent = new TimeExtent({
start: startDate,
end: endDate
});
query.outFields = ["*"];
query.returnGeometry = true;
return await layer.queryFeatures(query);
}
// Usage
const marchData = await queryByTimeRange(
featureLayer,
new Date("2024-03-01"),
new Date("2024-03-31")
);
Time-Based Highlighting
// Highlight features from current time period
timeSlider.watch("timeExtent", async (timeExtent) => {
// Clear previous highlights
if (highlightHandle) {
highlightHandle.remove();
}
// Query features in current time extent
const query = featureLayer.createQuery();
query.timeExtent = timeExtent;
const layerView = await view.whenLayerView(featureLayer);
const results = await featureLayer.queryFeatures(query);
highlightHandle = layerView.highlight(results.features);
});
TypeScript Usage
Time configurations use autocasting. For TypeScript safety, use as const:
// Use 'as const' for time slider configuration
const timeSlider = new TimeSlider({
view: view,
mode: "time-window",
stops: {
interval: {
value: 1,
unit: "hours"
}
} as const
});
// For layer time settings
layer.timeInfo = {
startField: "start_date",
endField: "end_date",
interval: {
value: 1,
unit: "days"
}
} as const;
Tip: See arcgis-core-maps skill for detailed guidance on autocasting vs explicit classes.
Common Pitfalls
Time Zone Issues: Dates are affected by time zones
// Use UTC dates for consistency const date = new Date("2024-06-15T00:00:00Z"); // Or specify timezone const localDate = new Date("2024-06-15T00:00:00-05:00");Layer Must Be Loaded: TimeInfo is only available after loading
await layer.load(); if (layer.timeInfo) { // Now timeInfo is available }TimeExtent Not Applied: View's timeExtent must be set
// Set on view to filter all time-aware layers view.timeExtent = timeExtent; // Or on specific layerView for per-layer filtering layerView.filter = { timeExtent };Null vs Undefined: Use null to show all data
// Show all data (no time filter) view.timeExtent = null;Performance: Large time ranges can be slow
// Consider using time-based queries instead of filtering const query = layer.createQuery(); query.timeExtent = smallerTimeRange;