-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
ENH: Allow for non-normalized and transformed markers #16773
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
ENH: Allow for non-normalized and transformed markers #16773
Conversation
96f4c8d
to
ca664a1
Compare
ca664a1
to
36d8de6
Compare
👍 Holding off approving for a bit more discussion about the name. The appveyor failure is not real. |
@ImportanceOfBeingErnest Can you please champion your own PR ? |
I think what can be discussed here is the following: Should the same argument that is introduced here be used later on to distinguish between several inbuild marker modifications, for example:
? |
One of the places we have historically gotten our selves into trouble is trying to fold too much functionality into to any given layer. From the point of view of I propose we start with: normalization: str, {'unit-scale', 'none'}, optional
In the future we will be able to add more known strings in a safe way and to allow callables to be passed. This rhymes with patterns in our codebase and numpy/scipy. [1] sometimes it seems that in programming that the cardinality of options seems to be 0 (no option), 2 (yes/no), or infinite |
Ok, let's think this through. So, for now:
In addition, in the future
Is this how we want to have it? |
Hmm, you are correct that the letter-named markers are not "unit-scale", maybe we should keep the default behavior as "default" and then add "unit-scale" that makes everything fit inside of a unit-box (centered at 0) for both Path and string based input, "none" which imposes no normalization on Paths that are passed in, and lets the string-based do what it does. In the future we can add "bounding-box-area" and "filled-area" normalization values that apply to everything? |
36d8de6
to
92192aa
Compare
I now went with
I think I am not implementing
|
92192aa
to
5e979ce
Compare
I know I'm a bit late to the conversation (had to escape the country because of covid), but I agree that something like "classic" would be best to convey the current behavior.
I haven't settled on good, short names for each of these behaviors yet, but I agree that something like:
seem useful enough to consider as future options. Just would like to bring up that if (1)-(3) are on the table, then we probably want to not call any one of them in particular "unit-scale" right now, as this description fits them all three pretty well.
I can comment on the difficulty of these calculations, having just written code to do the bounding box calculation for custom paths with finite-width stroked edges. The machinery for "bounding-box-area" is already in place (requires just some simple helpers added to bezier.py in #16607). The machinery for "filled-area" would need to be added. The best way to do this would be iterative refinement (calculating area of approximating polygon to the curve for increasingly fine discretizations until some tolerance is reached). I'd estimate a very dense collection of 2-4 helper functions would need to be added to path.py/bezier.py to accomplish this. On the other hand, this is code I've written before, and it can be easily tested and would be relatively self-contained. I think there's a very strong argument to be made that the scaling of the markers for each of these options should not take into account the |
Instead of
I would propose just adding a "classic scaling" attribute to each "marker" in On the other hand
Chiming in as outsider (since I've already stuck my nose in here) that I agree this would fit better in a separate PR. |
@brunobeltran Is it fair to summarize your position as: a) this is a non-trivial problem b) this lays down the skeleton of the public API we are going to need c) this should go in about as-is so we can build on it? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
modulo tests passing and a rebase.
5e979ce
to
824ce2b
Compare
Previously, the unscaled paths of the builtin markers were effectively not part of the public API; with this PR, they are. Do we want to change the size of the unscaled versions of the builtin markers while this is still possible? |
Mhh. |
Yup 👍
This doesn't seem true to me? Sure now users might rely on the size of a marker given I mean, you could argue that the path/transform were always publicly available ( This is especially true since, contrary to what was initially proposed above,
would not actually leave "D" unchanged, but scale it down by a factor of EDIT: @ImportanceOfBeingErnest beat me to this, agree with his comment. |
Fair enough, they were public if one tried hard enough, but this makes them much more easily accessible. |
By "more easily accessible" you mean "discoverable" or "useful"? Or else, I might not really understand the argument because accessibility does not change here. Arguably there is a high reduncancy between the path and the transform. So in principle one could define any marker just by a path. In that case, one could deprecate |
Missed the call today, but I would like to say that I highly prefer the original proposal, for a few reasons:
My alternate proposal (see #16891), would be to instead provide the user with two convenience arguments |
Can you clarify what the "original proposal" is? |
Sorry: by "original proposal" I meant @ImportanceOfBeingErnest's proposal to have the user pass in an arbitrary path if they want, and to only provide them with some helpers for rescaling/etc using string inputs (instead of asking them to control the marker's The built-in paths would by default not be affected but if you requested e.g. |
I guess adding a
|
I don't think that a |
I just don't think the user should have access to |
@brunobeltran OK, so by "Original proposal" you didn't mean this PR? I guess I agree that an expert user can just set a transform before passing to |
The transform is not an "implementation detail", because Indeed,
is just quicker / less convoluted than
This assumes |
@ImportanceOfBeingErnest You're right, if we get rid of the [get/set]_transform interface, for each new marker you want, you would need to pass the fully transformed But you definitely don't have to "construct the marker from scratch". The convoluted API you're describing is still assuming that Actually deprecating On the other hand, we can easily leave it in place and just have it always return Then the Path will be enough, you never need to access the transform at all (although it will still be possible to support all that old backend/collections code that depends on it).
or similar. |
I do mean this PR (and its associated discussion), as opposed to the alternate proposal from the call last week that we instead use a
Maybe whether there should be "a bunch" of normalization and offset options is a different question (I propose two kwargs, "normalization" and "centering", not "a bunch"). However, this PR adds a |
Note that deprecating The only clean way to do this would be to give both a new kwarg that sets the return, in the sense of I'm not convinced that this is really needed. Also, having the transform on a generic path is a nice thing and keeps the code short. The transform would also be useful for implementing any of the other types, which mostly are just a scaled or translated version of the existing ones. |
I've played around with custom markers more this week, and I am pretty convinced that allowing a user to modify the marker in-place at all is not ideal. Some issues:
and at that point, I might as well (i.e. it's not really extra typing, and definitely feels more intuitive/discoverable) to just do
where That said, I could easily be persuaded if I was given a use case where modifying the marker after creating it is useful, I just wasn't able to find one.
I probably just don't understand the matplotlib development constraints well enough yet, but these both seem like API changes to me? In one we remove a method, in another we change it's return value? It's just as easy to add a kwarg and a deprecation warning, saying that in the next version, we'll be switching to the The development burden to remove |
@ImportanceOfBeingErnest @brunobeltran is there interest in pushing this forward, either in the context of this PR or a new one? I guess the original motivation makes it seem that this would be generally helpful, though I'm not sure we agreed on the API. |
This functionality was implemented in #20914 in a way inline with #16773 (comment) (adding a transform keyword but composing it with the default transform so slightly differently parameterized than this proposal, but I think with the same capabilities). Thank you for your work on this @ImportanceOfBeingErnest and @brunobeltran . I do hope we hear from both of you again! |
PR Summary
This PR allows markers to be non-normalized or transformed:
MarkerStyle
now gets anormalize_path
argument, which defaults toTrue
, but can be setFalse
to allow for custom paths that are input to not be normalized to the unit square. That is, the following two will result in the same plotMarkerStyle
now gets aset_transform
method, that allows to externally set a transform to the marker. I.e., the following two result in the same plotWhy is this useful?
Until now, it is impossible use arbitrary custom markers. Custom markers are always normalized to the unit square. So for a given path, the size and center point of the marker are changed by matplotlib.
Common problems:
You want to rotate a given marker without it changing size. See
You want to create a custom marker that is sized relative to another marker, see e.g. https://matplotlib.org/gallery/lines_bars_and_markers/scatter_piecharts.html, without setting its size via the
markersize
property of the artist, or that is off-centered.Finally, you may want to create a set of markers that are uniform in area, or perceptually uniform, as desired in Sizes of different markers are not perceptually uniform #15703
PR Checklist