mapbox-data-visualization-patterns

安装量: 171
排名: #5064

安装

npx skills add https://github.com/mapbox/mapbox-agent-skills --skill mapbox-data-visualization-patterns

Data Visualization Patterns Skill Comprehensive 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. When to Use This Skill Use this skill when: Visualizing statistical data on maps (population, sales, demographics) Creating choropleth maps with color-coded regions Building heat maps or clustering for density visualization Adding 3D visualizations (building heights, terrain elevation) Implementing data-driven styling based on properties Animating time-series data Working with large datasets that require optimization Visualization Types Choropleth Maps Best for: Regional data (states, counties, zip codes), statistical comparisons Pattern: Color-code polygons based on data values map . on ( 'load' , ( ) => { // Add data source (GeoJSON with properties) map . addSource ( 'states' , { type : 'geojson' , data : 'https://example.com/states.geojson' // Features with population property } ) ; // Add fill layer with data-driven color map . addLayer ( { id : 'states-layer' , type : 'fill' , source : 'states' , paint : { 'fill-color' : [ 'interpolate' , [ 'linear' ] , [ 'get' , 'population' ] , 0 , '#f0f9ff' , // Light blue for low population 500000 , '#7fcdff' , 1000000 , '#0080ff' , 5000000 , '#0040bf' , // Dark blue for high population 10000000 , '#001f5c' ] , 'fill-opacity' : 0.75 } } ) ; // Add border layer map . addLayer ( { id : 'states-border' , type : 'line' , source : 'states' , paint : { 'line-color' : '#ffffff' , 'line-width' : 1 } } ) ; // Add hover effect with reusable popup const popup = new mapboxgl . Popup ( { closeButton : false , closeOnClick : false } ) ; map . on ( 'mousemove' , 'states-layer' , ( e ) => { if ( e . features . length

0 ) { map . getCanvas ( ) . style . cursor = 'pointer' ; const feature = e . features [ 0 ] ; popup . setLngLat ( e . lngLat ) . setHTML ( `

${ feature . properties . name }

Population: ${ feature . properties . population . toLocaleString ( ) }

) . addTo ( map ) ; } } ) ; map . on ( 'mouseleave' , 'states-layer' , ( ) => { map . getCanvas ( ) . style . cursor = '' ; popup . remove ( ) ; } ) ; } ) ; Color Scale Strategies: // Linear interpolation (continuous scale) 'fill-color' : [ 'interpolate' , [ 'linear' ] , [ 'get' , 'value' ] , 0 , '#ffffcc' , 25 , '#78c679' , 50 , '#31a354' , 100 , '#006837' ] // Step intervals (discrete buckets) 'fill-color' : [ 'step' , [ 'get' , 'value' ] , '#ffffcc' , // Default color 25 , '#c7e9b4' , 50 , '#7fcdbb' , 75 , '#41b6c4' , 100 , '#2c7fb8' ] // Case-based (categorical data) 'fill-color' : [ 'match' , [ 'get' , 'category' ] , 'residential' , '#ffd700' , 'commercial' , '#ff6b6b' , 'industrial' , '#4ecdc4' , 'park' , '#45b7d1' , '#cccccc' // Default ] Heat Maps Best for: Point density, event locations, incident clustering Pattern: Visualize density of points map . on ( 'load' , ( ) => { // Add data source (points) map . addSource ( 'incidents' , { type : 'geojson' , data : { type : 'FeatureCollection' , features : [ { type : 'Feature' , geometry : { type : 'Point' , coordinates : [ - 122.4194 , 37.7749 ] } , properties : { intensity : 1 } } // ... more points ] } } ) ; // Add heatmap layer map . addLayer ( { id : 'incidents-heat' , type : 'heatmap' , source : 'incidents' , maxzoom : 15 , paint : { // Increase weight based on intensity property 'heatmap-weight' : [ 'interpolate' , [ 'linear' ] , [ 'get' , 'intensity' ] , 0 , 0 , 6 , 1 ] , // Increase intensity as zoom level increases 'heatmap-intensity' : [ 'interpolate' , [ 'linear' ] , [ 'zoom' ] , 0 , 1 , 15 , 3 ] , // Color ramp for heatmap 'heatmap-color' : [ 'interpolate' , [ 'linear' ] , [ 'heatmap-density' ] , 0 , 'rgba(33,102,172,0)' , 0.2 , 'rgb(103,169,207)' , 0.4 , 'rgb(209,229,240)' , 0.6 , 'rgb(253,219,199)' , 0.8 , 'rgb(239,138,98)' , 1 , 'rgb(178,24,43)' ] , // Adjust radius by zoom level 'heatmap-radius' : [ 'interpolate' , [ 'linear' ] , [ 'zoom' ] , 0 , 2 , 15 , 20 ] , // Decrease opacity at higher zoom levels 'heatmap-opacity' : [ 'interpolate' , [ 'linear' ] , [ 'zoom' ] , 7 , 1 , 15 , 0 ] } } ) ; // Add circle layer for individual points at high zoom map . addLayer ( { id : 'incidents-point' , type : 'circle' , source : 'incidents' , minzoom : 14 , paint : { 'circle-radius' : [ 'interpolate' , [ 'linear' ] , [ 'zoom' ] , 14 , 4 , 22 , 30 ] , 'circle-color' : '#ff4444' , 'circle-opacity' : 0.8 , 'circle-stroke-color' : '#fff' , 'circle-stroke-width' : 1 } } ) ; } ) ; Clustering (Point Density) Best for: Grouping nearby points, aggregated counts, large point datasets Pattern: Client-side clustering for visualization Clustering is a valuable point density visualization technique alongside heat maps. Use clustering when you want discrete grouping with exact counts rather than a continuous density visualization. map . on ( 'load' , ( ) => { // Add data source with clustering enabled map . addSource ( 'locations' , { type : 'geojson' , data : { type : 'FeatureCollection' , features : [ // Your point features ] } , cluster : true , clusterMaxZoom : 14 , // Max zoom to cluster points clusterRadius : 50 // Radius of each cluster (default 50) } ) ; // Clustered circles - styled by point count map . addLayer ( { id : 'clusters' , type : 'circle' , source : 'locations' , filter : [ 'has' , 'point_count' ] , paint : { // Color clusters by count (step expression) 'circle-color' : [ 'step' , [ 'get' , 'point_count' ] , '#51bbd6' , 10 , '#f1f075' , 30 , '#f28cb1' ] , // Size clusters by count 'circle-radius' : [ 'step' , [ 'get' , 'point_count' ] , 20 , 10 , 30 , 30 , 40 ] } } ) ; // Cluster count labels map . addLayer ( { id : 'cluster-count' , type : 'symbol' , source : 'locations' , filter : [ 'has' , 'point_count' ] , layout : { 'text-field' : [ 'get' , 'point_count_abbreviated' ] , 'text-font' : [ 'DIN Offc Pro Medium' , 'Arial Unicode MS Bold' ] , 'text-size' : 12 } } ) ; // Individual unclustered points map . addLayer ( { id : 'unclustered-point' , type : 'circle' , source : 'locations' , filter : [ '!' , [ 'has' , 'point_count' ] ] , paint : { 'circle-color' : '#11b4da' , 'circle-radius' : 6 , 'circle-stroke-width' : 1 , 'circle-stroke-color' : '#fff' } } ) ; // Click handler to expand clusters map . on ( 'click' , 'clusters' , ( e ) => { const features = map . queryRenderedFeatures ( e . point , { layers : [ 'clusters' ] } ) ; const clusterId = features [ 0 ] . properties . cluster_id ; // Get cluster expansion zoom map . getSource ( 'locations' ) . getClusterExpansionZoom ( clusterId , ( err , zoom ) => { if ( err ) return ; map . easeTo ( { center : features [ 0 ] . geometry . coordinates , zoom : zoom } ) ; } ) ; } ) ; // Change cursor on hover map . on ( 'mouseenter' , 'clusters' , ( ) => { map . getCanvas ( ) . style . cursor = 'pointer' ; } ) ; map . on ( 'mouseleave' , 'clusters' , ( ) => { map . getCanvas ( ) . style . cursor = '' ; } ) ; } ) ; Advanced: Custom Cluster Properties map . addSource ( 'locations' , { type : 'geojson' , data : data , cluster : true , clusterMaxZoom : 14 , clusterRadius : 50 , // Calculate custom cluster properties clusterProperties : { // Sum total values sum : [ '+' , [ 'get' , 'value' ] ] , // Calculate max value max : [ 'max' , [ 'get' , 'value' ] ] } } ) ; // Use custom properties in styling 'circle-color' : [ 'interpolate' , [ 'linear' ] , [ 'get' , 'sum' ] , 0 , '#51bbd6' , 100 , '#f1f075' , 1000 , '#f28cb1' ] ; When to use clustering vs heatmaps: Use Case Clustering Heatmap Visual style Discrete circles with counts Continuous gradient Interaction Click to expand/zoom Visual density only Data granularity Exact counts visible Approximate density Best for Store locators, event listings Crime maps, incident areas Performance with many points Excellent (groups automatically) Good User understanding Clear (numbered clusters) Intuitive (heat analogy) 3D Extrusions Best for: Building heights, elevation data, volumetric representation Pattern: Extrude polygons based on data Note: The example below works with classic styles only ( streets-v12 , dark-v11 , light-v11 , etc.). The Mapbox Standard style includes 3D buildings with much greater detail by default. map . on ( 'load' , ( ) => { // Insert the layer beneath any symbol layer for proper ordering const layers = map . getStyle ( ) . layers ; const labelLayerId = layers . find ( ( layer ) => layer . type === 'symbol' && layer . layout [ 'text-field' ] ) . id ; // Add 3D buildings from basemap map . addLayer ( { id : 'add-3d-buildings' , source : 'composite' , 'source-layer' : 'building' , filter : [ '==' , 'extrude' , 'true' ] , type : 'fill-extrusion' , minzoom : 15 , paint : { 'fill-extrusion-color' : '#aaa' , // Smoothly transition height on zoom 'fill-extrusion-height' : [ 'interpolate' , [ 'linear' ] , [ 'zoom' ] , 15 , 0 , 15.05 , [ 'get' , 'height' ] ] , 'fill-extrusion-base' : [ 'interpolate' , [ 'linear' ] , [ 'zoom' ] , 15 , 0 , 15.05 , [ 'get' , 'min_height' ] ] , 'fill-extrusion-opacity' : 0.6 } } , labelLayerId ) ; // Enable pitch and bearing for 3D view map . setPitch ( 45 ) ; map . setBearing ( - 17.6 ) ; } ) ; Using Custom Data Source: map . on ( 'load' , ( ) => { // Add your own buildings data map . addSource ( 'custom-buildings' , { type : 'geojson' , data : 'https://example.com/buildings.geojson' } ) ; // Add 3D buildings layer map . addLayer ( { id : '3d-custom-buildings' , type : 'fill-extrusion' , source : 'custom-buildings' , paint : { // Height in meters 'fill-extrusion-height' : [ 'get' , 'height' ] , // Base height if building on terrain 'fill-extrusion-base' : [ 'get' , 'base_height' ] , // Color by building type or height 'fill-extrusion-color' : [ 'interpolate' , [ 'linear' ] , [ 'get' , 'height' ] , 0 , '#fafa6e' , 50 , '#eca25b' , 100 , '#e64a45' , 200 , '#a63e3e' ] , 'fill-extrusion-opacity' : 0.9 } } ) ; } ) ; Data-Driven 3D Heights: // Population density visualization 'fill-extrusion-height' : [ 'interpolate' , [ 'linear' ] , [ 'get' , 'density' ] , 0 , 0 , 1000 , 500 , // 1000 people/sq mi = 500m height 10000 , 5000 ] // Revenue visualization (scale for visibility) 'fill-extrusion-height' : [ '*' , [ 'get' , 'revenue' ] , 0.001 // Scale factor ] Circle/Bubble Maps Best for: Point data with magnitude, proportional symbols Pattern: Size circles based on data values map . on ( 'load' , ( ) => { map . addSource ( 'earthquakes' , { type : 'geojson' , data : 'https://example.com/earthquakes.geojson' } ) ; // Size by magnitude, color by depth map . addLayer ( { id : 'earthquakes' , type : 'circle' , source : 'earthquakes' , paint : { // Size circles by magnitude 'circle-radius' : [ 'interpolate' , [ 'exponential' , 2 ] , [ 'get' , 'mag' ] , 0 , 2 , 5 , 20 , 8 , 100 ] , // Color by depth 'circle-color' : [ 'interpolate' , [ 'linear' ] , [ 'get' , 'depth' ] , 0 , '#ffffcc' , 50 , '#a1dab4' , 100 , '#41b6c4' , 200 , '#2c7fb8' , 300 , '#253494' ] , 'circle-stroke-color' : '#ffffff' , 'circle-stroke-width' : 1 , 'circle-opacity' : 0.75 } } ) ; // Add popup on click map . on ( 'click' , 'earthquakes' , ( e ) => { const props = e . features [ 0 ] . properties ; new mapboxgl . Popup ( ) . setLngLat ( e . features [ 0 ] . geometry . coordinates ) . setHTML (

Magnitude ${ props . mag }

Depth: ${ props . depth } km

Time: ${ new Date ( props . time ) . toLocaleString ( ) }

) . addTo ( map ) ; } ) ; } ) ; Line Data Visualization Best for: Routes, flows, connections, networks Pattern: Style lines based on data map . on ( 'load' , ( ) => { map . addSource ( 'traffic' , { type : 'geojson' , data : 'https://example.com/traffic.geojson' } ) ; // Traffic flow with data-driven styling map . addLayer ( { id : 'traffic-lines' , type : 'line' , source : 'traffic' , paint : { // Width by traffic volume 'line-width' : [ 'interpolate' , [ 'exponential' , 2 ] , [ 'get' , 'volume' ] , 0 , 1 , 1000 , 5 , 10000 , 15 ] , // Color by speed (congestion) 'line-color' : [ 'interpolate' , [ 'linear' ] , [ 'get' , 'speed' ] , 0 , '#d73027' , // Red: stopped 15 , '#fc8d59' , // Orange: slow 30 , '#fee08b' , // Yellow: moderate 45 , '#d9ef8b' , // Light green: good 60 , '#91cf60' , // Green: free flow 75 , '#1a9850' ] , 'line-opacity' : 0.8 } } ) ; } ) ; Animated Data Visualizations 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 ] ) ; } Performance Optimization Vector Tiles vs GeoJSON When to use each: Data Size Format Reason < 1 MB GeoJSON Simple, no processing needed 1-10 MB GeoJSON or Vector Tiles Consider data update frequency

10 MB Vector Tiles Better performance, progressive loading Vector Tile Pattern: map . addSource ( 'large-dataset' , { type : 'vector' , tiles : [ 'https://example.com/tiles/{z}/{x}/{y}.mvt' ] , minzoom : 0 , maxzoom : 14 } ) ; map . addLayer ( { id : 'data-layer' , type : 'fill' , source : 'large-dataset' , 'source-layer' : 'data-layer-name' , // Layer name in the tileset paint : { 'fill-color' : [ 'get' , 'color' ] , 'fill-opacity' : 0.7 } } ) ; Feature State for Dynamic Styling Pattern: Update styling without modifying geometry map . on ( 'load' , ( ) => { map . addSource ( 'states' , { type : 'geojson' , data : statesData , generateId : true // Important for feature state } ) ; map . addLayer ( { id : 'states' , type : 'fill' , source : 'states' , paint : { 'fill-color' : [ 'case' , [ 'boolean' , [ 'feature-state' , 'hover' ] , false ] , '#ff0000' , // Hover color '#3b9ddd' // Default color ] } } ) ; let hoveredStateId = null ; // Update feature state on hover map . on ( 'mousemove' , 'states' , ( e ) => { if ( e . features . length

0 ) { if ( hoveredStateId !== null ) { map . setFeatureState ( { source : 'states' , id : hoveredStateId } , { hover : false } ) ; } hoveredStateId = e . features [ 0 ] . id ; map . setFeatureState ( { source : 'states' , id : hoveredStateId } , { hover : true } ) ; } } ) ; map . on ( 'mouseleave' , 'states' , ( ) => { if ( hoveredStateId !== null ) { map . setFeatureState ( { source : 'states' , id : hoveredStateId } , { hover : false } ) ; } hoveredStateId = null ; } ) ; } ) ; Filtering Large Datasets Pattern: Filter data client-side for performance map . on ( 'load' , ( ) => { map . addSource ( 'all-data' , { type : 'geojson' , data : largeDataset } ) ; map . addLayer ( { id : 'filtered-data' , type : 'circle' , source : 'all-data' , filter : [ '>=' , [ 'get' , 'value' ] , 50 ] , // Only show values >= 50 paint : { 'circle-radius' : 6 , 'circle-color' : '#ff4444' } } ) ; // Update filter dynamically function updateFilter ( minValue ) { map . setFilter ( 'filtered-data' , [ '>=' , [ 'get' , 'value' ] , minValue ] ) ; } // Slider for dynamic filtering document . getElementById ( 'filter-slider' ) . addEventListener ( 'input' , ( e ) => { updateFilter ( parseFloat ( e . target . value ) ) ; } ) ; } ) ; Progressive Loading Pattern: Load data in chunks as needed // Helper to check if feature is in bounds function isFeatureInBounds ( feature , bounds ) { const coords = feature . geometry . coordinates ; // Handle different geometry types if ( feature . geometry . type === 'Point' ) { return bounds . contains ( coords ) ; } else if ( feature . geometry . type === 'LineString' ) { return coords . some ( ( coord ) => bounds . contains ( coord ) ) ; } else if ( feature . geometry . type === 'Polygon' ) { return coords [ 0 ] . some ( ( coord ) => bounds . contains ( coord ) ) ; } return false ; } const bounds = map . getBounds ( ) ; const visibleData = allData . features . filter ( ( feature ) => isFeatureInBounds ( feature , bounds ) ) ; map . getSource ( 'data-source' ) . setData ( { type : 'FeatureCollection' , features : visibleData } ) ; // Reload on map move with debouncing let updateTimeout ; map . on ( 'moveend' , ( ) => { clearTimeout ( updateTimeout ) ; updateTimeout = setTimeout ( ( ) => { const bounds = map . getBounds ( ) ; const visibleData = allData . features . filter ( ( feature ) => isFeatureInBounds ( feature , bounds ) ) ; map . getSource ( 'data-source' ) . setData ( { type : 'FeatureCollection' , features : visibleData } ) ; } , 150 ) ; } ) ; Legends and UI Controls Color Scale Legend < div class = " legend "

< h4

Population Density </ h4

< div class = " legend-scale "

< div class = " legend-item "

< span class = " legend-color " style = " background :

f0f9ff

; "

</ span

< span

0-500 </ span

</ div

< div class = " legend-item "

< span class = " legend-color " style = " background :

7fcdff

; "

</ span

< span

500-1000 </ span

</ div

< div class = " legend-item "

< span class = " legend-color " style = " background :

0080ff

; "

</ span

< span

1000-5000 </ span

</ div

< div class = " legend-item "

< span class = " legend-color " style = " background :

001f5c

; "

</ span

< span

5000+ </ span

</ div

</ div

</ div

< style

.legend { position : absolute ; bottom : 30 px ; right : 10 px ; background : white ; padding : 10 px ; border-radius : 3 px ; box-shadow : 0 1 px 2 px rgba ( 0 , 0 , 0 , 0.1 ) ; font-family : Arial , sans-serif ; font-size : 12 px ; } .legend h4 { margin : 0 0 10 px 0 ; font-size : 14 px ; } .legend-item { display : flex ; align-items : center ; margin-bottom : 5 px ; } .legend-color { width : 20 px ; height : 20 px ; margin-right : 10 px ; border : 1 px solid

ccc

; } </ style

Interactive Data Inspector map . on ( 'click' , 'data-layer' , ( e ) => { const feature = e . features [ 0 ] ; const properties = feature . properties ; // Build properties table const propsTable = Object . entries ( properties ) . map ( ( [ key , value ] ) => `

${ key } : ${ value }

) . join ( '' ) ; new mapboxgl . Popup ( ) . setLngLat ( e . lngLat ) . setHTML (

Feature Details

${ propsTable }

` ) . addTo ( map ) ; } ) ; Best Practices Color Accessibility // Use ColorBrewer scales for accessibility // https://colorbrewer2.org/ // Good: Sequential (single hue) const sequentialScale = [ '#f0f9ff' , '#bae4ff' , '#7fcdff' , '#0080ff' , '#001f5c' ] ; // Good: Diverging (two hues) const divergingScale = [ '#d73027' , '#fc8d59' , '#fee08b' , '#d9ef8b' , '#91cf60' , '#1a9850' ] ; // Good: Qualitative (distinct categories) const qualitativeScale = [ '#e41a1c' , '#377eb8' , '#4daf4a' , '#984ea3' , '#ff7f00' ] ; // Avoid: Red-green for color-blind accessibility // Use: Blue-orange or purple-green instead Data Preprocessing // Calculate statistical breaks for choropleth // Using classybrew library (npm install classybrew) import classybrew from 'classybrew' ; function calculateJenksBreaks ( values , numClasses ) { const brew = new classybrew ( ) ; brew . setSeries ( values ) ; brew . setNumClasses ( numClasses ) ; brew . classify ( 'jenks' ) ; return brew . getBreaks ( ) ; } // Normalize data for better visualization function normalizeData ( features , property ) { const values = features . map ( ( f ) => f . properties [ property ] ) ; const max = Math . max ( ... values ) ; const min = Math . min ( ... values ) ; const range = max - min ; // Handle case where all values are the same if ( range === 0 ) { return features . map ( ( feature ) => ( { ... feature , properties : { ... feature . properties , normalized : 0.5 } } ) ) ; } return features . map ( ( feature ) => ( { ... feature , properties : { ... feature . properties , normalized : ( feature . properties [ property ] - min ) / range } } ) ) ; } Error Handling // Handle missing or invalid data map . on ( 'load' , ( ) => { map . addSource ( 'data' , { type : 'geojson' , data : dataUrl } ) ; map . addLayer ( { id : 'data-viz' , type : 'fill' , source : 'data' , paint : { 'fill-color' : [ 'case' , [ 'has' , 'value' ] , // Check if property exists [ 'interpolate' , [ 'linear' ] , [ 'get' , 'value' ] , 0 , '#f0f0f0' , 100 , '#0080ff' ] , '#cccccc' // Default color for missing data ] } } ) ; // Handle source errors map . on ( 'error' , ( e ) => { if ( e . source === 'data' ) { console . error ( 'Failed to load data:' , e ) ; showNotification ( 'Unable to load visualization data' ) ; } } ) ; } ) ; Common Use Cases Election Results Map map . addLayer ( { id : 'election-results' , type : 'fill' , source : 'districts' , paint : { 'fill-color' : [ 'match' , [ 'get' , 'winner' ] , 'democrat' , '#3b82f6' , 'republican' , '#ef4444' , 'independent' , '#a855f7' , '#94a3b8' // No data ] , 'fill-opacity' : [ 'interpolate' , [ 'linear' ] , [ 'get' , 'margin' ] , 0 , 0.3 , // Close race: light 20 , 0.9 // Landslide: dark ] } } ) ; COVID-19 Case Map map . addLayer ( { id : 'covid-cases' , type : 'fill' , source : 'counties' , paint : { 'fill-color' : [ 'step' , [ '/' , [ 'get' , 'cases' ] , [ 'get' , 'population' ] ] , // Cases per capita '#ffffb2' , 0.001 , '#fed976' , 0.005 , '#feb24c' , 0.01 , '#fd8d3c' , 0.02 , '#fc4e2a' , 0.05 , '#e31a1c' , 0.1 , '#b10026' ] } } ) ; Real Estate Price Heatmap map . addLayer ( { id : 'real-estate' , type : 'circle' , source : 'properties' , paint : { 'circle-radius' : [ 'interpolate' , [ 'exponential' , 2 ] , [ 'get' , 'price' ] , 100000 , 5 , 1000000 , 20 , 10000000 , 50 ] , 'circle-color' : [ 'interpolate' , [ 'linear' ] , [ 'get' , 'price_per_sqft' ] , 0 , '#ffffcc' , 200 , '#a1dab4' , 400 , '#41b6c4' , 600 , '#2c7fb8' , 800 , '#253494' ] , 'circle-opacity' : 0.6 , 'circle-stroke-color' : '#ffffff' , 'circle-stroke-width' : 1 } } ) ; Resources Mapbox Expression Reference ColorBrewer - Color scales for maps Turf.js - Spatial analysis Simple Statistics - Data classification Data Visualization Tutorials

返回排行榜