Title: [ENH]: Add OrientedRectangleSelector (interactive rotated rectangle selector) · Issue #30442 · matplotlib/matplotlib · GitHub
Open Graph Title: [ENH]: Add OrientedRectangleSelector (interactive rotated rectangle selector) · Issue #30442 · matplotlib/matplotlib
X Title: [ENH]: Add OrientedRectangleSelector (interactive rotated rectangle selector) · Issue #30442 · matplotlib/matplotlib
Description: 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 interactiv...
Open Graph Description: Problem Matplotlib’s current RectangleSelector only supports axis-aligned rectangles. Many domains (image annotation, computer vision, OCR, remote sensing, microscopy, UI layout tools) need oriente...
X Description: Problem Matplotlib’s current RectangleSelector only supports axis-aligned rectangles. Many domains (image annotation, computer vision, OCR, remote sensing, microscopy, UI layout tools) need oriente...
Opengraph URL: https://github.com/matplotlib/matplotlib/issues/30442
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"[ENH]: Add OrientedRectangleSelector (interactive rotated rectangle selector)","articleBody":"### Problem\n\nMatplotlib’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.\n\n\n### Proposed solution\n\n\nIntroduce a new widget: **`OrientedRectangleSelector`**, an interactive selector that behaves like `RectangleSelector` but supports **arbitrary rotation**. It provides:\n\n* Corner and edge handles for resizing (with optional aspect-ratio lock).\n* A rotation handle with optional **angle snapping**.\n* Dragging the center to translate.\n* Live callbacks during interaction and a final `onselect` on release.\n* Visual feedback (cursors, handles) and optional blitting for performance.\n\nThis mirrors the mental model of `RectangleSelector` while adding rotation.\n\n### High-level API\n\n```python\nfrom matplotlib.widgets import OrientedRectangleSelector\n\nors = OrientedRectangleSelector(\n ax,\n onselect=None, # called on mouse release with final params\n onmove_callback=None, # called during interaction with live params\n *,\n useblit=False,\n button=None,\n minspanx=0, minspany=0,\n spancoords=\"data\",\n maxdist=10, # handle hit test (px)\n snap_angle=True,\n angle_snap_increment=1.0, # degrees\n maintain_aspect_ratio=False,\n min_size=0.02, # in data units\n initial_center=(0.5, 0.5),\n initial_width=0.3,\n initial_height=0.2,\n initial_angle=0.0, # degrees\n handle_props=None, # dict for corner/edge/rotate/center styles\n line_props=None, # rectangle edge props\n state_modifier_keys=None, # {'rotate': 'shift', 'aspect_ratio': 'control', 'snap': 'alt'}\n)\n```\n\n**Returned/Callback params**\n\n```python\n{\n \"center\": np.ndarray([cx, cy]),\n \"width\": float,\n \"height\": float,\n \"angle\": float, # degrees\n \"corners\": np.ndarray(shape=(4,2)), # BL, BR, TR, TL in data coords\n \"area\": float\n}\n```\n\n**Programmatic control**\n\n```python\nors.set_rectangle(center=(x, y), width=w, height=h, angle=a)\nparams = ors.get_rectangle_params()\nors.update_properties({\"snap_angle\": False, \"min_size\": 0.05})\n```\n\n### Example usage\n\n```python\nfig, ax = plt.subplots()\nax.imshow(img, cmap=\"gray\")\n\ndef onmove(params):\n # live feedback (e.g., show crop/metrics)\n pass\n\ndef ondone(params):\n print(params[\"center\"], params[\"width\"], params[\"height\"], params[\"angle\"])\n\nors = OrientedRectangleSelector(ax, onselect=ondone, onmove_callback=onmove,\n snap_angle=True, angle_snap_increment=1.0)\nplt.show()\n```\n\n### UX details\n\n* **Handles**: 4 corners, 4 edge midpoints, 1 rotation handle, 1 center “+”.\n* **Cursors**: move/resize/rotate cursors based on hover target.\n* **Snapping**: configurable granularity (default 1°; can be 15° etc.).\n* **Constraints**: configurable minimum size; easy to add “keep inside axes” later.\n\n\u003cimg width=\"1920\" height=\"1040\" alt=\"Image\" src=\"https://github.com/user-attachments/assets/600a473e-34fe-414f-8549-ceda6dca43d9\" /\u003e\n\n### Implementation notes\n\nA reference implementation is attached (ready to adapt to Matplotlib conventions):\n\n* Uses a `Rectangle` patch drawn at the origin and transformed via `Affine2D` (rotate + translate) for numerical stability.\n* Hit-testing in data space; handle detection uses pixel tolerance.\n* Optional blitting for smooth interaction.\n* Clean separation of interaction modes: translate, rotate, resize-corner, resize-edge.\n* Public methods mirror `RectangleSelector` where possible; extras are additive.\n\n### Backward compatibility\n\nNo breakage. New widget, opt-in. Naming follows `RectangleSelector`. Consider adding an alias `RotatedRectangleSelector` for searchability.\n\n### Performance\n\nBlitting support keeps interaction smooth; complexity is similar to existing selectors. Handle count is fixed (small); transform math is minimal.\n\n### Testing\n\n* Unit tests for:\n * Local↔world transform correctness.\n * Angle snapping correctness.\n * Aspect-ratio lock.\n * Min-size constraint.\n* Image tests for handle placement and rotation rendering.\n* Event simulation tests (press/motion/release) to validate callbacks.\n\n### Documentation\n\n* User guide with interactive examples.\n* API reference with detailed parameter descriptions.\n* Changelog for version updates.\n\n[I am ready to do that!]\n\n### Future extensions (non-blocking)\n\n* Constrain rectangle within axes/view limits.\n* Keyboard nudging \u0026 precise numeric entry.\n* Multi-rectangle manager as a helper (selection, add/remove).\n* Snapping to guide lines / other artists.\n\n\n\u003e Note: I have more ideas for this widget and ready to take full ownership and I can implement them.","author":{"url":"https://github.com/saranmahadev","@type":"Person","name":"saranmahadev"},"datePublished":"2025-08-19T08:01:19.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":3},"url":"https://github.com/30442/matplotlib/issues/30442"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:a314b40a-4742-d8ab-af7e-13be45e71abe |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | B04E:43AB7:2A5D031:39DEC13:6A544B2B |
| html-safe-nonce | 993de8b4f775beb6254f521bbc9d5274e66cef0c80e01547dc9c88b561d31375 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJCMDRFOjQzQUI3OjJBNUQwMzE6MzlERUMxMzo2QTU0NEIyQiIsInZpc2l0b3JfaWQiOiI0NDcxMDY1ODU3NDc3NTkzODk5IiwicmVnaW9uX2VkZ2UiOiJpYWQiLCJyZWdpb25fcmVuZGVyIjoiaWFkIn0= |
| visitor-hmac | 3c3464588a5314ccde38ba5b9a24449f012cf2d9521daa537560a265b4f31bb1 |
| hovercard-subject-tag | issue:3333351146 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/matplotlib/matplotlib/30442/issue_layout |
| twitter:image | https://opengraph.githubassets.com/e0d0a7a916e524df527c8911335a1f9ce9d5ed13cd726d12c507fb3a205c9380/matplotlib/matplotlib/issues/30442 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/e0d0a7a916e524df527c8911335a1f9ce9d5ed13cd726d12c507fb3a205c9380/matplotlib/matplotlib/issues/30442 |
| og:image:alt | Problem Matplotlib’s current RectangleSelector only supports axis-aligned rectangles. Many domains (image annotation, computer vision, OCR, remote sensing, microscopy, UI layout tools) need oriente... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | saranmahadev |
| hostname | github.com |
| expected-hostname | github.com |
| None | b9a586c06a05a7a86fc7e3f4dbd03e42f6869085879aa184aa6369456dbd50fb |
| turbo-cache-control | no-preview |
| go-import | github.com/matplotlib/matplotlib git https://github.com/matplotlib/matplotlib.git |
| octolytics-dimension-user_id | 215947 |
| octolytics-dimension-user_login | matplotlib |
| octolytics-dimension-repository_id | 1385122 |
| octolytics-dimension-repository_nwo | matplotlib/matplotlib |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 1385122 |
| octolytics-dimension-repository_network_root_nwo | matplotlib/matplotlib |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 07a982c1d40157c619b364352b704c3ce66bb332 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width