Browse files

Merge pull request #1090 from pelson/external_transform_api

External transform api
  • Loading branch information...
2 parents dc535b4 + 456a723 commit c0ee100e33def5804ea92c9773906e52d7f35d43 @efiring efiring committed Aug 20, 2012
View
8 lib/matplotlib/artist.py
@@ -3,7 +3,8 @@
import matplotlib
import matplotlib.cbook as cbook
from matplotlib import docstring, rcParams
-from transforms import Bbox, IdentityTransform, TransformedBbox, TransformedPath
+from transforms import Bbox, IdentityTransform, TransformedBbox, \
+ TransformedPath, Transform
from path import Path
## Note, matplotlib artists use the doc strings for set and get
@@ -223,7 +224,7 @@ def set_transform(self, t):
ACCEPTS: :class:`~matplotlib.transforms.Transform` instance
"""
self._transform = t
- self._transformSet = True
+ self._transformSet = t is not None
self.pchanged()
def get_transform(self):
@@ -233,6 +234,9 @@ def get_transform(self):
"""
if self._transform is None:
self._transform = IdentityTransform()
+ elif (not isinstance(self._transform, Transform)
+ and hasattr(self._transform, '_as_mpl_transform')):
+ self._transform = self._transform._as_mpl_transform(self.axes)
return self._transform
def hitlist(self, event):
View
7 lib/matplotlib/axes.py
@@ -6022,7 +6022,7 @@ def scatter(self, x, y, s=20, c='b', marker='o', cmap=None, norm=None,
edgecolors = edgecolors,
linewidths = linewidths,
offsets = zip(x,y),
- transOffset = self.transData,
+ transOffset = kwargs.pop('transform', self.transData),
)
collection.set_transform(mtransforms.IdentityTransform())
collection.set_alpha(alpha)
@@ -6550,7 +6550,7 @@ def stackplot(self, x, *args, **kwargs):
def streamplot(self, x, y, u, v, density=1, linewidth=None, color=None,
cmap=None, norm=None, arrowsize=1, arrowstyle='-|>',
- minlength=0.1):
+ minlength=0.1, transform=None):
if not self._hold: self.cla()
lines = mstream.streamplot(self, x, y, u, v,
density=density,
@@ -6560,7 +6560,8 @@ def streamplot(self, x, y, u, v, density=1, linewidth=None, color=None,
norm=norm,
arrowsize=arrowsize,
arrowstyle=arrowstyle,
- minlength=minlength)
+ minlength=minlength,
+ transform=transform)
return lines
streamplot.__doc__ = mstream.streamplot.__doc__
View
13 lib/matplotlib/collections.py
@@ -156,9 +156,16 @@ def set_paths(self):
def get_transforms(self):
return self._transforms
+ def get_offset_transform(self):
+ t = self._transOffset
+ if (not isinstance(t, transforms.Transform)
+ and hasattr(t, '_as_mpl_transform')):
+ t = t._as_mpl_transform(self.axes)
+ return t
+
def get_datalim(self, transData):
transform = self.get_transform()
- transOffset = self._transOffset
+ transOffset = self.get_offset_transform()
offsets = self._offsets
paths = self.get_paths()
@@ -192,7 +199,7 @@ def _prepare_points(self):
"""Point prep for drawing and hit testing"""
transform = self.get_transform()
- transOffset = self._transOffset
+ transOffset = self.get_offset_transform()
offsets = self._offsets
paths = self.get_paths()
@@ -1407,7 +1414,7 @@ def draw(self, renderer):
if not self.get_visible(): return
renderer.open_group(self.__class__.__name__, self.get_gid())
transform = self.get_transform()
- transOffset = self._transOffset
+ transOffset = self.get_offset_transform()
offsets = self._offsets
if self.have_units():
View
4 lib/matplotlib/contour.py
@@ -774,6 +774,8 @@ def __init__(self, ax, *args, **kwargs):
raise ValueError('Either colors or cmap must be None')
if self.origin == 'image': self.origin = mpl.rcParams['image.origin']
+ self.transform = kwargs.get('transform', None)
+
self._process_args(*args, **kwargs)
self._process_levels()
@@ -822,6 +824,7 @@ def __init__(self, ax, *args, **kwargs):
antialiaseds = (self.antialiased,),
edgecolors= 'none',
alpha=self.alpha,
+ transform=self.transform,
zorder=zorder)
self.ax.add_collection(col)
self.collections.append(col)
@@ -841,6 +844,7 @@ def __init__(self, ax, *args, **kwargs):
linewidths = width,
linestyle = lstyle,
alpha=self.alpha,
+ transform=self.transform,
zorder=zorder)
col.set_label('_nolegend_')
self.ax.add_collection(col, False)
View
6 lib/matplotlib/pyplot.py
@@ -3001,7 +3001,7 @@ def stackplot(x, *args, **kwargs):
draw_if_interactive()
finally:
ax.hold(washold)
-
+
return ret
# This function was autogenerated by boilerplate.py. Do not edit as
@@ -3047,7 +3047,7 @@ def step(x, y, *args, **kwargs):
@_autogen_docstring(Axes.streamplot)
def streamplot(x, y, u, v, density=1, linewidth=None, color=None, cmap=None,
norm=None, arrowsize=1, arrowstyle='-|>', minlength=0.1,
- hold=None):
+ transform=None, hold=None):
ax = gca()
# allow callers to override the hold state by passing hold=True|False
washold = ax.ishold()
@@ -3058,7 +3058,7 @@ def streamplot(x, y, u, v, density=1, linewidth=None, color=None, cmap=None,
ret = ax.streamplot(x, y, u, v, density=density, linewidth=linewidth,
color=color, cmap=cmap, norm=norm,
arrowsize=arrowsize, arrowstyle=arrowstyle,
- minlength=minlength)
+ minlength=minlength, transform=transform)
draw_if_interactive()
finally:
ax.hold(washold)
View
9 lib/matplotlib/quiver.py
@@ -408,10 +408,11 @@ def __init__(self, ax, *args, **kw):
self.width = kw.pop('width', None)
self.color = kw.pop('color', 'k')
self.pivot = kw.pop('pivot', 'tail')
+ self.transform = kw.pop('transform', ax.transData)
kw.setdefault('facecolors', self.color)
kw.setdefault('linewidths', (0,))
collections.PolyCollection.__init__(self, [], offsets=self.XY,
- transOffset=ax.transData,
+ transOffset=self.transform,
closed=False,
**kw)
self.polykw = kw
@@ -529,8 +530,6 @@ def _angles_lengths(self, U, V, eps=1):
lengths = np.absolute(dxy[:,0] + dxy[:,1]*1j) / eps
return angles, lengths
-
-
def _make_verts(self, U, V):
uv = (U+V*1j)
if self.angles == 'xy' and self.scale_units == 'xy':
@@ -592,7 +591,6 @@ def _make_verts(self, U, V):
return XY
-
def _h_arrows(self, length):
""" length is in arrow width units """
# It might be possible to streamline the code
@@ -824,6 +822,7 @@ def __init__(self, ax, *args, **kw):
self.barb_increments = kw.pop('barb_increments', dict())
self.rounding = kw.pop('rounding', True)
self.flip = kw.pop('flip_barb', False)
+ transform = kw.pop('transform', ax.transData)
#Flagcolor and and barbcolor provide convenience parameters for setting
#the facecolor and edgecolor, respectively, of the barb polygon. We
@@ -851,7 +850,7 @@ def __init__(self, ax, *args, **kw):
#Make a collection
barb_size = self._length**2 / 4 #Empirically determined
collections.PolyCollection.__init__(self, [], (barb_size,), offsets=xy,
- transOffset=ax.transData, **kw)
+ transOffset=transform, **kw)
self.set_transform(transforms.IdentityTransform())
self.set_UVC(u, v, c)
View
11 lib/matplotlib/streamplot.py
@@ -14,7 +14,7 @@
def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None,
cmap=None, norm=None, arrowsize=1, arrowstyle='-|>',
- minlength=0.1):
+ minlength=0.1, transform=None):
"""Draws streamlines of a vector flow.
*x*, *y* : 1d arrays
@@ -134,10 +134,15 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None,
line_colors.extend(color_values)
arrow_kw['color'] = cmap(norm(color_values[n]))
- p = patches.FancyArrowPatch(arrow_tail, arrow_head, **arrow_kw)
+ p = patches.FancyArrowPatch(arrow_tail,
+ arrow_head,
+ transform=transform,
+ **arrow_kw)
axes.add_patch(p)
- lc = mcollections.LineCollection(streamlines, **line_kw)
+ lc = mcollections.LineCollection(streamlines,
+ transform=transform,
+ **line_kw)
if use_multicolor_lines:
lc.set_array(np.asarray(line_colors))
lc.set_cmap(cmap)
View
BIN lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.pdf
Binary file not shown.
View
BIN lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
7,726 lib/matplotlib/tests/baseline_images/test_transforms/pre_transform_data.svg
7,726 additions, 0 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
View
61 lib/matplotlib/tests/test_transforms.py
@@ -4,7 +4,7 @@
from matplotlib.transforms import Affine2D, BlendedGenericTransform
from matplotlib.path import Path
from matplotlib.scale import LogScale
-from matplotlib.testing.decorators import cleanup
+from matplotlib.testing.decorators import cleanup, image_comparison
import numpy as np
import matplotlib.transforms as mtrans
@@ -52,6 +52,59 @@ def transform_non_affine(self, path):
plt.draw()
+@cleanup
+def test_external_transform_api():
+ class ScaledBy(object):
+ def __init__(self, scale_factor):
+ self._scale_factor = scale_factor
+
+ def _as_mpl_transform(self, axes):
+ return mtrans.Affine2D().scale(self._scale_factor) + axes.transData
+
+ ax = plt.axes()
+ line, = plt.plot(range(10), transform=ScaledBy(10))
+ ax.set_xlim(0, 100)
+ ax.set_ylim(0, 100)
+ # assert that the top transform of the line is the scale transform.
+ np.testing.assert_allclose(line.get_transform()._a.get_matrix(),
+ mtrans.Affine2D().scale(10).get_matrix())
+
+
+@image_comparison(baseline_images=['pre_transform_data'])
+def test_pre_transform_plotting():
+ # a catch-all for as many as possible plot layouts which handle pre-transforming the data
+ # NOTE: The axis range is important in this plot. It should be x10 what the data suggests it should be
+ ax = plt.axes()
+ times10 = mtrans.Affine2D().scale(10)
+
+ ax.contourf(np.arange(48).reshape(6, 8), transform=times10 + ax.transData)
+
+ ax.pcolormesh(np.linspace(0, 4, 7),
+ np.linspace(5.5, 8, 9),
+ np.arange(48).reshape(6, 8),
+ transform=times10 + ax.transData)
+
+ ax.scatter(np.linspace(0, 10), np.linspace(10, 0),
+ transform=times10 + ax.transData)
+
+
+ x = np.linspace(8, 10, 20)
+ y = np.linspace(1, 5, 20)
+ u = 2*np.sin(x) + np.cos(y[:, np.newaxis])
+ v = np.sin(x) - np.cos(y[:, np.newaxis])
+
+ ax.streamplot(x, y, u, v, transform=times10 + ax.transData,
+ density=(1, 1), linewidth=u**2 + v**2)
+
+ # reduce the vector data down a bit for barb and quiver plotting
+ x, y = x[::3], y[::3]
+ u, v = u[::3, ::3], v[::3, ::3]
+
+ ax.quiver(x, y + 5, u, v, transform=times10 + ax.transData)
+
+ ax.barbs(x - 3, y + 5, u**2, v**2, transform=times10 + ax.transData)
+
+
def test_Affine2D_from_values():
points = [ [0,0],
[10,20],
@@ -88,6 +141,7 @@ def test_Affine2D_from_values():
expected = np.array( [[0,6],[0,6],[0,6]] )
assert_almost_equal(actual,expected)
+
def test_clipping_of_log():
# issue 804
M,L,C = Path.MOVETO, Path.LINETO, Path.CLOSEPOLY
@@ -109,3 +163,8 @@ def test_clipping_of_log():
# operation must be replaced by a move to the first point.
assert np.allclose(tcodes, [ M, M, L, L, L ])
assert np.allclose(tpoints[-1], tpoints[0])
+
+
+if __name__=='__main__':
+ import nose
+ nose.runmodule(argv=['-s','--with-doctest'], exit=False)

0 comments on commit c0ee100

Please sign in to comment.