可以从降价链接控制 Jupyter 中的绘图图吗?

Posted

技术标签:

【中文标题】可以从降价链接控制 Jupyter 中的绘图图吗?【英文标题】:Possible to control a plotly diagram in Jupyter from markdown links? 【发布时间】:2021-10-26 15:00:43 【问题描述】:

我想使用 Plotly,在 Jupyter 中绘制一个相当简单的线图,比如:

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))

我找到了https://towardsdatascience.com/interactive-visualization-of-decision-trees-with-jupyter-widgets-ca15dd312084,它使用from ipywidgets import interactive 来获取交互式小部件,可以控制 Plotly 情节。

但是,我想要的是有 Markdown 链接,更改 Plotly 图。更具体地说,我想更改范围 - 所以如果我们继续 Plotly: How to set the range of the y axis? 或 How to force Plot.ly Python to use a given yaxis range? ,我希望有如下链接:

[click here for yrange of 0-1000](???) 将执行 figSubs['layout']['yaxis1'].update(range=[0, 1000], autorange=False) [click here for yrange of 950-1000](???) 将执行 figSubs['layout']['yaxis1'].update(range=[950, 1000], autorange=False)

在大部分 Plotly 设置代码都在 Python 中的 Jupyter Notebook 中,这样的事情是否可行?

【问题讨论】:

【参考方案1】:

嗯,显然,这是可能的 - 但是男孩,要获得所有这些信息,以便获得一个可行的示例是 困难 ...

首先,这些是我安装的版本:

$ 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

还有一些关键点:

虽然原则上,might 可以在 link to javascript 内使用 link to javascript - 实际上,它不是:Jupyter Markdown 单元格链接是 sanitized,特别是如果它们使用冒号,则不能href 也被传播给他们(在那里也尝试了 urlencoding,但没有成功)。 这意味着,唯一的解决方案是将您的 Jupyter 单元定义为代码(在本例中为 IPython 内核),然后使用“魔术”命令%%html,以便能够逐字输出 HTML 和 JavaScript 这里的主要技巧是使用Jupyter.notebook.kernel.execute JavaScript 函数,从 JavaScript 调用内核(这里是 IPython)代码;但是,请注意 Document the IPython.notebook.kernel.execute function · Issue #2219 · jupyter/notebook 有点缺乏; 我通过How to correctly use kernel.execute method of Jupyter notebook in Javascript (timing issues)? · Issue #4587 · jupyter/notebook 和IPython notebook ~ Using javascript to run python code? 得到了正确的使用示例(JavaScript Promiseiopub 回调)-> this gist: Run Python code from JavaScript in a Jupyter notebook 使用这种方法,无论何时使用 figure.show()pio.show(figure) 方法,JavaScript/Python 连接中的某些内容都会中断,并且图形将无法更新 - 即使不是这样,代码似乎也可以运行(如由console.log 痕迹证明);在这种情况下似乎唯一有效的功能是display(figure)

请注意,在此示例中:

有一个 Plotly/IPython 交互式下拉菜单,可独立更改绘图范围 有一个 Python 函数可以调整范围,这里既可以从 Python 也可以从 JavaScript(链接)调用它

话虽如此,这应该是一个工作示例;在第一个单元格中,使用生成图形的 Python 代码(因此,定义为代码单元格):


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

第二个单元格,本应是 OP 中所需的“Markdown links”单元格,再次必须是代码单元格,使用 %%html 魔术命令:

%%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>

【讨论】:

以上是关于可以从降价链接控制 Jupyter 中的绘图图吗?的主要内容,如果未能解决你的问题,请参考以下文章

Pycharm、Spyder、Jupyter notebook“弹出窗绘图”和“控制台绘图”设置

在jupyter-notebook中的markdown单元格边框 - 如何删除?

为 Jupyter 笔记本调整 Julia 内核中的绘图大小

使用 %matplotlib 笔记本时修复 Jupyter 笔记本中的绘图

jupyter实验室中的plot.ly离线模式不显示绘图

使用jupyter pycharm时如何获取pyplot的交互式绘图