From bc5bd1eef134d95d4d71d219a61615c917269115 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Wed, 22 Feb 2023 11:31:34 +0100 Subject: [PATCH] Simplify transforms invalidation system. The INVALID_NON_AFFINE state is never used, except at init, but there it can be replaced by a fully invalid state (the difference is that calls to invalidate() will not be propagated, but that's OK because any (necessarily new) dependent of the new node will also already be in an invalid state as well). Thus, for clarity, replace the 4 possible values of `._invalid` (0-3) by only 3 values (0-2), and get rid of bit-twiddling in TransformedPath._revalidate. Also rename "value" to (invalidation) "level", invert the logic in _invalidate_internal (I find the new order easier to follow), and get rid of a seemingly incorrect statement about invalidation stickiness (as far as I can tell, once a node has been revalidated (`._invalid = 0`), it can again be invalidated as affine_only). --- .../deprecations/25282-AL.rst | 3 + lib/matplotlib/transforms.py | 67 +++++++------------ 2 files changed, 29 insertions(+), 41 deletions(-) create mode 100644 doc/api/next_api_changes/deprecations/25282-AL.rst diff --git a/doc/api/next_api_changes/deprecations/25282-AL.rst b/doc/api/next_api_changes/deprecations/25282-AL.rst new file mode 100644 index 000000000000..0915ba5c65f0 --- /dev/null +++ b/doc/api/next_api_changes/deprecations/25282-AL.rst @@ -0,0 +1,3 @@ +``INVALID_NON_AFFINE``, ``INVALID_AFFINE``, ``INVALID`` attributes of ``TransformNode`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +These attributes are deprecated. diff --git a/lib/matplotlib/transforms.py b/lib/matplotlib/transforms.py index f545bd7a1f1f..cfb9877ad31f 100644 --- a/lib/matplotlib/transforms.py +++ b/lib/matplotlib/transforms.py @@ -92,9 +92,12 @@ class TransformNode: # Invalidation may affect only the affine part. If the # invalidation was "affine-only", the _invalid member is set to # INVALID_AFFINE_ONLY - INVALID_NON_AFFINE = 1 - INVALID_AFFINE = 2 - INVALID = INVALID_NON_AFFINE | INVALID_AFFINE + INVALID_NON_AFFINE = _api.deprecated("3.8")(_api.classproperty(lambda cls: 1)) + INVALID_AFFINE = _api.deprecated("3.8")(_api.classproperty(lambda cls: 2)) + INVALID = _api.deprecated("3.8")(_api.classproperty(lambda cls: 3)) + + # Possible values for the _invalid attribute. + _VALID, _INVALID_AFFINE_ONLY, _INVALID_FULL = range(3) # Some metadata about the transform, used to determine whether an # invalidation is affine-only @@ -117,10 +120,8 @@ def __init__(self, shorthand_name=None): ``str(transform)`` when DEBUG=True. """ self._parents = {} - - # TransformNodes start out as invalid until their values are - # computed for the first time. - self._invalid = 1 + # Initially invalid, until first computation. + self._invalid = self._INVALID_FULL self._shorthand_name = shorthand_name or '' if DEBUG: @@ -159,37 +160,24 @@ def invalidate(self): Invalidate this `TransformNode` and triggers an invalidation of its ancestors. Should be called any time the transform changes. """ - value = self.INVALID - if self.is_affine: - value = self.INVALID_AFFINE - return self._invalidate_internal(value, invalidating_node=self) + return self._invalidate_internal( + level=self._INVALID_AFFINE_ONLY if self.is_affine else self._INVALID_FULL, + invalidating_node=self) - def _invalidate_internal(self, value, invalidating_node): + def _invalidate_internal(self, level, invalidating_node): """ Called by :meth:`invalidate` and subsequently ascends the transform stack calling each TransformNode's _invalidate_internal method. """ - # determine if this call will be an extension to the invalidation - # status. If not, then a shortcut means that we needn't invoke an - # invalidation up the transform stack as it will already have been - # invalidated. - - # N.B This makes the invalidation sticky, once a transform has been - # invalidated as NON_AFFINE, then it will always be invalidated as - # NON_AFFINE even when triggered with a AFFINE_ONLY invalidation. - # In most cases this is not a problem (i.e. for interactive panning and - # zooming) and the only side effect will be on performance. - status_changed = self._invalid < value - - if self.pass_through or status_changed: - self._invalid = value - - for parent in list(self._parents.values()): - # Dereference the weak reference - parent = parent() - if parent is not None: - parent._invalidate_internal( - value=value, invalidating_node=self) + # If we are already more invalid than the currently propagated invalidation, + # then we don't need to do anything. + if level <= self._invalid and not self.pass_through: + return + self._invalid = level + for parent in list(self._parents.values()): + parent = parent() # Dereference the weak reference. + if parent is not None: + parent._invalidate_internal(level=level, invalidating_node=self) def set_children(self, *children): """ @@ -2379,20 +2367,17 @@ def frozen(self): return frozen.frozen() return frozen - def _invalidate_internal(self, value, invalidating_node): + def _invalidate_internal(self, level, invalidating_node): # In some cases for a composite transform, an invalidating call to # AFFINE_ONLY needs to be extended to invalidate the NON_AFFINE part # too. These cases are when the right hand transform is non-affine and # either: # (a) the left hand transform is non affine # (b) it is the left hand node which has triggered the invalidation - if (value == Transform.INVALID_AFFINE and - not self._b.is_affine and + if (not self._b.is_affine and (not self._a.is_affine or invalidating_node is self._a)): - value = Transform.INVALID - - super()._invalidate_internal(value=value, - invalidating_node=invalidating_node) + level = Transform._INVALID_FULL + super()._invalidate_internal(level, invalidating_node) def __eq__(self, other): if isinstance(other, (CompositeGenericTransform, CompositeAffine2D)): @@ -2757,7 +2742,7 @@ def __init__(self, path, transform): def _revalidate(self): # only recompute if the invalidation includes the non_affine part of # the transform - if (self._invalid & self.INVALID_NON_AFFINE == self.INVALID_NON_AFFINE + if (self._invalid == self._INVALID_FULL or self._transformed_path is None): self._transformed_path = \ self._transform.transform_path_non_affine(self._path)