jupyter / nbconvert

Jupyter Notebook Conversion
https://nbconvert.readthedocs.io/
BSD 3-Clause "New" or "Revised" License
1.72k stars 564 forks source link

Altair charts in ipywidgets.Output #1948

Open theeldermillenial opened 1 year ago

theeldermillenial commented 1 year ago

I'm trying to render an Altair chart in an ipywidget.Output widget. Altair charts properly render with Voila, but not nbconvert.

Here is a test notebook I created to replicate the issue.

# test.ipynb
import ipywidgets as widgets
import pandas as pd

import altair as alt

source = pd.DataFrame({"category": [1, 2, 3, 4, 5, 6], "value": [4, 6, 10, 3, 7, 8]})

output_donut = widgets.Output()

with output_donut:
    donut = alt.Chart(source).mark_arc(innerRadius=50).encode(
        theta=alt.Theta(field="value", type="quantitative"),
        color=alt.Color(field="category", type="nominal"),
    )
    donut.display()

output_donut

If I run the cell, the Altair chart displays properly.

Rendering with voila renders the chart:

voila test.ipynb

When I run nbconvert, it looks like the <div> along with the <script> gets rendered as text. I ran the following to try and somewhat replicate what Voila does.

jupyter nbconvert --to HTML --no-input --execute test.ipynb

I have read through a few related issues, including some weird display ordering. I haven't found anything that works properly. It looks like the other solutions might have worked in older versions of nbconvert. Here are the versions I'm using for this example.

altair==4.2.2
nbconvert==7.2.9
pandas==1.5.3
theeldermillenial commented 1 year ago

I just tested with plotly as well with the same results as Altair. This may be related to #1657

theeldermillenial commented 1 year ago

Digging through the source code and the HTML generated by nbconvert, it seems as though there is an issue in how the widget state is being handled. Basically, this will be a problem for any visualization library that is not generating a static image for it's output. The widget is being stored in the output HTML as JSON, but for some reason the text/html element is rendering the output data as a string rather than just raw. This does not appear to be an issue within the nbconvert package itself, rather it seems like there is an issue in the way the npm library is handling the JSON.

I could be reading this wrong. I'm going to continue digging through the source code to find a workaround.

theeldermillenial commented 1 year ago

It does seem that the issue is fundamentally how charts are being dumped to JSON and subsequently rendered. I was testing out to see if Bokeh has the same issues as Altair and Plotly. I found a new update to a Bokeh package, and it was possible to render the chart using a combination of them dumping the raw JavaScript into the main document and me running a post-HTML generation script to remove URL encoding from the JSON string.

https://github.com/bokeh/jupyter_bokeh/pull/178

mherrmann3 commented 1 year ago

I have a similar problem with pandas DataFrames within ipywidgets: in the HTML file, they are simply rendered as

<div> <style scoped> .dataframe tbody tr th:only-of-type
...
</style> <table border="1" class="dataframe"> <thead> <tr style="text-align: right;"> { vertical-align: middle; }
...
</table> <p>35 rows × 35 columns</p> </div>

It's weird, because plotly's FigureWidget render well within ipywidgets. 🤷‍♀️ It's likely due to the new ipywidgets v8, because plotly needed to adapt certain things to make it work: https://github.com/plotly/plotly.py/pull/3930

Not sure if the issue should be fixed on the side of pandas, ipywidgets (v8), or nbconvert (v7). It once worked well (ipywidgets v7 & nbconvert v6). I found no remedy so far, not even a hack like you did.