Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 98f6eb2

Browse files
committed
Merge pull request #731 from pelson/plot_limit_with_transform
Plot limit with transform
2 parents 22e6070 + 4b0fbb5 commit 98f6eb2

File tree

9 files changed

+745
-239
lines changed

9 files changed

+745
-239
lines changed

doc/api/api_changes.rst

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,54 @@ Changes in 1.2.x
7272
original keyword arguments will override any value provided by
7373
*capthick*.
7474

75+
* Transform subclassing behaviour is now subtly changed. If your transform
76+
implements a non-affine transformation, then it should override the
77+
``transform_non_affine`` method, rather than the generic ``transform`` method.
78+
Previously transforms would define ``transform`` and then copy the
79+
method into ``transform_non_affine``:
80+
81+
class MyTransform(mtrans.Transform):
82+
def transform(self, xy):
83+
...
84+
transform_non_affine = transform
85+
86+
This approach will no longer function correctly and should be changed to:
87+
88+
class MyTransform(mtrans.Transform):
89+
def transform_non_affine(self, xy):
90+
...
91+
92+
* Artists no longer have ``x_isdata`` or ``y_isdata`` attributes; instead
93+
any artist's transform can be interrogated with
94+
``artist_instance.get_transform().contains_branch(ax.transData)``
95+
96+
* Lines added to an axes now take into account their transform when updating the
97+
data and view limits. This means transforms can now be used as a pre-transform.
98+
For instance:
99+
100+
>>> import matplotlib.pyplot as plt
101+
>>> import matplotlib.transforms as mtrans
102+
>>> ax = plt.axes()
103+
>>> ax.plot(range(10), transform=mtrans.Affine2D().scale(10) + ax.transData)
104+
>>> print(ax.viewLim)
105+
Bbox('array([[ 0., 0.],\n [ 90., 90.]])')
106+
107+
* One can now easily get a transform which goes from one transform's coordinate system
108+
to another, in an optimized way, using the new subtract method on a transform. For instance,
109+
to go from data coordinates to axes coordinates::
110+
111+
>>> import matplotlib.pyplot as plt
112+
>>> ax = plt.axes()
113+
>>> data2ax = ax.transData - ax.transAxes
114+
>>> print(ax.transData.depth, ax.transAxes.depth)
115+
3, 1
116+
>>> print(data2ax.depth)
117+
2
118+
119+
for versions before 1.2 this could only be achieved in a sub-optimal way, using
120+
``ax.transData + ax.transAxes.inverted()`` (depth is a new concept, but had it existed
121+
it would return 4 for this example).
122+
75123
Changes in 1.1.x
76124
================
77125

lib/matplotlib/artist.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,6 @@ def __init__(self):
101101
self._remove_method = None
102102
self._url = None
103103
self._gid = None
104-
self.x_isdata = True # False to avoid updating Axes.dataLim with x
105-
self.y_isdata = True # with y
106104
self._snap = None
107105

108106
def remove(self):

lib/matplotlib/axes.py

Lines changed: 51 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1461,17 +1461,52 @@ def add_line(self, line):
14611461

14621462
self._update_line_limits(line)
14631463
if not line.get_label():
1464-
line.set_label('_line%d'%len(self.lines))
1464+
line.set_label('_line%d' % len(self.lines))
14651465
self.lines.append(line)
14661466
line._remove_method = lambda h: self.lines.remove(h)
14671467
return line
14681468

14691469
def _update_line_limits(self, line):
1470-
p = line.get_path()
1471-
if p.vertices.size > 0:
1472-
self.dataLim.update_from_path(p, self.ignore_existing_data_limits,
1473-
updatex=line.x_isdata,
1474-
updatey=line.y_isdata)
1470+
"""Figures out the data limit of the given line, updating self.dataLim."""
1471+
path = line.get_path()
1472+
if path.vertices.size == 0:
1473+
return
1474+
1475+
line_trans = line.get_transform()
1476+
1477+
if line_trans == self.transData:
1478+
data_path = path
1479+
1480+
elif any(line_trans.contains_branch_seperately(self.transData)):
1481+
# identify the transform to go from line's coordinates
1482+
# to data coordinates
1483+
trans_to_data = line_trans - self.transData
1484+
1485+
# if transData is affine we can use the cached non-affine component
1486+
# of line's path. (since the non-affine part of line_trans is
1487+
# entirely encapsulated in trans_to_data).
1488+
if self.transData.is_affine:
1489+
line_trans_path = line._get_transformed_path()
1490+
na_path, _ = line_trans_path.get_transformed_path_and_affine()
1491+
data_path = trans_to_data.transform_path_affine(na_path)
1492+
else:
1493+
data_path = trans_to_data.transform_path(path)
1494+
else:
1495+
# for backwards compatibility we update the dataLim with the
1496+
# coordinate range of the given path, even though the coordinate
1497+
# systems are completely different. This may occur in situations
1498+
# such as when ax.transAxes is passed through for absolute
1499+
# positioning.
1500+
data_path = path
1501+
1502+
if data_path.vertices.size > 0:
1503+
updatex, updatey = line_trans.contains_branch_seperately(
1504+
self.transData
1505+
)
1506+
self.dataLim.update_from_path(data_path,
1507+
self.ignore_existing_data_limits,
1508+
updatex=updatex,
1509+
updatey=updatey)
14751510
self.ignore_existing_data_limits = False
14761511

14771512
def add_patch(self, p):
@@ -1507,11 +1542,14 @@ def _update_patch_limits(self, patch):
15071542
if vertices.size > 0:
15081543
xys = patch.get_patch_transform().transform(vertices)
15091544
if patch.get_data_transform() != self.transData:
1510-
transform = (patch.get_data_transform() +
1511-
self.transData.inverted())
1512-
xys = transform.transform(xys)
1513-
self.update_datalim(xys, updatex=patch.x_isdata,
1514-
updatey=patch.y_isdata)
1545+
patch_to_data = (patch.get_data_transform() -
1546+
self.transData)
1547+
xys = patch_to_data.transform(xys)
1548+
1549+
updatex, updatey = patch.get_transform().\
1550+
contains_branch_seperately(self.transData)
1551+
self.update_datalim(xys, updatex=updatex,
1552+
updatey=updatey)
15151553

15161554

15171555
def add_table(self, tab):
@@ -1599,13 +1637,13 @@ def _process_unit_info(self, xdata=None, ydata=None, kwargs=None):
15991637
if xdata is not None:
16001638
# we only need to update if there is nothing set yet.
16011639
if not self.xaxis.have_units():
1602-
self.xaxis.update_units(xdata)
1640+
self.xaxis.update_units(xdata)
16031641
#print '\tset from xdata', self.xaxis.units
16041642

16051643
if ydata is not None:
16061644
# we only need to update if there is nothing set yet.
16071645
if not self.yaxis.have_units():
1608-
self.yaxis.update_units(ydata)
1646+
self.yaxis.update_units(ydata)
16091647
#print '\tset from ydata', self.yaxis.units
16101648

16111649
# process kwargs 2nd since these will override default units
@@ -3424,7 +3462,6 @@ def axhline(self, y=0, xmin=0, xmax=1, **kwargs):
34243462
trans = mtransforms.blended_transform_factory(
34253463
self.transAxes, self.transData)
34263464
l = mlines.Line2D([xmin,xmax], [y,y], transform=trans, **kwargs)
3427-
l.x_isdata = False
34283465
self.add_line(l)
34293466
self.autoscale_view(scalex=False, scaley=scaley)
34303467
return l
@@ -3489,7 +3526,6 @@ def axvline(self, x=0, ymin=0, ymax=1, **kwargs):
34893526
trans = mtransforms.blended_transform_factory(
34903527
self.transData, self.transAxes)
34913528
l = mlines.Line2D([x,x], [ymin,ymax] , transform=trans, **kwargs)
3492-
l.y_isdata = False
34933529
self.add_line(l)
34943530
self.autoscale_view(scalex=scalex, scaley=False)
34953531
return l
@@ -3546,7 +3582,6 @@ def axhspan(self, ymin, ymax, xmin=0, xmax=1, **kwargs):
35463582
verts = (xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin)
35473583
p = mpatches.Polygon(verts, **kwargs)
35483584
p.set_transform(trans)
3549-
p.x_isdata = False
35503585
self.add_patch(p)
35513586
self.autoscale_view(scalex=False)
35523587
return p
@@ -3603,7 +3638,6 @@ def axvspan(self, xmin, xmax, ymin=0, ymax=1, **kwargs):
36033638
verts = [(xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin)]
36043639
p = mpatches.Polygon(verts, **kwargs)
36053640
p.set_transform(trans)
3606-
p.y_isdata = False
36073641
self.add_patch(p)
36083642
self.autoscale_view(scaley=False)
36093643
return p
@@ -3909,7 +3943,6 @@ def plot(self, *args, **kwargs):
39093943
self.add_line(line)
39103944
lines.append(line)
39113945

3912-
39133946
self.autoscale_view(scalex=scalex, scaley=scaley)
39143947
return lines
39153948

lib/matplotlib/lines.py

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
# TODO: expose cap and join style attrs
77
from __future__ import division, print_function
88

9+
import warnings
10+
911
import numpy as np
1012
from numpy import ma
1113
from matplotlib import verbose
@@ -249,17 +251,15 @@ def contains(self, mouseevent):
249251
if len(self._xy)==0: return False,{}
250252

251253
# Convert points to pixels
252-
if self._transformed_path is None:
253-
self._transform_path()
254-
path, affine = self._transformed_path.get_transformed_path_and_affine()
254+
path, affine = self._get_transformed_path().get_transformed_path_and_affine()
255255
path = affine.transform_path(path)
256256
xy = path.vertices
257257
xt = xy[:, 0]
258258
yt = xy[:, 1]
259259

260260
# Convert pick radius from points to pixels
261-
if self.figure == None:
262-
warning.warn('no figure set when check if mouse is on line')
261+
if self.figure is None:
262+
warnings.warn('no figure set when check if mouse is on line')
263263
pixels = self.pickradius
264264
else:
265265
pixels = self.figure.dpi/72. * self.pickradius
@@ -446,13 +446,26 @@ def recache(self, always=False):
446446
self._invalidy = False
447447

448448
def _transform_path(self, subslice=None):
449+
"""
450+
Puts a TransformedPath instance at self._transformed_path,
451+
all invalidation of the transform is then handled by the
452+
TransformedPath instance.
453+
"""
449454
# Masked arrays are now handled by the Path class itself
450455
if subslice is not None:
451456
_path = Path(self._xy[subslice,:])
452457
else:
453458
_path = self._path
454459
self._transformed_path = TransformedPath(_path, self.get_transform())
455460

461+
def _get_transformed_path(self):
462+
"""
463+
Return the :class:`~matplotlib.transforms.TransformedPath` instance
464+
of this line.
465+
"""
466+
if self._transformed_path is None:
467+
self._transform_path()
468+
return self._transformed_path
456469

457470
def set_transform(self, t):
458471
"""
@@ -482,8 +495,8 @@ def draw(self, renderer):
482495
subslice = slice(max(i0-1, 0), i1+1)
483496
self.ind_offset = subslice.start
484497
self._transform_path(subslice)
485-
if self._transformed_path is None:
486-
self._transform_path()
498+
499+
transformed_path = self._get_transformed_path()
487500

488501
if not self.get_visible(): return
489502

@@ -507,7 +520,7 @@ def draw(self, renderer):
507520

508521
funcname = self._lineStyles.get(self._linestyle, '_draw_nothing')
509522
if funcname != '_draw_nothing':
510-
tpath, affine = self._transformed_path.get_transformed_path_and_affine()
523+
tpath, affine = transformed_path.get_transformed_path_and_affine()
511524
if len(tpath.vertices):
512525
self._lineFunc = getattr(self, funcname)
513526
funcname = self.drawStyles.get(self._drawstyle, '_draw_lines')
@@ -528,7 +541,7 @@ def draw(self, renderer):
528541
gc.set_linewidth(self._markeredgewidth)
529542
gc.set_alpha(self._alpha)
530543
marker = self._marker
531-
tpath, affine = self._transformed_path.get_transformed_points_and_affine()
544+
tpath, affine = transformed_path.get_transformed_points_and_affine()
532545
if len(tpath.vertices):
533546
# subsample the markers if markevery is not None
534547
markevery = self.get_markevery()

lib/matplotlib/patches.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,9 +167,21 @@ def get_transform(self):
167167
return self.get_patch_transform() + artist.Artist.get_transform(self)
168168

169169
def get_data_transform(self):
170+
"""
171+
Return the :class:`~matplotlib.transforms.Transform` instance which
172+
maps data coordinates to physical coordinates.
173+
"""
170174
return artist.Artist.get_transform(self)
171175

172176
def get_patch_transform(self):
177+
"""
178+
Return the :class:`~matplotlib.transforms.Transform` instance which
179+
takes patch coordinates to data coordinates.
180+
181+
For example, one may define a patch of a circle which represents a
182+
radius of 5 by providing coordinates for a unit circle, and a
183+
transform which scales the coordinates (the patch coordinate) by 5.
184+
"""
173185
return transforms.IdentityTransform()
174186

175187
def get_antialiased(self):

0 commit comments

Comments
 (0)