Reputation: 5468
Using Jupyter Notebook (v. 6.0.0) via Anaconda (Navigator v. 1.9.7) on Windows 10. Consider a situation where I have a ipywidgets
slider on top, then a whole lotta text in between, then a graph on the botton that responds to the slider. Here is an image of such a webpage in Firefox, zoomed out so that it can be shown in full:
Clearly, this is difficult to use - and what I would prefer in this case, is to "detach" the widget that contains the slider, and drag it down to approx where the graph is, then manipulate the slider; in that case, I also would not have to use the webpage rendering zoomed out. Then when done manipulating, I'd like to click a button, and have the widget reattached to where it was.
However, I am not sure, how could I implement this in Jupyter notebook with ipywidgets
? The only thing close to this I've found is [Question] Maintain position of floating widget when browser tab is closed · Issue #2520 · jupyter-widgets/ipywidgets, which seems to imply that intervention in JavaScript is needed.
Here is the code to recreate the screenshot (there is already a "Detach" button in the widget containing the slider, except it does nothing else but change its text):
Cell 1 - Python 3:
import plotly.graph_objs as go
import numpy as np
from ipywidgets import widgets, Layout
from IPython.display import display
from IPython.display import Markdown as md
inSlider = widgets.FloatSlider(
value=1.0,
min=0.0,
max=10.0,
step=0.01,
description='In:',
continuous_update=True
)
output2 = widgets.Output()
def on_detButton_click(b):
if b.description == "Detach": b.description = "Attach"
else: b.description = "Detach"
detButton = widgets.Button(description="Detach")
detButton.on_click(on_detButton_click)
myctlwidget = widgets.VBox([widgets.HBox([inSlider, detButton]), output2])
display(myctlwidget)
Cell 2 is Markdown - pasted at end of this post;
Cell 3 - Python 3:
fig = go.FigureWidget( layout=go.Layout() )
fig.add_trace(go.Scatter(x=[0,10], y=[0,10],
mode='lines',
name='Test plot'))
# set range to prevent autoranging when slider sets a new value
fig.update_yaxes(range=[0, 11])
def update_graph(change):
with fig.batch_update():
curval = inSlider.value
fig.data[0].y=[0, curval]
inSlider.observe(update_graph, names="value")
widgets.VBox([fig])
Cell 2 - Markdown (note two spaces at end of each first line, to introduce a line break)
Test paragraph 0
Some text 0, 0.... some text 0
Test paragraph 1
Some text 1, 1.... some text 1
Test paragraph 2
Some text 2, 2.... some text 2
Test paragraph 3
Some text 3, 3.... some text 3
Test paragraph 4
Some text 4, 4.... some text 4
Test paragraph 5
Some text 5, 5.... some text 5
Test paragraph 6
Some text 6, 6.... some text 6
Test paragraph 7
Some text 7, 7.... some text 7
Test paragraph 8
Some text 8, 8.... some text 8
Test paragraph 9
Some text 9, 9.... some text 9
Test paragraph 10
Some text 10, 10.... some text 10
Test paragraph 11
Some text 11, 11.... some text 11
Test paragraph 12
Some text 12, 12.... some text 12
Test paragraph 13
Some text 13, 13.... some text 13
Test paragraph 14
Some text 14, 14.... some text 14
Test paragraph 15
Some text 15, 15.... some text 15
Test paragraph 16
Some text 16, 16.... some text 16
Test paragraph 17
Some text 17, 17.... some text 17
Test paragraph 18
Some text 18, 18.... some text 18
Test paragraph 19
Some text 19, 19.... some text 19
Test paragraph 20
Some text 20, 20.... some text 20
Test paragraph 21
Some text 21, 21.... some text 21
Test paragraph 22
Some text 22, 22.... some text 22
Test paragraph 23
Some text 23, 23.... some text 23
Test paragraph 24
Some text 24, 24.... some text 24
Test paragraph 25
Some text 25, 25.... some text 25
Test paragraph 26
Some text 26, 26.... some text 26
Test paragraph 27
Some text 27, 27.... some text 27
Test paragraph 28
Some text 28, 28.... some text 28
Test paragraph 29
Some text 29, 29.... some text 29
Test paragraph 30
Some text 30, 30.... some text 30
Upvotes: 2
Views: 2233
Reputation: 5468
Ok, I think I got it working - thankfully, this version of Jupyter notebook automatically loads jquery-ui
, so I can use .draggable()
...
Note that:
If anyone comes up with a solution for the (second) problem, I'd love to hear it.
In the meantime, the workaround is to place a plain Markdown cell, below the cell output that contains the plotly diagram, which will then be able to properly "host" the dragged slider element. Here is how the fix looks like in this case (the little "(h)" is the handle):
Here is my fix:
Cell 1 - Python 3:
import plotly.graph_objs as go
import numpy as np
from ipywidgets import widgets, Layout
from IPython.display import display, Javascript
from IPython.display import Markdown as md
inSlider = widgets.FloatSlider(
value=1.0,
min=0.0,
max=10.0,
step=0.01,
description='In:',
continuous_update=True
)
output2 = widgets.Output()
def on_detButton_click(b):
if b.description == "Detach":
b.description = "Attach"
# must wrap in display() - Javascript() call on its own does not effectuate!
# https://stackoverflow.com/questions/15193640/jquery-ui-draggable-reset-to-original-position
# NOTE: in spite of zIndex manipulation: do NOT place dragged widget over textarea (it will lose focus),
# also, it will be under a plot.ly diagram regardless!
display(Javascript("""
var $dragwidget = $("div.myctlwidget").parent();
$dragwidget.data({
'originalLeft': $dragwidget.css('left'),
'originalTop': $dragwidget.css('top'),
'originalZindex': $dragwidget.css('z-index')
});
$dragwidget.css({ 'z-index': 5000});
$dragwidget.draggable({ disabled: false, handle: "div.myctlhandle" });
"""))
else:
b.description = "Detach"
display(Javascript("""
var $dragwidget = $("div.myctlwidget").parent();
$dragwidget.draggable({ disabled: true });
$dragwidget.css({
'left': $dragwidget.data('originalLeft'),
'top': $dragwidget.data('originalTop'),
'z-index': $dragwidget.data('originalZindex'),
});
"""))
detButton = widgets.Button(description="Detach")
detButton.on_click(on_detButton_click)
# NB: Button still is visually clickable, even when disabled :(
handleButton = widgets.Label("(h)", _dom_classes = ['myctlhandle'])
myctlwidget = widgets.VBox([widgets.HBox([inSlider, detButton, handleButton])], _dom_classes = ['myctlwidget'])
display(myctlwidget)
#myctlwidget._dom_classes = ['myctlwidget'] # nowork; only added as argument to VBox _dom_classes works!
#print(myctlwidget._dom_classes)
Cell 3 - Python 3:
fig = go.FigureWidget( layout=go.Layout() )
fig.add_trace(go.Scatter(x=[0,10], y=[0,10],
mode='lines',
name='Test plot'))
# set range to prevent autoranging
fig.update_yaxes(range=[0, 11])
def update_graph(change):
with fig.batch_update():
curval = inSlider.value
fig.data[0].y=[0, curval]
inSlider.observe(update_graph, names="value")
#spacer = widgets.VBox([widgets.Text(": :"), widgets.Label(": :")]) # nope
display(md("Markdown here does NOT work as spacer!\n\n... does NOT work as spacer!"))
display(widgets.VBox([fig]))
Upvotes: 1