Frontend Python Callbacks¶
Most layers in pyglobegl are driven entirely by typed data models. A few
globe.gl accessors, though, are computed in the browser rather than mapped from a
single Python datum — the hex bin layer (whose bins
are aggregated client-side) and the
heatmaps layer (whose colormap is a
function of normalised density) are the main examples. Rather than make you write
JavaScript, pyglobegl lets you decorate a Python function with @frontend_python
and run it on the frontend.
from pyglobegl import frontend_python
@frontend_python
def hex_altitude(hexbin):
return hexbin["sumWeight"] * 0.01
Pass the decorated function wherever an accessor callback is accepted (for
example hex_altitude=, hex_top_color=, hex_label=, the heatmaps
heatmap_color_fn=, the arc/path/ring gradient accessors arc_color_fn=,
path_color_fn=, ring_color_fn=, or any layer's tooltip accessor point_label=,
arc_label=, polygon_label=, … — see the
reference).
Most of these callbacks run at data-change time to bake globe.gl's render buffers
(for example, the heatmap colormap is sampled to build a fixed colour lookup),
not on every animation frame. Label/tooltip callbacks (the per-layer *_label
accessors, such as point_label and hex_label) are the exception — they
are evaluated lazily on hover to build the tooltip. Either way, keep the body
cheap: ordinary Python arithmetic or string formatting is fast enough.
What your callback receives, and what it can access¶
A frontend callback is a pure function:
- It takes exactly one argument — the value globe.gl passes for that
accessor: a single number
tin[0, 1]for the gradient/colormap callbacks, or a datumdictfor the hex-bin accessors. Nothing else is in scope. - It returns one value — a CSS colour string, a number, or tooltip text, depending on the accessor (see the reference table).
- It runs in the browser, not your Python process. It cannot see your notebook's variables, your imports, or any backend state — only the function's own source is shipped (keep it self-contained; see below).
- Treat it as pure; it is not a security sandbox. By convention a callback
maps its one argument to one return value, and that return value is all
pyglobegl reads back — to change the globe, call the widget's
set_*/update_*methods from Python rather than reaching out from a callback. Note this is a convention, not an enforced boundary: the body is executed as source in a shared MicroPython runtime where thejsinterop module is in scope (and the globe is reachable as a browser global), so callback code technically can touch browser/network APIs and globe state. Only run callback bodies you trust as code, and don't rely on isolation for safety.
Typing your callback¶
@frontend_python is signature-preserving, so the annotations you put on the
callback survive, and pyglobegl exports type aliases for the two callback shapes:
ColorInterpolator—(t: float) -> str, for the gradient/colormap callbacks. Thearc_color_fn/path_color_fn/ring_color_fn/heatmap_color_fnfields are typed to it, so an annotated callback is checked at the call site (a wrong signature is flagged).HexBin— aTypedDictfor the aggregated bin passed to the hex-bin styling callbacks ({"h3Idx", "points", "sumWeight"}). Annotate against it for autocomplete on the bin keys. (It is an opt-in aid: the hex-bin fields stay loosely typed so a plaindef fn(b: dict)still type checks.)
MicroPython parses and ignores annotations, so annotating is always safe:
from pyglobegl import ColorInterpolator, frontend_python
@frontend_python
def gradient(t: float) -> str: # ColorInterpolator; t may be int at the endpoints
red = int(255 * t)
return f"rgb({red},0,{255 - red})"
How it works¶
@frontend_pythonreturns aFrontendPythonFunction. The function's source is shipped to the widget and executed in the browser via MicroPython, so it runs client-side without a Python round-trip per datum.- Callback arguments are normalised to plain Python values (dict / list /
str / float / bool / None) when they are JSON-serializable, so dict methods
like
.get(...)work inside the callback.
Keep callbacks self-contained
Because the body runs in the browser's MicroPython runtime, it should rely only on its arguments and the Python builtins available there — not on closures over your notebook's variables or third-party imports. Only the function's own source is shipped, so module-level names defined elsewhere are not available when it runs.
Callback reference¶
Every accessor below takes a single argument and returns a single value; nothing else is accessible inside the callback (see above).
| Callback (layer) | Argument | Returns | When it runs |
|---|---|---|---|
arc_color_fn (arcs) |
t: float in [0, 1], start → end |
CSS colour str |
data change |
path_color_fn (paths) |
t: float in [0, 1], start → end |
CSS colour str |
data change |
ring_color_fn (rings) |
t: float in [0, 1], propagation |
CSS colour str |
per frame while a ring expands |
heatmap_color_fn (heatmaps) |
t: float in [0, 1], normalised density |
CSS colour str |
data change |
hex_top_color / hex_side_color (hex bin) |
hexbin: dict |
CSS colour str |
data change |
hex_altitude (hex bin) |
hexbin: dict |
non-negative float (globe-radius units) |
data change |
hex_label (hex bin) |
hexbin: dict |
HTML/text tooltip str |
on hover |
hex_bin_point_lat / _lng / _weight (hex bin) |
point: dict |
float |
data change |
hex_margin (hex bin) |
hexbin: dict |
float |
data change |
point_label / arc_label / path_label / polygon_label / hex_polygon_label / tile_label / particle_label / label_label |
datum: dict |
HTML/text tooltip str |
on hover |
The colour-gradient callbacks (arc_color_fn, path_color_fn, ring_color_fn)
are a layer-level override: when set, they replace the per-datum color field
for every element in the layer. Pass None (the default) to keep per-datum
colours.
The per-layer tooltip accessors (point_label, arc_label, path_label,
polygon_label, hex_polygon_label, tile_label, particle_label,
label_label) are likewise layer-level overrides of the per-datum label
field. Each accepts a @frontend_python callback (datum → tooltip string), a
plain string (one fixed tooltip for every element), or None (the default) to
fall back to each datum's label. (One exception: the particles layer passes the
hovered individual particle point, a ParticlePointDatum, not the group.) The
callback's datum argument stays loosely typed (dict), so a plain def fn(d) type
checks. Swap them at runtime with GlobeWidget.set_point_label(...) and friends.
Read dict inputs defensively
Upstream globe.gl shapes (such as the hex-bin aggregate) can change between
versions. Prefer arg.get("key", default) over direct indexing so a missing
key degrades gracefully. See the
hex bin callback I/O table for the exact
hexbin / point dict shapes.