deck.gl visualization library for marimo notebooks — interactive maps with 33 layer types
Project description
deckgl-marimo
Interactive deck.gl visualization library for marimo notebooks. Render GPU-accelerated maps with 33 layer types, powered by MapLibre GL and anywidget.
Features
- 12 deck.gl layer types — scatter plots, hexagonal bins, heatmaps, arcs, paths, polygons, GeoJSON, 3D columns, lines, point clouds, and more
- Binary data transfer — bypass JSON serialization for large datasets (30x faster, 4x smaller payloads)
- Multi-layer maps — compose multiple layers on a single map
- Standalone layers — display any layer directly without explicit map setup
- Marimo-native reactivity — bind layer properties to sliders, dropdowns, and other widgets
- Performance metrics — built-in FPS counter and frame time tracking via
perf_metricstraitlet - Color scales — map numeric columns to color palettes (viridis, plasma, etc.) with linear or log scaling
- Callable accessors — pass Python functions to any
get_*accessor for custom per-row logic - Multiple data sources — pandas, polars, geopandas, DuckDB, GeoJSON dicts, and URLs
- Authenticated data loading — pass HTTP headers, API keys, or credentials for remote data sources
- Fully offline — all JavaScript bundled in the package, no CDN dependencies
- Viewport readback — read the current map center, zoom, pitch, and bearing from Python
- Click & hover events — inspect picked objects reactively in downstream cells
Installation
pip install deckgl-marimo
# or
uv add deckgl-marimo
Quickstart
Standalone layer
import marimo as mo
import deckgl_marimo as dgl
layer = dgl.ScatterplotLayer(
data=df,
get_position=["longitude", "latitude"],
get_fill_color=[255, 140, 0],
get_radius="population",
radius_scale=10,
)
# Displays a map with one layer
widget = mo.ui.anywidget(layer)
Multi-layer map
m = dgl.Map(
layers=[
dgl.ScatterplotLayer(
data=cities_df,
get_position=["lon", "lat"],
get_fill_color=[255, 140, 0],
get_radius=5,
radius_min_pixels=3,
),
dgl.ArcLayer(
data=flights_df,
get_source_position=["src_lon", "src_lat"],
get_target_position=["dst_lon", "dst_lat"],
get_source_color=[0, 128, 255],
get_target_color=[255, 0, 128],
),
],
basemap="dark-matter",
center=(-98.5, 39.8),
zoom=4,
pitch=45,
)
widget = mo.ui.anywidget(m)
Reactive controls
Important: Create the
Mapwidget in a cell that does not depend on slider values. Update layers by assigning tomap_widget.layer_specsin a separate cell. This keeps the map instance stable — sliders update layers via traitlet sync instead of recreating the entire map, which would cause tile reloads and a black screen flash.
# Cell 1 — sliders
radius = mo.ui.slider(200, 5000, value=1000, label="Radius")
# Cell 2 — create map widget (NO slider deps — stable, never re-executes)
map_widget = dgl.Map(basemap="dark-matter", center=(-1.4, 52.2), zoom=6, pitch=40)
widget = mo.ui.anywidget(map_widget)
# Cell 3 — display
widget
# Cell 4 — update layers reactively (re-executes when slider changes)
map_widget.layer_specs = [
dgl.HexagonLayer(
data=df,
get_position=["lon", "lat"],
radius=radius.value,
extruded=True,
elevation_scale=250,
).to_spec()
]
# Cell 5 — viewport readback
vp = widget.value.get("viewport", {})
mo.md(f"Zoom: {vp.get('zoom', 'N/A'):.1f}")
Color scales
Map a numeric column to interpolated colors using ColorScale. Supports named palettes, custom color ramps, and linear or logarithmic scaling.
# Named palette
layer = dgl.ScatterplotLayer(
data=df,
get_position=["lon", "lat"],
get_fill_color=dgl.ColorScale("temperature", palette="viridis"),
)
# Two-color ramp with log scale
layer = dgl.ScatterplotLayer(
data=df,
get_position=["lon", "lat"],
get_fill_color=dgl.ColorScale("population", colors=["blue", "red"], scale="log"),
)
Available palettes: viridis, plasma, inferno, magma, cividis, coolwarm, RdBu, spectral, turbo
ColorScale parameters:
| Parameter | Default | Description |
|---|---|---|
column |
(required) | Numeric column name to map |
palette |
— | Named palette (mutually exclusive with colors) |
colors |
— | List of 2+ colors: names ("blue"), hex ("#FF0000"), or RGB ([255, 0, 0]) |
domain |
auto | (min, max) value range; auto-detected from data if omitted |
scale |
"linear" |
"linear" or "log" |
alpha |
255 |
Alpha channel (0–255) for all output colors |
Callable accessors
Pass a Python function to any get_* accessor for full control over per-row values:
layer = dgl.ScatterplotLayer(
data=df,
get_position=["lon", "lat"],
get_fill_color=lambda row: [
int(row["temperature"] * 2.55),
50,
255 - int(row["temperature"] * 2.55),
200,
],
)
Both ColorScale and callable accessors work with the binary data path (use_binary=True) — colors are resolved in Python and packed into the binary buffer automatically.
DuckDB integration
import duckdb
rel = duckdb.sql("SELECT lon, lat, value FROM 'data.parquet' WHERE value > 100")
layer = dgl.ScatterplotLayer(data=rel, get_position=["lon", "lat"])
Binary data for large datasets
For datasets with 100k+ rows, binary data transfer bypasses JSON serialization entirely, sending typed arrays directly to the GPU via deck.gl's native binary format.
Using use_binary=True (automatic packing from list-of-dicts):
layer = dgl.ScatterplotLayer(
data=large_df.to_dict("records"),
get_position=["lon", "lat"],
get_fill_color="color",
get_radius="radius",
use_binary=True,
)
Using pre-built numpy arrays (fastest — zero dict iteration):
import numpy as np
from deckgl_marimo._binary import pack_binary
# Prepare arrays
positions = np.column_stack([lons, lats]).astype(np.float32)
colors = np.array(color_data, dtype=np.uint8) # (n, 4)
# Create layer spec (no data — binary provides it)
layer = dgl.ScatterplotLayer(
get_fill_color=[255, 140, 0], # constant props still go in spec
radius_min_pixels=2,
use_binary=True,
)
spec = layer.to_spec()
# Pack binary buffer
meta, buf = pack_binary(
n=len(positions),
attributes={
"getPosition": (positions, "float32", 2),
"getFillColor": (colors, "uint8", 4),
},
)
meta["id"] = spec["id"]
# Send to map
map_widget.binary_metadata = {"layers": [meta]}
map_widget.binary_data = buf
map_widget.layer_specs = [spec]
Performance at 200k polygons:
| Mode | Serialization | Payload | Speedup |
|---|---|---|---|
| JSON | 1,167 ms | 62 MB | — |
| Binary | 39 ms | 14.5 MB | 30x faster, 4.3x smaller |
Binary data is supported on: ScatterplotLayer, PolygonLayer, PathLayer, ArcLayer, LineLayer, ColumnLayer, and PointCloudLayer.
Performance metrics
The Map widget includes a built-in FPS counter that reports metrics back to Python:
map_widget = dgl.Map(basemap="dark-matter", center=(0, 0), zoom=1)
widget = mo.ui.anywidget(map_widget)
# Read performance metrics (updated every 500ms)
perf = widget.value.get("perf_metrics", {})
fps = perf.get("fps") # frames per second
frame_time = perf.get("frameTimeAvg") # ms per frame
Authenticated remote data
Any layer that loads data from a URL supports custom HTTP headers via
fetch_headers, or full control over the fetch request via load_options.
# Bearer token
layer = dgl.GeoJsonLayer(
data="https://secure-api.example.com/data.geojson",
fetch_headers={"Authorization": "Bearer my-token"},
get_fill_color=[0, 180, 230, 160],
)
# API key
layer = dgl.GeoJsonLayer(
data="https://api.example.com/features",
fetch_headers={"X-API-Key": "abc123"},
)
# Full fetch control (mTLS / CORS / custom options)
layer = dgl.GeoJsonLayer(
data="https://internal.example.com/data.geojson",
load_options={
"fetch": {
"credentials": "include",
"mode": "cors",
"headers": {"Authorization": "Bearer token"},
}
},
)
Both parameters are available on all layer types via BaseLayer. When
fetch_headers and load_options both specify headers, the load_options
headers take precedence.
Available layers
Fully tested (12)
| Layer | Use case | Binary support |
|---|---|---|
ScatterplotLayer |
Point data | Yes |
GeoJsonLayer |
Polygons, lines, points from GeoJSON/GeoDataFrame | — |
ArcLayer |
Origin-destination flows | Yes |
PathLayer |
Routes, trajectories | Yes |
PolygonLayer |
Filled regions | Yes |
IconLayer |
Marker icons | — |
TextLayer |
Labels | — |
ColumnLayer |
3D bars on map | Yes |
HexagonLayer |
Hexagonal binning | — |
HeatmapLayer |
Density visualization | — |
LineLayer |
Straight lines between point pairs | Yes |
PointCloudLayer |
3D point clouds (LiDAR, etc.) | Yes |
LineLayer and PointCloudLayer are newly exported experimental layers.
Experimental (21+)
All additional deck.gl layers are available as experimental stubs via deckgl_marimo.layers:
from deckgl_marimo.layers import TripsLayer, MVTLayer, H3HexagonLayer, ContourLayer
# ... and more
Basemaps
dgl.Basemaps.list_available()
# ['bright', 'dark', 'dark-matter', 'embedded', 'liberty', 'light', 'none', 'osm', 'positron', 'voyager']
# Use any MapLibre-compatible style URL
dgl.Map(basemap="https://my-tileserver.example.com/style.json")
Troubleshooting
Content-Length errors in the marimo console
This is fixed in Marimo >= 0.22.0 please update to that.
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file deckgl_marimo-0.6.0.tar.gz.
File metadata
- Download URL: deckgl_marimo-0.6.0.tar.gz
- Upload date:
- Size: 875.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0cf364564944daae36f0ac4508fadb7e513a5870f6c86811b8cb5f12513d4825
|
|
| MD5 |
adda959abfe50a9fe76351a8760ee19e
|
|
| BLAKE2b-256 |
dcc5279fa4c859cdab7304c38bdecf068814f24404a2971d367106f14a3f1d10
|
Provenance
The following attestation bundles were made for deckgl_marimo-0.6.0.tar.gz:
Publisher:
publish.yml on kihaji/deckgl-marimo
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
deckgl_marimo-0.6.0.tar.gz -
Subject digest:
0cf364564944daae36f0ac4508fadb7e513a5870f6c86811b8cb5f12513d4825 - Sigstore transparency entry: 1360168564
- Sigstore integration time:
-
Permalink:
kihaji/deckgl-marimo@f44a2bc6b12de9bdba3e48c10994b09e423c5c8a -
Branch / Tag:
refs/tags/0.6.0 - Owner: https://github.com/kihaji
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f44a2bc6b12de9bdba3e48c10994b09e423c5c8a -
Trigger Event:
release
-
Statement type:
File details
Details for the file deckgl_marimo-0.6.0-py3-none-any.whl.
File metadata
- Download URL: deckgl_marimo-0.6.0-py3-none-any.whl
- Upload date:
- Size: 685.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
76b6a43a63ad404ff5887e6a8285803d2f6acc07cc05c5f407f46aa3d9322095
|
|
| MD5 |
fa131fc13bfbc20ffb91ddf230155e37
|
|
| BLAKE2b-256 |
e5cb7bbc2a1fa32cfc8ce60f380a6ea294e60582f2366b288a419140fa9111c9
|
Provenance
The following attestation bundles were made for deckgl_marimo-0.6.0-py3-none-any.whl:
Publisher:
publish.yml on kihaji/deckgl-marimo
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
deckgl_marimo-0.6.0-py3-none-any.whl -
Subject digest:
76b6a43a63ad404ff5887e6a8285803d2f6acc07cc05c5f407f46aa3d9322095 - Sigstore transparency entry: 1360168604
- Sigstore integration time:
-
Permalink:
kihaji/deckgl-marimo@f44a2bc6b12de9bdba3e48c10994b09e423c5c8a -
Branch / Tag:
refs/tags/0.6.0 - Owner: https://github.com/kihaji
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f44a2bc6b12de9bdba3e48c10994b09e423c5c8a -
Trigger Event:
release
-
Statement type: