Skip to content

Inconsistent eager vs delayed resolution of Cn in plot() #12967

Open

Description

@anntzer
from pylab import *

plt.plot([1, 2], "C0")
plt.plot([2, 3], c="C0")
rcParams["axes.prop_cycle"] = cycler(color=["r", "g", "b"])
show()

test

The first call to plot() resolves the color eagerly, the second one resolves it lazily (which I think is the intended behavior).

mpl master (~3.0.x)

Activity

  1. timhoffm commented on Dec 25, 2018

    @timhoffm
    Member

    Note: Delayed evaluation of 'C0' may be counter-intuitive. The color_cycle is evaluated immediately.
    This example might be anticipated to have the same colors for the line and the marker. But they have not

    from pylab import *
    plt.plot([1, 2])
    plt.plot([1], 'C0o')
    rcParams["axes.prop_cycle"] = cycler(color=["r", "g", "b"])
    show()
    

    Generated with the delayed evaluation in #13053:

    image

  2. WeatherGod commented on Dec 26, 2018

    @WeatherGod
    Member
  3. timhoffm commented on Dec 26, 2018

    @timhoffm
    Member

    This allows for applying styles after construction of the plot, which helps with separation of concerns in some libraries.

    But that would mean that the property cycle should also be evaluated lazily?

  4. WeatherGod commented on Dec 26, 2018

    @WeatherGod
    Member
  5. timhoffm commented on Dec 26, 2018

    @timhoffm
    Member

    Similar, but for patches: #11710

  6. anntzer commented on Dec 29, 2018

    @anntzer
    ContributorAuthor

    I was originally fine with delayed evaluation (after all I even wrote #11711) but... I think @timhoffm made a critical point here:

    But that would mean that the property cycle should also be evaluated lazily?

    If the goal is to "allow for applying styles after construction of the plot", then certainly

    plt.plot([0, 1]); plt.plot([2, 3])
    rcParams["axes.prop_cycle"] = cycler(color=["r", "g", "b"])
    

    should also change the first line color to red and the second line color to green (my bet is that code that relies on implicitly advancing the property cycle is thousands of times more common than explicit use of Cn). Actually that wouldn't be too hard to do, one can just make plot() use Cn as default line color instead of explicitly advancing the property cycle. But... what if the property cycle is cycling on another property? e.g.

    plt.plot([0, 1]); plt.plot([2, 3])
    rcParams["axes.prop_cycle"] = cycler(linestyle=[":", "-."])
    

    If we want to make that work in a consistent fashion, we suddenly need to make all properties support Cn-like syntax, or at least some way to internally say, "this property should be set at draw time based on the n-th value of the default property cycle".

    While I don't think this is impossible to implement, it would clearly be a pretty big endeavor, so one should instead ask whether that is really worth it?

    @WeatherGod says that delayed evaluation

    applying styles after construction of the plot [...] helps with separation of concerns in some libraries.

    To be honest, it is not clear to me why this helps with separation of concerns, nor why the libraries could not apply the style before constructing the plot instead of after (and again, right now, this basically works only for a very limited subset of plots, namely those that plot lines using (some variant of) plot(..., c="Cn")). (I guess this could help if one wants to fully restyle a figure after generating it, but realistically we're nowhere close to that being doable.)


    I wouldn't mind moving towards fully delayed evaluation, but only if there is a realistic plan for that (and again, it would be a lot of work, per the above -- feel free to mention if I missed anything, of course). In the meantime, I think consistent eager evaluation of properties is clearly preferable, and would support changing the behavior of the few places that currently use delayed evaluation. If someday, fully delayed evaluation is finally implemented, then that'd be a much bigger API break anyways, and I don't think it would be made more or less painful by moving, right now, to eager evaluation.

  7. tacaswell commented on Dec 29, 2018

    @tacaswell
    Member

    I think moving to eager evaluation is the correct path here. I have a vague memory of there being a justification for the delayed evaluation, however these subtle inconsistencies are probably too high of a cost to pay (unless we have a concrete application).

  8. added this to the v3.1 milestone on Dec 29, 2018
  9. jklymak commented on Dec 29, 2018

    @jklymak
    Member

    Agree w/ @tacaswell in that I don't see the use case for lazy evaluation. If I want to play with styles I just remake the plot each time. I don't think changing parameters later in a script should magically affect things you've done previously in the script, unless you are directly manipulating the objects you created. That just seems confusing and needlessly mysterious.

  10. timhoffm commented on Dec 29, 2018

    @timhoffm
    Member

    +1 for eager resolution.

    It’s much simpler to implement and also to understand for the user. Delayed resolution would have a hidden state of which plot element was created in which order so that we can later find out which element gets which property from the cycle. Also, if I create a Line2D and query it for its color, I wouldn‘t get a color but an intermediate marker like C0. That would be technically correct, but is harder to understand for the user.

    I don‘t see a need for restyling an existing plot. Redoing the plot is almost always ok. I also assume that there are subtle difficulties when trying to restyle more complex plots.

    Separation of concerns can be achieved with contexts.

  11. WeatherGod commented on Dec 30, 2018

    @WeatherGod
    Member
  12. anntzer commented on Dec 30, 2018

    @anntzer
    ContributorAuthor

    Well, it's not too late to admit that having a function returning the nth color would have been a better design (and yes, we're likely already falling back on black when the property cycle does not include color, but that's not the problem here).
    Again, I'm actually personally not against delayed evaluation, but right now we're very, very far away from having that (we both agree that it would be desirable to have it for all properties, if we do want to have that). Until someone comes up with a PR (or at least a design) implementing that, it seems better to be consistent by using eager evaluation everywhere, rather than an inconsistent mishmash.

  13. WeatherGod commented on Dec 30, 2018

    @WeatherGod
    Member
  14. anntzer commented on Dec 30, 2018

    @anntzer
    ContributorAuthor

    You realize that Cn colors already always look up the default property cycler and never care about the owning axes?

    from pylab import *
    rcdefaults()
    fig, ax = plt.subplots()
    ax.set_prop_cycle(color=["r", "g", "b"])
    ax.plot([0, 1], c="C0")
    ax.plot([2, 3], c="C1")
    plt.show()
    

    results in lines in "tab:blue"/"tab:orange", not "r"/"g".

    (In fact, I just realized that if you want to have delayed evaluation, having to look up the owning axes instead of the default property cycler would be an additional complication, because this would mean the evaluation cannot be implemented just once by the renderer (which doesn't know what the owning axes is) (right now when a Cn color gets sent to mplcairo's draw_path method it indeed (effectively) looks up rcParams["axes.prop_cycle"]), but would have to be done by each artist draw() separately.)

  15. removed this from the v3.1.0 milestone on Mar 3, 2019
  16. 10 remaining items

  17. added this to the v3.7.0 milestone on Jul 8, 2022
  18. github-actions commented on Nov 15, 2024

    @github-actions

    This issue has been marked "inactive" because it has been 365 days since the last comment. If this issue is still present in recent Matplotlib releases, or the feature request is still wanted, please leave a comment and this label will be removed. If there are no updates in another 30 days, this issue will be automatically closed, but you are free to re-open or create a new issue if needed. We value issue reports, and this procedure is meant to help us resurface and prioritize issues that have not been addressed yet, not make them disappear. Thanks for your help!

  19. timhoffm commented on Nov 15, 2024

    @timhoffm
    Member

    How do we want to proceed on this? I suggest:

    • Use eager resolution everywhere
    • Decide how to target of "CN" resolution. Currently, we resolve to the default property cycle (Inconsistent eager vs delayed resolution of Cn in plot() #12967 (comment)). Options are
      • (1) Stick to resolution to the default property cycle.

        Advantage: Nothing to do, and likely good enough.

      • (2) Use the Axes property cycle.
        This likely needs a bit of restructuring:

        How would we do eager evaluation for an Artist that doesn't have an axes
        assigned to it yet?

        Real standalone artists would still go to the default property cycle.

        But plotting functions usually follow the pattern 1. Create the artist 2. add it to the Axes. In step 1. the artist is formally standalone. We would need a mechanism to handle the axes context during creation. (Note for plot(), we have _process_plot_var_args as a preprocessing step that takes over these aspects, but it's not generaliziable, we'd need something more universal that works for all artists)

        Advantage: Logically more consistent. The automatic colors are chosen from the Axes property cycle, one could expect that "CN" then also goes to that property cycle.

    I'm inclined to go with option (1). This has been the behavior for several years, and I'm not aware of a complaint - apart from the issue created here. The behavior seems good enough. We can always reconsider. There's not much difference in consistency/user impact between changing now and changing later. So let's save the effort for now.

  20. story645 commented on Nov 15, 2024

    @story645
    Member

    This has been the behavior for several years, and I'm not aware of a complaint - apart from the issue created here.

    Someone at scipy tracked #28475 (inconsistent resolution of none) to being an issue w/ eager conversion to color that relies on 'none'/empty array for if statements to preserve the correct alpha values.

  21. timhoffm commented on Nov 16, 2024

    @timhoffm
    Member

    This has been the behavior for several years, and I'm not aware of a complaint - apart from the issue created here.

    Someone at scipy tracked #28475 (inconsistent resolution of none) to being an issue w/ eager conversion to color that relies on 'none'/empty array for if statements to preserve the correct alpha values.

    What is the connection to this issue? I read is that there's an issue with the result of to_rgba_array("none") and somebody found out that it has to do with eager conversion. Ok, now we have two issues that relate to eager resolution. Yours looks like an actual bug. The "CN" topic is a bit surprising, but I won't call it a bug. But more importantly, they don't have anything to do with each other.

  22. story645 commented on Nov 17, 2024

    @story645
    Member

    What is the connection to this issue?

    Bullet point one of using eager resolution everywhere - was just trying to point out that that may create more issues/exacerbate the bugs we already have, b/c I assumed you were saying eager resolution for all colors and not just CN colors (b/c that inconsistency would be a mess).

  23. timhoffm commented on Nov 17, 2024

    @timhoffm
    Member

    Thanks for the clarification. I haven't dug into the details of the issue you mentioned, but just from the title - what hinders us from adding if c == "none": return np.array([[0., 0., 0., 0.]])? Anyway, I'm convinced that lazy evaluation is generally significantly more complex to implement and harder to understand. I thus believe any step towards more lazy evaluation will worsen the situation. If we have a mixture, that is tolerable as a status quo. But if touching something we should move towards eager resolution.

  24. story645 commented on Nov 19, 2024

    @story645
    Member

    what hinders us from adding if c == "none": return np.array([[0., 0., 0., 0.]])?

    It breaks contours and hatches, as described in the issue summary. And I worry that casting "none" the same as the other none in same_color could cause unintended side effects since the conversion is inconsistent.

  25. timhoffm commented on Nov 20, 2024

    @timhoffm
    Member

    It breaks contours and hatches,

    To be investigated: Is the assumption “none” == (0., 0., 0., 0.) invalid or is the breakage an implementation detail of contours?

  26. timhoffm commented on Nov 20, 2024

    @timhoffm
    Member

    It breaks contours and hatches,

    To be investigated: Is the assumption “none” == (0., 0., 0., 0.) invalid or is the breakage an implementation detail of contours?
    If the latter, we should fix the contour implementation and then change to_rgba_array. If the former, we need to reconsider the meaning of “none”.

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions