# Shortcuts for plotting PyGeode vars
# Extends wrapper.py to automatically use information from the Pygeode Vars.
from . import wrappers as wr
from . import cnt_helpers as ch
import numpy as np
def _getplotatts(var):
# {{{
''' Builds plotatts dictionary, using variable attributes as suitable defaults. '''
# Use var.units over var.atts['units'] if both exist
units = var.units
if (units == '' or units is None) and 'units' in var.atts:
units = var.atts['units']
plt = dict(plotname = var.name, plotunits = units, plotfmt = var.formatstr)
plt.update([(k, v) for k, v in var.plotatts.items() if v is not None])
return plt
# }}}
def _buildaxistitle(plotname=None, plottitle=None, plotunits=None, **dummy):
# {{{
if plottitle is not None: return plottitle
elif plotname is not None: title = plotname
else: title = ''
if plotunits not in [None, '']: title += ' [%s]' % plotunits
return title
# }}}
def _buildvartitle(axes=None, plotname=None, plottitle=None, plotunits=None, **dummy):
# {{{
if plottitle is not None: return plottitle
elif plotname is not None: title = plotname
else: title = ''
if plotunits not in [None, '']: title += ' [%s]' % plotunits
# Add information on degenerate axes to the title
if axes is not None:
for a in [a for a in axes if len(a) == 1]:
title += ', ' + a.formatvalue(a.values[0])
return title
# }}}
def scalevalues(var):
# {{{
sf = var.plotatts.get('scalefactor', None)
of = var.plotatts.get('offset', None)
v = var.get().copy()
if sf is not None: v *= sf
if of is not None: v += of
return v
# }}}
def axes_parm(axis):
# {{{
vals = scalevalues(axis).ravel()
lims = np.nanmin(vals), np.nanmax(vals)
plt = _getplotatts(axis)
return plt.get('plotscale', 'linear'), \
_buildaxistitle(**plt), \
lims[::plt.get('plotorder', 1)], \
axis.formatter(), \
axis.locator()
# }}}
def set_xaxis(axes, axis, lbl, xscale=True):
# {{{
scale, label, lim, form, loc = axes_parm(axis)
pl, pb, pr, pt = axes.pad
prm = {}
axprm = dict(major_locator = loc)
if xscale:
prm['xscale'] = scale
prm['xlim'] = lim
if lbl:
import pylab as pyl
prm['xlabel'] = label
axprm['major_formatter'] = form
axprm['minor_formatter'] = pyl.NullFormatter()
else:
prm['xticklabels'] = []
axes.setp(**prm)
axes.setp_xaxis(**axprm)
if len(label) > 0 and lbl:
axes.pad = [pl, 0.5, pr, 0.3]
else:
axes.pad = [pl, 0.3, pr, 0.3]
# }}}
def set_yaxis(axes, axis, lbl, yscale=True):
# {{{
scale, label, lim, form, loc = axes_parm(axis)
pl, pb, pr, pt = axes.pad
prm = {}
axprm = dict(major_locator = loc)
if yscale:
prm['yscale'] = scale
prm['ylim'] = lim
if lbl:
import pylab as pyl
prm['ylabel'] = label
axprm['major_formatter'] = form
axprm['minor_formatter'] = pyl.NullFormatter()
else:
prm['yticklabels'] = []
axes.setp(**prm)
axes.setp_yaxis(**axprm)
if len(label) > 0 and lbl:
axes.pad = [0.8, pb, 0.1, pt]
else:
axes.pad = [0.5, pb, 0.1, pt]
# }}}
def build_projection(lons, lats, **kwargs):
# {{{
if wr.cartopy_avail:
# Use cartopy preferentially, if it is available.
if not hasattr(wr, 'CartopyAxes'):
return wr.AxesWrapper()
prd = dict(projection = 'PlateCarree')
prd.update(kwargs)
return wr.CartopyAxes(**prd)
elif wr.basemap_avail:
# Use basemap if available
prd = dict(projection = 'cyl', resolution = 'c')
prd.update(kwargs)
proj = prd['projection']
bnds = {}
if proj not in ['sinu', 'moll', 'hammer', 'npstere', 'spstere', 'nplaea', 'splaea', 'npaeqd', \
'spaeqd', 'robin', 'eck4', 'kav7', 'mbtfpq', 'ortho', 'nsper']:
bnds = {'llcrnrlat':lats.min(),
'urcrnrlat':lats.max(),
'llcrnrlon':lons.min(),
'urcrnrlon':lons.max()}
if proj == 'npstere':
bnds = {'boundinglat':20, 'lon_0':0}
if proj == 'spstere':
bnds = {'boundinglat':-20, 'lon_0':0}
bnds.update(prd)
prd.update(bnds)
return wr.BasemapAxes(**prd)
else:
return wr.AxesWrapper()
# }}}
def decorate_projection(axes, xaxis, yaxis, **kwargs):
# {{{
if wr.iscartopyaxis(axes):
cst_def = dict(resolution = '110m')
cst = kwargs.pop('coastlines', cst_def)
if isinstance(cst, dict):
axes.coastlines(**cst)
grd = kwargs.pop('gridlines', {})
if isinstance(grd, dict):
lblprjs = ['PlateCarree', 'Mercator']
grd_def = dict(color = 'k', linestyle = ':', linewidth = 1.)
if axes.prj_name in lblprjs:
grd_def['draw_labels'] = True
else:
grd_def['draw_labels'] = False
grd_def.update(grd)
scale, label, lim, xform, xloc = axes_parm(xaxis)
scale, label, lim, yform, yloc = axes_parm(yaxis)
# Set default grid appearance and labeling if appropriate
grd_prm = {}
if grd_def['draw_labels']:
grd_prm['top_labels'] = False
grd_prm['right_labels'] = False
grd_prm['xformatter'] = xform
grd_prm['yformatter'] = yform
if 'xlocs' not in grd_def:
grd_prm['xlocator'] = xloc
if 'ylocs' not in grd_def:
grd_prm['ylocator'] = yloc
mod_args = ['rotate_labels', 'top_labels', 'bottom_labels', 'right_labels', 'left_labels']
for arg in mod_args:
if arg in grd_def:
grd_prm[arg] = grd_def.pop(arg)
gl = axes.gridlines(**grd_def)
if len(grd_prm) > 0:
axes.modifygridlines(gl, **grd_prm)
axes.pad = [0.6, 0.5, 0.2, 0.2]
elif wr.isbasemapaxis(axes):
if kwargs.pop('bluemarble', False):
axes.bluemarble()
# Add coastlines, meridians, parallels
cld = {}
merd = dict(meridians=[-180,-90,0,90,180,270,360],
labels=[1,0,0,1])
pard = dict(circles=[-90,-60,-30,0,30,60,90],
labels=[1,0,0,1])
cld.update(kwargs.pop('coastlines', {}))
merd.update(kwargs.pop('meridians', {}))
pard.update(kwargs.pop('parallels', {}))
axes.pad=(0.6, 0.4, 0.4, 0.4)
axes.drawcoastlines(**cld)
axes.drawmeridians(**merd)
axes.drawparallels(**pard)
# }}}
def _parse_autofmt_kwargs(Z, kwargs):
# {{{
''' Used by showvar and showgrid to parse kwargs for auto-contouring options. '''
# Process auto contouring dictionaries
if 'clevs' in list(kwargs.keys()) or 'clines' in list(kwargs.keys()):
tdef = 'raw'
else:
tdef = 'clf'
typ = kwargs.pop('type', tdef)
if typ == 'raw':
return kwargs
elif typ == 'clf':
cdelt = kwargs.pop('cdelt', None)
if cdelt is None:
z = scalevalues(Z)
style = kwargs.pop('style', None)
ndiv = kwargs.pop('ndiv', None)
cdelt, dct = ch.guessclimits(z, style=style, ndiv=ndiv)
else:
dct = {}
if 'min' in kwargs and 'style' not in kwargs:
dct['style'] = 'seq'
dct['ndiv'] = 5
dct.update(kwargs)
kwargs = ch.clfdict(cdelt, **dct)
return kwargs
elif typ == 'cl':
cdelt = kwargs.pop('cdelt', None)
if cdelt is None:
z = scalevalues(Z)
style = kwargs.pop('style', None)
ndiv = kwargs.pop('ndiv', None)
cdelt, dct = ch.guessclimits(z, style=style, ndiv=ndiv, clf=False)
verbose = True
else:
ndiv = kwargs.pop('ndiv', 10)
range = kwargs.pop('range', ndiv*cdelt)
dct = dict(range=range)
verbose = False
dct.update(kwargs)
kwargs = ch.cldict(cdelt, **dct)
if verbose:
print('Contour Interval: %0.2g' % cdelt)
print('Minimum value: %3g, Maximum value: %3g' % (np.min(z), np.max(z)))
print('Minimum contour: %3g, Maximum contour: %3g' % (kwargs['clines'][0], kwargs['clines'][-1]))
return kwargs
elif typ in ['log', 'log1s']:
dct = {}
dct.update(kwargs)
if 'cmin' not in dct:
raise ValueError('Must specify cmin (lower bound) for logarithmically spaced contours')
kwargs = ch.log1sdict(**dct)
return kwargs
elif typ == 'log2s':
dct = {}
dct.update(kwargs)
if not ('cmin' in dct) :
raise ValueError('Must specify cmin (inner boundary for linear spaced interval) for two-sided logarithmically spaced contours')
kwargs = ch.log2sdict(**dct)
return kwargs
else:
raise ValueError("Unrecognized 2d plot type '%s'" % typ)
# }}}
# Do a 1D line plot
[docs]def vplot(var, fmt='', axes=None, transpose=False, lblx=True, lbly=True, **kwargs):
# {{{
'''
Create a line plot of a variable.
Parameters
----------
var : :class:`Var`
The variable to plot. Should have 1 non-degenerate axis.
fmt : string, optional
Format of the line. See :func:`pylab.plot`.
'''
Y = var.squeeze().load()
assert Y.naxes == 1, 'Variable to plot must have exactly one non-degenerate axis.'
X = Y.axes[0].load()
yord = True
# If a vertical axis is present transpose the plot
from pygeode.axis import ZAxis
if isinstance(X, ZAxis):
X, Y = Y, X
yord = False
if transpose:
X, Y = Y, X
yord = not yord
if yord:
scalex = kwargs.get('scalex', True)
scaley = kwargs.get('scaley', False)
else:
scalex = kwargs.get('scalex', False)
scaley = kwargs.get('scaley', True)
hold = kwargs.pop('hold', False)
x = scalevalues(X)
y = scalevalues(Y)
if axes is None:
axes = wr.AxesWrapper()
axes.plot(x, y, fmt, **kwargs)
# Apply the custom axes args
if not hold:
axes.pad = (0.1, 0.1, 0.1, 0.1)
set_xaxis(axes, X, lblx, scalex)
set_yaxis(axes, Y, lbly, scaley)
plt = _getplotatts(var)
lbl = _buildvartitle(var.axes, **plt)
axes.setp(title=lbl, label=lbl)
return axes
# }}}
[docs]def vhist(var, axes=None, lblx=True, lbly=True, **kwargs):
# {{{
'''
Create a histogram of values taken by a variable.
Parameters
----------
var : :class:`Var`
The variable to compute the histogram of.
'''
V = var.squeeze().load()
v = scalevalues(V).ravel()
if axes is None:
axes = wr.AxesWrapper()
axes.hist(v, **kwargs)
# Apply the custom axes args
axes.pad = [0.5, 0.1, 0.1, 0.1]
set_xaxis(axes, V, lblx)
plt = _getplotatts(var)
lbl = _buildvartitle(var.axes, **plt)
axes.setp(title=lbl, label=lbl)
return axes
# }}}
# Do a scatter plot
[docs]def vscatter(varx, vary, axes=None, lblx=True, lbly=True, **kwargs):
# {{{
'''
Create a scatter plot from two variables with the same shape.
Parameters
----------
varx : :class:`Var`
Variable to use as abscissa values. Must have the same size as vary.
vary : :class:`Var`
Variable to use as ordinate values.
Notes
-----
Wraps matplotlib.scatter
'''
assert varx.size == vary.size, 'Variables must have same size to do a scatter plot.'
x = scalevalues(varx).ravel()
y = scalevalues(vary).ravel()
if axes is None:
axes = wr.AxesWrapper()
axes.scatter(x, y, **kwargs)
# Apply the custom axes args
axes.pad = (0.1, 0.1, 0.1, 0.1)
set_xaxis(axes, varx, lblx)
set_yaxis(axes, vary, lbly)
return axes
# }}}
# Do a 2D contour plot
[docs]def vcontour(var, clevs=None, clines=None, axes=None, lblx=True, lbly=True, label=True, transpose=None, **kwargs):
# {{{
'''Create a contour plot (lines, filled, or both) from a variable.
Parameters
----------
var : :class:`Var`
The variable to plot. Should have 2 non-degenerate axes.
clevs : integer or collection of numbers, optional
Levels at which to construct filled contours through an underlying call to
:func:`matplotlib.contourf`. If None is specified, no filled contours will
be produced, unless clines is also None. If a number is specified, that
number of equally spaced contours are chosen. Otherwise the explicit
values are used.
clines : integer or collection of numbers, optional
Levels at which to construct contour lines through an underlying call to
:func:`matplotlib.contour`. If None is specified, no contour lines are
produced. If a number is specified, that number of equally spaced contours
are chosen. Otherwise the explicit values are used.
axes : :class:`AxisWrapper`, optional
Axes on which to produce contour plot. If none is specified, one is created.
lblx : bool, optional
If True, add appropriate tick labels and an axis label on the x axis; if
False, the x axis a is left unlabeled. Defaults to True.
lbly : bool, optional
If True, add appropriate tick labels and an axis label on the y axis; if
False, the y axis a is left unlabeled. Defaults to True.
transpose : bool, optional
The x and y axes are chosen based on the two degenerate axes of the variable
to plot. This order can be reversed by setting transpose to True.
map : dict, optional
If
*args, **kwargs : arguments to pass on to underlying matplotlib contour
plotting routines, see Notes.
Returns
-------
axes : :class:`AxesWrapper`
The axes object containing the contour plots.
Notes
-----
If the two axes of the variable are a :class:`Lat` and :class:`Lon` axes,
a map projection is created automatically.
See Also
--------
showvar, colorbar
'''
Z = var.squeeze()
assert Z.naxes == 2, 'Variable to contour must have two non-degenerate axes.'
X, Y = Z.axes
# If a vertical axis is present transpose the plot
from pygeode.axis import ZAxis, Lat, Lon
if transpose is None:
if isinstance(X, ZAxis):
X, Y = Y, X
if isinstance(X, Lat) and isinstance(Y, Lon):
X, Y = Y, X
elif transpose:
X, Y = Y, X
x = scalevalues(X)
y = scalevalues(Y)
z = scalevalues(Z.transpose(Y, X))
map = kwargs.pop('map', {})
mapd = kwargs.pop('mapdecor', {})
if axes is None:
if isinstance(X, Lon) and isinstance(Y, Lat) and map is not False:
axes = build_projection(x, y, **map)
else:
axes = wr.AxesWrapper()
if clevs is None and clines is None:
# If both clevs and clines are None, use default
axes.contourf(x, y, z, 21, **kwargs)
if not clevs is None:
lw = kwargs.pop('linewidths', None)
axes.contourf(x, y, z, clevs, **kwargs)
if lw is not None:
kwargs['linewidths'] = lw
# Special case; if plotting both filled and unfilled contours
# with a single call, set the color of the latter to black
kwargs['colors'] = 'k'
kwargs['cmap'] = None
if not clines is None:
axes.contour(x, y, z, clines, **kwargs)
# Apply the custom axes args
if label:
axes.pad = (0.1, 0.1, 0.1, 0.1)
if wr.ismapaxis(axes) and mapd is not False:
decorate_projection(axes, X, Y, **mapd)
else:
set_xaxis(axes, X, lblx)
set_yaxis(axes, Y, lbly)
plt = _getplotatts(var)
axes.setp(title = _buildvartitle(var.axes, **plt))
return axes
# }}}
# Do a 2D significance mask
[docs]def vsigmask(pval, axes, mjsig=0.95, mnsig=None, mjsigp = None, mnsigp = None, nsigp = None, transpose=None):
# {{{
'''
Add significance shading to a contour plot from a variable.
Parameters
----------
pval : :class:`Var`
The variable containing a p-value of the significance mask. The mask will
be applied where abs(pval) > mjsig (and optionally an additional mask will
be applied for mnsig < abs(pval) < mjsig. Signed p-values for two-sided
tests ensure that a gap will appear between significant regions of opposite sign.
Should have two non-degenerate axes that match the quantity plotted.
axes : :class:`AxesWrapper`
The axis on which to add the mask.
mjsig : float, optional [0.95]
The p-value dividing significant from non-significant values.
mnsig : float or None, optional [None]
The p-value dividing minor significance from non-significant values.
mjsigp : dictionary, optional
A dictionary of keyword arguments that determine the properties of the
(major) significant filled contours. See notes.
mnsigp : dictionary, optional
A dictionary of keyword arguments that determine the properties of the
minor significant filled contours. See notes.
nsigp : dictionary, optional
A dictionary of keyword arguments that determine the properties of the
non-significant filled contour. See notes.
transpose : bool or None, optional [None]
If True, transpose the axes of the plot.
Notes
-----
The significance mask is plotted as three (or five) filled contours, with
boundaries at [-1.1, -mjsig, mjsig, 1.1] or [-1.1, -mjsig, -mnsig, mnsig,
mjsig, 1.1]. Their respective graphical properties can be set using the
dictionary kw arguments.
By default, the non-significant contours are set to be invisible and the
significant contours are set to a hatching pattern; this is equivalent to
passing in mjsigp = dict(alpha = 0., hatch = '...') and nsigp = dict(visible
= False). Any property of the filled contour can be set.
Returns
-------
:class:`AxesWrapper` object with plot.
'''
Z = pval.squeeze()
assert Z.naxes == 2, 'Variable to contour must have two non-degenerate axes.'
X, Y = Z.axes
# If a vertical axis is present transpose the plot
from pygeode.axis import ZAxis, Lat, Lon
if transpose is None:
if isinstance(X, ZAxis):
X, Y = Y, X
if isinstance(X, Lat) and isinstance(Y, Lon):
X, Y = Y, X
elif transpose:
X, Y = Y, X
x = scalevalues(X)
y = scalevalues(Y)
z = scalevalues(Z.transpose(Y, X))
if mjsigp is None: mjsigp = dict(alpha = 0., hatch = '...')
if mnsigp is None: mnsigp = dict(alpha = 0., hatch = '..')
if nsigp is None: nsigp = dict(visible = False)
if mnsig is None:
cl = [-1.1, -mjsig, mjsig, 1.1]
axes.contourf(x, y, z, cl, colors='w')
cnt = axes.plots[-1]
axes.modifycontours(cnt, ind=[0, 2], **mjsigp)
axes.modifycontours(cnt, ind=[1], **nsigp)
else:
cl = [-1.1, -mnsig,-mjsig, mjsig, mnsig, 1.1]
axes.contourf(x, y, z, cl, colors='w')
cnt = axes.plots[-1]
axes.modifycontours(cnt, ind=[0, 4], **mjsigp)
axes.modifycontours(cnt, ind=[1, 3], **mnsigp)
axes.modifycontours(cnt, ind=[2], **nsigp)
return axes
# }}}
# Do a stream plot
[docs]def vstreamplot(varu, varv, axes=None, lblx=True, lbly=True, label=True, transpose=None, **kwargs):
# {{{
'''
Create a streamplot from two variables.
Parameters
----------
'''
U = varu.squeeze()
V = varv.squeeze()
assert U.naxes == 2 and V.naxes == 2, 'Variables to quiver must have two non-degenerate axes.'
X, Y = U.axes
from pygeode.axis import Lat, Lon
# If a vertical axis is present transpose the plot
from pygeode.axis import ZAxis, Lat, Lon
if transpose is None:
if isinstance(X, ZAxis):
X, Y = Y, X
if isinstance(X, Lat) and isinstance(Y, Lon):
X, Y = Y, X
elif transpose:
X, Y = Y, X
x = scalevalues(X)
y = scalevalues(Y)
u = scalevalues(U.transpose(Y, X))
v = scalevalues(V.transpose(Y, X))
map = kwargs.pop('map', {})
mapd = kwargs.pop('mapdecor', {})
if axes is None:
if isinstance(X, Lon) and isinstance(Y, Lat) and map is not False:
axes = build_projection(x, y, **map)
else:
axes = wr.AxesWrapper()
axes.streamplot(x, y, u, v, **kwargs)
# Apply the custom axes args
if label:
axes.pad = (0.1, 0.1, 0.1, 0.1)
if wr.ismapaxis(axes) and mapd is not False:
decorate_projection(axes, X, Y, **mapd)
else:
set_xaxis(axes, X, lblx)
set_yaxis(axes, Y, lbly)
plt = _getplotatts(varu)
axes.setp(title = _buildvartitle(varu.axes, **plt))
return axes
# }}}
# Do a quiver plot
[docs]def vquiver(varu, varv, varc=None, axes=None, lblx=True, lbly=True, label=True, transpose=None, **kwargs):
# {{{
'''
Create a quiver plot from two variables.
Parameters
----------
'''
U = varu.squeeze()
V = varv.squeeze()
assert U.naxes == 2 and V.naxes == 2, 'Variables to quiver must have two non-degenerate axes.'
assert U.axes == V.axes, 'Variables U and V must have the same axes.'
X, Y = U.axes
if varc is not None:
C = varc.squeeze()
assert U.axes == C.axes, 'Color values must have same axes as U and V'
from pygeode.axis import Lat, Lon
# If a vertical axis is present transpose the plot
from pygeode.axis import ZAxis, Lat, Lon
if transpose is None:
if isinstance(X, ZAxis):
X, Y = Y, X
if isinstance(X, Lat) and isinstance(Y, Lon):
X, Y = Y, X
elif transpose:
X, Y = Y, X
x = scalevalues(X)
y = scalevalues(Y)
u = scalevalues(U.transpose(Y, X))
v = scalevalues(V.transpose(Y, X))
if varc is not None:
c = scalevalues(C.transpose(Y, X))
map = kwargs.pop('map', {})
mapd = kwargs.pop('mapdecor', {})
if axes is None:
if isinstance(X, Lon) and isinstance(Y, Lat) and map is not False:
axes = build_projection(x, y, **map)
else:
axes = wr.AxesWrapper()
if varc is not None:
axes.quiver(x, y, u, v, c, **kwargs)
else:
axes.quiver(x, y, u, v, **kwargs)
# Apply the custom axes args
if label:
axes.pad = (0.1, 0.1, 0.1, 0.1)
if wr.ismapaxis(axes) and mapd is not False:
decorate_projection(axes, X, Y, **mapd)
else:
set_xaxis(axes, X, lblx)
set_yaxis(axes, Y, lbly)
plt = _getplotatts(varu)
axes.setp(title = _buildvartitle(varu.axes, **plt))
return axes
# }}}
# Generic catch all interface (plotvar replacement)
[docs]def showvar(var, *args, **kwargs):
# {{{
'''Plot variable, showing a contour plot for 2d variables or a line plot for
1d variables.
Parameters
----------
var : :class:`Var`
The variable to plot. Should have either 1 or 2 non-degenerate axes.
Arguments below relevant only for the 1 dimensional case are labelled [1D],
those relevant only for the 2 dimensional case are labelled [2D].
fmt : string, optional
[1D] matplotlib format to plot line. See :func:`matplotlib.plot()`. Will also
be recognized as the second positional argument (after var).
type : string, optional ['clf']
[2D] style of plot to produce. See Notes.
axes : :class:`AxesWrapper` instance, optional
Axes object on which to plot variable. A new one is created if this is not specified.
transpose: boolean, optional [False]
If True, reverse axes.
lblx: boolean, optional [True]
If True, label horizontal axes
lbly: boolean, optional [True]
If True, label vertical axes
*args, **kwargs :
Further arguments are passed on to the underlying plotting routine. See
Notes.
Notes
-----
This function is intended as the simplest way to display the contents of a
variable, choosing appropriate parameter values as automatically as possible.
For 1d variables it calls :func:`Var.vplot()`, and for 2d variables
:func:`Var.vcontour`. In the latter case, if filled contours were produced, it
calls :func:`AxesWrapper.colorbar()`. A dictionary ``colorbar`` can be provided to
pass arguments through. Setting ``colorbar`` to ``False`` suppresses the
colorbar.
Returns
-------
:class:`AxesWrapper` object with plot.
See Also
--------
vplot, vcontour, colorbar
'''
Z = var.squeeze()
assert Z.naxes in [1, 2], 'Variable %s has %d non-generate axes; must have 1 or 2.' % (var.name, Z.naxes)
var = var.load()
fig = kwargs.pop('fig', None)
size = kwargs.pop('size', None)
if Z.naxes == 1:
ax = vplot(var, *args, **kwargs)
if size is not None: ax.size = size
elif Z.naxes == 2:
kwargs = _parse_autofmt_kwargs(Z, kwargs)
cbar = kwargs.pop('colorbar', dict(orientation='vertical'))
ax = vcontour(var, *args, **kwargs)
if size is not None: ax.size = size
cf = ax.find_plot(wr.Contourf)
cl = ax.find_plot(wr.Contour)
if cbar and cf is not None:
if cl is not None: cbar['lcnt'] = cl
ax = wr.colorbar(ax, cf, **cbar)
import pylab as pyl
if pyl.isinteractive():
ax.render(fig)
return ax
# }}}
def showcol(vs, size=(4.1,2), **kwargs):
# {{{
'''
Plot column of contour plots. Superseded by :func:`showgrid`.
Parameters
----------
v : list of lists of :class:`Var`
The variables to plot. Should have either 1 or 2 non-degenerate axes.
Notes
-----
This function is intended as the simplest way to display the contents of a variable,
choosing appropriate parameter values as automatically as possible.
'''
Z = [v.squeeze() for v in vs]
assert Z[0].naxes in [1, 2], 'Variables %s has %d non-generate axes; must have 1 or 2.' % (Z.name, Z.naxes)
for z in Z[1:]:
assert Z[0].naxes == z.naxes, 'All variables must have the same number of non-generate dimensions'
#assert all([a == b for a, b in zip(Z[0].axes, z.axes)])
fig = kwargs.pop('fig', None)
if Z[0].naxes == 1:
axs = []
ydat = []
for v in vs:
lblx = (v is vs[-1])
ax = vplot(v, lblx = lblx, **kwargs)
ax.size = size
axs.append([ax])
ydat.append(ax.find_plot(wr.Plot).plot_args[1])
Ax = wr.grid(axs)
ylim = (np.min([np.min(y) for y in ydat]), np.max([np.max(y) for y in ydat]))
Ax.setp(ylim = ylim, children=True)
elif Z[0].naxes == 2:
axs = []
for v in vs:
lblx = (v is vs[-1])
ax = vcontour(v, lblx = lblx, **kwargs)
ax.size = size
axs.append([ax])
Ax = wr.grid(axs)
cbar = kwargs.pop('colorbar', dict(orientation='vertical'))
cf = Ax.axes[0].find_plot(wr.Contourf)
if cbar and cf is not None:
Ax = wr.colorbar(Ax, cf, **cbar)
import pylab as pyl
if pyl.isinteractive(): Ax.render(fig)
return Ax
# }}}
[docs]def showgrid(vf, vl=[], ncol=1, size=(3.5,1.5), lbl=True, **kwargs):
# {{{
'''
Create grid of contour plots of multiple variables.
Parameters
----------
vf : list of lists of :class:`Var`
The variables to plot. Should have 2 non-degenerate axes.
ncol : integer
Number of columns
'''
from pygeode import Var
if isinstance(vf, Var): vf = [vf]
if isinstance(vl, Var): vl = [vl]
assert all([v.squeeze().naxes == 2 for v in vf]), 'Variables (vf) should have 2 degenerate axes.'
nVf = len(vf)
assert all([v.squeeze().naxes == 2 for v in vl]), 'Variables (vl) should have 2 degenerate axes.'
nVl = len(vl)
if nVf > 1 and nVl > 1: assert nVl == nVf, 'Must have the same number of filled and contour-line variables.'
fig = kwargs.pop('fig', None)
kwargs = _parse_autofmt_kwargs(vf[0], kwargs)
cbar = kwargs.pop('colorbar', dict(orientation='vertical'))
xpad = 0.
ypad = 0.
kwlines = {}
if nVl > 0:
kwlines['colors'] = kwargs.pop('colors', 'k')
kwlines['clines'] = kwargs.pop('clines', 11)
for k in ['linewidths', 'linestyles']:
if k in kwargs: kwlines[k] = kwargs.pop(k)
kwfill = {}
if nVf > 0:
kwfill['clevs'] = kwargs.pop('clevs', 31)
kwfill['cmap'] = kwargs.pop('cmap', None)
kwlines['label'] = False
if cbar:
if cbar.get('orientation', 'vertical') == 'vertical':
ypad = cbar.get('width', 0.8)
else:
xpad = cbar.get('height', 0.4)
kwcb = {}
nV = max(nVl, nVf)
nrow = np.ceil(nV / float(ncol))
axpad = kwargs.pop('axpad', 0.2)
aypad = kwargs.pop('aypad', 0.4)
if lbl:
axpadl = axpad + kwargs.pop('lblxpad', 0.7)
aypadl = aypad + kwargs.pop('lblypad', 0.15)
else:
axpadl = axpad
aypadl = aypad
axw, axh = size
axs = []
row = []
for i in range(nV):
lblx = (i / ncol == nrow - 1)
lbly = (i % ncol == 0)
ax = None
if nVf > 0:
v = vf[i % nVf]
kwfill.update(kwargs)
ax = vcontour(v, axes=ax, lblx = lblx, lbly = lbly, **kwfill)
if nVl > 0:
v = vl[i % nVl]
kwlines.update(kwargs)
ax = vcontour(v, axes=ax, lblx = lblx, lbly = lbly, **kwlines)
if hasattr(wr, 'BasemapAxes') and isinstance(ax, wr.BasemapAxes):
w = axw
h = axh
else:
pl, pb, pr, pt = ax.pad
if lblx:
py = aypadl
else:
py = aypad
if lbly:
px = axpadl
else:
px = axpad
h = axh + py
w = axw + px
ax.pad = [px-pr, py-pt, pr, pt]
ax.size = (w, h)
row.append(ax)
if i % ncol == ncol - 1:
axs.append(row)
row = []
if len(row) > 0:
row.extend([None] * (ncol - len(row)))
axs.append(row)
Ax = wr.grid(axs)
cf = Ax.axes[0].find_plot(wr.Contourf)
if cbar and cf is not None:
Ax = wr.colorbar(Ax, cf, **cbar)
import pylab as pyl
if pyl.isinteractive(): Ax.render(fig)
return Ax
# }}}
[docs]def showlines(vs, fmts=None, labels=None, size=(4.1,2), lblx=True, lbly=True, **kwargs):
# {{{
'''
Produce line plots of a list of 1D variables on a single figure.
Parameters
----------
vs : list of :class:`Var`
The variables to plot. Should all have 1 non-degenerate axis.
'''
Z = [v.squeeze() for v in vs]
for z in Z:
assert z.naxes == 1, 'Variable %s has %d non-generate axes; must have 1.' % (z.name, z.naxes)
fig = kwargs.pop('fig', None)
ax = wr.AxesWrapper(size=size)
ydat = []
for i, v in enumerate(vs):
if fmts is None:
fmt = ''
elif hasattr(fmts, '__len__'):
fmt = fmts[i]
else:
fmt = fmts
if labels is None:
lbl = v.name
elif hasattr(labels, '__len__'):
lbl = labels[i]
else:
lbl = labels
vplot(v, axes=ax, fmt=fmt, label=lbl)
ydat.append(ax.find_plot(wr.Plot).plot_args[1])
#ylim = (np.min([np.min(y) for y in ydat]), np.max([np.max(y) for y in ydat]))
#kwargs.update(dict(ylim=ylim))
kwleg = kwargs.pop('legend', dict(loc='best', frameon=False))
ax.legend(**kwleg)
ax.setp(**kwargs)
import pylab as pyl
if pyl.isinteractive(): ax.render(fig)
return ax
# }}}
def savepages(figs, fn, psize='A4', marg=0.5, scl=1.):
# {{{
sizes = dict(A4 = (8.3, 11.7),
A4l = (11.7, 8.3))
if psize in sizes:
pwidth, pheight = sizes[psize]
else:
pwidth, pheight = psize
hmarg = marg
wmarg = marg
psize = (pwidth, pheight)
fwidth = pwidth - 2*wmarg
fheight = pheight - 2*hmarg
ymarg = hmarg/pheight
xmarg = wmarg/pwidth
y = 1. - ymarg
x = xmarg
ax = wr.AxesWrapper(size=psize)
from matplotlib.backends.backend_pdf import PdfPages
pp = PdfPages(fn)
page = 1
nfigs = 0
hlast = 0
for f in figs:
w = f.size[0] / pwidth * scl
h = f.size[1] / pheight * scl
if x + w < 1. - xmarg:
r = [x, y - h, x + w, y]
ax.add_axis(f, r)
x += w
hlast = max(h, hlast)
nfigs += 1
else:
x = xmarg
y = y - hlast
if nfigs > 0 and y - h < ymarg:
fig = ax.render('page%d' % page)
pp.savefig(fig)
ax = wr.AxesWrapper(size=psize)
y = 1. - ymarg
print('Page %d, %d figures.' % (page, nfigs))
page += 1
nfigs = 0
r = [x, y - h, x + w, y]
ax.add_axis(f, r)
x = x + w
hlast = h
nfigs += 1
print('Page %d, %d figures.' % (page, nfigs))
fig = ax.render('page%d'%page, show=False)
pp.savefig(fig)
pp.close()
# }}}
__all__ = ['showvar', 'showcol', 'showgrid', 'showlines', 'vplot', 'vscatter', \
'vhist', 'vcontour', 'vsigmask', 'vstreamplot', 'vquiver', 'savepages']