Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Axhline doesn’t cycle through colors #16714

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

Closed
01baftb opened this issue Mar 9, 2020 · 19 comments
Closed

Axhline doesn’t cycle through colors #16714

01baftb opened this issue Mar 9, 2020 · 19 comments

Comments

@01baftb
Copy link

01baftb commented Mar 9, 2020

As requested by @story645, I am posting a possible bug I reported in the forum https://discourse.matplotlib.org/t/axhline-doesnt-cycle-through-colors/20938

When I plot horizontal lines using axhline, there resulting lines are all the same color. It doesn’t cycle automatically. Is this behavior expected? Do I need to manually setup the color cycling? If so how can I do this easily? Why does matplotlib not automatically cycle through the colors for axhline?

matplotlib v3.2.0

%matplotlib widget
import matplotlib.pyplot as plt

fig, ax = plt.subplots()

count = 5

for n in range(count):
    ax.axhline(y=n**2, label=n) 
    
ax.legend()
plt.show()

image

@WeatherGod
Copy link
Member

WeatherGod commented Mar 9, 2020 via email

@jklymak
Copy link
Member

jklymak commented Mar 9, 2020

I don’t think axhline should cycle.

@story645
Copy link
Member

story645 commented Mar 9, 2020

I think it should since it's a line plotting method and those usually cycle

@QuLogic
Copy link
Member

QuLogic commented Mar 9, 2020

This was discussed before in #14222 with the conclusion (paraphrasing a bit) that if it were to change, it would be in 4.0. These changes are collected in #14331 going forward.

@jklymak
Copy link
Member

jklymak commented Mar 9, 2020

Given that the lines are typically used for special values not data, always intercept the perpendicular axes, and hence you know the value of the line, and the lines do not cross one another, having them cycle color doesn't seem to add any information, and just serves to complicate the plot needlessly.

@mwaskom
Copy link

mwaskom commented Mar 10, 2020

FWIW I don't think these need to or should cycle. But 100% of the time I use this function, I set them to a gray color. So, a vote in favor of an (eventual) move away from "C0" as the default.

@timhoffm
Copy link
Member

Closing as this is tracked in #14331. But there‘s a clear tendency that we want a single color, possibly the grid color.

@01baftb
Copy link
Author

01baftb commented Mar 10, 2020

I agree with @story645 that axhline should cycle.

Below is an example use case where color cycling would be helpful. I would want the color of the axhline to match the corresponding ax.plot() line. In the example below, you can see that the color on the axhline doesn't cycle. This burdens the end user to keep track of the current plot color and manually specify the color for the axhline. Alternately there are cases where user wouldn't want the color to cycle, in such case they can specify the color and keep it constant every time as @mwaskom mentioned where he sets them to a gray color. Thus, if axhline color is cycled for default, it is much easier to keep it constant color when desired, as opposed to if it didn't cycle by default, the end user is burdened to keep track of the color cycling in each loop.

plot

%matplotlib widget
import matplotlib.pyplot as plt
import numpy as np


fig, ax = plt.subplots()

decay = lambda C, K, x: C *(1-np.exp(-1*K*x))

x = np.linspace(0,10,10)
twin_ax = ax.twinx()

for n in range(5):
    C = n**2 + 5
    K = 0.75
    # Plot the exponential delcay on left y axis 
    ax.plot(x,decay(C,K,x), marker='o', linestyle='--', label=C)
    # Plot the limit of decay on right y axis 
    twin_ax.axhline(y=C) 
    
ax.legend()
plt.show()

# fix x limits
ax.set_xlim(0,10.5)
# Align the both left and right y axis 
twin_ax.set_ylim(ax.get_ylim())

ax.set_xlabel('X')
ax.set_ylabel('Measured values')
twin_ax.set_ylabel('Theoritical limit')

@mwaskom
Copy link

mwaskom commented Mar 10, 2020

FWIW all you need to do here is

   ...

    line, = ax.plot(x,decay(C,K,x), marker='o', linestyle='--', label=C)
    # Plot the limit of decay on right y axis 
    twin_ax.axhline(y=C, color=line.get_color()) 

   ...

@QuLogic
Copy link
Member

QuLogic commented Mar 10, 2020

Note, if axhline cycled, it would be part of the cycle, and would not get you what you want here. That is, it would not cycle independently of regular plots, so the colours would not match.

Instead, the bottom curve would be blue, the line at 5 would be orange, the next curve would be green, the line at 6 would be red, etc.

@01baftb
Copy link
Author

01baftb commented Mar 10, 2020

FWIW all you need to do here is

   ...

    line, = ax.plot(x,decay(C,K,x), marker='o', linestyle='--', label=C)
    # Plot the limit of decay on right y axis 
    twin_ax.axhline(y=C, color=line.get_color()) 

   ...

Yes, I agree. I will use this approach in my current end use case.

However, I created this issue as an opinion that might be looked into. I understand the end implementations might be complicated or may not make sense as a standard approach, in which case my opinion can be ignored.

Note, if axhline cycled, it would be part of the cycle, and would not get you what you want here. That is, it would not cycle independently of regular plots, so the colours would not match.

Instead, the bottom curve would be blue, the line at 5 would be orange, the next curve would be green, the line at 6 would be red, etc.

Wouldn't axhline have its own instance of the cycler as @WeatherGod previously mentioned? For example, updating my example code to include both ax.scatter() and ax.plot(), you can see each of them have their own instance of the cycler, thus the colors match up.

    # Plot the exponential delcay on left y axis 
    ax.scatter(x,decay(C,K,x), marker='o', )
    ax.plot(x,decay(C,K,x), marker='', linestyle='--', label=C)
    # Plot the limit of decay on right y axis 
    twin_ax.axhline(y=C) 

@QuLogic
Copy link
Member

QuLogic commented Mar 10, 2020

Ah true, I guess that depends on whether it's considered a line like a plot or not. But that discussion can continue on #14331.

@jklymak
Copy link
Member

jklymak commented Mar 10, 2020

Wouldn't axhline have its own instance of the cycler as @WeatherGod previously mentioned?

.... but that is still fragile in that you must keep the cycler instances in sync as you do the plotting. Even worse than having just one color would be to have the wrong color.

Is there any precedence for using two cyclers? I'm not aware of us doing that anywhere.

@WeatherGod
Copy link
Member

WeatherGod commented Mar 10, 2020 via email

@tacaswell
Copy link
Member

This is yet another case where having aspect of being both an application and a library causes us pain on the edges. On the application-like side of things plt.plot(y1); plt.plot(y2) should produce lines of two different colors because requiring people working at a prompt to specify the color in every case would be deeply annoying. That was the winning argument around v2 when we moved many thing to follow the color cycles (see #6291). We do this with a bunch of effectively global state that lets us "do the right thing" in many cases.

On the other hand, when used as a library (which you are doing @01baftb ) to write your plotting tools, this global state can start to fight back / be more trouble than it's worth. If we were to start to letting axhline and axvline to color cycle we would have to make a decision about which cycle to pull from (one of the two existing ones or new ones? If new ones, then should we give every other plotting method it's own cycle? That would be an API change and likely break someone who is expecting two things to cycle together that no longer are. Do we want to add a flag to every plotting routine to pick which cycle it participates in? Right now the cycles are scoped to the Axes, what if we want figure level cycles?). As both @WeatherGod and @jklymak say this gets complicated and brittle quickly. As a side note, I no longer remember and don't see any discussion in that PR over which cycle scatter was going to pull from.

In these cases where you have many things that you want to cycle together, I suggest explicitly using a cycler

for sty in my_cycler:
    ax.plot(..., **sty)
    ax.axhline(...., **sty)

and so on. See https://matplotlib.org/cycler/ for more details.


in which case my opinion can be ignored.

We do not want to ignore your opinion, feed back from users is very important. However, everything is a trade off, making one use case easier almost always another harder and in a worst case impossible.

@ImportanceOfBeingErnest
Copy link
Member

Funnily, bar and scatter use the same cycler, which is ax._get_patches_for_fill.prop_cycler.
plot has a different one (ax._get_lines.prop_cycler).

@timhoffm
Copy link
Member

timhoffm commented Mar 11, 2020

Funnily, bar and scatter use the same cycler

Probably nobody ever noticed. Who would put a scatter plot on top of a bar plot?

@tacaswell
Copy link
Member

Who would put a scatter plot on top of a bar plot?

Our user base is both very diverse and very clever, I'm sure someone has a good reason for this (bars for averages, scatter for outliers like a roll-your-own boxplot?).

@mwaskom
Copy link

mwaskom commented Mar 16, 2020

Who would put a scatter plot on top of a bar plot?

This is probably the most common style of plot in Nature journals these days, which has mandated a “show the observations” rule.

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

No branches or pull requests

9 participants