Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

4. Lonboard ↔ Anywidget Interop (Static)

A CounterWidget drives a lonboard ScatterplotLayer’s point radius via a WidgetBinder. All wiring is JS-only — no kernel running on the static page. Click the counter’s + / - and watch the dots grow / shrink.

Why radius rather than view zoom: lonboard’s Map uses deck.gl’s initialViewState (uncontrolled) so external view_state changes don’t propagate to the rendered map. Layer-level props like get_radius re-render correctly because the layer model fires change:* events that lonboard’s React layer hook subscribes to.

import sys
import pathlib

sys.path.insert(0, str(pathlib.Path().absolute().parent))

import geopandas as gpd
from shapely.geometry import Point
from lonboard import Map, ScatterplotLayer

from widgets.counter_widget import CounterWidget
from widgets.widget_binder import WidgetBinder
from IPython.display import display

1. The map

Five points around San Francisco. Default zoom 12; we’ll override that from the counter.

points = [
    Point(-122.4194, 37.7749),
    Point(-122.4313, 37.7849),
    Point(-122.4094, 37.7649),
    Point(-122.4214, 37.7949),
    Point(-122.4394, 37.7549),
]
gdf = gpd.GeoDataFrame({'geometry': points}, crs='EPSG:4326')

layer = ScatterplotLayer.from_geopandas(
    gdf,
    get_radius=300,
    get_fill_color=[40, 110, 220, 200],
)

m = Map(
    layers=[layer],
    view_state={
        'longitude': -122.4194,
        'latitude': 37.7749,
        'zoom': 12,
        'pitch': 0,
        'bearing': 0,
    },
    height=400,
)
m

2. The counter

An ordinary CounterWidget. Initial value 5 → starting radius 5 × 100 = 500m. widget_id lets the binder find it.

radius_counter = CounterWidget(
    label="Point radius (× 100 m)",
    widget_id="radius_ctrl",
    value=5,
)
radius_counter

3. The binder

source = the counter (radius_ctrl). target = the scatterplot layer, looked up by its _layer_type alias (_layer_type:scatterplot). The plugin’s shim registers each lonboard sub-model under multiple keys — model_id (UUID), _anywidget_id, widget_id, and (for layers) _layer_type:<type> — so binders can target a layer without knowing its UUID.

target_field="get_radius" — when the model fires change:get_radius, lonboard’s React layer hook re-reads it and rebuilds the deck.gl layer. With multiplier=100, counter value 5 → radius 500m.

binder = WidgetBinder(
    source_widget_id="radius_ctrl",
    source_field="value",
    target_widget_id="_layer_type:scatterplot",
    target_field="get_radius",
    multiplier=100.0,
    offset=0.0,
    label="counter.value → layer.get_radius",
)
binder