Skip to content

Instantly share code, notes, and snippets.

@niallrobinson
Created July 11, 2013 12:25
Show Gist options
  • Save niallrobinson/5975003 to your computer and use it in GitHub Desktop.
Save niallrobinson/5975003 to your computer and use it in GitHub Desktop.
Cube explorer
'''
Proof of concept class for making interactive plot object which allow the addition
of navigation buttons.
Created on Jul 3, 2013
@author: nrobin
'''
import iris
import iris.plot as iplt
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
import numpy as np
import matplotlib.cm as mpl_cm
import iris.quickplot as qplt
import time
from iris.exceptions import CoordinateNotFoundError
import sys
class CubeExplorer(object):
def __init__(self, cube, plot_func, current_slice, *args, **kwargs):
"""
Args:
* cube: cube of data to plot
* plot_func: pointer to plotting function
* current_slice: index tuple which gives a slice of cube
which is compatible with cube
"""
self.cube = cube
self.plot_func = plot_func
self.current_slice = current_slice
self.disp_data = cube.data[tuple(current_slice)]
self.ax = None
self.axes_hook = kwargs.pop('axes_hook', None)
self.plot_args = args
self.plot_kwargs = kwargs
self.butts = {}
self.butt_fns = {}
self.make_plot()
def show(self):
plt.show()
def make_plot(self):
"""
Makes initial plot
"""
self.fig = plt.figure(num=None)
# Make the initial plot.
self.pl = self.plot_func(self.cube[tuple(self.current_slice)], *self.plot_args, **self.plot_kwargs)
self.ax = plt.gca()
if self.axes_hook is not None:
self.axes_hook(self.ax)
def add_nav_buttons(self, dim, button_names_tup, slot=0, circular=False):
"""
Adds a set of two buttons to the plot window which allow incrementing
or decrementing over the specified dimension
Args:
* dim: dimension number or name to traverse
* button_names_tup: tuple of two strings to be the names of the
increment and decrement buttons respectively.
* slot: level of the plot to display this button set
* circular: boolean - to loop round when limit is reached or not
"""
if type(dim) is str:
dim, = self.cube.coord_dims(self.cube.coord(dim))
plt.subplots_adjust(right=0.85)
if type(self.current_slice[dim]) is slice:
raise TypeError("Cannot iterate over a displayed dimension")
self.butts[button_names_tup[0]] = Button(plt.axes([0.875, 0.85-(slot*0.15), 0.11, 0.05]), button_names_tup[0])
self.butt_fns[button_names_tup[0]] = self._get_nav_fn(dim, 'inc', circular)
self.butts[button_names_tup[0]].on_clicked(self.butt_fns[button_names_tup[0]])
self.butts[button_names_tup[1]] = Button(plt.axes([0.875, 0.79-(slot*0.15), 0.11, 0.05]), button_names_tup[1])
self.butt_fns[button_names_tup[1]] = self._get_nav_fn(dim, 'dec', circular)
self.butts[button_names_tup[1]].on_clicked(self.butt_fns[button_names_tup[1]])
# set axis back to plot
plt.sca(self.ax)
def add_animate_buttons(self, dim, button_names_tup, slot=0, refresh_rate=0.2):
"""
Adds a set of two buttons to start/stop cycling through a dimension
of a cube.
Args
* dim: dimension number/name to traverse
* button_names_tup: tuple of two strings to be the names of the
play and stop buttons respectively.
* slot: level of the plot to display this button set
* refresh_rate: number of seconds to wait between each frame
"""
if type(dim) is str:
dim, = self.cube.coord_dims(self.cube.coord(dim))
plt.subplots_adjust(right=0.85)
self.butts[button_names_tup[0]] = Button(plt.axes([0.875, 0.85-(slot*0.15), 0.11, 0.05]), button_names_tup[0])
self.butts[button_names_tup[1]] = Button(plt.axes([0.875, 0.79-(slot*0.15), 0.11, 0.05]), button_names_tup[1])
play_fn, stop_fn = self._get_ani_fns(dim, refresh_rate)
self.butt_fns[button_names_tup[0]] = play_fn
self.butt_fns[button_names_tup[1]] = stop_fn
self.butts[button_names_tup[0]].on_clicked(self.butt_fns[button_names_tup[0]])
self.butts[button_names_tup[1]].on_clicked(self.butt_fns[button_names_tup[1]])
# set axis back to plot
plt.sca(self.ax)
def _get_ani_fns(self, dim, refresh_rate):
def play(event):
self.playing = True
while True:
self.fig.canvas.start_event_loop(timeout=refresh_rate)
if self.playing:
if self.current_slice[dim] < self.cube.shape[dim]-1:
self.current_slice[dim] += 1
else:
self.current_slice[dim] = 0
self._refresh_plot()
def stop(event):
self.playing = False
return play, stop
def _refresh_plot(self):
"""
Refreshes the displayed plot to display the slice defined
by current_slice.
"""
self.ax.clear()
self.plot_func(self.cube[tuple(self.current_slice)], *self.plot_args, **self.plot_kwargs)
if self.axes_hook is not None:
self.axes_hook(self.ax)
self.fig.canvas.draw()
def _get_nav_fn(self, dim, inc_or_dec, circular):
"""
Returns increment and decrement button functions for a dimension.
"""
if inc_or_dec is 'inc':
def fn(event):
if self.current_slice[dim] < self.cube.shape[dim]-1:
self.current_slice[dim] += 1
elif circular:
self.current_slice[dim] = 0
self._refresh_plot()
elif inc_or_dec is 'dec':
def fn(event):
if self.current_slice[dim] > 0:
self.current_slice[dim] -= 1
elif circular:
self.current_slice[dim] = self.cube.shape[dim]
self._refresh_plot()
return fn
if __name__ == '__main__':
cube = iris.load_cube(iris.sample_data_path('GloSea4', 'ensemble_001.pp'))
def axes_hook(ax):
ax.coastlines()
ax.set_title('Depth slices')
ce = CubeExplorer(cube, qplt.plot, [0, 50, slice(None)])#, cmap=mpl_cm.get_cmap('brewer_OrRd_09'), axes_hook=axes_hook)
ce.add_nav_buttons('time', ("Up", "Down"), slot=0)
ce.add_animate_buttons(0, ("Play", "Stop"), slot=1)
ce.add_na
@niallrobinson
Copy link
Author

sorry - last line is wrong due to copy/paste incompetency. Gist is being flakey and I can't update it, however. Just replace line 191 with ce.show()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment