Animated Data Visualizations

March 30, 2026 ยท View on GitHub

Time-Series Animation

Pattern: Animate data over time

let currentTime = 0;
const times = [0, 6, 12, 18, 24]; // Hours of day
let animationId;

map.on('load', () => {
  map.addSource('hourly-data', {
    type: 'geojson',
    data: getDataForTime(currentTime)
  });

  map.addLayer({
    id: 'data-layer',
    type: 'circle',
    source: 'hourly-data',
    paint: {
      'circle-radius': 8,
      'circle-color': ['get', 'color']
    }
  });

  // Animation loop
  function animate() {
    currentTime = (currentTime + 1) % times.length;

    // Update data
    map.getSource('hourly-data').setData(getDataForTime(times[currentTime]));

    // Update UI
    document.getElementById('time-display').textContent = `${times[currentTime]}:00`;

    animationId = setTimeout(animate, 1000); // Update every second
  }

  // Start animation
  document.getElementById('play-button').addEventListener('click', () => {
    if (animationId) {
      clearTimeout(animationId);
      animationId = null;
    } else {
      animate();
    }
  });
});

function getDataForTime(hour) {
  // Fetch or generate data for specific time
  return {
    type: 'FeatureCollection',
    features: data.filter((d) => d.properties.hour === hour)
  };
}

Real-Time Data Updates

Pattern: Update data from live sources

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

  map.addLayer({
    id: 'live-points',
    type: 'circle',
    source: 'live-data',
    paint: {
      'circle-radius': 6,
      'circle-color': '#ff4444'
    }
  });

  // Poll for updates every 5 seconds
  setInterval(async () => {
    const response = await fetch('https://api.example.com/live-data');
    const data = await response.json();

    // Update source
    map.getSource('live-data').setData(data);
  }, 5000);

  // Or use WebSocket for real-time updates
  const ws = new WebSocket('wss://api.example.com/live');

  ws.onmessage = (event) => {
    const data = JSON.parse(event.data);
    map.getSource('live-data').setData(data);
  };
});

Smooth Transitions

Pattern: Animate property changes

// Smoothly transition circle sizes
function updateVisualization(newData) {
  map.getSource('data-source').setData(newData);

  // Animate circle radius
  const currentRadius = map.getPaintProperty('data-layer', 'circle-radius');
  const targetRadius = ['get', 'newSize'];

  // Use setPaintProperty with transition
  map.setPaintProperty('data-layer', 'circle-radius', targetRadius);

  // Or use expressions for smooth interpolation
  map.setPaintProperty('data-layer', 'circle-radius', ['interpolate', ['linear'], ['get', 'value'], 0, 2, 100, 20]);
}