Skip to content

mplot3d: add set_offsets3d for 3D patch/path collections - #31279

Closed
xndvaz wants to merge 7 commits into
matplotlib:mainfrom
xndvaz:fix-784-offsets3d-clean
Closed

xndvaz wants to merge 7 commits into
matplotlib:mainfrom
xndvaz:fix-784-offsets3d-clean

Conversation

@xndvaz

@xndvaz xndvaz commented Mar 11, 2026

Copy link
Copy Markdown

PR summary

This PR improves mplot3d offset updates for 3D collections.

Path3DCollection and Patch3DCollection project 3D offsets to 2D during draw, so updating through set_offsets() (2D data) is lossy and does not update the 3D source offsets. This PR adds an explicit set_offsets3d(xs, ys, zs, *, zdir='z') method to both classes and routes set_3d_properties() through that path.

Tests added:

  • visual equality test for post-creation set_offsets3d() updates on Path3DCollection
  • visual equality test for post-creation set_offsets3d() updates on Patch3DCollection
  • visual equality test for post-creation set_array() updates on Path3DCollection
  • visual equality test for post-creation set_array() updates on Patch3DCollection

Local test command run:

  • python -m pytest -q lib/mpl_toolkits/mplot3d/tests/test_axes3d.py

Partially addresses #784.

AI Disclosure

I used AI tooling to help draft and iterate the code and tests. I manually reviewed the implementation, validated behavior locally, and ran the affected test suite before opening this PR.

PR checklist

@scottshambaugh

Copy link
Copy Markdown
Contributor

This does not address all the comments I left on the prior PR.

@xndvaz

xndvaz commented Mar 11, 2026

Copy link
Copy Markdown
Author

Thanks for the quick feedback.

I tried to address the points from the prior PR as follows:

  • used the PR template (including AI disclosure/checklist),
  • created a clean branch from upstream/main with only related commits,
  • introduced set_offsets3d(...) for Path3DCollection and Patch3DCollection (no 2D->3D round-trip through set_offsets()),
  • added check_figures_equal tests for post-creation set_offsets3d() and set_array() updates on both collection types.

If I am still missing one or more of your prior points, could you please point out which specific ones are still unresolved? I am happy to revise.

@scottshambaugh

Copy link
Copy Markdown
Contributor

@xndvaz

xndvaz commented Mar 11, 2026

Copy link
Copy Markdown
Author

Thanks for clarifying.

I updated the PR description to mark this as a partial fix (no longer claiming it closes #784).

For follow-up coverage, could you point me to the specific 3D artist classes you want included in this PR? I can extend it accordingly.

@scottshambaugh

Copy link
Copy Markdown
Contributor

Could you first please clarify if you are using AI assistance to write your comments here?

@xndvaz

xndvaz commented Mar 11, 2026

Copy link
Copy Markdown
Author

Yes. For comments, I use AI only for translation/grammar because I'm a Brazilian Portuguese speaker and wanna communicate clearly in English.

The technical reasoning is mine: I reviewed the code changes myself, ran the tests locally and I can explain each change in detail. I also disclosed AI assistance in the PR template. :)

@scottshambaugh

scottshambaugh commented Mar 11, 2026

Copy link
Copy Markdown
Contributor

Ok, translation is fine.

We would want to bring this in for all 3D artists at the same time, so that there is consistent behavior. We would also want to include get_offsets3d functions or similar to mirror the setters.

I also do not understand the purpose of the array modification tests, could you please explain why those are needed?

@xndvaz

xndvaz commented Mar 11, 2026

Copy link
Copy Markdown
Author

Thanks for clarifying.

Understood on consistency. I'll revise this PR to add a consistent 3D offset API across all relevant mplot3d artists, including a getter counterpart (get_offsets3d or equivalent) for each setter.

About the set_array tests: I added them because issue #784 explicitly mentions post-creation set_array() behavior (including color-update problems), so I wanted a regression check while touching the same classes. The intent was to guard existing behavior, not to broaden scope.

If you prefer this PR to stay strictly on offsets APIs, I can remove the set_array tests and keep them for a separate follow-up PR.

@scottshambaugh scottshambaugh added the status: autoclose candidate PRs that are not yet ready for review and may be automatically closed in two weeks label Mar 19, 2026
@github-actions

Copy link
Copy Markdown

⏰ This pull request might be automatically closed in two weeks from now.

Thank you for your contribution to Matplotlib and for the effort you have put into this PR. This pull request does not yet meet the quality and clarity standards needed for an effective review. Project maintainers have limited time for code reviews, and our goal is to prioritize well-prepared contributions to keep Matplotlib maintainable.

Matplotlib maintainers cannot provide one-to-one guidance on this PR. However, if you ask focused, well-researched questions, a community member may be willing to help. 💬

To increase the chance of a productive review:

As the author, you are responsible for driving this PR, which entails doing necessary background research as well as presenting its context and your thought process. If you are a new contributor, or do not know how to fulfill these requirements, we recommend that you familiarize yourself with Matplotlib's development conventions or engage with the community via our Discourse or one of our meetings before submitting code.

If you substantially improve this PR within two weeks, leave a comment and a team member may remove the status: needs work label and the PR stays open. Cosmetic changes or incomplete fixes will not be sufficient. Maintainers will assess improvements on their own schedule. Please do not ping (@) maintainers.

@xndvaz

xndvaz commented Mar 19, 2026

Copy link
Copy Markdown
Author

Thanks again for the detailed guidance on the previous review. I pushed a focused revision for the two collection classes in this PR (Path3DCollection and Patch3DCollection).

In this revision I added get_offsets3d to mirror set_offsets3d, made set/get roundtrip behavior explicit by storing data-space offsets, and kept backward-compatible fallback reconstruction for older internal state. I also added roundtrip tests for zdir in {"x", "y", "z"} on both classes, kept visual regression checks for post-creation set_offsets3d updates, and removed the set_array tests to keep the scope strictly on offsets API consistency.

After CI completed, the remaining failures appear unrelated to these mplot3d changes:

  • AppVeyor build 53729760 fails during bootstrap (Invoke-WebRequest to micro.mamba.pm) with a TLS trust error.
  • Python 3.12 on ubuntu-24.04-arm fails in test_backends_interactive.py::test_webagg with subprocess.TimeoutExpired after 120 seconds.

I did not find failures in the modified mplot3d tests in CI logs. I also see codecov/project/tests at 97.71% (-0.16%); if you want, I can add additional coverage in this PR.

If you prefer, I can also extend this PR to additional 3D artist classes to match a fully consistent offsets API across mplot3d.

@scottshambaugh

Copy link
Copy Markdown
Contributor

Again, we would want to bring this in for all 3D artists at the same time, so that there is consistent behavior.

@scottshambaugh scottshambaugh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All 3D artists need to be covered by these changes.

@xndvaz
xndvaz marked this pull request as draft March 20, 2026 12:28
- Add explicit set/get_verts_and_codes3d support to Collection3D and use it in collection_2d_to_3d.

- Preserve axlim_clip in Patch3D/PathPatch3D geometry setters unless explicitly overridden.

- Harden get_offsets3d legacy fallback when _zdir is missing in older instances.

- Add focused regression tests for Collection3D, legacy offsets fallback, and axlim_clip preservation; fix ruff line-length failure in tests.
@xndvaz
xndvaz marked this pull request as ready for review March 20, 2026 12:41
@xndvaz

xndvaz commented Mar 20, 2026

Copy link
Copy Markdown
Author

Hi @scottshambaugh, thanks for the review.

I moved this PR to draft while I fixed issues I had missed in the previous revision.

I just pushed a single atomic update focused on API consistency and compatibility:

  • Added explicit set/get_verts_and_codes3d support for Collection3D and routed collection_2d_to_3d through that API.
  • Preserved axlim_clip in Patch3D.set_verts3d and PathPatch3D.set_verts_and_codes3d unless explicitly overridden.
  • Hardened get_offsets3d legacy fallback for older instances where _zdir is not present.
  • Added focused regression tests for Collection3D set/get roundtrip, legacy Patch3DCollection.get_offsets3d without _zdir, and axlim_clip preservation in patch/pathpatch 3D setters.
  • Fixed the lint failure (E501) in the new tests.

I marked the PR as ready for review again.

@xndvaz
xndvaz marked this pull request as draft March 20, 2026 13:32
@scottshambaugh scottshambaugh removed the status: autoclose candidate PRs that are not yet ready for review and may be automatically closed in two weeks label Mar 20, 2026
@xndvaz
xndvaz force-pushed the fix-784-offsets3d-clean branch from a7cb6d6 to e98fe2c Compare March 20, 2026 18:01
@xndvaz
xndvaz marked this pull request as ready for review March 20, 2026 18:39
@xndvaz

xndvaz commented Mar 20, 2026

Copy link
Copy Markdown
Author

Marking this PR ready for review again.

Current remaining CI failures appear unrelated to this mplot3d change:

  • ubuntu-24.04-arm: flaky timeout in test_backends_interactive.py::test_webagg
  • AppVeyor: TLS/bootstrap failure fetching micromamba
  • codecov/project/tests drop seems tied to incomplete CI uploads from those failures

Core mplot3d jobs are passing.

@scottshambaugh scottshambaugh added the status: autoclose candidate PRs that are not yet ready for review and may be automatically closed in two weeks label Mar 23, 2026
@github-actions

This comment was marked as resolved.

@scottshambaugh
scottshambaugh dismissed their stale review March 23, 2026 22:24

Self-dismissed

@scottshambaugh scottshambaugh removed the status: autoclose candidate PRs that are not yet ready for review and may be automatically closed in two weeks label Mar 23, 2026

@scottshambaugh scottshambaugh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't mean to add the autoclose tag again, you can ignore that. And CI can be flaky - don't worry about failures that are unrelated to changes you make.

Some comments to address, but zooming out I think there is still a lot of work that needs to go into thinking through the API here. The high level goal is to have 3D getters/setters for positional data that mirrors the 2D ones. Some of the artists have their primary data coordinates as segments / verts / offsets, and some set offsets on top of that. I think some of the 3D artists should accept offsets, but currently don't.

I would recommend stepping back and making a table mapping out the holes in the API before implementing further changes. (You may want to pick an easier issue for your first contribution to the repo)

"""
if hasattr(self, "_offsets3d_data"):
return self._offsets3d_data
# Backward compatibility for instances that pre-date get_offsets3d.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_offsets3d is a new API and shouldn't need backwards compatibility, here and elsewhere

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point — agreed. get_offsets3d is a new API and should not carry backward-compatibility fallbacks. I'll remove this fallback here and align the same behavior in the similar getters.

codes : array-like or None
The path codes.
"""
if hasattr(self, "_codes3d_data"):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the fallbacks here?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. This fallback is unnecessary here. I added it to preserve behavior for older internal state, but for this API we should avoid extra fallback paths. I'll remove this fallback and keep a single canonical source for codes.

return xs, ys, zs


def _backjuggle_axes(xs, ys, zs, zdir):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is y/-y split but not x and z, and why _str_equal instead of ==? Please match the logic in juggle_axes and put in tests to verify each case.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right — this inversion logic should mirror juggle_axes exactly. I will rewrite _backjuggle_axes to follow the same case handling (including signed axis directions) and switch to the same comparison style. I will also add explicit tests for each supported zdir case to verify round-trip behavior.

self._axlim_clip = axlim_clip
self.stale = True

def set_offsets3d(self, xs, ys, zs, *, zdir='z'):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 2D case calls with Collection.set_offsets(offsets) rather than breaking out x and y as separate args, and we should match that pattern here

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, agreed. I will align this with Collection.set_offsets(offsets) and remove the split xs, ys signature.

if axlim_clip is None:
axlim_clip = getattr(self, "_axlim_clip", False)
self._verts3d = np.asanyarray(verts)
self._segment3d = self._verts3d

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have two private attrs pointing at the same data?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, this is redundant state. I’ll remove the duplicate attr and keep one canonical source of truth.

self.set_verts3d(verts, closed=False)
# And set our own codes instead.
self._codes3d = codes
self._codes3d_data = codes

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why have two attrs here pointing towards the same thing?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, agreed — this duplicates state unnecessarily. I'll keep a single canonical codes attribute and remove the second private attr.

segments : sequence of (N, 3) array-like
The 3D line segments to draw.
"""
self._segments3d = segments

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should check the shape on all set_segments3d calls.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, agreed. I’ll enforce shape checks in set_segments3d and add tests for both valid and invalid inputs.

If *None*, preserve the current setting.
"""
self.set_verts3d(verts, axlim_clip=axlim_clip)
self._code3d = codes

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should check that shape of verts and codes match (or codes can be broadcast to match).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. I’ll add compatibility checks between verts and codes (including broadcast support) plus tests for valid/invalid cases.

Comment on lines +478 to +479
@check_figures_equal()
def test_scatter3d_offsets3d_modification(fig_ref, fig_test):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please add similar check_figures_equal tests for all artists?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, agreed. I'll add corresponding check_figures_equal post-creation update tests across the relevant 3D artists touched by this API consistency pass.

self._axlim_clip = axlim_clip
self.stale = True

def set_offsets3d(self, xs, ys, zs, *, zdir='z'):

@scottshambaugh scottshambaugh Mar 23, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that we should use this to re-set zdir?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense — agreed. I will avoid using this setter to reconfigure zdir implicitly and keep axis-direction changes explicit in the appropriate 3D-property path. I’ll update the implementation/tests accordingly.

@xndvaz

xndvaz commented Mar 24, 2026

Copy link
Copy Markdown
Author

Hi @scottshambaugh, stepping back as suggested, I mapped the current positional API surface in art3d.py before making further changes.

This matrix is only about positional-data APIs for now; I am intentionally separating the set_array / colormap behavior from this pass until the positional API scope is clearer.

Single-artist classes

Artist 2D base positional API Current 3D positional API Reading
Text3D Text.get_position / set_position get_position_3d / set_position_3d, set_z Point + direction; no separate offsets layer expected
Line3D Line2D.get_data / set_data get_data_3d / set_data_3d Line vertices are the primary positional data
Patch3D patch path / derived vertices get_verts3d / set_verts3d Patch vertices are the primary positional data
PathPatch3D path vertices / codes get_verts_and_codes3d / set_verts_and_codes3d, get_codes3d Path vertices/codes are the primary positional data

Geometry-first collection classes

Artist 2D base positional API Current 3D positional API Primary 3D data Design question / potential hole
Collection3D Collection.get_paths + Collection.get_offsets / set_offsets get_verts_and_codes3d / set_verts_and_codes3d per-path verts/codes No explicit 3D offsets layer, even though the 2D base collection API supports offsets
Line3DCollection LineCollection.get_segments / set_segments, inherited set_offsets get_segments3d / set_segments3d segments No explicit 3D offsets layer on top of segments
Poly3DCollection PolyCollection.set_verts, set_verts_and_codes, inherited set_offsets get_verts3d / set_verts3d, get_verts_and_codes3d / set_verts_and_codes3d verts (+ optional codes) No explicit 3D offsets layer on top of verts

Offset-first collection classes

Artist 2D base positional API Current 3D positional API Primary 3D data Current mismatch
Patch3DCollection Collection.get_offsets / set_offsets get_offsets3d / set_offsets3d offsets Present, but does not yet mirror the 2D calling pattern / semantics cleanly
Path3DCollection Collection.get_offsets / set_offsets get_offsets3d / set_offsets3d offsets Same

My current reading of the main unresolved design question is:

  • For single artists, mirroring the primary geometry/position API is likely sufficient.
  • For collection subclasses whose 2D base API supports set_offsets, we should decide explicitly which 3D classes also need a 3D offsets layer on top of their primary geometry (Collection3D, Line3DCollection, Poly3DCollection).
  • Once that scope is settled, the inline review items can be applied coherently across the same set of artists.

Cross-cutting follow-ups from inline review, to apply consistently across whichever artist set is in scope:

  • Remove fallback paths for newly introduced getter APIs (e.g. get_offsets3d / similar cases).
  • Remove duplicated private state and keep a single canonical source of truth for each geometry/codes payload.
  • Align 3D offsets setter call style with the 2D Collection.set_offsets(offsets) pattern, and make zdir update semantics explicit.
  • Make _backjuggle_axes strictly mirror juggle_axes axis/sign handling, with explicit tests for each supported zdir.
  • Add shape validation for all set_segments3d paths.
  • Validate verts / codes compatibility (including supported broadcast cases) before assignment.
  • Add corresponding check_figures_equal post-update tests across all artists included in the scoped pass.

This is a preliminary matrix from the current art3d.py, so please correct me if I am grouping any artist incorrectly. If this matches your intent, I can use it to narrow the implementation plan before pushing further changes.

@scottshambaugh

scottshambaugh commented Mar 24, 2026

Copy link
Copy Markdown
Contributor

Yeah, the existing API is all a bit of a mess. In your tables please have columns for the existing 3D API and the proposed new API, rather than a current snapshot of this PR draft. And also include the full details for 2D Path and PathPatch

@xndvaz

xndvaz commented Mar 24, 2026

Copy link
Copy Markdown
Author

Hi @scottshambaugh, thanks for the clarification.

I rebuilt this matrix from current origin/main (so this is not a snapshot of my PR draft), and separated the API into existing 3D API vs proposed new API. I also expanded the 2D baseline for Path and PathPatch.

2D baseline: Path and PathPatch

2D type Canonical positional data Existing 2D API Shape / semantic constraints
Path vertices + optional codes are the canonical geometry representation Constructor: Path(vertices, codes=None, ...); mutable properties: path.vertices, path.codes vertices must be (N, 2); codes must be None or (N,); if codes is provided, length must match vertices and first code must be MOVETO
PathPatch Geometry is defined by an embedded Path object get_path(), set_path(path) Positional geometry and path codes come from path.vertices / path.codes; geometry updates are path replacement/update operations

3D artist matrix: existing vs proposed

3D artist Existing 3D API (origin/main) Proposed new API
Text3D get_position_3d, set_position_3d, set_z No change
Line3D get_data_3d, set_data_3d No change
Collection3D No explicit public 3D geometry getter/setter (internal _3dverts_codes is populated by conversion path) Add explicit get_verts_and_codes3d() / set_verts_and_codes3d(...)
Line3DCollection set_segments(...) currently carries 3D semantics, but no explicit 3D-named API pair Add get_segments3d() / set_segments3d(...); keep set_segments(...) as compatibility alias
Patch3D set_3d_properties(...) writes internal 3D segment state; no explicit public 3D verts getter/setter Add get_verts3d() / set_verts3d(...)
PathPatch3D set_3d_properties(path, ...) + internal verts/codes state; no explicit public geometry pair Add get_verts_and_codes3d() / set_verts_and_codes3d(...) (plus get_codes3d())
Patch3DCollection set_3d_properties(zs, zdir, ...) writes internal 3D offsets; no explicit 3D offsets pair Add get_offsets3d() / set_offsets3d(offsets, *, zdir='z')
Path3DCollection set_3d_properties(zs, zdir, ...) writes internal 3D offsets; no explicit 3D offsets pair Add get_offsets3d() / set_offsets3d(offsets, *, zdir='z')
Poly3DCollection set_verts(...) and set_verts_and_codes(...) already take 3D geometry but through 2D-style names; no explicit 3D getters Add get_verts3d() / set_verts3d(...), get_verts_and_codes3d() / set_verts_and_codes3d(...), and get_codes3d(); keep existing names as compatibility aliases

If this target surface matches your intent, I will implement this exact scope first, then apply the cross-cutting consistency items across the same approved artist set (shape checks, canonical internal state, and matching post-update figure-equality tests).

@scottshambaugh

scottshambaugh commented Mar 24, 2026

Copy link
Copy Markdown
Contributor

Sorry, could you please edit your last comment to re-add the base 2D api info for each artist in the table? Also, I thought you agreed that we should leave zdir out of set_offsets3d?

As a note - your comments seem heavily AI-written beyond direct translation. What was the extent of its usage here?

@xndvaz

xndvaz commented Mar 25, 2026

Copy link
Copy Markdown
Author

Hi @scottshambaugh, thanks for the follow-up. You are right; I updated the matrix below to re-add the 2D base API per artist, and I removed zdir from the proposed set_offsets3d surface.

2D baseline details (Path / PathPatch)

2D type Canonical positional data Existing 2D API Shape / semantic constraints
Path vertices + optional codes Path(vertices, codes=None, ...); mutable path.vertices, path.codes vertices: (N, 2); codes: None or (N,); if provided, length must match vertices, first code must be MOVETO
PathPatch Geometry is an embedded Path get_path(), set_path(path) Geometry/codes come from path.vertices / path.codes; updates happen by replacing/updating the Path

Artist matrix (origin/main): 2D base vs existing 3D vs proposed 3D

3D artist 2D base positional API Existing 3D API (origin/main) Proposed new API
Text3D Text.get_position / set_position get_position_3d, set_position_3d, set_z No change
Line3D Line2D.get_data / set_data get_data_3d, set_data_3d No change
Collection3D Collection.get_paths / set_paths; Collection.get_offsets / set_offsets No explicit public 3D geometry pair (internal _3dverts_codes) Add get_verts_and_codes3d() / set_verts_and_codes3d(...)
Line3DCollection LineCollection.get_segments / set_segments; inherited Collection.get_offsets / set_offsets set_segments(...) has 3D semantics; no explicit 3D-named pair Add get_segments3d() / set_segments3d(...); keep set_segments(...) alias
Patch3D Patch.get_path (geometry via path/verts) set_3d_properties(...); no explicit public 3D verts pair Add get_verts3d() / set_verts3d(...)
PathPatch3D PathPatch.get_path / set_path (path vertices/codes) set_3d_properties(path, ...); internal verts/codes state Add get_verts_and_codes3d() / set_verts_and_codes3d(...) and get_codes3d()
Patch3DCollection PatchCollection + inherited Collection.get_offsets / set_offsets set_3d_properties(zs, zdir, ...); internal _offsets3d Add get_offsets3d() / set_offsets3d(offsets3d)
Path3DCollection PathCollection + inherited Collection.get_offsets / set_offsets set_3d_properties(zs, zdir, ...); internal _offsets3d Add get_offsets3d() / set_offsets3d(offsets3d)
Poly3DCollection PolyCollection.set_verts / set_verts_and_codes; inherited Collection.get_offsets / set_offsets set_verts(...) and set_verts_and_codes(...) already take 3D geometry, but no explicit 3D getters Add get_verts3d() / set_verts3d(...), get_verts_and_codes3d() / set_verts_and_codes3d(...), get_codes3d(); keep existing names as aliases

On AI usage: I used AI for language polishing and table formatting, but I manually reviewed origin/main APIs and made the technical mapping/proposal myself. I can explain each row and rationale directly.

@xndvaz

xndvaz commented Apr 5, 2026

Copy link
Copy Markdown
Author

Hi @scottshambaugh, quick FUP on the matrix update I posted on March 25.

I’m ready to implement that exact scope next. Before I start coding, could you confirm whether that table is aligned with what you want, or point out which rows you want changed first?
I’ll adjust the matrix first if needed, then proceed.

@scottshambaugh

scottshambaugh commented Apr 5, 2026

Copy link
Copy Markdown
Contributor

Sorry for the delay. Collection3D should probably have a dedicated getter/setter too.

I'm a little hesitant to add such a large amount of additional public API that is all mismatched among the 3D artists. Better this than no ability to change things, but I do wonder if there's a more consistent way to structure it. We try to be a pretty careful with the public API since we'd much rather not have to do deprecations to change things after the fact. I'll ping in the discourse to see if other devs have opinions on this.

@xndvaz

xndvaz commented Apr 6, 2026

Copy link
Copy Markdown
Author

Thanks for the update, and no worries on timing.

Your concern about public API stability makes sense, especially to avoid deprecations later. I also agree that Collection3D should have a dedicated getter/setter.

I’ll pause further code changes until there is feedback on the Discourse thread so we can align on a consistent structure first. After that, I can update the matrix to reflect the agreed direction and implement only that scoped API.

One candidate direction from my side is to mirror each artist’s 2D primary positional API, and only add offsets3d where the 2D counterpart has offset semantics.

@QuLogic QuLogic added the status: needs comment/discussion needs consensus on next step label Apr 6, 2026
@melissawm melissawm moved this to Needs decision in First Time Contributors Apr 8, 2026
@xndvaz

xndvaz commented May 11, 2026

Copy link
Copy Markdown
Author

Hi, quick FUP on this PR. I’m still holding off on code changes until there is alignment on the public API direction.

Is there a discourse thread or other discussion I should follow, or should I keep this PR paused for now @scottshambaugh?

@timhoffm

timhoffm commented May 16, 2026

Copy link
Copy Markdown
Member

I haven't seen any discussion on discourse.

On the 3D API: This is unfortunately quite a mess historically. Deriving 3D from 2D is a questionable API choice as a lot is inherited and it is not clear which functionality still works and which not. In general, I would be hesitant to add a lot of new *_3d functions. This makes the API more cluttered, users need to bother with set_offsets() and set_offsets_3d. While there is precedence for Text3D.set_position_3D and Line2D.set_data_2d, I'm inclined to override the existing 2D methods for 3D, as we do with Axes3D plotting methods. Though I have to admit I have no clear overview how much or when we choose _3d vs. overriding.

@xndvaz
xndvaz requested a review from scottshambaugh June 24, 2026 12:13
@scottshambaugh

Copy link
Copy Markdown
Contributor

Here's the discourse thread, I'll bring it up during the Thursday meeting to get some discussion going: https://discourse.matplotlib.org/chat/c/-/2/t/129/654

@scottshambaugh

Copy link
Copy Markdown
Contributor

Hi @xndvaz, we talked about this during the july 2 meeting for a bit and think it's best to defer this work. We are planning to move to a new data containerization approach, which will commonize a lot of the underlying data structures across artists, and should make it possible to have a more unified API for these getters and setters. You can follow along with that effort here: #30865

Thank you for your work on this! I am going to close for now, with an eye on revisiting when that other work is done.

@github-project-automation github-project-automation Bot moved this from Needs decision to Done in First Time Contributors Jul 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Development

Successfully merging this pull request may close these issues.

5 participants