posit-dev / py-shiny

Shiny for Python
https://shiny.posit.co/py/
MIT License
1.1k stars 62 forks source link

Using Lets-plot chart in Shiny #448

Open Boadzie opened 1 year ago

Boadzie commented 1 year ago

Is possible to use lets-plot in shiny? Lets-plot is an interactive version of the grammar of graphic in Python.

nsiicm0 commented 1 year ago

There does not seem to be a solution in the current (public) API. However, by glancing into the code of how ggsave works, we can call the kbridge manually that handles the conversion of a PlotSpec into HTML. Here is a working example that I am using in my environment.


from shiny import App, render, ui, reactive
import pandas as pd
import lets_plot as lp

datasets = {
    'iris': {'name': 'Iris', 'url': 'https://raw.githubusercontent.com/JetBrains/lets-plot-docs/master/data/iris.csv'}
    , 'mpg': {'name': 'Mpg', 'url': 'https://raw.githubusercontent.com/JetBrains/lets-plot-docs/master/data/mpg.csv'}
}

app_ui = ui.page_fluid(
    ui.panel_title("Lets Plot Example", "Lets Plot Example")
    , ui.layout_sidebar(
        ui.panel_sidebar(
            ui.input_selectize(
                "dataset",
                "Dataset",
                {k: v.get('name') for k, v in datasets.items()}
            )
        )
        , ui.panel_main(
            ui.output_ui("letsplot")
        )
    )
)

def server(input, output, session):
    @output(id='letsplot')
    @render.ui
    @reactive.event(input.dataset)
    def compute():
        selection = input.dataset()
        df = pd.read_csv(datasets[selection]['url'])
        if selection == 'iris':
            p = (
                lp.ggplot(df) 
                + lp.geom_point(lp.aes('petal_length', 'petal_width', color='species'), size=5)
                + lp.ggsize(1000, 500)
            )
        elif selection == 'mpg':
            p = (
                lp.ggplot(df, lp.aes('displ', 'cty', fill='drv', size='hwy'))
                + lp.geom_point(shape=21, color='white')
                + lp.scale_size(range=[5, 15], breaks=[15, 40])
                + lp.ggsize(1000, 500)
            )
        else:
            raise ValueError(f'{selection=} is not valid.')

        phtml = lp._kbridge._generate_static_html_page(p.as_dict(), iframe=True)
        return ui.HTML(phtml)

app = App(app_ui, server)

Hope this helps. Hopefully there is at some point an official API that we can use.

Boadzie commented 1 year ago

Thank you very much. This works!

m-mahgoub commented 4 weeks ago

There does not seem to be a solution in the current (public) API. However, by glancing into the code of how ggsave works, we can call the kbridge manually that handles the conversion of a PlotSpec into HTML. Here is a working example that I am using in my environment.

from shiny import App, render, ui, reactive
import pandas as pd
import lets_plot as lp

datasets = {
    'iris': {'name': 'Iris', 'url': 'https://raw.githubusercontent.com/JetBrains/lets-plot-docs/master/data/iris.csv'}
    , 'mpg': {'name': 'Mpg', 'url': 'https://raw.githubusercontent.com/JetBrains/lets-plot-docs/master/data/mpg.csv'}
}

app_ui = ui.page_fluid(
    ui.panel_title("Lets Plot Example", "Lets Plot Example")
    , ui.layout_sidebar(
        ui.panel_sidebar(
            ui.input_selectize(
                "dataset",
                "Dataset",
                {k: v.get('name') for k, v in datasets.items()}
            )
        )
        , ui.panel_main(
            ui.output_ui("letsplot")
        )
    )
)

def server(input, output, session):
    @output(id='letsplot')
    @render.ui
    @reactive.event(input.dataset)
    def compute():
        selection = input.dataset()
        df = pd.read_csv(datasets[selection]['url'])
        if selection == 'iris':
            p = (
                lp.ggplot(df) 
                + lp.geom_point(lp.aes('petal_length', 'petal_width', color='species'), size=5)
                + lp.ggsize(1000, 500)
            )
        elif selection == 'mpg':
            p = (
                lp.ggplot(df, lp.aes('displ', 'cty', fill='drv', size='hwy'))
                + lp.geom_point(shape=21, color='white')
                + lp.scale_size(range=[5, 15], breaks=[15, 40])
                + lp.ggsize(1000, 500)
            )
        else:
            raise ValueError(f'{selection=} is not valid.')

        phtml = lp._kbridge._generate_static_html_page(p.as_dict(), iframe=True)
        return ui.HTML(phtml)

app = App(app_ui, server)

Hope this helps. Hopefully there is at some point an official API that we can use.

This works! even for multiple plots grouped in gggrid()... Thanks