On Sep 9, 2010, at 10:53 AM, Bernardo Rocha wrote:
> Hi everyone,
>
> I would like to know if it is possible (I guess it is) and how can I do a
> plot such as the one in the attached figure?
>
> Could someone help me with this? I know that this one was done in gnuplot,
> but I would like to use matplotlib.
>
> Thanks in advance.
> Bernardo M. R.
Hi Bernardo,
I have some functions which I use to draw slope markers (attached). There are
examples of how to use it in the if-main block at the bottom of the file. It
should be simple enough to use and works for linear and log scales.
The functions have grown sort of organically, so they're not as polished as
they could be. In particular, the handling of linear/log scales could be
refactored (probably by making slope marker a subclass of Polygon). Anyway, I
haven't gotten around to cleaning things up, but the functions are very usable
as is.
Hope that helps,
-Tony
import numpy as np
import matplotlib.pyplot as plt
def slope_marker(origin, slope, size_frac=0.1, pad_frac=0.1, ax=None,
invert=False):
"""Plot triangular slope marker labeled with slope.
Parameters
--
origin : (x, y)
tuple of x, y coordinates for the slope
slope : float or (rise, run)
the length of the slope triangle
size_frac : float
the fraction of the xaxis length used to determine the size of the slope
marker. Should be less than 1.
pad_frac : float
the fraction of the slope marker used to pad text labels. Should be less
than 1.
invert : bool
Normally, the slope marker is below a line for positive slopes and above
a line for negative slopes; `invert` flips the marker.
"""
if ax is None:
ax = plt.gca()
if np.iterable(slope):
rise, run = slope
slope = float(rise) / run
else:
rise = run = None
x0, y0 = origin
xlim = ax.get_xlim()
dx_linear = size_frac * (xlim[1] - xlim[0])
dx_decades = size_frac * (np.log10(xlim[1]) - np.log10(xlim[0]))
if invert:
dx_linear = -dx_linear
dx_decades = -dx_decades
if ax.get_xscale() == 'log':
log_size = dx_decades
dx = _log_distance(x0, log_size)
x_run = _text_position(x0, log_size/2., scale='log')
x_rise = _text_position(x0+dx, dx_decades*pad_frac, scale='log')
else:
dx = dx_linear
x_run = _text_position(x0, dx/2.)
x_rise = _text_position(x0+dx, pad_frac * dx)
if ax.get_yscale() == 'log':
log_size = dx_decades * slope
dy = _log_distance(y0, log_size)
y_run = _text_position(y0, -dx_decades*slope*pad_frac, scale='log')
y_rise = _text_position(y0, log_size/2., scale='log')
else:
dy = dx_linear * slope
y_run = _text_position(y0, -(pad_frac * dy))
y_rise = _text_position(y0, dy/2.)
x_pad = pad_frac * dx
y_pad = pad_frac * dy
va = 'top' if y_pad > 0 else 'bottom'
ha = 'left' if x_pad > 0 else 'right'
if rise is not None:
ax.text(x_run, y_run, str(run), va=va, ha='center')
ax.text(x_rise, y_rise, str(rise), ha=ha, va='center')
else:
ax.text(x_rise, y_rise, str(slope), ha=ha, va='center')
ax.add_patch(_slope_triangle(origin, dx, dy))
def log_displace(x0, dx_log=None, x1=None, frac=None):
"""Return point displaced by a logarithmic value.
For example, if you want to move 1 decade away from `x0`, set `dx_log` = 1,
such that for `x0` = 10, we have `displace(10, 1)` = 100
Parameters
--
x0 : float
reference point
dx_log : float
displacement in decades.
x1 : float
end point
frac : float
fraction of line (on logarithmic scale) between x0 and x1
"""
if dx_log is not None:
return 10**(np.log10(x0) + dx_log)
elif x1 is not None and frac is not None:
return 10**(np.log10(x0) + frac * np.log10(float(x1)/x0))
else:
raise ValueError('Specify `dx_log` or both `x1` and `frac`.')
def _log_distance(x0, dx_decades):
return log_displace(x0, dx_decades) - x0
def _text_position(x0, dx, scale='linear'):
if scale == 'linear':
return x0 + dx
elif scale == 'log':
return log_displace(x0, dx)
else:
raise ValueError('Unknown value for `scale`: %s' % scale)
def _slope_triangle(origin, dx, dy, ec='none', fc='0.8', **poly_kwargs):
"""Return Polygon representing slope.
/|
/ | dy
/__|
dx
"""
verts = [np.asarray(origin)]
verts.append(verts[0] + (dx, 0))
verts.append(verts[0] + (dx, dy))
return plt.Polygon(verts, ec=ec, fc=fc, **poly_kwargs)
if __name__ == '__main__':
plt.plot([0, 2], [0, 1])
slope_marker((1, 0.4), (1, 2))
x = np.logspace(0, 2)
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2)
ax1.loglog(x, x**0