[ENH]: Add OrientedRectangleSelector (interactive rotated rectangle selector) · Issue #30442 · matplotlib/matplotlib · GitHub
Skip to content

[ENH]: Add OrientedRectangleSelector (interactive rotated rectangle selector) #30442

Description

@saranmahadev

Problem

Matplotlib’s current RectangleSelector only supports axis-aligned rectangles. Many domains (image annotation, computer vision, OCR, remote sensing, microscopy, UI layout tools) need oriented/rotated bounding boxes with interactive resize/rotate/translate. Today, users hack around this with PolygonSelector (4 points) or custom event handlers, which leads to inconsistent UX, no angle snapping, and a lot of duplicated code.

Proposed solution

Introduce a new widget: OrientedRectangleSelector, an interactive selector that behaves like RectangleSelector but supports arbitrary rotation. It provides:

  • Corner and edge handles for resizing (with optional aspect-ratio lock).
  • A rotation handle with optional angle snapping.
  • Dragging the center to translate.
  • Live callbacks during interaction and a final onselect on release.
  • Visual feedback (cursors, handles) and optional blitting for performance.

This mirrors the mental model of RectangleSelector while adding rotation.

High-level API

from matplotlib.widgets import OrientedRectangleSelector

ors = OrientedRectangleSelector(
    ax,
    onselect=None,              # called on mouse release with final params
    onmove_callback=None,       # called during interaction with live params
    *,
    useblit=False,
    button=None,
    minspanx=0, minspany=0,
    spancoords="data",
    maxdist=10,                 # handle hit test (px)
    snap_angle=True,
    angle_snap_increment=1.0,   # degrees
    maintain_aspect_ratio=False,
    min_size=0.02,              # in data units
    initial_center=(0.5, 0.5),
    initial_width=0.3,
    initial_height=0.2,
    initial_angle=0.0,          # degrees
    handle_props=None,          # dict for corner/edge/rotate/center styles
    line_props=None,            # rectangle edge props
    state_modifier_keys=None,   # {'rotate': 'shift', 'aspect_ratio': 'control', 'snap': 'alt'}
)

Returned/Callback params

{
    "center": np.ndarray([cx, cy]),
    "width": float,
    "height": float,
    "angle": float,             # degrees
    "corners": np.ndarray(shape=(4,2)),  # BL, BR, TR, TL in data coords
    "area": float
}

Programmatic control

ors.set_rectangle(center=(x, y), width=w, height=h, angle=a)
params = ors.get_rectangle_params()
ors.update_properties({"snap_angle": False, "min_size": 0.05})

Example usage

fig, ax = plt.subplots()
ax.imshow(img, cmap="gray")

def onmove(params):
    # live feedback (e.g., show crop/metrics)
    pass

def ondone(params):
    print(params["center"], params["width"], params["height"], params["angle"])

ors = OrientedRectangleSelector(ax, onselect=ondone, onmove_callback=onmove,
                                snap_angle=True, angle_snap_increment=1.0)
plt.show()

UX details

  • Handles: 4 corners, 4 edge midpoints, 1 rotation handle, 1 center “+”.
  • Cursors: move/resize/rotate cursors based on hover target.
  • Snapping: configurable granularity (default 1°; can be 15° etc.).
  • Constraints: configurable minimum size; easy to add “keep inside axes” later.
Image

Implementation notes

A reference implementation is attached (ready to adapt to Matplotlib conventions):

  • Uses a Rectangle patch drawn at the origin and transformed via Affine2D (rotate + translate) for numerical stability.
  • Hit-testing in data space; handle detection uses pixel tolerance.
  • Optional blitting for smooth interaction.
  • Clean separation of interaction modes: translate, rotate, resize-corner, resize-edge.
  • Public methods mirror RectangleSelector where possible; extras are additive.

Backward compatibility

No breakage. New widget, opt-in. Naming follows RectangleSelector. Consider adding an alias RotatedRectangleSelector for searchability.

Performance

Blitting support keeps interaction smooth; complexity is similar to existing selectors. Handle count is fixed (small); transform math is minimal.

Testing

  • Unit tests for:
    • Local↔world transform correctness.
    • Angle snapping correctness.
    • Aspect-ratio lock.
    • Min-size constraint.
  • Image tests for handle placement and rotation rendering.
  • Event simulation tests (press/motion/release) to validate callbacks.

Documentation

  • User guide with interactive examples.
  • API reference with detailed parameter descriptions.
  • Changelog for version updates.

[I am ready to do that!]

Future extensions (non-blocking)

  • Constrain rectangle within axes/view limits.
  • Keyboard nudging & precise numeric entry.
  • Multi-rectangle manager as a helper (selection, add/remove).
  • Snapping to guide lines / other artists.

Note: I have more ideas for this widget and ready to take full ownership and I can implement them.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions