# -*- coding: utf-8 -*-
import utool as ut
from .. import plottool as pt
from . import abstract_interaction
from . import interact_helpers as ih
[docs]def check_if_subinteract(func):
try:
if ut.VERBOSE:
print('Checking if subinteraction')
print('func = %r' % (func,))
is_sub = issubclass(func, abstract_interaction.AbstractInteraction)
except TypeError:
is_sub = False
if ut.VERBOSE:
if is_sub:
print('... yup')
else:
print('... nope')
return is_sub
[docs]class ExpandableInteraction(abstract_interaction.AbstractInteraction):
"""
Append a list of functions that draw plots and this interaction will plot
them in appropriate subplots and let you click on them to zoom in.
Args:
fnum (int): figure number(default = None)
_pnumiter (None): (default = None)
interactive (None): (default = None)
**kwargs: nRows, nCols
CommandLine:
python -m wbia.plottool.interactions --exec-ExpandableInteraction --show
Example:
>>> # DISABLE_DOCTEST
>>> from wbia.plottool.interactions import * # NOQA
>>> import numpy as np
>>> import wbia.plottool as pt
>>> inter = pt.interactions.ExpandableInteraction()
>>> inter.append_plot(ut.partial(pt.plot_func, np.sin, stop=np.pi * 2))
>>> inter.append_plot(ut.partial(pt.plot_func, np.cos, stop=np.pi * 2))
>>> inter.append_plot(ut.partial(pt.plot_func, np.tan, stop=np.pi * 2))
>>> inter.start()
>>> pt.show_if_requested()
"""
def __init__(self, fnum=None, _pnumiter=None, interactive=None, **kwargs):
self.nRows = kwargs.get('nRows', None)
self.nCols = kwargs.get('nCols', None)
self._pnumiter = _pnumiter
self.pnum_list = []
self.interactive = interactive
self.ishow_func_list = []
self.func_list = []
self.fnum = pt.ensure_fnum(fnum)
self.fig = None
autostart = False
super(ExpandableInteraction, self).__init__(autostart=autostart, **kwargs)
def __iadd__(self, func):
"""inplace apppend a plot function"""
self.append_plot(func)
return self
[docs] def append_plot(self, func, pnum=None, ishow_func=None, px=None):
"""
Register a plotting function
Args:
func (callable): must take fnum and pnum as keyword arguments.
pnum (tuple): plot num / gridspec. Defaults based on append order
ishow_func (callable): an interactive version of func
px (int): None or a plot index into (nRows, nCols)
"""
if pnum is None:
if px is None:
if self._pnumiter is None:
pnum = None
else:
pnum = self._pnumiter()
else:
if isinstance(px, tuple):
rx, cx = px
px = (rx * self.nCols) + cx + 1
pnum = (self.nRows, self.nCols, px)
self.pnum_list.append(pnum)
self.func_list.append(func)
self.ishow_func_list.append(ishow_func)
[docs] def append_partial(self, func, *args, **kwargs):
"""
Register a plotting function with default arguments
Args:
func (callable): plotting function (does NOT need fnum/pnum).
*args: args to be passed to func
**kwargs: kwargs to be passed to func
"""
def _partial(fnum=None, pnum=None):
pt.figure(fnum=fnum, pnum=pnum)
func(*args, **kwargs)
self.append_plot(_partial)
# pnum = None
# if pnum is None:
# if self._pnumiter is None:
# pnum = None
# else:
# pnum = self._pnumiter()
# self.pnum_list.append(pnum)
# self.func_list.append(_partial)
# self.ishow_func_list.append(None)
[docs] def show_page(self):
if self.fig is None:
raise AssertionError('fig is None, did you run interction.start()?')
import wbia.plottool as pt
fig = ih.begin_interaction('expandable', self.fnum)
if not any(self.pnum_list):
# If no pnum was given, find a set that agrees with constraints
self.nRows, self.nCols = pt.get_num_rc(
len(self.pnum_list), nRows=self.nRows, nCols=self.nCols
)
nSubplots = len(self.func_list)
pnum_ = pt.make_pnum_nextgen(self.nRows, self.nCols, nSubplots=nSubplots)
pnum_list = [pnum_() for _ in self.pnum_list]
else:
pnum_list = self.pnum_list
for index, (pnum, func) in enumerate(zip(pnum_list, self.func_list)):
if check_if_subinteract(func):
# Hack
interclass = func
interclass.static_plot(fnum=self.fnum, pnum=pnum)
elif hasattr(func, 'plot'):
inter = func
inter.plot(fnum=self.fnum, pnum=pnum)
else:
try:
func(fnum=self.fnum, pnum=pnum)
except Exception as ex:
ut.printex(ex, 'failed plotting', keys=['func', 'fnum', 'pnum'])
raise
ax = pt.gca()
pt.set_plotdat(ax, 'plot_func', func)
pt.set_plotdat(ax, 'expandable_index', index)
# if self.interactive is None or self.interactive:
# ih.connect_callback(fig, 'button_press_event', self.onclick)
self.connect_callbacks()
self.fig = fig
return fig
[docs] def on_click(self, event):
print('[inter] clicked in expandable interact')
ax = event.inaxes
if ih.clicked_inside_axis(event):
func = pt.get_plotdat(ax, 'plot_func', None)
if ut.VERBOSE:
print('func = %r' % (func,))
if func is not None:
if ut.VERBOSE:
print('calling func = %r' % (func,))
fnum = pt.next_fnum()
# pt.figure(fnum=fnum)
pnum = (1, 1, 1)
index = pt.get_plotdat(ax, 'expandable_index', None)
if index is not None:
ishow_func = self.ishow_func_list[index]
else:
ishow_func = None
if ishow_func is not None:
inter = ishow_func(fnum=fnum)
else:
if check_if_subinteract(func):
inter = func(fnum=fnum)
inter.show_page()
elif hasattr(func, 'plot'):
inter = func
inter.start()
# func.plot(fnum=self.fnum, pnum=pnum)
else:
func(fnum=fnum, pnum=pnum)
# inter.show_page()
fig = pt.gcf()
pt.show_figure(fig)
# extra
[docs]def zoom_factory(ax=None, zoomable_list=[], base_scale=1.1):
"""
References:
https://gist.github.com/tacaswell/3144287
http://stackoverflow.com/questions/11551049/matplotlib-plot-zooming-with-scroll-wheel
"""
if ax is None:
ax = pt.gca()
def zoom_fun(event):
# print('zooming')
# get the current x and y limits
cur_xlim = ax.get_xlim()
cur_ylim = ax.get_ylim()
xdata = event.xdata # get event x location
ydata = event.ydata # get event y location
if xdata is None or ydata is None:
return
if event.button == 'up':
# deal with zoom in
scale_factor = 1 / base_scale
elif event.button == 'down':
# deal with zoom out
scale_factor = base_scale
else:
raise NotImplementedError('event.button=%r' % (event.button,))
# deal with something that should never happen
scale_factor = 1
print(event.button)
for zoomable in zoomable_list:
zoom = zoomable.get_zoom()
new_zoom = zoom / (scale_factor ** (1.2))
zoomable.set_zoom(new_zoom)
# Get distance from the cursor to the edge of the figure frame
x_left = xdata - cur_xlim[0]
x_right = cur_xlim[1] - xdata
y_top = ydata - cur_ylim[0]
y_bottom = cur_ylim[1] - ydata
ax.set_xlim([xdata - x_left * scale_factor, xdata + x_right * scale_factor])
ax.set_ylim([ydata - y_top * scale_factor, ydata + y_bottom * scale_factor])
# ----
ax.figure.canvas.draw() # force re-draw
fig = ax.get_figure() # get the figure of interest
# attach the call back
fig.canvas.mpl_connect('scroll_event', zoom_fun)
# return the function
return zoom_fun
[docs]def pan_factory(ax=None):
if ax is None:
ax = pt.gca()
self = PanEvents(ax)
ax = self.ax
fig = ax.get_figure() # get the figure of interest
self.cidBP = fig.canvas.mpl_connect('button_press_event', self.pan_on_press)
self.cidBR = fig.canvas.mpl_connect('button_release_event', self.pan_on_release)
self.cidBM = fig.canvas.mpl_connect('motion_notify_event', self.pan_on_motion)
# attach the call back
return self
[docs]class PanEvents(object):
def __init__(self, ax=None):
self.press = None
self.cur_xlim = None
self.cur_ylim = None
self.x0 = None
self.y0 = None
self.x1 = None
self.y1 = None
self.xpress = None
self.ypress = None
self.xzoom = True
self.yzoom = True
self.cidBP = None
self.cidBR = None
self.cidBM = None
self.cidKeyP = None
self.cidKeyR = None
self.cidScroll = None
self.ax = ax
# if ax is None:
# import wbia.plottool as pt
# ax = pt.gca()
# self.ax = ax
# self.connect()
[docs] def pan_on_press(self, event):
if event.button != 1:
return
ax = self.ax
if event.inaxes != ax:
return
self.cur_xlim = ax.get_xlim()
self.cur_ylim = ax.get_ylim()
self.press = self.x0, self.y0, event.xdata, event.ydata
self.x0, self.y0, self.xpress, self.ypress = self.press
[docs] def pan_on_release(self, event):
if event.button != 1:
return
ax = self.ax
self.press = None
ax.figure.canvas.draw()
[docs] def pan_on_motion(self, event):
ax = self.ax
if self.press is None:
return
if event.inaxes != ax:
return
dx = event.xdata - self.xpress
dy = event.ydata - self.ypress
self.cur_xlim -= dx
self.cur_ylim -= dy
ax.set_xlim(self.cur_xlim)
ax.set_ylim(self.cur_ylim)
ax.figure.canvas.draw()
ax.figure.canvas.flush_events()