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

Skip to content

Commit 41e75ec

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

File tree

13 files changed

+3844
-3771
lines changed
  • 13 files changed

    +3844
    -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: 46 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,29 @@ 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), offsets.max(axis=0)))
    243+
    return transforms.Bbox(points)
    244+
    return transforms.Bbox.null()
    206245

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

    13011340
    colors = mcolors.to_rgba_array(colors)
    1302-
    13031341
    Collection.__init__(
    13041342
    self,
    13051343
    edgecolors=colors,
    Binary file not shown.
    Loading

    0 commit comments

    Comments
     (0)
    0