Skip to content

Using @render_widget with non-UI ipywidgets #133

Closed
@kylebarron

Description

@kylebarron
  • shinywidgets version: 0.3
  • Python version: 3.11
  • Operating System: MacOS Sonoma 14.2.1

Description

I develop a Jupyter Widget called lonboard for geospatial data visualization. It uses the same underlying JS library as pydeck but is 10-50x faster/more capable because it uses efficient binary data serialization instead of JSON and because it can copy binary buffers directly to the GPU.

I'm trying to add an example in the lonboard documentation of how to use with shiny, but it's unclear how to handle reactivity of multiple widgets, where only one is intended to be rendered.

The architecture of lonboard is that there's one top-level Map widget but a variety of Layer classes to render points, lines, polygons etc. Each of these Layer classes is themselves a Widget so that their attributes can be reactive. But only the Map is associated with a JS view.

I'd like to have each widget as its own reactive element.

Describe what you were trying to get done.
Tell us what happened, what went wrong, and what you expected to happen.

What I Did

This first example worked but is a bit clunky

import geopandas as gpd
from shiny import reactive
from shiny.express import input, ui
from shinywidgets import render_widget

from lonboard import Map, ScatterplotLayer

colors = {
    "Red": [200, 0, 0],
    "Green": [0, 200, 0],
    "Blue": [0, 0, 200],
}

ui.input_select("color_select", "Color", choices=list(colors.keys()))


@render_widget
def map():
    gdf = gpd.read_file(gpd.datasets.get_path("naturalearth_cities"))
    layer = ScatterplotLayer.from_geopandas(gdf, radius_min_pixels=2)
    return Map(layer)


@reactive.effect
def set_fill_color():
    map.widget.layers[0].get_fill_color = colors[input.color_select()]
Screen.Recording.2024-02-06.at.5.13.27.PM.mov

Setting map.widget.layers[0].get_fill_color is pretty clunky because you're accessing the layer widget through the Map object. Instead, the usual suggestion in the docs in Jupyter is to edit layer.get_fill_color directly. So what I was hoping would work is the following:

import geopandas as gpd
from shiny import reactive
from shiny.express import input, ui
from shinywidgets import render_widget

from lonboard import Map, ScatterplotLayer

colors = {
    "Red": [200, 0, 0],
    "Green": [0, 200, 0],
    "Blue": [0, 0, 200],
}

ui.input_select("color_select", "Color", choices=list(colors.keys()))


@render_widget
def layer():
    gdf = gpd.read_file(gpd.datasets.get_path("naturalearth_cities"))
    return ScatterplotLayer.from_geopandas(gdf, radius_min_pixels=2)


@render_widget
def map():
    return Map(layer.widget)


@reactive.effect
def set_fill_color():
    layer.widget.get_fill_color = colors[input.color_select()]

Here there are two widgets, the layer and the map. This is simpler because set_fill_color is reactive onto the layer instead of the map. But nothing renders on screen. Is there a different decorator I should be using instead of render_widget on def layer?

image

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions