Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add widths, heights and angles setter to EllipseCollection #26375

Merged
merged 6 commits into from
Apr 1, 2024

Conversation

ericpre
Copy link
Member

@ericpre ericpre commented Jul 22, 2023

PR summary

Currently, the following example:

import matplotlib.pyplot as plt
from matplotlib.collections import EllipseCollection
import numpy as np

rng = np.random.default_rng(0)

widths = (2, )
heights = (3, )
angles = (45, )
offsets = rng.random((10, 2)) * 10

fig, ax = plt.subplots()

ec = EllipseCollection(
    widths=widths,
    heights=heights,
    angles=angles,
    offsets=offsets,
    units='x', 
    offset_transform=ax.transData,
    )

ax.add_collection(ec)
ax.set_xlim(-2, 12)
ax.set_ylim(-2, 12)

new_widths = rng.random((10, 2)) * 2
new_heights = rng.random((10, 2)) * 3
new_angles = rng.random((10, 2)) * 180

ec.set(widths=new_widths, heights=new_heights, angles=new_angles)

will fails with the errors:

  File ~\Dev\matplotlib\lib\matplotlib\artist.py:147 in <lambda>
    cls.set = lambda self, **kwargs: Artist.set(self, **kwargs)

  File ~\Dev\matplotlib\lib\matplotlib\artist.py:1221 in set
    return self._internal_update(cbook.normalize_kwargs(kwargs, self))

  File ~\Dev\matplotlib\lib\matplotlib\artist.py:1213 in _internal_update
    return self._update_props(

  File ~\Dev\matplotlib\lib\matplotlib\artist.py:1187 in _update_props
    raise AttributeError(

AttributeError: EllipseCollection.set() got an unexpected keyword argument 'widths'

PR checklist

@ericpre ericpre force-pushed the add_EllipseCollection_setter branch from c553db5 to 013f69b Compare July 22, 2023 11:41
Copy link
Contributor

@oscargus oscargus left a comment

Choose a reason for hiding this comment

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

It probably makes sense to use the newly added methods in the constructor.

(Are there getters as well?)

lib/matplotlib/tests/test_collections.py Show resolved Hide resolved
lib/matplotlib/collections.py Show resolved Hide resolved
@ericpre
Copy link
Member Author

ericpre commented Jul 24, 2023

Thank you @oscargus for the review, this should be all done.

(Are there getters as well?)

I didn't add the getters because we don't need them and I don't know how to get their value after the transform. But if someone give me some pointers, I am happy to do that in this PR - if there are setters, it would be fair to expect getters too!

Copy link
Contributor

@oscargus oscargus left a comment

Choose a reason for hiding this comment

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

I cannot help with the getters, unfortunately. While it is indeed ideal to have both, it is clearly better to have only setters compared to nothing.

@oscargus oscargus added this to the v3.8.0 milestone Aug 4, 2023
@ksunden ksunden modified the milestones: v3.8.0, v3.9.0 Aug 8, 2023
@tacaswell
Copy link
Member

We talked about this on the call and are 👍🏻 on it going in but would like getters.

The consensus on the call was for the getters to undo the geometric transformations, but don't worry about the ravel (if someone is passing in higher than 1D data, we would like to know why before bending over backwards to give it back).

@ericpre ericpre force-pushed the add_EllipseCollection_setter branch from bdbc78e to bdde2e9 Compare January 28, 2024 09:48
@ericpre
Copy link
Member Author

ericpre commented Jan 28, 2024

I added the getters which return the same as what is given to the setters and rebased. The CI failures shouldn't be related to the changes of this PR.

Copy link
Member

@timhoffm timhoffm left a comment

Choose a reason for hiding this comment

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

In a similar case #26410 (comment) where multiple related arrays can be set, we advocated for a central function that can take one or multiple of the arrays and make sure the final data (mixture of existing and new arrays) has consistent array lengths.

I suggest that we follow this approach here as well - at least for the geometry parameters widths, heights, angles, offsets. I grant that we won't check other potentially sequence-like parameters (e.g. linewidths), but AFAIK they are just cycled continuously and don't have to match in lengths.

@QuLogic
Copy link
Member

QuLogic commented Mar 21, 2024

A friendly ping on this; I'd like to put out 3.9 beta/rc next week.

@ericpre
Copy link
Member Author

ericpre commented Mar 21, 2024

Thanks @QuLogic, yes it would be good to finish this PR.

@timhoffm, does it sound good to overwrite the set method? In the example added in this PR, set is used these values in a single call. This has the advantage of keeping the API minimal and consistent.

@timhoffm
Copy link
Member

timhoffm commented Mar 21, 2024

@timhoffm, does it sound good to overwrite the set method?

That may work, but it's a bit tricky because the set methods are auto-generated to include all properties of the class:

if not hasattr(cls.set, '_autogenerated_signature'):
# Don't overwrite cls.set if the subclass or one of its parents
# has defined a set method set itself.
# If there was no explicit definition, cls.set is inherited from
# the hierarchy of auto-generated set methods, which hold the
# flag _autogenerated_signature.
return
cls.set = lambda self, **kwargs: Artist.set(self, **kwargs)
cls.set.__name__ = "set"
cls.set.__qualname__ = f"{cls.__qualname__}.set"
cls._update_set_signature_and_docstring()

You may have to care for _autogenerated_signature and/or _update_set_signature_and_docstring() to get explicit set signature and docstring without manually having to specify all parameters. This is some you would have to fiddle into.


Edit: I see this is slightly more complicated than #26410 (comment). I thought we make an equivalent to set_XYUVC(), but that would be something like set_widths_heights_angles_offsets() which is quite bulky, and I don't believe this would genralize well to arbitrary collections.
Thus, I'm not sure we get a viable solution with length consistency checks quickly. Therefore, if people want the fundamental functionality to set these parameters (without saftety checks) in 3.9, I would be ok to merge the current proposal as is. It's not adding any API that we'd regret later.

@ericpre ericpre requested a review from timhoffm March 23, 2024 10:38
@ericpre
Copy link
Member Author

ericpre commented Mar 23, 2024

I added a _check_length method to check the length of the input parameter using offsets as reference for the number of items in the collection.

@ericpre ericpre force-pushed the add_EllipseCollection_setter branch from 0cfe854 to bdde2e9 Compare March 26, 2024 22:07
@ericpre
Copy link
Member Author

ericpre commented Mar 26, 2024

Similarly as in #26410, I remove checking the length of the parameters to leave for another PR.

doc/users/next_whats_new/add_EllipseCollection_setters.rst Outdated Show resolved Hide resolved
lib/matplotlib/collections.py Outdated Show resolved Hide resolved
lib/matplotlib/collections.py Outdated Show resolved Hide resolved
lib/matplotlib/collections.py Outdated Show resolved Hide resolved
Comment on lines +431 to +433
assert_array_almost_equal(ec._widths, np.array(widths).ravel() * 0.5)
assert_array_almost_equal(ec._heights, np.array(heights).ravel() * 0.5)
assert_array_almost_equal(ec._angles, np.deg2rad(angles).ravel())
Copy link
Member

Choose a reason for hiding this comment

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

Do we need to check internals now that we have the getters?

Copy link
Member Author

Choose a reason for hiding this comment

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

These check that the internal values are correct (multiplication of 0.5) and these are different from the values returned by the getter, so I would say that this is still worth checking these values?

Copy link
Member

Choose a reason for hiding this comment

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

Testing internals is not ideal, but OTOH these tests do not impose a big liability. It's unlikely that we will refactor the internals and if so, the tests can be easily adapted.

Alternatives would be an image comparison (not favored) or checking the bounding box of an EllipseCollection with a single entry.

Copy link
Member Author

Choose a reason for hiding this comment

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

If this is find to keep to check as it is I would prefer that option because it is straighforward and more simple than the bounding box alternative.

@ericpre ericpre force-pushed the add_EllipseCollection_setter branch from bdde2e9 to 729467c Compare March 28, 2024 14:51
Copy link
Member Author

@ericpre ericpre left a comment

Choose a reason for hiding this comment

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

Thanks @QuLogic, I have addressed your comments!

lib/matplotlib/collections.py Outdated Show resolved Hide resolved
Comment on lines +431 to +433
assert_array_almost_equal(ec._widths, np.array(widths).ravel() * 0.5)
assert_array_almost_equal(ec._heights, np.array(heights).ravel() * 0.5)
assert_array_almost_equal(ec._angles, np.deg2rad(angles).ravel())
Copy link
Member Author

Choose a reason for hiding this comment

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

These check that the internal values are correct (multiplication of 0.5) and these are different from the values returned by the getter, so I would say that this is still worth checking these values?

Copy link
Member

@timhoffm timhoffm left a comment

Choose a reason for hiding this comment

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

Take or leave the internal check.

@QuLogic QuLogic merged commit 4b42dbb into matplotlib:main Apr 1, 2024
41 of 43 checks passed
@QuLogic
Copy link
Member

QuLogic commented Apr 1, 2024

I think we can ignore the AppVeyor failure here, as it seems to be something to do with wxPython.

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

Successfully merging this pull request may close these issues.

None yet

7 participants