1

I would like to use Plotly, to draw a rather simple line plot in Jupyter, say something like:

import plotly.graph_objs as go
from plotly.subplots import make_subplots

figSubs = go.FigureWidget(
    make_subplots(rows=2, cols=1, specs = [[{}], [{}]], vertical_spacing = 0.05)
)
figSubs.add_trace(
    go.Scatter(mode='lines+markers', x=[0, 1, 2, 3, 4], y=[0, 1000, 990, 980, 970], name='Test', marker={'color': 'red'}, xaxis="x1"),
    row=1, col=1
)
figSubs.update_layout(margin=go.layout.Margin(l=20,t=10,b=10,pad=4))

I have found https://towardsdatascience.com/interactive-visualization-of-decision-trees-with-jupyter-widgets-ca15dd312084, which uses from ipywidgets import interactive to get interactive widgets, that can control the Plotly plot.

However, what I would like instead, is to have Markdown links, change the Plotly diagram. More specifically, I want to change the range - so if we go along Plotly: How to set the range of the y axis? or How to force Plot.ly Python to use a given yaxis range? , I'd like to have links like:

  • [click here for yrange of 0-1000](???) which would execute figSubs['layout']['yaxis1'].update(range=[0, 1000], autorange=False)
  • [click here for yrange of 950-1000](???) which would execute figSubs['layout']['yaxis1'].update(range=[950, 1000], autorange=False)

Is something like this possible in a Jupyter notebook, where most of the Plotly setup code is in Python?

sdbbs
  • 4,270
  • 5
  • 32
  • 87

1 Answers1

2

Well, apparently, it is possible - but boy, it was difficult to get all this info in place, so as to get a working example ...

First, these are the versions I have installed:

$ python3 --version
Python 3.8.10
$ pip3 list | grep 'jupyter \|nbextensions\|plotly'
jupyter                           1.0.0
jupyter-contrib-nbextensions      0.5.1
jupyter-nbextensions-configurator 0.4.1
plotly                            5.2.2

And some key points:

Note that in this example:

  • There is a Plotly/IPython interactive dropdown, which independently changes the range of the plot
  • There is a Python function that adjusts the range, and here it is being called both from Python and from JavaScript (links)

That being said, here is what should be a working example; in the first cell, with the Python code that produces the graphs (so, defined as a code cell):


import plotly.graph_objs as go
import pandas as pd
from plotly.subplots import make_subplots
# import plotly.io as pio # https://plotly.com/python/getting-started-with-chart-studio/
from IPython.display import display, HTML

df = pd.read_csv("https://raw.githubusercontent.com/plotly/datasets/master/volcano.csv")

figSubs = go.FigureWidget(
    make_subplots(rows=2, cols=1, specs = [[{}], [{}]], vertical_spacing = 0.05)
)
figSubs.add_trace(
    go.Scatter(mode='lines+markers', x=df["V1"], y=df["V55"], name='Test1', marker={'color': 'red'}, xaxis="x1"),
    row=1, col=1
)
figSubs.add_trace(
    go.Scatter(mode='lines+markers', x=[0, 1, 2, 3, 4], y=[0, 990, 980, 970, 960], name='Test21', marker={'color': 'blue'}, xaxis="x1"),
    row=2, col=1
)
figSubs.add_trace(
    go.Scatter(mode='lines+markers', x=[0, 1, 2, 3, 4], y=[0, 980, 970, 960, 950], name='Test22', marker={'color': 'violet'}, xaxis="x1"),
    row=2, col=1
)
figSubs.update_layout(margin=go.layout.Margin(l=20,t=10,b=10,pad=4))
figSubs.update_yaxes(zeroline=True,showline=True,zerolinewidth=1,zerolinecolor="#000", row=1, col=1)
figSubs.update_yaxes(zeroline=True,showline=True,zerolinewidth=1,zerolinecolor="#000", row=2, col=1)

# Add dropdown
figSubs.update_layout(
    updatemenus=[
        dict(
            buttons=list([
                dict(
                    args=[{"yaxis.range": [0, 1000], "yaxis.autorange": False, "row": 1, "col": 1}],
                    label="[0, 1000]",
                    method="relayout"
                ),
                dict(
                    args=[{"yaxis.range": [100, 200], "yaxis.autorange": False}],
                    label="[100, 200]",
                    method="relayout"
                )
            ]),
            direction="down",
            pad={"r": 10, "t": 10},
            showactive=True,
            x=0.1,
            xanchor="left",
            y=1.12,
            yanchor="top"
        ),
    ]
)

# the Python function to adjust the Y range of the first plot - which is also called from JavaScript
def PsetMyYRange(ymin, ymax, dodraw=True):
    figSubs['layout']['yaxis'].update(range=[ymin, ymax], autorange=False)
    #figSubs.update_yaxes(range=[ymin, ymax]) # changes both!
    #figSubs.update_layout(margin=go.layout.Margin(l=200,t=100,b=100,pad=40))
    #figSubs.show() # do NOT call this, else cannot manupulate the plot via JavaScript calls of this function later on!
    if dodraw:
        display(figSubs) #MUST have this to update the plot from JavaScript->Python; note with Plotly in a Jupyter extension, there is no `Plotly` javascript object accessible on the page! 
    return "{}; {}; {}".format(figSubs, ymin, ymax) # just for the console.log printout

PsetMyYRange(110,120,dodraw=False) # call once to make sure it also works from here; but don't "draw", else we get two plots

#figSubs.show()    # NOTE: .show() at end, will prevent the PsetMyYRange being able to redraw!
#pio.show(figSubs) # NOTE: also pio.show() at end, will prevent the PsetMyYRange being able to redraw!
display(figSubs)   # ... display() works fine however

And the second cell, which would have been the "Markdown links" cell as desired in the OP, again has to be a Code cell, with the %%html magic command:

%%html
<script type='text/javascript'>
window.executePython = function(python) {
    return new Promise((resolve, reject) => {
        var callbacks = {
            iopub: {
                output: (data) => resolve(data.content.text.trim())
            }
        };
        Jupyter.notebook.kernel.execute(`print(${python})`, callbacks);    
    });
}

function setMyYRange(ymin, ymax){
  // NONE of the below quite works - we must call via Promise:
  //objstring = IPython.notebook.kernel.execute("global figSubs; print(figSubs)");
  //prevstring = IPython.notebook.kernel.execute("print(Jupyter.notebook.get_prev_cell())");
  //runstring = "global figSubs; figSubs['layout']['yaxis'].update(range=["+ymin+", "+ymax+"], autorange=False)";
  //console.log("setMyYRange " + ymin + " " + ymax + " ... " + objstring + " ... " + prevstring + " ... " + runstring);
  //IPython.notebook.kernel.execute(runstring);

  // the only thing needed for the code to work:
  window.executePython("PsetMyYRange("+ymin+","+ymax+")")
    .then(result => console.log(result));
}
</script>

<a onclick="javascript:setMyYRange(0,1000);" href="javascript:void(0);">here (0,1000)</a>
<a onclick="javascript:setMyYRange(100,200);" href="javascript:void(0);">here (100,200)</a>
sdbbs
  • 4,270
  • 5
  • 32
  • 87