GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — [Bug]: Cannot use empty markers in scatter · Issue #24404 · matplotlib/matplotlib

[Bug]: Cannot use empty markers in scatter #24404

New issue
Copy link
New issue
Copy link
Open
Open
[Bug]: Cannot use empty markers in scatter#24404
#29130 (+2)
Copy link
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
future releases

Description

@Atcold
Atcold
opened on Nov 8, 2022
Issue body actions

Bug summary

I'd like to have a scatter plot with empty 'o' markers (just the edge).
This seems impossible.

Code for reproduction

from matplotlib.pylab import *
x = arange(0, 10)
scatter(x, x, c=x, facecolors='none')

Actual outcome

image

Expected outcome

Empty markers.

Matplotlib Version

3.5.1

Activity

Next
  1. jklymak commented on Nov 8, 2022

    @jklymak
    jklymak
    on Nov 8, 2022
    Member
    More actions

    It's inconvenient, but not impossible.

    import numpy as np
    import matplotlib.pyplot as plt
    import matplotlib as mpl
    
    x = np.arange(0, 10)
    norm = plt.Normalize(0, 10)
    cmap = mpl.colormaps['viridis']
    cols = cmap(norm(x))
    plt.scatter(x, x, facecolors='none', edgecolors=cols)
  2. Atcold commented on Nov 8, 2022

    @Atcold
    Atcold
    on Nov 8, 2022
    Author
    More actions

    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.

  3. jklymak commented on Nov 8, 2022

    @jklymak
    jklymak
    on Nov 8, 2022
    Member
    More actions

    scatter already 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 like c=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 for scatter so there wouldn't be ambiguity: Nx3 or Nx4 would be treated as colors. Nx1 would be passed through norm and colormap.

    Reacted by Alfredo Canziani, Kevin Mi, ewpardijs and alkan
  4. tacaswell commented on Nov 8, 2022

    @tacaswell
    tacaswell
    on Nov 8, 2022
    Member
    More actions

    The default behavior is for the edge colors to follow the face color, layer with if you specify c we 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 ScalarMappable that is really two and the question of how you make a color bar out of that gets complicated....

    Sorry for rambling post.

  5. jklymak commented on Nov 9, 2022

    @jklymak
    jklymak
    on Nov 9, 2022
    Member
    More actions

    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.

  6. jklymak
    added
    API: consistencyConsistency of the matplotlib API, including naming, behavior, defaults, …Consistency of the matplotlib API, including naming, behavior, defaults, …
    on Nov 9, 2022
  7. Atcold commented on Nov 10, 2022

    @Atcold
    Atcold
    on Nov 10, 2022
    Author
    More actions

    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.

  8. tacaswell
    added this to the v3.7.0 milestone on Nov 11, 2022
  9. tacaswell
    added
    Difficulty: Mediumhttps://matplotlib.org/devdocs/devel/contribute.html#good-first-issueshttps://matplotlib.org/devdocs/devel/contribute.html#good-first-issues
    🌱 Good first issueOpen a pull request against these issues if there are no active ones!Open a pull request against these issues if there are no active ones!
    on Nov 11, 2022
  10. tacaswell commented on Nov 11, 2022

    @tacaswell
    tacaswell
    on Nov 11, 2022
    Member
    More actions

    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:

    • switch from using 'face' as the edge color by default in the case where there is color
    • make sure the edge color also being mapped does not mean we do the data -> norm -> colormap pipelines twice
    • add tests of making empty markers with edges that are still mapped
    • extend one of the scatter examples to show this
    • need to make sure that edge color still tracks the face color is all of the cases where it currently does when changed post-hoc (which maybe annoying, but I think would be an API change). I suspect this will be the thorniest part of this work.

    I 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 scatter method 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.

    Reacted by Alfredo Canziani
  11. greglucas commented on Nov 11, 2022

    @greglucas
    greglucas
    on Nov 11, 2022
    Contributor
    More actions

    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)

    Reacted by Alfredo Canziani
  12. GraceJiang0312 commented on Dec 5, 2022

    @GraceJiang0312
    GraceJiang0312
    on Dec 5, 2022
    More actions

    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')

    scatter_facecolor

    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)

    scatter_edgecolor

  13. wu-theodore commented on Dec 6, 2022

    @wu-theodore
    wu-theodore
    on Dec 6, 2022
    More actions

    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 scatter method under _axes.py and the edgecolor parameter seems to be set by _parse_scatter_color_args. There, the specified edge color argument precedence seems to be as follows:

    • kwargs['edgecolor']
    • edgecolors (is an explicit kw argument in scatter())
    • kwargs['color'] (==kwcolor)
    • 'face' if not in classic mode else None

    Now, the proposed change seems to be related to the case where edgecolor and color are implicitly None. In this case, we would skip the first 3 cases and enter the 4th case, which would be to set edgecolor = 'face'. As far as I can see, this condition is implemented by

    if edgecolors is None and not mpl.rcParams['_internal.classic_mode']:
         edgecolors = mpl.rcParams['scatter.edgecolors']

    A fix then would be to add another check prior to this conditional that checks for whether c is defined, and if so, try to assign c to edgecolor (without breaking duplicating the data->norm->colormap pipeline, of course).

    I suppose that alternatively, we could instead handle edgecolor value assignment when the face color is none within cbook._combine_masks from

    if plotnonfinite and colors is None:
                c = np.ma.masked_invalid(c)
                x, y, s, edgecolors, linewidths = \
                    cbook._combine_masks(x, y, s, edgecolors, linewidths)

    This would more closely resemble what is already being done when the facecolor (i.e., colors) is None.

    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?

    Reacted by Alfredo Canziani
  14. Atcold commented on Dec 6, 2022

    @Atcold
    Atcold
    on Dec 6, 2022
    Author
    More actions

    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')

    scatter_facecolor

    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)

    scatter_edgecolor

    I'm not sure what this message means.
    I was reporting a bug.

  15. 3 remaining items

  16. melissawm commented on Jan 9, 2023

    @melissawm
    melissawm
    on Jan 9, 2023
    Member
    More actions

    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!

  17. QuLogic
    modified the milestones: v3.7.0, future releases on Jan 25, 2023
  18. story645
    closed this as completedin #29130on Jan 3, 2025
  19. claytharrison commented on Feb 19, 2025

    @claytharrison
    claytharrison
    on Feb 19, 2025
    More actions

    Sorry but how does #29130 close this? Nothing in this issue is about problems using c and facecolors at the same time , it's about being unable to use c for just edgecolors while setting facecolors to "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.

  20. jklymak
    reopened this on Feb 19, 2025
  21. story645 commented on Feb 19, 2025

    @story645
    story645
    on Feb 19, 2025
    Member
    More actions

    Sorry but how does #29130 close this

    Sorry, GitHub auto close behavior, should have double checked what it was auto closing.

  22. giboul commented on Jul 21, 2025

    @giboul
    giboul
    on Jul 21, 2025
    More actions

    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.

    import numpy as np
    import matplotlib.pyplot as plt
    
    x = np.arange(0, 10)
    colors = plt.cm.viridis((x-x.min())/(x.max()-x.min()))
    plt.scatter(x, x, c='none', edgecolors=colors)
    plt.show()

    [matplotlib-3.10.3]

  23. ashishamith commented on Aug 20, 2025

    @ashishamith
    ashishamith
    on Aug 20, 2025
    More actions

    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.

    Reacted by Tim Hoffmann
  24. cairibep commented on Oct 22, 2025

    @cairibep
    cairibep
    on Oct 22, 2025
    More actions

    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?

  25. pirzada-ahmadfaraz
    added a commit that references this issue on Jan 14, 2026

    Fix scatter edge colors not mapped when facecolors='none'

    This issue will close once commit bcdca3b is merged into the 'main' branch.
    bcdca3b
  26. timhoffm commented on Jan 14, 2026

    @timhoffm
    timhoffm
    on Jan 14, 2026
    Member
    More actions

    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):

    I'd expect that everything should be handled in Collections, i.e. the interaction between data, (norm, cmap), and facecolors, edgecolors. Any logic outside of Collections is likely bound to fail with inconsistent behavior if some properties of the collection are updated later.

    Ideally, there's one universal logic in Collection tying everything together, but it might be that PathCollection requires a different logic, or in theory even a PathCollection representing makers might have different requirements. This is something that needs much more in-depth investigation.

    What I first want to see is systematic summary of the logic how data, (norm, cmap), and facecolors, edgecolors resolve to the eventual colors, both what the current state is and what the desired target state is.

    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

    def update_scalarmappable(self):
    . If recomputation is an issue, caching is still the lesser evil to managing state.

    I'm removing "Good first issue" as I think this needs a deep architecture investigation.

  27. timhoffm
    removed
    🌱 Good first issueOpen a pull request against these issues if there are no active ones!Open a pull request against these issues if there are no active ones!
    on Jan 14, 2026
  28. pirzada-ahmadfaraz
    added 2 commits that reference this issue on Jan 15, 2026

    Rework fix: Move edge color mapping logic to Collection

    This issue will close once commit 4af2e00 is merged into the 'main' branch.
    4af2e00

    Rework fix: Move edge color mapping logic to Collection

    This issue will close once commit 05f7443 is merged into the 'main' branch.
    05f7443
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    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

    Type

    No type

    Projects

    No projects

      Milestone

      • future releasesNo due date

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      You are visitor 01402520 — GeezerVison does not store the pages you look up.
      Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.