Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Animated Plotly Graphs Not Rendering #156

Open
Secret-Ambush opened this issue Aug 6, 2024 · 2 comments
Open

Animated Plotly Graphs Not Rendering #156

Secret-Ambush opened this issue Aug 6, 2024 · 2 comments

Comments

@Secret-Ambush
Copy link

Secret-Ambush commented Aug 6, 2024

I was trying to generate an animated 3D scatter plot using plotly and display it on a dashboard I created using PyShiny Express. When checking the code for an animated plotly graph on a jupyter notebook - it works perfectly, but I am not able to get the animation running on Shiny.

The graph is interactive - that is the tool tip shows the data but there's no animation when clicking on Play.

import io
from pathlib import Path
from shiny import reactive
from shiny.express import input, ui, render
from shiny.types import FileInfo
import pandas as pd
import plotly.graph_objects as go
import plotly.express as px
from shinywidgets import render_widget
import asyncio

ui.page_opts(
    title="Visualisation",
    fillable=True,
    id="page",
    )

with ui.card(full_screen=True):
    @render_widget
    def coordinate_space():
        try:
            with ui.Progress(min=1, max=5) as p:
                data = {
                    'X': [1, 2, 3, 4, 5],
                    'Y': [5, 4, 3, 2, 1],
                    'Z': [2, 3, 4, 5, 6],
                    'FZ_new': [10, 20, 30, 40, 50],
                    'FZ': [5, 10, 15, 20, 25]
                }

                scaled_coordinate_system = pd.DataFrame(data)

                p.set(2, message="Data loaded, preparing plot...")


                fig = px.scatter_3d(scaled_coordinate_system, x='X', y='Y', z='Z',
                    size='FZ_new', color='FZ_new', opacity=0.7)

                fig1 = go.Figure(fig)
                p.set(3, message="Adding traces to the plot...")

                    
                frames = [
                    go.Frame(data=[go.Scatter3d(
                        x=scaled_coordinate_system['X'], 
                        y=scaled_coordinate_system['Y'], 
                        z=scaled_coordinate_system['Z'], 
                        mode='markers', 
                        marker=dict(size=scaled_coordinate_system['FZ_new'] * 10, color="Red"),
                        opacity=0.9,
                        name='Frame 1',
                        
                    )]),
                    go.Frame(data=[go.Scatter3d(
                        x=scaled_coordinate_system['X'], 
                        y=scaled_coordinate_system['Y'], 
                        z=scaled_coordinate_system['Z'], 
                        mode='markers', 
                        marker=dict(size=scaled_coordinate_system['FZ'], color=scaled_coordinate_system['FZ']),
                        opacity=0.7,
                        name='Frame 2'
                    )])
                ]

                print("Frames")

                fig1.frames = frames

                p.set(4, message="Updating layout...")

                fig1.update_layout(
                    scene=dict(
                        xaxis_title='X AXIS',
                        yaxis_title='Y AXIS',
                        zaxis_title='Z AXIS'
                    ),
                    updatemenus=[dict(
                        type='buttons', 
                        direction = "down",
                        showactive=True, 
                        buttons=[
                            dict(
                                label='Play',
                                method='animate', 
                                args=[None, dict(
                                    frame=dict(duration=500, redraw=True),
                                    fromcurrent=True, 
                                    transition=dict(duration=0)
                                )]
                            ),
                            dict(
                                label='Pause',
                                method='animate',
                                args=[[None], dict(
                                    frame=dict(duration=0, redraw=False),
                                    mode='immediate'
                                )]
                            )
                        ]
                    )]
                )

                fig1.update_layout(margin=dict(l=0, r=0, b=0, t=0))
                camera = dict(
                    eye=dict(x=2, y=2, z=0.5)
                )

                fig1.update_layout(scene_camera=camera)
                p.set(5, message="Rendering plot...")
                return fig1

        except Exception as e:
            ui.notification_show(f"{e}", duration=20, type="error")
            return None

Above is a minimal reproducible code for the function to render a plot. I was thinking if the animation rendering issue has something to do with async functions so I changed this to sync. I'm not sure what to do.

My animation just affects the size of the markers in the scatter plot.

Could someone help me out?

@cpsievert
Copy link
Collaborator

cpsievert commented Aug 20, 2024

Thanks for the report -- this is a particularly tricky one.

In your example, fig1 is a go.Figure(). Since @render_widget is expecting an object that inherits from ipywidgets.Widget, it'll actually implicitly transform that go.Figure() into a go.FigureWidget(). And, if you try a minimal go.FigureWidget() in a Jupyter notebook with frames, you get:

ValueError: 
Frames are not supported by the plotly.graph_objs.FigureWidget class.
Note: Frames are supported by the plotly.graph_objs.Figure class

That said, if you just want the plot to render as it does in the notebook (i.e., without transforming Figure to FigureWidget), you can do that by changing two lines: (1) @render_widget -> @render.ui and (2) return fig1 -> return ui.HTML(fig1.to_html()).

Unfortunately, this is mainly a limitation of FigureWidget, but I will transfer this issue over {shinywidgets} since it should probably be throwing that same ValueError instead of silently dropping frames

@cpsievert cpsievert transferred this issue from posit-dev/py-shiny Aug 20, 2024
@Secret-Ambush
Copy link
Author

Secret-Ambush commented Aug 21, 2024

Thanks a lot, @cpsievert!
Rendering inside a HTML tag works perfectly, and I'm now able to understood why it's not working when using @render_widget

Kind Regards

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants