Skip to main content

deck.gl visualization library for marimo notebooks — 15 fully tested layer types on MapLibre basemaps, plus experimental access to the full deck.gl catalog

Project description

deckgl-marimo

PyPI Python CI License: MIT Open in molab

Interactive deck.gl visualization library for marimo notebooks. Render GPU-accelerated maps with 15 fully tested layer types (plus experimental access to the full deck.gl catalog), powered by MapLibre GL and anywidget.

Features

  • 15 fully tested layer types — scatter plots, hexagonal bins, heatmaps, arcs, paths, polygons, GeoJSON, 3D columns, lines, point clouds, and more — plus 17 experimental layers covering the rest of the deck.gl catalog
  • 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_metrics traitlet
  • 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 Map widget in a cell that does not depend on slider values. Update layers by assigning to map_widget.layer_specs in 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.set_layers([
    dgl.HexagonLayer(
        data=df,
        get_position=["lon", "lat"],
        radius=radius.value,
        extruded=True,
        elevation_scale=250,
    )
])

# 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 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 an opt-in FPS counter that reports metrics back to Python. It is off by default (the tracker pushes updates every 500 ms, which is wasted traffic for an idle map):

map_widget = dgl.Map(basemap="dark-matter", center=(0, 0), zoom=1, show_perf_metrics=True)
widget = mo.ui.anywidget(map_widget)

# Read performance metrics (updated every 500ms while enabled)
perf = widget.value.get("perf_metrics", {})
fps = perf.get("fps")           # frames per second
frame_time = perf.get("frameTimeAvg")  # ms per frame

# Toggle at runtime
map_widget.show_perf_metrics = False

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 (15)

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
SolidPolygonLayer Filled regions without stroke (fastest polygons) 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
DisplacementLayer Origin vs reported-position arcs + dots (composite)
EllipseLayer Ellipses from center/axes/orientation (composite)

Experimental (17)

All additional deck.gl layers are available as experimental stubs via deckgl_marimo.layers (they emit a warning on construction and may not be fully tested):

from deckgl_marimo.layers import TripsLayer, MVTLayer, H3HexagonLayer, ContourLayer
# ... and more

Basemaps

dgl.Basemaps.list_available()
# ['bright', 'dark', 'dark-matter', '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

Fixed in marimo >= 0.22.0 — update marimo.

Map flashes black / camera resets when a slider moves

The Map widget is being recreated on every reactive run. Create the Map in a cell with no slider dependencies and update layers from a separate cell with map_widget.set_layers([...]) — see Reactive controls above. Assigning map_widget.layer_specs directly also works for JSON layers, but skips binary re-packing; prefer set_layers.

Blank map / WebGL errors on WSL or headless environments

deck.gl requires WebGL2. In WSL, make sure your browser has hardware acceleration enabled (check chrome://gpu or https://webglreport.com). For headless Chromium testing, launch with --use-gl=angle --use-angle=swiftshader --enable-unsafe-swiftshader.

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

deckgl_marimo-0.7.0.tar.gz (106.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

deckgl_marimo-0.7.0-py3-none-any.whl (921.5 kB view details)

Uploaded Python 3

File details

Details for the file deckgl_marimo-0.7.0.tar.gz.

File metadata

  • Download URL: deckgl_marimo-0.7.0.tar.gz
  • Upload date:
  • Size: 106.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for deckgl_marimo-0.7.0.tar.gz
Algorithm Hash digest
SHA256 2b46d2a80778baaeae0695b3828c4c3acf126e2da915a40dbccc72ee1f852268
MD5 51b6800cd994c3f934690436eff77a23
BLAKE2b-256 64a81bb4a5db2dfb8ee38fe82940996fdcb6922b290ba0fa4f48092df3bb97aa

See more details on using hashes here.

Provenance

The following attestation bundles were made for deckgl_marimo-0.7.0.tar.gz:

Publisher: publish.yml on kihaji/deckgl-marimo

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file deckgl_marimo-0.7.0-py3-none-any.whl.

File metadata

  • Download URL: deckgl_marimo-0.7.0-py3-none-any.whl
  • Upload date:
  • Size: 921.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for deckgl_marimo-0.7.0-py3-none-any.whl
Algorithm Hash digest
SHA256 cfc79af6e30bd616366f17c9761528c2ac4113f43945729e9097e84882e91598
MD5 635b6cec1748a4ad034086e7e1d0533a
BLAKE2b-256 a5d3a13cbeff2980837a402936ec5924a666c0c51eca716092313be60b2faeba

See more details on using hashes here.

Provenance

The following attestation bundles were made for deckgl_marimo-0.7.0-py3-none-any.whl:

Publisher: publish.yml on kihaji/deckgl-marimo

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page