8000 FIX: change autolim exclude shapes not in data co-ordinates · matplotlib/matplotlib@a005562 · GitHub
[go: up one dir, main page]

Skip to content
Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit a005562

Browse files
committed
FIX: change autolim exclude shapes not in data co-ordinates
1 parent 4728e70 commit a005562

File tree

13 files changed

+3845
-3771
lines changed

13 files changed

+3845
-3771
lines changed

doc/api/next_api_changes/2019-03-04-AL.rst

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,41 @@ without an explicit call to `Axes.autoscale_view`.
1313
In some cases, this can result in different limits being reported. If this is
1414
an issue, consider triggering a draw with `fig.canvas.draw`.
1515

16+
Autoscaling changes for Collections
17+
```````````````````````````````````
18+
19+
Autoscaling has also changed for artists that are based on the `.Collection`
20+
class. Previously, the method that calculates the automatic limits
21+
`.Collection.get_datalim` tried to take into account the size of objects
22+
in the collection and make the limits large enough to not clip any of the
23+
object, i.e., for `.Axes.scatter` it would make the limits large enough to not
24+
clip any markers in the scatter. This is problematic when the object size is
25+
specified in physical space, or figure-relative space, because the transform
26+
from physical units to data limits requires knowing the data limits, and
27+
becomes invalid when the new limits are applied. This is an inverse
28+
problem that is theoretically solvable (if the object is physically smaller
29+
than the axes), but the extra complexity was not deemed worth it, particularly
30+
as the most common use case is for markers in scatter that are usually small
31+
enough to be accommodated by the default data limit margins.
32+
33+
While the new behavior is algorithmically simpler, it is conditional on
34+
properties of the `.Collection` object:
35+
36+
1. ``offsets = None``, ``transform`` is a child of `.Axes.transData`: use the paths
37+
for the automatic limits (i.e. for `.LineCollection` in `Axes.streamplot`).
38+
2. ``offsets != None``, and ``offset_transform`` is child of `.Axes.transData`:
39+
40+
a) ``transform`` is child of `.Axes.transData`: use the ``path + offset`` for
41+
limits (i.e., for `.Axes.bar`).
42+
b) ``transform`` is not a child of `.Axes.transData`: just use the offsets
43+
for the limits (i.e. for scatter)
44+
45+
3. otherwise return a null `.Bbox`.
46+
47+
While this seems complicated, the logic is simply to use the information from
48+
the object that are in data space for the limits, but not information that is
49+
in physical units.
50+
1651
LogLocator.nonsingular now maintains the orders of its arguments
1752
````````````````````````````````````````````````````````````````
1853

lib/matplotlib/collections.py

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,8 @@ def __init__(self,
146146
self._joinstyle = None
147147

148148
self._offsets = np.zeros((1, 2))
149+
# save if offsets passed in were none...
150+
self._offsetsNone = offsets is None
149151
self._uniform_offsets = None
150152
if offsets is not None:
151153
offsets = np.asanyarray(offsets, float)
@@ -179,9 +181,30 @@ def get_offset_transform(self):
179181
return t
180182

181183
def get_datalim(self, transData):
184+
185+
# Get the automatic datalim of the collection.
186+
#
187+
# This operation depends on the transforms for the data in the
188+
# collection and whether the collection has offsets.
189+
#
190+
# 1) offsets = None, transform child of transData: use the paths for
191+
# the automatic limits (i.e. for LineCollection in streamline).
192+
# 2) offsets != None: offset_transform is child of transData:
193+
# a) transform is child of transData: use the path + offset for
194+
# limits (i.e for bar).
195+
# b) transform is not a child of transData: just use the offsets
196+
# for the limits (i.e. for scatter)
197+
# 3) otherwise return a null Bbox.
198+
182199
transform = self.get_transform()
183200
transOffset = self.get_offset_transform()
201+
if (not self._offsetsNone and
202+
not transOffset.contains_branch(transData)):
203+
# if there are offsets but in some co-ords other than data,
204+
# then don't use them for autoscaling.
205+
return transforms.Bbox.null()
184206
offsets = self._offsets
207+
185208
paths = self.get_paths()
186209

187210
if not transform.is_affine:
@@ -196,13 +219,30 @@ def get_datalim(self, transData):
196219
# get_path_collection_extents handles nan but not masked arrays
197220

198221
if len(paths) and len(offsets):
199-
result = mpath.get_path_collection_extents(
200-
transform.frozen(), paths, self.get_transforms(),
201-
offsets, transOffset.frozen())
202-
result = result.inverse_transformed(transData)
203-
else:
204-
result = transforms.Bbox.null()
205-
return result
222+
if transform.contains_branch(transData):
223+
# collections that are just in data units (like quiver)
224+
# can properly have the axes limits set by their shape +
225+
# offset. LineCollections that have no offsets can
226+
# also use this algorithm (like streamplot).
227+
result = mpath.get_path_collection_extents(
228+
transform.frozen(), paths, self.get_transforms(),
229+
offsets, transOffset.frozen())
230+
return result.inverse_transformed(transData)
231+
if not self._offsetsNone:
232+
# this is for collections that have their paths (shapes)
233+
# in physical, axes-relative, or figure-relative units
234+
# (i.e. like scatter). We can't uniquely set limits based on
235+
# those shapes, so we just set the limits based on their
236+
# location.
237+
# Finish the transform:
238+
offsets = transOffset.transform(offsets)
239+
offsets = transData.inverted().transform(offsets)
240+
offsets = np.ma.masked_invalid(offsets)
241+
if not offsets.mask.all():
242+
points = np.row_stack((offsets.min(axis=0),
243+
offsets.max(axis=0)))
244+
return transforms.Bbox(points)
245+
return transforms.Bbox.null()
206246

207247
def get_window_extent(self, renderer):
208248
# TODO: check to ensure that this does not fail for
@@ -1299,7 +1339,6 @@ def __init__(self, segments, # Can be None.
12991339
antialiaseds = (mpl.rcParams['lines.antialiased'],)
13001340

13011341
colors = mcolors.to_rgba_array(colors)
1302-
13031342
Collection.__init__(
13041343
self,
13051344
edgecolors=colors,
Binary file not shown.
Loading

0 commit comments

Comments
 (0)
0