5

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

  • Did you happen to try my solution below? – James Draper Aug 03 '17 at 16:27
  • 1
    Yes, I think you are on the right track. If you find a way to make it work by unwrapping the construction of the figure and axis from the update_plot function I will accept you answer. Take a lot at the Output() object. I think this will solve our problem. – Bruno Ruas De Pinho Aug 03 '17 at 16:43

2 Answers2

4

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.

  • This seems to not work with 0.11.9, any clues why? Display litterally remains blank after running this snippet. – cvanelteren Oct 25 '19 at 09:38
3

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]))
James Draper
  • 5,110
  • 5
  • 40
  • 59
  • 1
    You partially solved the problem, but wrapping the construction of the matplotlib figure inside the update_plot function resulted in this bad refreshing behavior and, also, the figure is built when you first update the slider. There might be a way to capture the output with the matplotlib inside it. Check this: https://github.com/jupyter-widgets/ipywidgets/blob/master/ipywidgets/widgets/widget_output.py – Bruno Ruas De Pinho Aug 03 '17 at 16:29
  • @BrunoRuasDePinho the flicker may have something to do with the browser that you are or what version of ipywidgets you are working with using as noted in [this issue](https://github.com/jupyter-widgets/ipywidgets/issues/1532). You may see marginal improvements using `%matplotlib inline` instead of `%matplotlib notebook` but you use some of the interactive functionality. So IDK this may be as good as it gets for right now. I'll keeping looking but at least throw me an upvote for the partial soln :) – James Draper Aug 03 '17 at 16:46
  • 1
    Sure James! Thanks!! – Bruno Ruas De Pinho Aug 03 '17 at 16:49
  • No prob! Also the [ipywidgets](https://gitter.im/jupyter-widgets/Lobby) is a another great resource for solving related problems. – James Draper Aug 03 '17 at 16:55