Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

MNT: set the facecolor of nofill markers#17850

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
jklymak merged 1 commit intomatplotlib:masterfromtacaswell:mnt_nofill_facecolor
Jan 29, 2021

Conversation

tacaswell
Copy link
Member

PR Summary

Even though we are going to ignore it, set the facecolors to the user
specified color and edgecolor to 'face' to maintain
back-compatibility.

We now warn if the user passes in an edgecolor that we ignore.

closes#17849

partially reverts#17543 /d86cc2b

Do we want an API change note for the warning?

PR Checklist

  • Has Pytest style unit tests
  • Code isFlake 8 compliant
  • Documentation is sphinx and numpydoc compliant
  • Documented in doc/api/next_api_changes/* if API changed in a backward-incompatible way

@tacaswelltacaswell added this to thev3.3.0 milestoneJul 7, 2020
Copy link
Member

@timhoffmtimhoffm left a comment

Choose a reason for hiding this comment

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

I don‘t think this is the right solution. Explanation follows later today.

tacaswell reacted with thumbs up emoji
@timhoffm
Copy link
Member

timhoffm commentedJul 7, 2020
edited
Loading

Sorry for being a bit terse above. I didn't have time to think it though completely and write up details, but already wanted to announce that the rabbit hole is deeper.

We have three cases of markers:

  1. Regular filled markers with a fill color (e.g.maker='o')
  2. Only the outlines of 1. if their fillstyle is set to 'none' (MarkerStyle(marker='o', fillstyle='none'))
  3. Markers that cannot be filled (e.g.marker='x')

Original bug and fixed version

The original bug#17527 is:scatter() draws case 2 with filling.
This was fixed via#17543:

before:
grafik
Note that the MarkerStyle row should have unfilled circles.

after:
grafik

Click for code of the example plots

importnumpyasnpimportmatplotlib.pyplotaspltfrommatplotlibimportmarkersfrommatplotlibimporttickerx=np.random.random(41)y=np.random.random(41)fig,axs=plt.subplots(3,4)fc='none'ec='tab:orange'markers= ['o',markers.MarkerStyle(marker='o',fillstyle='none'),'x']fori,markerinenumerate(markers):axs[i,0].scatter(x,y,marker=marker)axs[i,1].scatter(x,y,marker=marker,facecolor=fc)axs[i,2].scatter(x,y,marker=marker,edgecolor=ec)axs[i,3].scatter(x,y,marker=marker,facecolor=fc,edgecolor=ec)axs[0,0].set_title('no args')axs[0,1].set_title('fc')axs[0,2].set_title('ec')axs[0,3].set_title('fc + ec')axs[0,0].set_ylabel("'o'")axs[1,0].set_ylabel("MarkerStyle")axs[2,0].set_ylabel("'x'")foraxinaxs.flat:ax.set_xticks([])ax.set_yticks([])

The MarkerStyle row now has unfilled circles. Cases 2 and 3 are handled equivalently, which IMHO makes sense.
Note however, that there is no orange in the bottom two rows becauseedgecolor is ignored for unfilled markers.


Details of the fix

#17543 addresses this in two steps/commits:

  • Markers with fillstyle 'none' should return False foris_filled()
    Thinking of it again I'm not sure anymore if this fix is right. One could also interpretis_filled() as "can have a fill color". The returned value for case 2 would be different depending on the interpretation.
  • scatter() usesis_filled() as a way of determining whetheredgecolor should be ignored.

Why only setting the facecolor for nofill markers is not correct.

For the new interpretationMarkerStyle(marker='o', fillstyle='none').is_filled() == False we would again fill the marker.

It may work if we also revertis_filled() to the old behavior ("can have a fill color"), i.e. revert#17543 completely. But I'm not clear how to fix the original bug#17527. Would need additional consideration.


General comments and way forward

  • IMHO it is ok for now to revertFix linewidths and colors for scatter() with unfilled markers #17543 and leave the bugThe markers are not hollow when I use ax.scatter() and set markers.MarkerStyle()'s fillstyle to 'none'. My usage is wrong? #17527 open for the 3.3 release.
  • We should clarify how we interpretis_filled() and document that.
  • We should clarify which outputs we expect in the above matrix of markers and coloring parameters.
    In particular, I would interpret both 'x' and unfilled 'o' as only having edges, but no faces. Thereforefacecolor should be ignored notedgecolor. It's another aspect if we actually want to go through the deprecation hassle. But it helps to have a clear understanding how it the semantics should ideally be and then see how close we can get. We may need to adapt the color inference logic in_parse_scatter_color_args().
  • I just realize that in the above example it makes a difference if I use fc or facecolor (and ec or edgecolor) 😨. But that's a separate story that should be targeted after we have fixed this mess here.
tacaswell reacted with heart emoji

@tacaswell
Copy link
MemberAuthor

I spoke about this with@QuLogic on the phone, we decided that this is something that we need to think about carefully and make sure we cover all the cases so are going to revert#17543 only for v3.3.x and then fix it on master for 3.4.

timhoffm reacted with thumbs up emoji

@QuLogic
Copy link
Member

The direct revert tov3.3.x is in#17861.

@tacaswelltacaswell added the Release criticalFor bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions. labelJul 28, 2020
@QuLogic
Copy link
Member

QuLogic commentedAug 5, 2020
edited
Loading

Note, we had a similar to#17543, but not yet merged PR in#10008. There is some discussion there as well.

@tacaswell
Copy link
MemberAuthor

On further consideration I have concluded@timhoffm 's judgement of what the behavior should be was correct. This PR tweaks how we go about implementing it.

From reading the code it seems thatmarker.is_filled() andmaker.get_fillstelye() == 'none' are not always the same (critically for the non-fillable markers!). This adds a special case for fillable makers that the user has asked us not to fill from unfillable markers where we do not worry if the user asked us to fill them because we can not.

This also helps to clarify when we should collapse the edge / face colors. For un-fillable makers we don't have a face so we must collapse them and for back-compatibility reasons we leave the face color alone and set the edge color to 'face'. On the other hand, if the user gave us a fillable makers that they want to not be filled, then we have to set the face color to be'none' (as that is the only way to preventPathCollection from filling in the paths).

With the code in this PR we have:

image


If we drop promoting the facecolor to edge color for un-filled-fillable markers then we get

image

which I think is better, in that it makes the behavior between'o' andMakerStyle('o', fill_style='none') more consistent. However, the no-args case is pretty bad and I do see the argument that'x' andMarkerStyle('o', fillstyle='none') should be consistent.


It looks like we don't callcbook._normalize_kwargs before we pass off to_parse_scatter_color_args so the short aliases are crashing through toArtist.set. That should in principle be a simple fix, but given how much other special casing we are doing I was hesitant to tack that onto this PR.

@tacaswell
Copy link
MemberAuthor

We discussed this on the call. We either need to adopt this and bank the step forward that was made in#17543 or take a step back and accept the broken state and punt a more through fix (as outlined by@brunobeltran in#10008 's comments) for 3.5.

Copy link
Member

@timhoffmtimhoffm left a comment
edited
Loading

Choose a reason for hiding this comment

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

TL;DR

I think this is the right way to go. (May be incremental, but is better than before because it fixes#17527 and does so without breaking seaborn, which my first attempt#17543 did).

Explanation

This PR only concerns the special case ofMarkerStyle(..., fillstyle='none').

The interplay of that with colors has been obviously wrong because such a marker should never be filled (see bug#17527).

The implementation in the PR follows the claims in our documentation:

For non-filled markers, the edgecolors kwarg is ignored and forced to 'face' internally.

https://matplotlib.org/3.3.3/api/_as_gen/matplotlib.axes.Axes.scatter.html

Whether or not this color mangling is a good choice is a different question, but it has always been working for true unfilled markersplt.scatter(1, 2, marker='x', facecolor='r').
Changing color mangling generally (and further marker characteristics as proposed in#10008 (comment)) is an API change not only a bug fix. That would require much more careful thinking and is definitively not for 3.4.

This PR "only" fixes a bug forMarkerStyle(..., fillstyle='none') and does so in a way consistent with unfilled markers.

Further remarks

As@tacaswell correctly observesmarker.is_filled() andmaker.get_fillstyle() == 'none' are not necessarily equivalent. Both methods should get a note on this in the docstring and explain more precisely what they mean for for the different kinds of markers.

#
# While not an ideal situation, but is better than the
# alternatives.
if marker_obj.get_fillstyle() == 'none':
Copy link
Member

Choose a reason for hiding this comment

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

I'm still somewhat confused why we need special-casing in the drawing function. Could this not be handled in theMarkerStyle so thatMarkerStyle('o', fillstyle='none') andMarkerStyle('x') have an identical behavior?

@jklymak
Copy link
Member

TL;DR

I think this is the right way to go. (May be incremental, but is better than before because it fixes#17527 and does so without breaking seaborn, which my first attempt#17543 did).

Explanation

This PR only concerns the special case ofMarkerStyle(..., fillstyle='none').

I'm still concerned about this. It was broken, so we are fixing it here. However, I'm not convinced we want it. While its broken we could instead remove it.

In particular what is the goal offillstyle='none'? It was added whenfillstyle='left' and friends were added. However, currently, if you don't want to have a facecolor, you can also just setfacecolor='none'. Having fillstylealso set that seems unnecessary, and an alternative way to go is to just forbid "none" as a possible argument tofillstyle. That strikes me a simpler.

@timhoffm
Copy link
Member

timhoffm commentedJan 19, 2021
edited
Loading

In particular what is the goal offillstyle='none'?

To a certain extend, it is appealing to be able to define "outline-only" markers and then be able to use them like the unfilled markers*. The advantage is that I can define the marker oncering_marker = MarkerStyle('o', fillstyle='none') and then reuse it whenever I want without having to care about setting facecolor additionally. For example consider a GUI that let's you choose your marker. It would be really cumbersome to support a ring marker in there if, the corresponding plot calls would need to be adapted for facecolor.

*This is maybe the pointfillstyle='none' markers and unfilled markers should IMHO be equivalent because they are just "strokes". Ping@story645 in case you want to comment on that from a topological point of view. But apparently they are different, so that we have to jump some extra hoops in this PR to make them render their strokes using the same color logic.
See also#17850 (comment).

So my refined opinion:

  • We still want fillstyle='none' markers
  • The "correct" implementation would be to make them an unfilled marker upon creation.
  • If we don't have the time to look into the "correct" solution before 3.4, this PR is still feasible because it gives the desired result, but with a hacky implementation. But we should cleanup afterwards then.
  • By the way: I think we should makeMarkerStyle objects immutable and removefillstyle property fromLine2D. If one wants to change the fillstyle, one should create a new marker.

But can't we keep it simple and removefillstyle='none'?

  1. We'd loose some functionality, i.e. you don't have ring markers then. You can only mimic them usingfacecolor='none' but at the expense of coupling the desired shape to coloring.
  2. Additionally,fillstyle='none' only has problems withscatter(), AFAIK it works forplot(). Removing it would force users to rewrite working code.

Since we have unfilled markers anyway, it should be relatively straight forward to makefillstyle='none' markersbe unfilled markers. That seems less of a dance than telling users to usefacecolor='none'.

story645 reacted with thumbs up emoji

@mwaskom
Copy link

IMHO it would be extremely nice if there were a native "ring marker" in matplotlib

story645 reacted with thumbs up emoji

@timhoffm
Copy link
Member

timhoffm commentedJan 19, 2021
edited
Loading

IMHO it would be extremely nice if there were a native "ring marker" in matplotlib

Agreed. That's probably the most common use case forMarkerStyle(..., fillstyle='none').

In principle that's not difficult. We could easily add a new marker, say'r'. I'm just wondering if hard-coding this special case is reasonable. If people then start wanting native open squares then, we're running out of letters at some point. Alternatively, we could introduce a pattern[marker]_ so that the ring marker could be written as'o_'. The underscore postfix hinting at the outline-only characteristics. OTOH that might be too clever?

@mwaskom
Copy link

From a higher-level library perspective, having the ability to setfillstyle="none" on a generic marker is quite useful, and using only the facecolor attribute to make a hollow marker is constraining. But from a user persepctive, empty rings are such a useful marker that having a special-cased native type for it would also be nice.

@story645
Copy link
Member

story645 commentedJan 20, 2021
edited
Loading

But from a user persepctive, empty rings are such a useful marker that having a special-cased native type for it would also be nice.

I think even from a library perspective, like@timhoffm said unfilled + unfillable markers inherently encode at least 1 fewer variable than filled markers and so I think it could be good to have that cleanly pinned down as a distinct class/set of marker choices.
Basically we've got

  • can't be filled, unfilled, latex really only support 1 color, facecolor always equals edgecolor,
  • filled supports facecolor, edgecolor, facealtcolor if half filled (which don't like that altcolor is dependent on fill style but 🤷)

so that the ring marker could be written as 'o_'

There's cmap_r and that feels straightforward, I think so long as the convention is documented is fine.

@mwaskom
Copy link

Note that there's already a sort of convention for unfilled/filled markers with"x" -> "X" and"+" -> "P", although it's not possible to make it consistent with creating unfilled markers that already have a lowercase marker code.

@timhoffm
Copy link
Member

That's not the same. 'P_' would give an outlined thick plus, while '+' is just a horizontal and a vertical line.

@mwaskom
Copy link

Hm, good point, from an implementation perspective. Though if I were representing a variable by mapping it to marker type, I would likely pair a filled circle and plus with a hollow circle and line-art plus.

@timhoffmtimhoffm reopened thisJan 20, 2021
@timhoffm
Copy link
Member

Sorry hit the wrong button ... 🙄

@timhoffm
Copy link
Member

sort of convention for unfilled/filled markers with"x" -> "X" and"+" -> "P"

This is more or less coincidence and does not have anything to do with removing the filling.

Though if I were representing a variable by mapping it to marker type, I would likely pair a filled circle and plus with a hollow circle and line-art plus.

I'm not trying to pair anything here. We're havingMarkerStyle([marker], fillstyle='none') and the proposal is to make these easier accessible via the string"[marker]_".

@mwaskom
Copy link

Right, I understand the implementation details. I am saying that there exists a quasi-convention for doing something similar (and something that might appear identical from the perspective of someone who has not thought through the implementation details), and it may be useful to keep that in mind when you add a new convention.

@timhoffm
Copy link
Member

I understand your point. However, IMHO this "quasi-convention" cannot be applied to more cases. It only works if the filled marker looks like "made of strokes". Among thefilled markers we have this is only the case for the exact two cases you mentioned. All others are more "solid" shapes and I couldn't come up with a reasonable corresponding unfilled maker, except one tracing their outline. So I think there is no way to take the quasi-convention into account for more cases. '+' and 'x' are just two exceptions.

@tacaswelltacaswellforce-pushed themnt_nofill_facecolor branch 2 times, most recently from47b9c19 toa89443cCompareJanuary 22, 2021 18:31
@tacaswell
Copy link
MemberAuthor

@timhoffm can you clear your review?

jklymak
jklymak previously approved these changesJan 27, 2021
@jklymak
Copy link
Member

This seems to be genuinely failing:

Unexpectedfailingexamples:/home/circleci/project/examples/animation/rain.pyfailedleavingtraceback:Traceback (mostrecentcalllast):File"/home/circleci/project/examples/animation/rain.py",line40,in<module>scat=ax.scatter(rain_drops['position'][:,0],rain_drops['position'][:,1],File"/home/circleci/project/lib/matplotlib/__init__.py",line1405,ininnerreturnfunc(ax,*map(sanitize_sequence,args),**kwargs)File"/home/circleci/project/lib/matplotlib/axes/_axes.py",line4494,inscatterorig_edgecolor=edgecolorsorkwargs.get('edgecolor',None)ValueError:Thetruthvalueofanarraywithmorethanoneelementisambiguous.Usea.any()ora.all()

@jklymakjklymak self-requested a reviewJanuary 27, 2021 21:00
Copy link
Member

@jklymakjklymak left a comment

Choose a reason for hiding this comment

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

The example passes if you do this. I guess that means we need an extra text where edgecolors is an array.

This is brittle, but matches the behavior in Line2D.MarkerStyle objects have two coupled, but not fully redundant methodsfor determining if the maker is filled: the `is_filled` and`get_fillstyle` methods.  If `ms.get_fillstyle() == 'none'` then`ms.is_filled() is False`, however the converse is not True.  Inparticular the markers that can not be filled (because the Paths theyare made out of can not be closed) have `ms.get_fillstyle() == 'full'`and `ms.is_filled() is False`.  In Line2D we filter on the value of`get_fillstyle` not on `is_filled` so do the same in `Axes.scatter`.In Line2D we do the validation at draw time (because Line2D holds ontoits MarkerStyle object instead of just extracting the path).The logic for fillstyle on Markers came in viamatplotlib#447/213459e.closesmatplotlib#17849Revisesmatplotlib#17543 /d86cc2bCo-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>Co-authored-by: Jody Klymak <jklymak@gmail.com>Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>
@jklymakjklymak merged commit603cf1a intomatplotlib:masterJan 29, 2021
@tacaswelltacaswell deleted the mnt_nofill_facecolor branchFebruary 4, 2021 15:47
has2k1 added a commit to has2k1/plotnine that referenced this pull requestApr 6, 2022
To maintain back-compatibility, matplotlib decided to ignore the issue.fixes#100Ref:matplotlib/matplotlib#10008,matplotlib/matplotlib#17850
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

@timhoffmtimhoffmtimhoffm approved these changes

@jklymakjklymakjklymak approved these changes

Assignees

@timhoffmtimhoffm

Labels
Release criticalFor bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions.topic: collections and mappables
Projects
None yet
Milestone
v3.4.0
Development

Successfully merging this pull request may close these issues.

Problems caused by changes to logic of scatter coloring in matplotlib 3.3.0.rc1
6 participants
@tacaswell@timhoffm@QuLogic@jklymak@mwaskom@story645

[8]ページ先頭

©2009-2025 Movatter.jp