lets-plot icon indicating copy to clipboard operation
lets-plot copied to clipboard

Better Marimo Support?

Open jsnelgro opened this issue 1 year ago • 4 comments
trafficstars

Let-plot is my favorite plotting library and for the past few weeks I've been happily experimenting with Marimo, a reactive Jupyter alternative. Unfortunately though, Lets-plot doesn't play very nicely with it. The big issues are:

  1. Charts keep getting appended to the cell output instead of modifying the chart in place (see screenshot)
  2. The output is awkwardly sized and not responsive, so you need to scroll within the iframe
  3. it's not clear how the imperative call LetsPlot.setup_html() works in a reactive environment like this (including it doesn't seem to make any difference)
  4. (less important) Marimo doesn't allow star imports, so chart declarations can be a bit harder to read

Here's an example screenshot: Screenshot 2024-02-15 at 16 44 28

And here's a minimal example Marimo notebook to reproduce the issue:

import marimo

__generated_with = "0.1.86"
app = marimo.App()


@app.cell
def __():
    import numpy as np
    import marimo as mo
    import lets_plot as lp

    lp.LetsPlot.setup_html()
    np.random.seed(42)
    return lp, mo, np


@app.cell
def __(mo):
    stddev = mo.ui.slider(1, 10, step=0.001, label="std dev")
    stddev
    return stddev,


@app.cell
def __(np, stddev):
    data = dict(
        cond=np.repeat(['A', 'B'], 200),
        rating=np.concatenate(
            (np.random.normal(0, 1, 200), np.random.normal(1, stddev.value, 200))
        )
    )
    return data,


@app.cell
def __(data, lp, mo):
    # necessary, otherwise charts keep getting appended to the frame.
    # However, this makes the cell output redraw everything on changes, causing flashes
    mo.output.clear()

    chart = lp.ggplot(data, lp.aes(x='rating', fill='cond')) + lp.ggsize(700, 300) + \
        lp.geom_density(color='dark_green', alpha=.7) + lp.scale_fill_brewer(type='seq') + \
        lp.theme(panel_grid_major_x='blank')
    chart
    return chart,


@app.cell
def __():
    return


if __name__ == "__main__":
    app.run()

I'm not sure where the easiest integration point would be (with Lets plot or Marimo?), but I figured I'd at least get it on your radar. Honestly, a quick and simple QoL improvement would just be make to_html return the html as a string if no filepath was provided. I'm happy to take a stab at patching the to_html, to_svg, etc methods if it seems like a reasonable feature to add.

jsnelgro avatar Feb 16 '24 00:02 jsnelgro

Hi, by contract, IPython notebook calls our magic _repr_html_() method which indeed returns a string.

What happens with this string in the notebook is beyond our scope.

Does Marimo provide some kind of API alternative to IPython?

alshan avatar Feb 16 '24 19:02 alshan

interesting, yes it looks like they provide some APIs that might do the trick (for example, rendering arbitrary html). Although, based on their interactive Altair, Plotly, and matplotlib helper methods, I'm wondering if interactive charts would be more of an integration on their end 🤔

jsnelgro avatar Feb 18 '24 04:02 jsnelgro

So I think if LetsPlot.setup_html(isolated_frame=False) didn't have a hard dependency on IPython's display_html method here, it could possibly solve the issue. Right now, setting isolated_frame=False just throws an error unfortunately, due to the display_html method being None. Calling mo.Html(_repr_html()) almost does the trick but the method returns a whole html page with javascript to attach to a newly created DOM node. And it seems isolated_frame=False is needed to change this behavior?

jsnelgro avatar Feb 21 '24 17:02 jsnelgro

isolated_frame=True is used when notebook wraps any output in iframe. In this case the output should be self sufficient and includes both the library script and plot data and JS that calls the library.

When isolated_frame=False the library is added to the output of LetsPlot.setup_html() and plot cells only contain JS calling the library.

If display_html is None then there is no IPython in the env and I can't say how anything is shown at all :)

Calling mo.Html(_repr_html()) almost does the trick but the method returns a whole html page with javascript to attach to a newly created DOM node. And it seems isolated_frame=False is needed to change this behavior?

If this is not what you need then absolutely try isolated_frame=False. But in absence of display_html the LetsPlot.setup_html() will not be able to inject the library JS into the notebook :( As a workaround you can try to call _configure_connected_script and then pass the result to mo.Html().

alshan avatar Feb 22 '24 02:02 alshan

@jsnelgro , @signup2k, @VivaldoMendes Hi guys, good new: my PR-2084 was accepted and just made it to the latest marimo 0.8.1 release.

Now Marimo knows how to display lets-plot charts. (also, LetsPlot.setup_html() is no longer required). p.show() however won't work.

Try this simple demo:

import numpy as np
from lets_plot import ggplot, geom_point, aes

x = np.random.rand(100)
y = 2 * x + 1 + np.random.normal(0, 0.1, 100)

data = dict(x = x, y = y)

p = ggplot(data) + geom_point(aes('x', 'y'))
p

alshan avatar Aug 22 '24 19:08 alshan