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

init_notebook_mode(connected=False) and pio.renderers.default='notebook' should not include full plotly.js on each call, but should check if already loaded #1531

Closed
ociule opened this issue Apr 19, 2019 · 8 comments

Comments

@ociule
Copy link

ociule commented Apr 19, 2019

The current js generated by init_notebook_mode(connected=False) is naive: it includes a require.undef("plotly") to remove plotly and the full plotlyjs code to create it again. This adds 6MB for each call to init_notebook_mode to the notebook size. It also makes opening the notebook slower.

Including it once should be enough.

def iplot_my_thing():
    init_notebook_mode(connected=False)
    xs = [0, 1, 2, 3, 4]
    ys = [4, 3, 2, 1, 0]
    trace = go.Scatter(
        x = xs,
        y = ys,
        mode = 'markers'
    )
    data = [trace]
    plotly.offline.iplot(data)

Now just call iplot_my_thing twice, in two different jupyter cells. Save the notebook after each call and check its size.

The code shown wraps init_notebook_mode and iplot. This is necessary so calls to iplot_my_thing work under all circumstances, including users that load a notebook without restarting the kernel and not running the cell containing init_notebook_mode.

If this js fix is implemented, plotly.offline.iplot could just call init_notebook_mode directly and init_notebook_mode would not be needed in the public plotly API which would be a better UX.

@jonmmease
Copy link
Contributor

Hi @ociule,

Thanks for the report. This behavior should be possible with the new renderers subsystem that was recently merged in 3.8.0. See #1474 for more info. Note this is still considered somewhat experimental and isn't documented yet. That will wait until version 4.0.

Basically, you would set the default renderer to 'notebook' once at the top of the notebook. This doesn't immediately cause plotly.js to be loaded in the notebook, that will happen lazily (and only once) the first time the figure is displayed.

Something like

import plotly.graph_objs as go
import plotly.io as pio
pio.renderers.default = 'notebook'

def my_thing():
    xs = [0, 1, 2, 3, 4]
    ys = [4, 3, 2, 1, 0]
    trace = go.Scatter(
        x = xs,
        y = ys,
        mode = 'markers'
    )
    data = [trace]
    fig = go.Figure(data=data)
    pio.show(fig)

    # First call causes plotly.js to be loaded in the notebook
    my_thing()

    # Subsequent calls don't load plotly.js again
    my_thing()

The nice thing about this approach is that you can completely change rendering context without modifying my_thing. If you change pio.renderers.default = 'jupyterlab' for use in JupyterLab then plotly.js won't be loaded into the notebook at all.

Does this work for your usecase?

@ociule
Copy link
Author

ociule commented May 14, 2019

This looks really good, testing it soon.

I'm only concerned about how the decision to load plotly.js or not is made when pio.show is called. If it's made in Python, not js, then it can't be aware of the state of the js environment. That's necessary for my usecase, unfortunately: people tend to keep a running jupyter notebook that they reopen, which does not reload the js.

@ociule
Copy link
Author

ociule commented May 14, 2019

This is fantastic, and works as expected. It reads js env state to check if plotly.js is already loaded, so calling pio.show reliably loads plotly.js on the first call in a new browser tab (new js env), even if the notebook was running (old python env).

This is great news and covers my use case.

@ociule ociule closed this as completed May 14, 2019
@ociule ociule reopened this May 15, 2019
@ociule
Copy link
Author

ociule commented May 15, 2019

After further testing, I have to correct my previous comment: it does not seem to check js env state.

Here's a conclusive test:
Copy paste this into a new notebook:

import plotly.graph_objs as go
import plotly.io as pio

pio.renderers.default = 'notebook'

# And the following in a *NEW* cell

def my_thing():
    
    xs = [0, 1, 2, 3, 4]
    ys = [4, 3, 2, 1, 0]
    trace = go.Scatter(
        x = xs,
        y = ys,
        mode = 'markers'
    )
    data = [trace]
    fig = go.Figure(data=data)
    pio.show(fig)

my_thing()

When executing the two cells sequentially, this works as expected.
But if one clears cell output, saves, closes and reopens the notebook - without stopping the python kernel, and we reexecute the second cell, this behaves exactly as init_notebook_mode and offline.iplot: it shows a blank space only, no plot.

Afaik I can investigate through my tests, pio.renderers.default = 'notebook' behaves like init_notebook_mode and show behaves like iplot from this point of view.

Moving pio.renderers.default = 'notebook' into my_thing is bad, as it adds plotly.js to the notebook when it's called. So every call to my_thing adds plotly.js and 3MB to the notebook. Same as init_notebook_mode(connected=False).

It would be a much better solution if calling pio.renderers.default = 'notebook' checked if plotly.js was already loaded. Of course, only the js code can do that. This would allow me to move pio.renderers.default into my_thing, making my_thing load plotly.js if needed. There's nothing one can do python side to fix this, again, it's a change to the js code that's needed. A simple proof of this is, in the scenario above that ends with a blank space, we can save a state variable in Python to know that we've called pio.renderers.default = 'notebook'. But the following steps, clearing the output and reloading the notebook clears the js env, resulting in a missing plotly.js, while the python process can't know that.

@ociule
Copy link
Author

ociule commented May 15, 2019

To help others, to fix my above issue temporarily, I'm using pio.renderers.default = 'notebook_connected' inside my_thing. This is equivalent to calling init_notebook_mode(connected=True) inside my_thing. This means one has to have an internet connection for the notebook to work, rendering the existence of connected=False moot.

@ociule ociule changed the title init_notebook_mode(connected=False) should not include full plotly.js on each call init_notebook_mode(connected=False) and pio.renderers.default='notebook' should not include full plotly.js on each call, but should check if already loaded May 15, 2019
@ociule
Copy link
Author

ociule commented May 15, 2019

My tests were on plotly 3.9.0, btw.

@jonmmease
Copy link
Contributor

Hi @ociule,

Yeah, the only difference between pio.renderers.default = 'notebook' and init_notebook_mode(connected=False) is that the plotly.js bundle is loaded into the notebook lazily, the first time show is called after setting the default renderer.

You're right that it's not really possible, as far as I'm aware, to check whether plotly.js is loaded into the browser from Python. In addition to the notebook_connected workflow, here are a couple of options/thoughts.

  1. Use JupyterLab and the @jupyterlab/plotly-extension extension and don't call init_notebook_mode at all. In this case, plotly.js is never loaded into the notebook, instead plotly.js is made available in the JupyterLab extension.
  2. Display figures as FigureWidget objects. https://plot.ly/python/figurewidget/. This works in either the classic notebook or JupyterLab and also does note load plotly.js directly into the notebook, because plotly.js is probided by the plotlywidget extension.
  3. Use the iframe renderer. See https://community.plot.ly/t/plotly-notebook-very-slow-render-with-many-graphs/16861/7. This works by saving figures as separate html files and displaying them in the notebook using iframes. So again, plotly.js is not loaded into the notebook.

@gvwilson
Copy link
Contributor

Hi - we are currently trying to tidy up Plotly's public repositories to help us focus our efforts on things that will help users most. Since this issue has been sitting for several years, I'm going to close it. If it's still a concern, we'd be grateful if you could open a new issue (with a short reproducible example if appropriate) so that we can add it to our backlog. Thanks for your help - @gvwilson

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

3 participants