[Bug]: Cannot use empty markers in scatter #24404
Copy link
Copy link
Open
Labels
API: consistencyConsistency of the matplotlib API, including naming, behavior, defaults, …Consistency of the matplotlib API, including naming, behavior, defaults, …Difficulty: Mediumhttps://matplotlib.org/devdocs/devel/contribute.html#good-first-issueshttps://matplotlib.org/devdocs/devel/contribute.html#good-first-issues
Milestone
Description
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
API: consistencyConsistency of the matplotlib API, including naming, behavior, defaults, …Consistency of the matplotlib API, including naming, behavior, defaults, …Difficulty: Mediumhttps://matplotlib.org/devdocs/devel/contribute.html#good-first-issueshttps://matplotlib.org/devdocs/devel/contribute.html#good-first-issues

Activity
jklymak commentedon Nov 8, 2022
It's inconvenient, but not impossible.
Atcold commentedon Nov 8, 2022
Okay, rephrasing.
It's impossible without additional code.
Can we add your code to this function when
c=x, facecolors='none'are specified?It seems like reasonable behaviour to me.
jklymak commentedon Nov 8, 2022
scatteralready has a lot of implicit behaviour around colormapping that has accrued over the years. I would personally lean towards the less-used case needing a few more lines of explicit user code rather than more implicit behaviour.facecolors='none'doesn't necessarily mean "please add edgecolors".A counter proposal would be that
plt.scatter(x, y, facecolors='none', edgecolors=x)would do the mapping (just likec=x) for the edgecolor. Thats what I tried first, but it didn't work. I think it's usually clear that x is not an array of colors forscatterso there wouldn't be ambiguity: Nx3 or Nx4 would be treated as colors. Nx1 would be passed through norm and colormap.tacaswell commentedon Nov 8, 2022
The default behavior is for the edge colors to follow the face color, layer with if you specify
cwe ignore the facecolor. I agree with @Atcold that still doing the mapping but just setting the edges is the expected behavior, however I think it might be messy internally.I have a worry about allowing the edge color to be mapped independently. On one hand you could say the face color and edge color must share the name norm and cmap, but if it will soon seem reasonable to relax that and let each have their own and new we will end up with a
ScalarMappablethat is really two and the question of how you make a color bar out of that gets complicated....Sorry for rambling post.
jklymak commentedon Nov 9, 2022
Oh, you are right - we indeed show and color the edges. In that case, I think I also agree with @Atcold that this is a "bug". Not sure how easy it is to fix without breaking the brittle dance we do here.
Atcold commentedon Nov 10, 2022
Rather than having the edge colours ‘follow’ the face colours, we could have a mapping that is given to both the face and edge. If either face or edge colour is specified, that takes precedence over the mapping.
tacaswell commentedon Nov 11, 2022
That is reasonable, but there is a fair amount of book-keeping that will have to be reworked through (having the edge color follow the face it is easier in the case where you re-set the data / colormap / norm in that we only have to track it one place internally and the rest "just works"). Because scatter both takes mappings and fixed colors iirc using
'face'as the color of the edges also helps in that case.The work here is:
'face'as the edge color by default in the case where there is colorI think this is a good first issue because there is no API design here (that this should work seems non-controversial), but medium difficulty as it will require reading and understanding the internals of both the
scattermethod and the collections (sub-)classes to sort out how to do the book keeping correctly. This is not a good issue for someone new to Python, but is a good issue for someone new to working on Matplotlib.greglucas commentedon Nov 11, 2022
This should probably follow similar semantics to the pcolormesh face/edge discussion as well. xref to this comment which has a nice summary plot of the combinations: #18480 (comment)
GraceJiang0312 commentedon Dec 5, 2022
non-empty face colors and non-empty edge colors:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(0, 10)
plt.scatter(x, x, c=x, facecolors='none')
empty face colors and non-empty edge colors:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm
x = np.arange(0, 10)
norm = plt.Normalize(0, 10)
cmap = matplotlib.cm.get_cmap('viridis')
cols = cmap(norm(x))
plt.scatter(x, x, c='none', facecolors='none', edgecolors=cols)
wu-theodore commentedon Dec 6, 2022
Hello, new contributor here. I'd like to outline my exploration/thoughts so far and invite discussion to clarify the source of the issue and what the change would look like.
I took a look at the internals of the
scattermethod under_axes.pyand theedgecolorparameter seems to be set by_parse_scatter_color_args. There, the specified edge color argument precedence seems to be as follows:Now, the proposed change seems to be related to the case where
edgecolorandcolorare implicitlyNone. In this case, we would skip the first 3 cases and enter the 4th case, which would be to setedgecolor = 'face'. As far as I can see, this condition is implemented byA fix then would be to add another check prior to this conditional that checks for whether
cis defined, and if so, try to assignctoedgecolor(without breaking duplicating the data->norm->colormap pipeline, of course).I suppose that alternatively, we could instead handle
edgecolorvalue assignment when the face color isnonewithincbook._combine_masksfromThis would more closely resemble what is already being done when the
facecolor(i.e.,colors) isNone.As someone new to working with MatPlotLib, I am not sure about the ramifications of this change and would like some input from a more experienced contributor. Specifically, would this create any issues with the bookkeeping in
collections? What kind of unexpected implicit behaviour might arise from such a change?Atcold commentedon Dec 6, 2022
I'm not sure what this message means.
I was reporting a bug.
3 remaining items
melissawm commentedon Jan 9, 2023
Hello @Gairick52 - as pointed above we don't assign issues. As long as there is no Pull Request associated with the issue, you can feel free to work on it. Please take a look at our Contributing guide and if you have specific questions you can also join our gitter room or one of our monthly New contributor meetings (on Zoom). Cheers!
claytharrison commentedon Feb 19, 2025
Sorry but how does #29130 close this? Nothing in this issue is about problems using
candfacecolorsat the same time , it's about being unable to usecfor justedgecolorswhile settingfacecolorsto"none". It's still impossible to have empty scatter symbols using just keyword args - which gets annoying when using pyplot through wrappers like e.g. Xarray's.story645 commentedon Feb 19, 2025
Sorry, GitHub auto close behavior, should have double checked what it was auto closing.
giboul commentedon Jul 21, 2025
Hello everyone, new matplotlib lover here
For anyone interested in a solution that could be a one-liner, I was satisfied with the following
Edit: this might be less useful than @jklymak's answer when a colorbar is needed.
[matplotlib-3.10.3]
ashishamith commentedon Aug 20, 2025
Hello 👋, I’d like to take this up. Planning to submit a small patch that handles empty markers properly in scatter(). Please let me know if that works.
cairibep commentedon Oct 22, 2025
Hello!
I've been trying to solve this problem internally and have had some interesting results. I'd like to request a PR on this.
Can I take on this task?
Fix scatter edge colors not mapped when facecolors='none'
timhoffm commentedon Jan 14, 2026
I'm not clear what the target state is here; and thus also not whether moving away from "face" and doing some "bookkeeping" strategy is reasonable.
Citing myself #30962 (comment):
Overall, I'm very sceptical on having state. Ideally, everything is determined dynamically at draw-time. So I would fundamentally question the current Collection architecture with
update_scalarmappable()matplotlib/lib/matplotlib/collections.py
Line 1009 in 77b5114
I'm removing "Good first issue" as I think this needs a deep architecture investigation.
Rework fix: Move edge color mapping logic to Collection
Rework fix: Move edge color mapping logic to Collection