posit-dev / py-shinywidgets

Render ipywidgets inside a PyShiny app
MIT License
41 stars 2 forks source link

Altair dynamic chart rendering issue #140

Closed adampinky85 closed 3 months ago

adampinky85 commented 3 months ago

Description

Hi team,

We dynamically generate a variable number of charts depending on the dataset. Following upgrading version, our Altair charts no longer rendering correctly.

In the minimal example below, the code generates three charts and renders those on the dashboard. The version upgrade installed anywidget 0.9.1 and now only the first of the three Altair charts renders. Interestingly, the three Plotly charts still renders as expected.

I'd really appreciate your help as I'm not entirely sure what is causing this issue? Many thanks!

Versions

Python: 3.11.6 OS: Ubuntu 22.04.4 LTS altair==5.2.0 anywidget==0.9.1 <-- working in 0.9.0, failing in 0.9.1 pandas==2.2.1 plotly==5.19.0 shiny==0.8.1 shinyswatch==0.5.1 shinywidgets==0.3.1

Minimal Example

import altair
import pandas as pd
import plotly.express
import shiny
import shinywidgets

_SOURCE_DATA = pd.DataFrame(
    {
        "a": ["A", "B", "C", "D", "E", "F", "G", "H", "I"],
        "b": [28, 55, 43, 91, 81, 53, 19, 87, 52],
    }
)

def server(_, output: shiny.Outputs, __):
    """Shiny main server"""

    # not working
    def render_altair():
        @shinywidgets.render_widget
        def _render():
            return altair.Chart(_SOURCE_DATA).mark_bar().encode(x="a", y="b")

        return _render

    # works as expected
    def render_plotly():
        @shinywidgets.render_widget
        def _render():
            return plotly.express.bar(_SOURCE_DATA, x='a', y='b')

        return _render

    @output
    @shiny.render.ui
    def plots() -> shiny.ui.TagList:
        tags = []
        for i in range(1, 4):
            plot_name = f"plot_{i}"

            tag = shiny.ui.card(
                shiny.ui.card_header(plot_name),
                shinywidgets.output_widget(plot_name),
            )
            tags.append(tag)

            # not working
            output(render_altair(), id=plot_name)

            # works as expected
            #output(render_plotly(), id=plot_name)

        return shiny.ui.TagList(tags)

UI = shiny.ui.page_navbar(
    shiny.ui.nav_panel("Dashboard", shiny.ui.output_ui("plots")),
)

app = shiny.App(UI, server)
jcheng5 commented 3 months ago

Can repro here, this is what I see in the JS console:

image
cpsievert commented 3 months ago

Looks like a fix for this was merged a few days ago in altair https://github.com/altair-viz/altair/pull/3364

Does installing the development version from GitHub fix it?

pip install git+https://github.com/altair-viz/altair.git
jcheng5 commented 3 months ago

I git bisected to: https://github.com/manzt/anywidget/commit/f3be3a4e77a3b0caeba41b149b1781204ee4bf86

jcheng5 commented 3 months ago

@cpsievert That changes things but doesn't completely fix it (app looks broken in the same way):

image
cpsievert commented 3 months ago

Ah, I missed that error message, I'll look into it

jcheng5 commented 3 months ago

It looks like at the time that _maybeResize is called: https://github.com/posit-dev/py-shinywidgets/blob/6719b0ede22069af3871b19a4c80a4c7f575df0e/js/src/output.ts#L113

lmWidget.children.length is 0. If I delay _maybeResize by a bit with a setTimeout, the error and problem go away.

BTW, the plots aren't rendering correctly in Safari even when they look good in Chrome:

https://github.com/posit-dev/py-shinywidgets/assets/129551/ae0b2bdf-85a2-4d86-902f-5ea1a30fa32f

cpsievert commented 3 months ago

@jcheng5 looks like the safari thing is a separate issue. I started a new issue for that in https://github.com/posit-dev/py-shinywidgets/issues/144