Layer Styling: Visualization
The UDF builder displays data from your UDF on the map. You can customize the visual representation using the visualization icon located on the map.
Overview
Fused's visualization system is built on DeckGL, a powerful JavaScript framework for large-scale geospatial data visualizations. The system uses a three-layer architecture that automatically selects the appropriate rendering method based on your UDF output.
Layer Architecture
Every Visualization uses this JSON structure:
{
"tileLayer": {
"@@type": "TileLayer", // For viewport-optimized rendering
"minZoom": 0,
"maxZoom": 19,
"tileSize": 256,
"pickable": true
},
"rasterLayer": {
"@@type": "BitmapLayer", // For raster data (images, arrays)
"pickable": true
},
"vectorLayer": {
"@@type": "GeoJsonLayer", // For vector data (GeoDataFrames)
"stroked": true,
"filled": true,
"pickable": true
}
}
How Fused Selects Visualization Layers
Fused automatically determines which layer to use based on your UDF output:
UDF Output | Layer Used | Layer Type | Use Case |
---|---|---|---|
GeoDataFrame | vectorLayer | GeoJsonLayer | Polygons, lines, points with geometry |
DataFrame with H3 column | vectorLayer | H3HexagonLayer | Hexagonal grid data |
PNG/array/image | rasterLayer | BitmapLayer | Satellite imagery, heat maps |
Key Points:
- You define all three layers in your JSON, but Fused uses only the appropriate one based on your data type
- Unused layers are ignored automatically
- Visualizations use JSON configuration, not Python code
{
"tileLayer": { "@@type": "TileLayer", ... }, // Always defined
"rasterLayer": { "@@type": "BitmapLayer", ... }, // Used for images/arrays
"vectorLayer": { "@@type": "GeoJsonLayer", ... } // Used for GeoDataFrames
}
Default Map View
You can set a default map view that automatically centers on your data:

Presets
Use the "Preset" button to quickly generate styling configurations. You can always undo changes with Ctrl + Z
.
Layer Types
Vector: GeoJsonLayer
Use for GeoDataFrame
outputs or any DataFrame
with a geometry column. Supports dynamic styling based on data properties.
Expected DataFrame structure:
geometry |
---|
POLYGON(...) |
POLYGON(...) |
POLYGON(...) |
Expand to see Visualise code
{
"tileLayer": {
"@@type": "TileLayer",
"minZoom": 0,
"maxZoom": 19,
"tileSize": 256,
"pickable": true
},
"rasterLayer": {
"@@type": "BitmapLayer",
"pickable": true
},
"vectorLayer": {
"@@type": "GeoJsonLayer",
"stroked": true,
"filled": true,
"pickable": true,
"extruded": false,
"lineWidthMinPixels": 1,
"getFillColor": [100, 150, 200, 180],
"getLineColor": [50, 50, 50, 255]
}
}
Key Properties:
stroked
: Show polygon/line outlinesfilled
: Fill polygon interiorsextruded
: Enable 3D elevation effects (See 3D Visualization for more)pickable
: Enable hover tooltips and interaction
Example UDF
Vector: H3HexagonLayer
Use for DataFrame
outputs with H3 hexagon indices (no geometry column required).
Expected DataFrame structure:
hex | population | metric |
---|---|---|
8a2a1072b59ffff | 1250 | 45.2 |
8a2a1072b5bffff | 890 | 32.1 |
8a2a1072b5dffff | 2100 | 67.8 |
Expand to see Visualise code
{
"tileLayer": {
"@@type": "TileLayer",
"minZoom": 0,
"maxZoom": 19,
"tileSize": 256,
"pickable": true
},
"rasterLayer": {
"@@type": "BitmapLayer",
"pickable": true
},
"vectorLayer": {
"@@type": "H3HexagonLayer",
"stroked": true,
"filled": true,
"pickable": true,
"extruded": false,
"opacity": 1,
"coverage": 0.9,
"lineWidthMinPixels": 5,
"getHexagon": "@@=properties.hex",
"getFillColor": [255, 165, 0, 180],
"getLineColor": [200, 200, 200, 255]
}
}
Required Property:
getHexagon
: Specify the DataFrame column containing H3 indices (e.g.,"@@=properties.hex"
)
Example UDF
Raster: BitmapLayer
Use for raster outputs like satellite imagery, elevation models, or PNG files.
Expected UDF output:
Output Type | Description | Example |
---|---|---|
numpy array | 3D array (height, width, channels) | (256, 256, 3) RGB image |
PIL Image | PNG/JPEG image object | Image.open('satellite.png') |
bytes | Encoded image data | PNG/JPEG bytes |
Expand to see Visualise code
{
"tileLayer": {
"@@type": "TileLayer",
"minZoom": 0,
"maxZoom": 19,
"tileSize": 256,
"pickable": true
},
"rasterLayer": {
"@@type": "BitmapLayer",
"pickable": true
},
"vectorLayer": {
"@@type": "GeoJsonLayer",
"pickable": true
}
}
Example UDF
Color Styling Methods (getFillColor
)
There are four ways to style colors in Fused. Each method serves different use cases:
1. Hardcoded Colors
Use fixed RGB/RGBA values for uniform styling.

Expected DataFrame structure:
geometry | any_column |
---|---|
POLYGON(...) | any_value |
POLYGON(...) | any_value |
Note: Column values don't affect styling - all features get the same color.
Expand to see Visualise code
{
"tileLayer": {
"@@type": "TileLayer",
"minZoom": 0,
"maxZoom": 19,
"tileSize": 256,
"pickable": true
},
"rasterLayer": {
"@@type": "BitmapLayer",
"pickable": true
},
"vectorLayer": {
"@@type": "GeoJsonLayer",
"stroked": true,
"filled": true,
"pickable": true,
"lineWidthMinPixels": 1,
"getFillColor": [20, 200, 200, 100],
"getLineColor": [50, 50, 50, 255]
}
}
When to use: Simple visualizations, proof of concepts, or when all features should look identical.
2. Property-Based Colors
Use data column values to calculate colors dynamically.

Expected DataFrame structure:
geometry | value | height |
---|---|---|
POLYGON(...) | 5 | 25 |
POLYGON(...) | 8 | 40 |
POLYGON(...) | 3 | 15 |
Note: Numerical columns are used directly in color calculations.
Expand to see Visualise code
{
"tileLayer": {
"@@type": "TileLayer",
"minZoom": 0,
"maxZoom": 19,
"tileSize": 256,
"pickable": true
},
"rasterLayer": {
"@@type": "BitmapLayer",
"pickable": true
},
"vectorLayer": {
"@@type": "GeoJsonLayer",
"stroked": true,
"filled": true,
"pickable": true,
"extruded": true,
"getElevation": "@@=properties.value * 10",
"lineWidthMinPixels": 1,
"getFillColor": "@@=[properties.value * 50, properties.value * 30, properties.value * 2]",
"getLineColor": [50, 50, 50, 255]
}
}
When to use: When you want colors to directly reflect data values with custom calculations.
Example UDF
3. Conditional Colors with hasProp
Handle missing data gracefully with fallback colors.
Expected DataFrame structure:
geometry | value | status |
---|---|---|
POLYGON(...) | 25 | active |
POLYGON(...) | NaN | inactive |
POLYGON(...) | 15 | active |
Note: Can handle missing/null values with fallback styling.
Expand to see Visualise code
{
"tileLayer": {
"@@type": "TileLayer",
"minZoom": 0,
"maxZoom": 19,
"tileSize": 256,
"pickable": true
},
"rasterLayer": {
"@@type": "BitmapLayer",
"pickable": true
},
"vectorLayer": {
"@@type": "GeoJsonLayer",
"stroked": true,
"filled": true,
"pickable": true,
"extruded": true,
"getElevation": {
"@@function": "hasProp",
"property": "value",
"present": "@@=properties.value",
"absent": 1
},
"lineWidthMinPixels": 1,
"getFillColor": {
"@@function": "hasProp",
"property": "value",
"present": "@@=[properties.value * 50, properties.value * 3, properties.value * 2]",
"absent": [255, 0, 255]
}
}
}
When to use: When your data might have missing values and you need reliable fallbacks.
Example UDF
Understanding hasProp
Function
The hasProp
function is a core Fused utility for conditional styling based on data property presence. It's the foundation for handling missing or null values gracefully in visualizations.
How it works:
"getFillColor": {
"@@function": "hasProp",
"property": "metric",
"present": "@@=[255, (1 - properties.metric/500) * 255, 0]",
"absent": [220, 255, 100]
}
Key Properties:
"@@function": "hasProp"
: Declares the conditional function"property": "column_name"
: Specifies which DataFrame column to check"present": expression
: Styling when the property exists and is not null"absent": value
: Fallback styling when the property is missing or null
Common Use Cases:
- Missing data: Handle null/NaN values in datasets
- Optional columns: Style features that may not have certain attributes
- Data validation: Provide visual feedback for incomplete records
- Progressive enhancement: Add styling layers based on data availability
Example with elevation:
"getElevation": {
"@@function": "hasProp",
"property": "height",
"present": "@@=properties.height * 10",
"absent": 1
}
This ensures buildings without height data still appear with a default elevation of 1 unit.
4. Category Colors
Use predefined color palettes for categorical data.

Expected DataFrame structure:
geometry | building_type | zone |
---|---|---|
POLYGON(...) | residential | A |
POLYGON(...) | commercial | B |
POLYGON(...) | industrial | A |
Note: Categorical columns with distinct values for color mapping.
Expand to see Visualise code
{
"tileLayer": {
"@@type": "TileLayer",
"minZoom": 0,
"maxZoom": 19,
"tileSize": 256,
"pickable": true
},
"rasterLayer": {
"@@type": "BitmapLayer",
"pickable": true
},
"vectorLayer": {
"@@type": "GeoJsonLayer",
"stroked": true,
"filled": true,
"pickable": true,
"lineWidthMinPixels": 1,
"getFillColor": {
"@@function": "colorCategories",
"attr": "building_type",
"domain": [
"residential",
"commercial",
"industrial",
"public"
],
"colors": "Bold"
}
}
}
When to use: Categorical data like building types, land use classifications, or administrative regions.
Note that unexpected behaviors may arise if too many domains are used.
Example UDF
5. Continuous Color Scales
Use gradient color scales for continuous numerical data.

Expected DataFrame structure:
hex | population | density |
---|---|---|
8a2a1072b59ffff | 1250 | 45.2 |
8a2a1072b5bffff | 890 | 32.1 |
8a2a1072b5dffff | 2100 | 67.8 |
Note: Numerical columns with values within the specified domain range.
Expand to see Visualise code
{
"tileLayer": {
"@@type": "TileLayer",
"minZoom": 0,
"maxZoom": 19,
"tileSize": 256,
"pickable": true
},
"rasterLayer": {
"@@type": "BitmapLayer",
"pickable": true
},
"vectorLayer": {
"@@type": "H3HexagonLayer",
"stroked": true,
"filled": true,
"pickable": true,
"extruded": true,
"opacity": 1,
"coverage": 0.9,
"lineWidthMinPixels": 5,
"getHexagon": "@@=properties.hex",
"getFillColor": {
"@@function": "colorContinuous",
"attr": "population",
"domain": [0, 10000],
"steps": 15,
"colors": "SunsetDark",
"nullColor": [184, 184, 184]
},
"getElevation": {
"@@function": "hasProp",
"property": "population",
"present": "@@=properties.population / 100",
"absent": 1
},
"elevationScale": 10
}
}
When to use: Continuous numerical data like population density, temperature, or elevation values.
Example UDF
6. Transparency & Opacity
Control the transparency of your visualizations for layering effects and visual hierarchy.
Vector Color Transparency:
"getFillColor": [255, 100, 100, 200], // RGBA: Red, Green, Blue, Alpha
"getLineColor": [50, 50, 50, 255] // Fully opaque outline
Opacity Guidelines:
- Good visibility: Use 180-255 for clearly visible features
- Semi-transparent: Use 100-179 for overlay effects
- Avoid very low opacity: Values below 100 make features barely visible
- Solid colors: Omit alpha channel for full opacity:
[255, 100, 100]
Examples:
// Good examples
"getFillColor": [255, 100, 100, 200], // Clearly visible red
"getFillColor": [100, 150, 200], // Solid blue (alpha defaults to 255)
// Poor examples
"getFillColor": [255, 255, 255, 25], // Too transparent, barely visible
"getFillColor": [100, 150, 200, 50] // Hard to see on most backgrounds
Raster Transparency:
- RGB images: Black pixels (0,0,0) are automatically transparent
- RGBA images: Use the 4th channel (alpha) for transparency control
Advanced Features
3D Visualization
Enable 3D effects by setting extruded: true
and using getElevation
:
Expected DataFrame structure:
geometry | height_value | floors |
---|---|---|
POLYGON(...) | 45 | 15 |
POLYGON(...) | 120 | 40 |
POLYGON(...) | 30 | 10 |
Note: Numerical columns used for building height/elevation in 3D view. Use "@@=properties.column_name"
syntax to reference height data.
Expand to see Visualise code
{
"tileLayer": {
"@@type": "TileLayer",
"minZoom": 0,
"maxZoom": 19,
"tileSize": 256,
"pickable": true
},
"rasterLayer": {
"@@type": "BitmapLayer",
"pickable": true
},
"vectorLayer": {
"@@type": "GeoJsonLayer",
"stroked": true,
"filled": true,
"pickable": true,
"extruded": true,
"getElevation": "@@=properties.height_value * 5",
"elevationScale": 10,
"getFillColor": [100, 150, 200, 180]
}
}
Hold Cmd
(macOS) or Ctrl
(Windows/Linux) while dragging to tilt the map view and see 3D effects.
Example UDF
Debug Layers (DebugTileLayer
)
Use DebugTileLayer
with custom loading and error states for development:
Expand to see Visualise code
{
"tileLayer": {
"@@type": "DebugTileLayer",
"minZoom": 0,
"maxZoom": 15,
"tileSize": 256,
"pickable": true
},
"rasterLayer": {
"@@type": "BitmapLayer",
"pickable": true
},
"vectorLayer": {
"@@type": "GeoJsonLayer",
"stroked": true,
"filled": true,
"pickable": true,
"extruded": true,
"lineWidthMinPixels": 1
},
"loadingLayer": {
"@@type": "GeoJsonLayer",
"stroked": true,
"filled": false,
"pickable": true,
"lineWidthMinPixels": 10,
"getLineColor": [25, 55, 0, 255]
},
"errorLayer": {
"@@type": "GeoJsonLayer",
"stroked": true,
"filled": true,
"pickable": true,
"lineWidthMinPixels": 10,
"getLineColor": [255, 255, 0, 255],
"getFillColor": [255, 20, 255, 40]
}
}
Performance Optimization
- Zoom levels: Set appropriate
minZoom
andmaxZoom
to control data loading - Line width units: Use
lineWidthUnits: "pixels"
for consistent appearance across zoom levels - Tile size: Adjust
tileSize
for optimal performance with your data density
FAQ: Debugging your visualizations
Why is my vector layer not colorized?
Let's take the example of a UDF that returns a GeoDataFrame
with hex
values:
@fused.udf()
def udf(
bounds: fused.types.Bounds = None,
):
# get_hex() is a non-important function for this demo that gives us US counties
df_hex = get_hex(gdf, hex_res)
df_hex['state_id'] = [id[:2] for id in df_hex["GEOID"]]
return df_hex
And our visualization JSON looks like this:
{
"hexLayer": {
"@@type": "H3HexagonLayer",
"filled": true,
"pickable": true,
"extruded": false,
"getHexagon": "@@=properties.hex",
"getFillColor": {
"@@function": "colorContinuous",
"attr": "state_id",
"domain": [
0,
50
],
"steps": 2,
"colors": "Magenta"
}
}
}
You should make sure:
hexLayer > getFillColor > attr
is set to a column that exists in theGeoDataFrame
(in this casestate_id
)- Make sure your
attr
column is in eitherint
orfloat
type, not instr
. In this case we should caststate_id
toint
:
@fused.udf()
def udf(
bounds: fused.types.Bounds = None,
):
df_hex = get_hex(gdf, hex_res)
df_hex['state_id'] = [id[:2] for id in df_hex["GEOID"]]
df_hex['state_id'] = df_hex['state_id'].astype(int)
return df_hex
- Making sure your values are within the correct domain (
hexLayer > getFillColor > domain
). In our case, we're showing US States, so the domain should be[0, 50]
.
Why are my 3D buildings not showing?
Common 3D visualization issues:
-
Wrong elevation function: Don't use distance functions like
haversine
for elevation// ❌ Wrong - haversine is for distances
"getElevation": {
"@@function": "haversine",
"attr": "HEIGHT"
}
// ✅ Correct - direct property reference
"getElevation": "@@=properties.HEIGHT" -
Missing required properties: Ensure you have both
extruded: true
and a height column"vectorLayer": {
"@@type": "GeoJsonLayer",
"extruded": true,
"getElevation": "@@=properties.height_value",
"elevationScale": 10
} -
Low transparency: Avoid very low alpha values that make buildings invisible
// ❌ Too transparent (barely visible)
"getFillColor": [255, 255, 255, 25]
// ✅ Good visibility
"getFillColor": [100, 150, 200, 180]
Why is my layer not appearing at all?
Common visibility issues:
- Pickable setting: Check that
pickable: true
is set for interactivity - Data type mismatch: Verify your UDF returns the expected data type (
GeoDataFrame
for vector,array
for raster) - Zoom level issues: Ensure zoom levels (
minZoom
,maxZoom
) include your current map zoom level - Missing geometry: GeoJsonLayer requires a geometry column in your GeoDataFrame
- Incorrect layer type: Make sure you're using the right
@@type
for your data (GeoJsonLayer vs H3HexagonLayer)
For more details on DeckGL properties, see the DeckGL documentation.