marimo-team / marimo

A reactive notebook for Python — run reproducible experiments, execute as a script, deploy as an app, and version with git.
https://marimo.io
Apache License 2.0
7.78k stars 273 forks source link

Holoviews HoloMap and DynamicMap don't work #979

Closed mgunyho closed 4 days ago

mgunyho commented 7 months ago

Describe the bug

If I try to display a Holoviews HoloMap or DynamicMap, I get blank/textual output instead of an interactive plot (example code taken from Holoviews docs):

marimo-holoviews-text

See here how it should look like: https://holoviews.org/reference/containers/bokeh/HoloMap.html

I would like to use Holoviews with HoloMap/DynamicMap to visualize multidimensional data using sliders, which I currently do very often in Jupyter. I understand that this is a bit redundant since marimo has it's own slider widget, but I couldn't find a better way to do this kind of slicing through multidimensional data (I tried Altair, but there the sliders cannot handle values that are not evenly spaced).

This can probably be fixed by adding one of the superclasses of HoloMap to HoloviewsFormatter._show_chart here. Here's the inheritance hierarchy of HoloMap:

(<class 'holoviews.core.spaces.HoloMap'>,
 <class 'holoviews.core.layout.Layoutable'>,
 <class 'holoviews.core.ndmapping.UniformNdMapping'>,
 <class 'holoviews.core.ndmapping.NdMapping'>,
 <class 'holoviews.core.ndmapping.MultiDimensionalMapping'>,
 <class 'holoviews.core.dimension.Dimensioned'>,
 <class 'holoviews.core.dimension.LabelledData'>,
 <class 'param.parameterized.Parameterized'>,
 <class 'holoviews.core.overlay.Overlayable'>,
 <class 'object'>)

I'm not familiar enough with the internals of holoviews to say which of these should be added to _show_chart. DynamicMap is a subclass of HoloMap.

As a bonus, if I convert the HoloMap to a layout with hv.Layout(hv.HoloMap(...)), the cell grows indefinitely (similar to #978):

https://github.com/marimo-team/marimo/assets/20118130/3d316294-ea77-4f73-8a88-839bc65f6e02

I'm using a virtualenv with pip install marimo matplotlib tornado holoviews. Here's the full output of pip list: Click to show

Package             Version    
------------------- -----------
anyio               4.3.0      
black               24.3.0     
bleach              6.1.0      
bokeh               3.1.1      
certifi             2024.2.2   
charset-normalizer  3.3.2      
click               8.1.7      
colorcet            3.1.0      
contourpy           1.1.1      
cycler              0.12.1     
docutils            0.20.1     
exceptiongroup      1.2.0      
fonttools           4.50.0     
h11                 0.14.0     
holoviews           1.17.1     
idna                3.6        
importlib-metadata  7.1.0      
importlib-resources 6.4.0      
jedi                0.19.1     
Jinja2              3.1.3      
kiwisolver          1.4.5      
linkify-it-py       2.0.3      
marimo              0.3.3      
Markdown            3.6        
markdown-it-py      3.0.0      
MarkupSafe          2.1.5      
matplotlib          3.7.5      
mdit-py-plugins     0.4.0      
mdurl               0.1.2      
mypy-extensions     1.0.0      
numpy               1.24.4     
packaging           24.0       
pandas              2.0.3      
panel               1.2.3      
param               2.0.2      
parso               0.8.3      
pathspec            0.12.1     
pillow              10.2.0     
pip                 20.0.2     
pkg-resources       0.0.0      
platformdirs        4.2.0      
pygments            2.17.2     
pymdown-extensions  10.7.1     
pyparsing           3.1.2      
python-dateutil     2.9.0.post0
pytz                2024.1     
pyviz-comms         3.0.1      
PyYAML              6.0.1      
requests            2.31.0     
setuptools          44.0.0     
six                 1.16.0     
sniffio             1.3.1      
starlette           0.37.2     
tomli               2.0.1      
tomlkit             0.12.4     
tornado             6.4        
tqdm                4.66.2     
typing-extensions   4.10.0     
tzdata              2024.1     
uc-micro-py         1.0.3      
urllib3             2.2.1      
uvicorn             0.29.0     
webencodings        0.5.1      
websockets          12.0       
xyzservices         2023.10.1  
zipp                3.18.1     

I am using Python 3.8 with Ubuntu 20.04 on Firefox 123.0.1

Environment

{
  "marimo": "0.3.3",
  "OS": "Linux",
  "OS Version": "5.15.0-100-generic",
  "Processor": "x86_64",
  "Python Version": "3.8.10",
  "Binaries": {
    "Chrome": "123.0.6312.58",
    "Node": "--"
  },
  "Requirements": {
    "black": "24.3.0",
    "click": "8.1.7",
    "jedi": "0.19.1",
    "pygments": "2.17.2",
    "pymdown-extensions": "10.7.1",
    "starlette": "0.37.2",
    "tomlkit": "0.12.4",
    "uvicorn": "0.29.0"
  }
}

Code to reproduce

import marimo

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

@app.cell
def __():
    import numpy as np
    import holoviews as hv
    hv.extension("bokeh")
    return hv, np

@app.cell
def __(hv, np):
    _t = np.linspace(0, 10, 101)
    hv.HoloMap(
        {f: hv.Curve((_t, np.sin(f * _t))) for f in [0.5, 0.75, 1.0]},
        kdims="frequency",
    )
    return

@app.cell
def __(hv, np):
    _t = np.linspace(0, 10, 101)
    _hm = hv.HoloMap(
        {f: hv.Curve((_t, np.sin(f * _t))) for f in [0.5, 0.75, 1.0]}, 
        kdims="frequency"
    )
    hv.Layout(_hm)
    return

@app.cell
def __(hv):
    from pprint import pprint
    pprint(hv.HoloMap.__mro__)
    return pprint,

if __name__ == "__main__":
    app.run()
mgunyho commented 6 months ago

Hi, I tested this with 0.5.2 now, and the HoloMap is displayed, but still not fully functioning: the slider does not appear.

image

Compare to the example in the documentation:

image

Or in Jupyter:

image

Having these sliders is the main reason for using Holoviews / HoloMap for me, and this is keeping me from using marimo as my main notebook.

For reference, here is the code for the MWE

import numpy as np
import holoviews as hv
hv.extension("bokeh")

_t = np.linspace(0, 10, 101)
hv.HoloMap(
    {f: hv.Curve((_t, np.sin(f * _t))) for f in [0.5, 0.75, 1.0]},
    kdims="frequency",
)

Also here's the output of marimo env, let me know if you need some more information.

{
  "marimo": "0.5.2",
  "OS": "Linux",
  "OS Version": "5.15.0-105-generic",
  "Processor": "x86_64",
  "Python Version": "3.8.10",
  "Binaries": {
    "Browser": "124.0.6367.207",
    "Node": "--"
  },
  "Requirements": {
    "click": "8.1.7",
    "importlib-resources": "6.4.0",
    "jedi": "0.19.1",
    "markdown": "3.6",
    "pymdown-extensions": "10.8.1",
    "pygments": "2.18.0",
    "tomlkit": "0.12.5",
    "uvicorn": "0.29.0",
    "starlette": "0.37.2",
    "websocket": "missing",
    "typing-extensions": "4.11.0",
    "black": "24.4.2"
  }
}
mscolnick commented 6 months ago

Right now, we render HoloMap by converting it to its backend (bokeh/plotly/matplotlib) and then rendering using those specific formatters (e.g. bokeh) to convert to HTML. I think the fix would be to render the HoloMap using panel which seems to be the one adding the slider. I am not familiar enough with that library to support rendering going from HoloMap -> HTML using panel, but if anyone is, we would welcome a contribution.

smutch commented 1 month ago

I can't claim to have a lot of expertise here, but I would be willing to put in some time to work on this if someone is able to provide some guidance to get me started.

I rely heavily on the holoviews ecosystem for data exploration (using e.g. datashader) and would love to be able to give marimo a real go as a jupyter replacement.

mscolnick commented 1 month ago

Hey @smutch, @philippjfr is actually going to look at helping us support Panel this week. He or I can report back if we could use some help here.

smutch commented 2 weeks ago

Hi @mscolnick & @philippjfr. Thanks for the great work on this. Really excited to see more of the Holoviz ecosystem make its way into Marimo.

I saw that the #2719 was merged and tried to give things a go but I found that holoviews plots are still not updating with panel sliders etc. Minimal working example below. Hopefully this is useful and apologies if it's known and on the roadmap - I recognize this is still early days!

import marimo

__generated_with = "0.9.14"
app = marimo.App(width="medium")

@app.cell
def __():
    import holoviews as hv
    import panel as pn
    import marimo as mo

    pn.extension()
    return hv, mo, pn

@app.cell
def __(hv):
    hv.DynamicMap(
        lambda v: hv.Curve([1, 2 * v, 3]).opts(title="Doesn't work"),
        kdims=hv.Dimension("v", range=(0, 5), default=1),
    )
    return

@app.cell
def __(pn):
    slider = pn.widgets.IntSlider(start=0, end=10, name="Does work")
    slider.rx() * "🎉"
    return (slider,)

@app.cell
def __(hv, pn):
    plot_slider = pn.widgets.IntSlider(start=0, end=10, name="Doesn't work either")
    pn.rx(lambda v: hv.Curve([1, 2 * v, 3]))(plot_slider)
    return (plot_slider,)

@app.cell
def __():
    return

if __name__ == "__main__":
    app.run()
smutch commented 4 days ago

Further investigation seems to suggest that the error is possibly due to embedded state and binary buffers as the error message (see below) is similar to https://github.com/holoviz/holoviews/issues/5601#issuecomment-1415306414.

Uncaught Error: Expected binary fragment but received text fragment
    _assume_binary https://cdn.bokeh.org/bokeh/release/bokeh-3.6.0.min.js:236
    _BUFFER_PAYLOAD https://cdn.bokeh.org/bokeh/release/bokeh-3.6.0.min.js:236
    consume https://cdn.bokeh.org/bokeh/release/bokeh-3.6.0.min.js:236
    w http://localhost:2718/assets/index-BLbE17NO.js:72
    n http://localhost:2718/assets/index-BLbE17NO.js:31
    broadcastMessage http://localhost:2718/assets/index-BLbE17NO.js:70
    broadcastMessage http://localhost:2718/assets/index-BLbE17NO.js:70
    D http://localhost:2718/assets/useMarimoWebSocket-x2rME3kL.js:21
    R http://localhost:2718/assets/useMarimoWebSocket-x2rME3kL.js:21
    e http://localhost:2718/assets/useMarimoWebSocket-x2rME3kL.js:14
    _addListeners http://localhost:2718/assets/useMarimoWebSocket-x2rME3kL.js:14
    _connect http://localhost:2718/assets/useMarimoWebSocket-x2rME3kL.js:14
    promise callback*_connect http://localhost:2718/assets/useMarimoWebSocket-x2rME3kL.js:14
    reconnect http://localhost:2718/assets/useMarimoWebSocket-x2rME3kL.js:14
    P http://localhost:2718/assets/useMarimoWebSocket-x2rME3kL.js:21
    sc http://localhost:2718/assets/index-BLbE17NO.js:31
    Pu http://localhost:2718/assets/index-BLbE17NO.js:31
    fu http://localhost:2718/assets/index-BLbE17NO.js:31
    Bi http://localhost:2718/assets/index-BLbE17NO.js:31
    Cu http://localhost:2718/assets/index-BLbE17NO.js:31
    Cu http://localhost:2718/assets/index-BLbE17NO.js:31
    lu http://localhost:2718/assets/index-BLbE17NO.js:31
    w http://localhost:2718/assets/index-BLbE17NO.js:22
    E http://localhost:2718/assets/index-BLbE17NO.js:22
    EventHandlerNonNull* http://localhost:2718/assets/index-BLbE17NO.js:22
    <anonymous> http://localhost:2718/assets/index-BLbE17NO.js:22
5 bokeh-3.6.0.min.js:236:1101

I've tried to debug this myself, but there is too much going on for me to understand where exactly the potential issue lies without further guidance I'm afraid.

mscolnick commented 4 days ago

huge thanks to @philippjfr for fixing this!