← MapboxCONTENT HISTORYWHAT CHANGED · RULE-BASED ANALYSIS
Update to Mapbox
Snapshot Sep 30, 2026 · 23:11 UTC · version 1.0.0
Collection source: not recorded for this historical snapshot.
First saved snapshot
No earlier snapshot is available to establish a change.
Compare saved observations
Download comparison JSONFull technical diff · 0 changed fields
Full snapshot data
{
"name": "mapbox-data-visualization-patterns",
"description": "Patterns for visualizing data on maps including choropleth maps, heat maps, 3D visualizations, data-driven styling, and animated data. Covers layer types, color scales, and performance optimization.",
"included_files": [
{
"relative_path": "AGENTS.md",
"size_in_bytes": 12622
},
{
"relative_path": "evals/evals.json",
"size_in_bytes": 2371
},
{
"relative_path": "references/3d-extrusions.md",
"size_in_bytes": 2970
},
{
"relative_path": "references/animation.md",
"size_in_bytes": 2818
},
{
"relative_path": "references/circles-lines.md",
"size_in_bytes": 2403
},
{
"relative_path": "references/clustering.md",
"size_in_bytes": 3932
},
{
"relative_path": "references/legends-use-cases.md",
"size_in_bytes": 4488
},
{
"relative_path": "references/performance.md",
"size_in_bytes": 4334
}
],
"skill_md_contents": "---\nname: mapbox-data-visualization-patterns\ndescription: Patterns for visualizing data on maps including choropleth maps, heat maps, 3D visualizations, data-driven styling, and animated data. Covers layer types, color scales, and performance optimization.\n---\n\n# Data Visualization Patterns Skill\n\nComprehensive patterns for visualizing data on Mapbox maps. Covers choropleth maps, heat maps, 3D extrusions, data-driven styling, animated visualizations, and performance optimization for data-heavy applications.\n\n## When to Use This Skill\n\nUse this skill when:\n\n- Visualizing statistical data on maps (population, sales, demographics)\n- Creating choropleth maps with color-coded regions\n- Building heat maps or clustering for density visualization\n- Adding 3D visualizations (building heights, terrain elevation)\n- Implementing data-driven styling based on properties\n- Animating time-series data\n- Working with large datasets that require optimization\n\n## Visualization Types\n\n### Choropleth Maps\n\n**Best for:** Regional data (states, counties, zip codes), statistical comparisons\n\n**Pattern:** Color-code polygons based on data values\n\n```javascript\nmap.on('load', () => {\n // Add data source (GeoJSON with properties)\n map.addSource('states', {\n type: 'geojson',\n data: 'https://example.com/states.geojson' // Features with population property\n });\n\n // Add fill layer with data-driven color\n map.addLayer({\n id: 'states-layer',\n type: 'fill',\n source: 'states',\n paint: {\n 'fill-color': [\n 'interpolate',\n ['linear'],\n ['get', 'population'],\n 0,\n '#f0f9ff', // Light blue for low population\n 500000,\n '#7fcdff',\n 1000000,\n '#0080ff',\n 5000000,\n '#0040bf', // Dark blue for high population\n 10000000,\n '#001f5c'\n ],\n 'fill-opacity': 0.75\n }\n });\n\n // Add border layer\n map.addLayer({\n id: 'states-border',\n type: 'line',\n source: 'states',\n paint: {\n 'line-color': '#ffffff',\n 'line-width': 1\n }\n });\n\n // Add hover effect with reusable popup\n const popup = new mapboxgl.Popup({\n closeButton: false,\n closeOnClick: false\n });\n\n map.on('mousemove', 'states-layer', (e) => {\n if (e.features.length > 0) {\n map.getCanvas().style.cursor = 'pointer';\n\n const feature = e.features[0];\n popup\n .setLngLat(e.lngLat)\n .setHTML(\n `\n <h3>${feature.properties.name}</h3>\n <p>Population: ${feature.properties.population.toLocaleString()}</p>\n `\n )\n .addTo(map);\n }\n });\n\n map.on('mouseleave', 'states-layer', () => {\n map.getCanvas().style.cursor = '';\n popup.remove();\n });\n});\n```\n\n> **`step` vs `interpolate`:** The example above uses `interpolate` for smooth color gradients. For **discrete color buckets** (e.g., \"low / medium / high\"), use `['step', ['get', 'population'], '#f0f0f0', 500000, '#fee0d2', 2000000, '#fc9272', 10000000, '#de2d26']` instead. Prefer `step` when data has natural categories or when exact boundary values matter.\n\n**Color Scale Strategies:**\n\n```javascript\n// Linear interpolation (continuous scale)\n'fill-color': [\n 'interpolate',\n ['linear'],\n ['get', 'value'],\n 0, '#ffffcc',\n 25, '#78c679',\n 50, '#31a354',\n 100, '#006837'\n]\n\n// Step intervals (discrete buckets)\n'fill-color': [\n 'step',\n ['get', 'value'],\n '#ffffcc', // Default color\n 25, '#c7e9b4',\n 50, '#7fcdbb',\n 75, '#41b6c4',\n 100, '#2c7fb8'\n]\n\n// Case-based (categorical data)\n'fill-color': [\n 'match',\n ['get', 'category'],\n 'residential', '#ffd700',\n 'commercial', '#ff6b6b',\n 'industrial', '#4ecdc4',\n 'park', '#45b7d1',\n '#cccccc' // Default\n]\n```\n\n### Heat Maps\n\n**Best for:** Point density, event locations, incident clustering\n\n**Pattern:** Visualize density of points\n\n```javascript\nmap.on('load', () => {\n // Add data source (points)\n map.addSource('incidents', {\n type: 'geojson',\n data: {\n type: 'FeatureCollection',\n features: [\n {\n type: 'Feature',\n geometry: {\n type: 'Point',\n coordinates: [-122.4194, 37.7749]\n },\n properties: {\n intensity: 1\n }\n }\n // ... more points\n ]\n }\n });\n\n // Add heatmap layer\n map.addLayer({\n id: 'incidents-heat',\n type: 'heatmap',\n source: 'incidents',\n maxzoom: 15,\n paint: {\n // Increase weight based on intensity property\n 'heatmap-weight': ['interpolate', ['linear'], ['get', 'intensity'], 0, 0, 6, 1],\n // Increase intensity as zoom level increases\n 'heatmap-intensity': ['interpolate', ['linear'], ['zoom'], 0, 1, 15, 3],\n // Color ramp for heatmap\n 'heatmap-color': [\n 'interpolate',\n ['linear'],\n ['heatmap-density'],\n 0,\n 'rgba(33,102,172,0)',\n 0.2,\n 'rgb(103,169,207)',\n 0.4,\n 'rgb(209,229,240)',\n 0.6,\n 'rgb(253,219,199)',\n 0.8,\n 'rgb(239,138,98)',\n 1,\n 'rgb(178,24,43)'\n ],\n // Adjust radius by zoom level\n 'heatmap-radius': ['interpolate', ['linear'], ['zoom'], 0, 2, 15, 20],\n // Decrease opacity at higher zoom levels\n 'heatmap-opacity': ['interpolate', ['linear'], ['zoom'], 7, 1, 15, 0]\n }\n });\n\n // Add circle layer for individual points at high zoom\n map.addLayer({\n id: 'incidents-point',\n type: 'circle',\n source: 'incidents',\n minzoom: 14,\n paint: {\n 'circle-radius': ['interpolate', ['linear'], ['zoom'], 14, 4, 22, 30],\n 'circle-color': '#ff4444',\n 'circle-opacity': 0.8,\n 'circle-stroke-color': '#fff',\n 'circle-stroke-width': 1\n }\n });\n});\n```\n\n## Best Practices\n\n### Color Accessibility\n\n```javascript\n// Use ColorBrewer scales for accessibility\n// https://colorbrewer2.org/\n\n// Good: Sequential (single hue)\nconst sequentialScale = ['#f0f9ff', '#bae4ff', '#7fcdff', '#0080ff', '#001f5c'];\n\n// Good: Diverging (two hues)\nconst divergingScale = ['#d73027', '#fc8d59', '#fee08b', '#d9ef8b', '#91cf60', '#1a9850'];\n\n// Good: Qualitative (distinct categories)\nconst qualitativeScale = ['#e41a1c', '#377eb8', '#4daf4a', '#984ea3', '#ff7f00'];\n\n// Avoid: Red-green for color-blind accessibility\n// Use: Blue-orange or purple-green instead\n```\n\n### Error Handling\n\n```javascript\n// Handle missing or invalid data\nmap.on('load', () => {\n map.addSource('data', {\n type: 'geojson',\n data: dataUrl\n });\n\n map.addLayer({\n id: 'data-viz',\n type: 'fill',\n source: 'data',\n paint: {\n 'fill-color': [\n 'case',\n ['has', 'value'], // Check if property exists\n ['interpolate', ['linear'], ['get', 'value'], 0, '#f0f0f0', 100, '#0080ff'],\n '#cccccc' // Default color for missing data\n ]\n }\n });\n\n // Handle map errors\n map.on('error', (e) => {\n console.error('Map error:', e.error);\n });\n});\n```\n\n## Data Size Rule\n\n- **< 1 MB**: Use GeoJSON directly\n- **1–10 MB**: Consider either GeoJSON or vector tiles depending on complexity\n- **> 10 MB**: Use vector tiles (upload to Mapbox as tileset)\n\nSee [references/performance.md](references/performance.md) for implementation details.\n\n## Reference Files\n\nFor additional visualization patterns, load the relevant reference file:\n\n- **[references/clustering.md](references/clustering.md)** — Point clustering, custom cluster properties, clustering vs heatmap comparison\n- **[references/3d-extrusions.md](references/3d-extrusions.md)** — 3D building extrusions, custom data sources, data-driven heights\n- **[references/circles-lines.md](references/circles-lines.md)** — Circle/bubble maps, line data visualization, traffic flow styling\n- **[references/animation.md](references/animation.md)** — Time-series animation, real-time data updates, smooth transitions\n- **[references/performance.md](references/performance.md)** — Vector tiles vs GeoJSON, feature state, filtering, progressive loading\n- **[references/legends-use-cases.md](references/legends-use-cases.md)** — Legend UI, data inspector, data preprocessing, election/COVID/real-estate examples\n\n## Resources\n\n- [Mapbox Expression Reference](https://docs.mapbox.com/style-spec/reference/expressions/)\n- [ColorBrewer](https://colorbrewer2.org/) - Color scales for maps\n- [Turf.js](https://turfjs.org/) - Spatial analysis\n- [Simple Statistics](https://simple-statistics.github.io/) - Data classification\n- [Data Visualization Tutorials](https://docs.mapbox.com/help/tutorials/#data-visualization)\n"
}SHA-256: 9776070f9ff7a02ea9657ffa0a0ad8bd05a542f0d7553d0d1daaa9abfbbc33ee