Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to change the default position of a ipywidget slider to the side of a matplotlib figure?

I am looking into a way to change the position of the vertical IntSlider to the right of the matplotlib fig. Here is the code:

from ipywidgets import interact, fixed, IntSlider
import numpy as np
from matplotlib import pyplot as plt

%matplotlib notebook

fig = plt.figure(figsize=(8,4))

xs = np.random.random_integers(0, 5000, 50)
ys = np.random.random_integers(0, 5000, 50)

ax = fig.add_subplot(111)
scat, = ax.plot(xs, ys, 'kx', markersize=1)
ax.grid(which='both', color='.25', lw=.1)
ax.set_aspect('equal'), ax.set_title('Rotate')

def rotate(theta, xs, ys):
    new_xs = xs * np.cos(np.deg2rad(theta)) - ys * np.sin(np.deg2rad(theta))
    new_xs -= new_xs.min()
    new_ys = xs * np.sin(np.deg2rad(theta)) + ys * np.cos(np.deg2rad(theta))
    new_ys -= new_ys.min()
    return new_xs, new_ys

def update_plot(theta, xs, ys):
    new_xs, new_ys = rotate(theta, xs, ys)
    scat.set_xdata(new_xs), scat.set_ydata(new_ys)
    ax.set_xlim(new_xs.min() - 500, new_xs.max() + 500)
    ax.set_ylim(new_ys.min() - 500, new_ys.max() + 500)

w = interact(update_plot, 
             theta=IntSlider(min=-180, max=180, step=5,value=0, orientation='vertical'), 
             xs=fixed(xs), 
             ys=fixed(ys))

This is what I have:

enter image description here

This is what I want:

enter image description here

There might be a very simple way to do this, but I can't figure out myself.

I tried to place both the fig and the interactive widget into a VBox then wrapping the VBox with IPython.display and it didn't work.

Could not find a straight solution to this in the examples.

EDIT1:

ipywidgets provides an Output() class that captures the output area and use it inside the widget context.

I will try to figure out how to use it.

This is the object: https://github.com/jupyter-widgets/ipywidgets/blob/master/ipywidgets/widgets/widget_output.py

like image 650
Bruno Ruas De Pinho Avatar asked Mar 08 '23 11:03

Bruno Ruas De Pinho


2 Answers

I decided to try this example using bqplot instead of matplotlib and it turned out to be way more simple.

import numpy as np
from bqplot import pyplot as plt
from IPython.display import display
from ipywidgets import interactive, fixed, IntSlider, HBox, Layout

plt.figure(min_aspect_ratio=1, max_aspect_ratio=1)

xs = np.random.randint(0, 5000 + 1, 100)
ys = np.random.randint(0, 5000 + 1, 100)

scat = plt.scatter(xs, ys)

def rotate(theta, xs, ys):
    new_xs = xs * np.cos(np.deg2rad(theta)) - ys * np.sin(np.deg2rad(theta))
    new_xs -= new_xs.min()
    new_ys = xs * np.sin(np.deg2rad(theta)) + ys * np.cos(np.deg2rad(theta))
    new_ys -= new_ys.min()
    return new_xs, new_ys

def update_plot(theta, xs, ys):
    new_xs, new_ys = rotate(theta, xs, ys)
    scat.x, scat.y = new_xs, new_ys

w = interactive(update_plot, 
             theta=IntSlider(min=-180, max=180, step=5,value=0, orientation='vertical'), 
             xs=fixed(xs), 
             ys=fixed(ys))

box_layout = Layout(display='flex', flex_flow='row', justify_content='center', align_items='center')
display(HBox([plt.current_figure(), w], layout=box_layout))

bqplot is designed to be an interactive widget. This is way it could be simply added to the output without having to wrap it into the update_plot function.

From the bqplot documentation:

In bqplot, every single attribute of the plot is an interactive widget. This allows the user to integrate any plot with IPython widgets to create a complex and feature rich GUI from just a few simple lines of Python code.

I will keep the accepted James answer because it answered the original question.

like image 187
Bruno Ruas De Pinho Avatar answered Mar 11 '23 04:03

Bruno Ruas De Pinho


You can solve this by creating an interactive widget and then loading the children into a HBox. The child widgets of an interactive follow this convention; (widget_0, widget_1 ..., output) where the last member of the tuple is the output of the control widgets. You can define the layout of the HBox before or after you declare it. Read more on the layouts available here.

The following solution has a couple caveats; the graph may not show up initially you may have to tweak the control before it appears, second when using the %matplotlib notebook magic the control may lead to a lot of flickering on when updating. Other than that I think that this should work like you want;

from IPython.display import display
from ipywidgets import interactive, fixed, IntSlider, HBox, Layout
import numpy as np
import matplotlib.pylab as plt
%matplotlib notebook

def rotate(theta, xs, ys):
    new_xs = xs * np.cos(np.deg2rad(theta)) - ys * np.sin(np.deg2rad(theta))
    new_xs -= new_xs.min()
    new_ys = xs * np.sin(np.deg2rad(theta)) + ys * np.cos(np.deg2rad(theta))
    new_ys -= new_ys.min()
    return new_xs, new_ys

def update_plot(theta, xs, ys):
    fig = plt.figure(figsize=(8,4))
    ax = fig.add_subplot(111)
    scat, = ax.plot(xs, ys, 'kx', markersize=1)
    ax.grid(which='both', color='.25', lw=.1)
    ax.set_aspect('equal'), ax.set_title('Rotate')
    new_xs, new_ys = rotate(theta, xs, ys)
    scat.set_xdata(new_xs), scat.set_ydata(new_ys)
    ax.set_xlim(new_xs.min() - 500, new_xs.max() + 500)
    ax.set_ylim(new_ys.min() - 500, new_ys.max() + 500)

xs = np.random.randint(0, 5000, 50)
ys = np.random.randint(0, 5000, 50)
w = interactive(update_plot,
                theta=IntSlider(min=-180, max=180, step=5, value=0,orientation='vertical'), 
                xs=fixed(xs),
                ys=fixed(ys))

# Define the layout here.
box_layout = Layout(display='flex', flex_flow='row', justify_content='space-between', align_items='center')

display(HBox([w.children[1],w.children[0]], layout=box_layout))

Update:

This is Jason Grout's solution from the ipywidgets gitter.

from IPython.display import display, clear_output
from ipywidgets import interact, fixed, IntSlider, HBox, Layout, Output, VBox
import numpy as np 
import matplotlib.pyplot as plt
%matplotlib inline

def rotate(theta, xs, ys):
    new_xs = xs * np.cos(np.deg2rad(theta)) - ys * np.sin(np.deg2rad(theta))
    new_xs -= new_xs.min()
    new_ys = xs * np.sin(np.deg2rad(theta)) + ys * np.cos(np.deg2rad(theta))
    new_ys -= new_ys.min()
    return new_xs, new_ys

out = Output(layout={'width': '300px', 'height': '300px'})

def update_plot(change): 
    theta = change['new'] # new slider value 
    with out: 
        clear_output(wait=True)
        fig = plt.figure(figsize=(4,4))
        ax = fig.add_subplot(111)
        scat, = ax.plot(xs, ys, 'kx', markersize=1)
        ax.grid(which='both', color='.25', lw=.1)
        ax.set_aspect('equal'), ax.set_title('Rotate')
        new_xs, new_ys = rotate(theta, xs, ys) 
        scat.set_xdata(new_xs), scat.set_ydata(new_ys)
        ax.set_xlim(new_xs.min() - 500, new_xs.max() + 500)
        ax.set_ylim(new_ys.min() - 500, new_ys.max() + 500)
        plt.show()

xs = np.random.randint(0, 5000, 50) 
ys = np.random.randint(0, 5000, 50) 

slider = IntSlider(min=-180, max=180, step=5, value=0, orientation='vertical') 
slider.observe(update_plot, 'value')
update_plot({'new': slider.value}) 
display(HBox([out, slider]))
like image 27
James Draper Avatar answered Mar 11 '23 04:03

James Draper