Skip to content

BUG: CompositeGenericTransform.contains_branch_separately only checks second component, ignoring first #32099

Description

@sanrishi

Bug summary

CompositeGenericTransform.contains_branch_separately() only checks the second component (_b) of the composite transform, completely ignoring the first component (_a). This means if a composed transform is A + B and the target transform is a subtree of A, it returns (False, False) instead of the correct result.

Code for reproduction

A composite transform A + B where the query transform is found in component A:

import matplotlib.transforms as mtransforms
from matplotlib.transforms import Transform, BlendedGenericTransform

# Two independent non-affine transforms
class NonAffine(Transform):
    input_dims = 2
    output_dims = 2
    is_separable = True
    is_affine = False
    def transform_non_affine(self, values): return values

tn1 = NonAffine()
tn2 = NonAffine()

# Blended transform: _x=tn1, _y=tn2
blend = BlendedGenericTransform(tn1, tn2)

# Composite: _a=blend, _b=tn1
composite = blend + tn1

# tn1 is a subtree of _a.x, but NOT of _b
x, y = composite.contains_branch_separately(tn1)
print(f'x={x}, y={y}')  # Got: False, False. Expected: True, False

Actual outcome

contains_branch_separately returns (False, False).

Expected outcome

contains_branch_separately should return (True, False) since tn1 is in the _a.x branch.

Buggy code location

https://github.com/matplotlib/matplotlib/blob/main/lib/matplotlib/transforms.py#L2450-L2457

def contains_branch_separately(self, other_transform):
    if self.output_dims != 2:
        raise ValueError('contains_branch_separately only supports '
                         'transforms with 2 output dimensions')
    if self == other_transform:
        return (True, True)
    return self._b.contains_branch_separately(other_transform)  # <-- only _b!

The fix should check both children:

def contains_branch_separately(self, other_transform):
    if self.output_dims != 2:
        raise ValueError('contains_branch_separately only supports '
                         'transforms with 2 output dimensions')
    if self == other_transform:
        return (True, True)
    a_result = self._a.contains_branch_separately(other_transform)
    b_result = self._b.contains_branch_separately(other_transform)
    return (a_result[0] or b_result[0], a_result[1] or b_result[1])

Impact

This bug affects any code that uses contains_branch_separately on composite transforms, including autoscaling and axis limit updates in axes/_base.py, axes/_axes.py, collections.py, and contour.py. When a composite transform's first component contains transData, the method would incorrectly return False, potentially causing autoscaling to not trigger.

Matplotlib version

main branch (all versions with this code).

Note: CompositeAffine2D does NOT have this bug because it doesn't override contains_branch_separately and inherits the base Transform implementation which uses contains_branch -> _iter_break_from_left_to_right, which correctly iterates both children.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions