Source code for bumps.plotutil

"""
Pylab plotting utilities.
"""

__all__ = [
    "config_matplotlib",
    "auto_shift",
    "coordinated_colors",
    "dhsv",
    "next_color",
    "plot_quantiles",
    "form_quantiles",
]


[docs] def set_mplconfig(appdatadir): r""" Point the matplotlib config dir to %LOCALAPPDATA%\{appdatadir}\mplconfig. """ import os import sys if hasattr(sys, "frozen"): if os.name == "nt": mplconfigdir = os.path.join(os.environ["LOCALAPPDATA"], appdatadir, "mplconfig") elif sys.platform == "darwin": mplconfigdir = os.path.join(os.path.expanduser("~/Library/Caches"), appdatadir, "mplconfig") else: return # do nothing on linux mplconfigdir = os.environ.setdefault("MPLCONFIGDIR", mplconfigdir) if not os.path.exists(mplconfigdir): os.makedirs(mplconfigdir)
[docs] def config_matplotlib(backend=None): """ Setup matplotlib to use a particular backend. The backend should be 'WXAgg' for interactive use, or 'Agg' for batch. This distinction allows us to run in environments such as cluster computers which do not have wx installed on the compute nodes. This function must be called before any imports to matplotlib. To allow this, modules should not import matplotlib at the module level, but instead import it for each function/method that uses it. Exceptions can be made for modules which are completely dedicated to plotting, but these modules should never be imported at the module level. """ import os import sys import matplotlib as mpl # When running from a frozen environment created by py2exe, we will not # have a range of backends available, and must set the default to WXAgg. # With a full matplotlib distribution we can use whatever the user prefers. if hasattr(sys, "frozen"): if "MPLCONFIGDIR" not in os.environ: raise RuntimeError(r"MPLCONFIGDIR should be set to e.g., %LOCALAPPDATA%\YourApp\mplconfig") if backend is None: backend = "WXAgg" ## CRUFT: check that backend is valid, trying alternates if an import fails # if backend is None: # backend = os.environ.get('MPLBACKEND', mpl.rcParams['backend']) # import importlib # for name in (backend, 'MacOSX', 'Qt5Agg', 'Qt4Agg', 'Gtk3Agg', 'TkAgg', 'WXAgg'): # path = 'matplotlib.backends.backend_' + name.lower() # try: # importlib.import_module(path) # backend = name # break # except ImportError: # backend = None # Specify the backend to use for plotting and import backend dependent # classes. This must be done before importing pyplot to have an # effect. If no backend is given, let pyplot use the default. if backend is not None: mpl.use(backend) # Disable interactive mode so that plots are only updated on show() or # draw(). The interactive function must be called before importing pyplot, # otherwise it will have no effect. mpl.interactive(False) # configure the plot style line_width = 1 pad = 2 font_family = "Arial" if os.name == "nt" else "sans-serif" font_size = 12 plot_style = { "xtick.direction": "in", "ytick.direction": "in", "lines.linewidth": line_width, "axes.linewidth": line_width, "xtick.labelsize": font_size, "ytick.labelsize": font_size, "xtick.major.size": 5, "ytick.major.size": 5, "xtick.minor.size": 2.5, "ytick.minor.size": 2.5, "xtick.major.width": line_width, "ytick.major.width": line_width, "xtick.minor.width": line_width, "ytick.minor.width": line_width, "xtick.major.pad": pad, "ytick.major.pad": pad, "xtick.top": True, "ytick.right": True, "font.size": font_size, "font.family": font_family, "svg.fonttype": "none", "savefig.dpi": 100, } mpl.rcParams.update(plot_style)
[docs] def auto_shift(offset): """ Return a y-offset coordinate transform for the current axes. Each call to auto_shift increases the y-offset for the next line by the given number of points (with 72 points per inch). Example:: from matplotlib import pyplot as plt from bumps.plotutil import auto_shift trans = auto_shift(plt.gca()) plot(x, y, trans=trans) """ from matplotlib.transforms import ScaledTranslation import matplotlib.pyplot as plt ax = plt.gca() if ax.lines and hasattr(ax, "_auto_shift"): ax._auto_shift += offset else: ax._auto_shift = 0 trans = plt.gca().transData if ax._auto_shift: trans += ScaledTranslation(0, ax._auto_shift / 72.0, plt.gcf().dpi_scale_trans) return trans
# ======== Color functions ========
[docs] def next_color(axes=None): """ Return the next color in the plot color cycle. Example:: from matplotlib import pyplot as plt from bumps.plotutil import next_color, dhsv color = next_color() plt.errorbar(x, y, yerr=dy, fmt='.', color=color) # Draw the theory line with the same color as the data, but darker plt.plot(x, y, '-', color=dhsv(color, dv=-0.2)) """ import matplotlib.pyplot as plt if axes is None: axes = plt.gca() lines = axes._get_lines try: base = lines.get_next_color() except Exception: try: # Cruft 1.7 - 3.7? base = next(lines.prop_cycler)["color"] except Exception: try: # Cruft 1.4-1.6? base = next(lines.color_cycle) except Exception: # Cruft 1.3 and earlier base = lines._get_next_cycle_color() return base
[docs] def coordinated_colors(base=None): """ Return a set of coordinated colors as c['base|light|dark']. If *base* is not provided, use the next color in the color cycle as the base. Light is bright and pale, dark is dull and saturated. """ if base is None: base = next_color() return dict( base=base, light=dhsv(base, dv=+0.3, ds=-0.2), dark=dhsv(base, dv=-0.25, ds=+0.35), )
[docs] def dhsv(color, dh=0.0, ds=0.0, dv=0.0, da=0.0): """ Modify color on hsv scale. *dv* change intensity, e.g., +0.1 to brighten, -0.1 to darken. *dh* change hue *ds* change saturation *da* change transparency Color can be any valid matplotlib color. The hsv scale is [0,1] in each dimension. Saturation, value and alpha scales are clipped to [0,1] after changing. The hue scale wraps between red to violet. :Example: Make sea green 10% darker: >>> from bumps.plotutil import dhsv >>> darker = dhsv('seagreen', dv=-0.1) >>> print([int(v*255) for v in darker]) [37, 113, 71, 255] """ from matplotlib.colors import colorConverter from colorsys import rgb_to_hsv, hsv_to_rgb from numpy import clip, array, fmod r, g, b, a = colorConverter.to_rgba(color) # print "from color",r,g,b,a h, s, v = rgb_to_hsv(r, g, b) s, v, a = [clip(val, 0.0, 1.0) for val in (s + ds, v + dv, a + da)] h = fmod(h + dh, 1.0) r, g, b = hsv_to_rgb(h, s, v) # print "to color",r,g,b,a return array((r, g, b, a))
# ==== Specialized plotters =====
[docs] def plot_quantiles(x, y, contours, color, alpha=None, axes=None): """ Plot quantile curves for a set of lines. *x* is the x coordinates for all lines. *y* is the y coordinates, one row for each line. *contours* is a list of confidence intervals expressed as percents. *color* is the color to use for the quantiles. Quantiles are draw as a filled region with alpha transparency. Higher probability regions will be covered with multiple contours, which will make them lighter and more saturated. *alpha* is the transparency level to use for all fill regions. The default value, alpha=2./(#contours+1), works pretty well. """ _, q = form_quantiles(y, contours) _plot_quantiles(x, q, color, alpha, axes=axes)
def _plot_quantiles(x, q, color, alpha, axes=None): import matplotlib.pyplot as plt # print "p",p # print "q",q[:,:,0] # print "y",y[:,0] if axes is None: axes = plt.gca() if alpha is None: alpha = 2.0 / (len(q) + 1) edgecolor = dhsv(color, ds=-(1 - alpha), dv=(1 - alpha)) for lo, hi in q: axes.fill_between(x, lo, hi, facecolor=color, edgecolor=edgecolor, alpha=alpha)
[docs] def form_quantiles(y, contours): """ Return quantiles and values for a list of confidence intervals. *contours* is a list of confidence interfaces [a, b,...] expressed as percents. Returns: *quantiles* is a list of intervals [[a_low, a_high], [b_low, b_high], ...] in [0,1]. *values* is a list of intervals [[A_low, A_high], ...] with one entry in A for each row in y. """ from numpy import reshape from scipy.stats.mstats import mquantiles p = _convert_contours_to_probabilities(contours) q = mquantiles(y, prob=p, axis=0) p = reshape(p, (2, -1)) q = reshape(q, (-1, 2, len(y[0]))) return p, q
def _convert_contours_to_probabilities(contours): """ Given confidence intervals [a, b,...] as percents, return probability in [0,1] for each interval as [a_low, a_high, b_low, b_high, ...]. """ from numpy import hstack # lower quantile for ci in percent = (100 - ci)/2 # upper quantile = 100 - lower quantile = 100 - (100-ci)/2 = (100 + ci)/2 # divide by an additional 100 to get proportion from 0 to 1 return hstack([(100.0 - p, 100.0 + p) for p in contours]) / 200.0