获取 matplotlib 颜色循环状态

105

是否可以查询matplotlib颜色循环的当前状态?换句话说,是否有一个函数get_cycle_state可以像下面这样工作?

>>> plot(x1, y1)
>>> plot(x2, y2)
>>> state = get_cycle_state()
>>> print state
2

我希望状态可以成为绘制图形中下一个要使用的颜色的索引。或者,如果它返回下一个颜色(例如上面示例中的默认循环中的“r”),那也可以。


这可以实现,甚至不需要改变当前状态,详见下面的答案 - sancho.s ReinstateMonicaCellio
10个回答

129

访问颜色循环迭代器

没有“公共”方法可以访问底层迭代器,但可以通过“私有”(惯例上)方法访问它。但是,你无法在改变迭代器的情况下获取其状态。

设置颜色循环

快速说一下:你可以以多种方式设置颜色/属性循环(例如,在版本<1.5中使用ax.set_color_cycle或在版本>=1.5中使用ax.set_prop_cycler)。请查看此处示例(版本1.5或更高版本)此处旧版样式

访问底层迭代器

然而,虽然没有公共方法可以访问可迭代对象,但你可以通过_get_lines辅助类实例来为给定的轴对象(ax)访问它。虽然ax._get_lines的命名有点令人困惑,但它是幕后机制,可以处理所有plot命令可能被调用的奇怪和多变的方式。它是跟踪自动分配颜色的关键之一。同样,ax._get_patches_for_fill用于控制默认填充颜色和 path 属性的循环。

总之,颜色循环可迭代对象是ax._get_lines.color_cycle(线条)和ax._get_patches_for_fill.color_cycle(路径)。在 matplotlib >=1.5 中,这已更改为使用cycler库,可迭代对象称为prop_cycler而不是color_cycle,并且产生一个属性字典而不仅仅是颜色。

总之,你可以这样做:

import matplotlib.pyplot as plt

fig, ax = plt.subplots()
color_cycle = ax._get_lines.color_cycle
# or ax._get_lines.prop_cycler on version >= 1.5
# Note that prop_cycler cycles over dicts, so you'll want next(cycle)['color']

无法查看迭代器的状态

然而,这个对象是一个“裸”的iterator。我们可以轻松地获取下一个项目(例如,next_color = next(color_cycle)),但这意味着接下来的颜色之后将被绘制。按设计,没有办法在不改变迭代器的情况下获取其当前状态。

v1.5或更高版本中,获取使用的cycler对象会比较好,因为我们可以推断出它的当前状态。但是,cycler对象本身在任何地方都无法访问(公开或私有)。相反,只能访问从cycler对象创建的itertools.cycle实例。无论如何,都无法访问颜色/属性循环器的基础状态。

匹配先前绘制的项目的颜色

在您的情况下,听起来您想要匹配刚刚绘制的某个东西的颜色。与其试图确定颜色/属性将是什么,不如基于绘制内容的属性设置新项目的颜色/等等。

例如,在您描述的情况下,我会这样做:

import matplotlib.pyplot as plt
import numpy as np

def custom_plot(x, y, **kwargs):
    ax = kwargs.pop('ax', plt.gca())
    base_line, = ax.plot(x, y, **kwargs)
    ax.fill_between(x, 0.9*y, 1.1*y, facecolor=base_line.get_color(), alpha=0.5)

x = np.linspace(0, 1, 10)
custom_plot(x, x)
custom_plot(x, 2*x)
custom_plot(x, -x, color='yellow', lw=3)

plt.show()

这不是唯一的方法,但在这种情况下比事先获取绘制线条的颜色更清晰。

输入图像描述


13
如果你只是想匹配特定物体的颜色,你可以获取它的颜色。例如line, = ax.plot(x, y),然后使用line.get_color()来获取之前绘制线条的颜色。 - Joe Kington
3
在1.5版本中似乎ax._get_lines.color_cycle不存在了? - endolith
6
我认为(近似)相当的内容是一个可迭代对象ax._get_lines.prop_cycler,你可以使用类似以下结构来构建:如果 ax._get_lines._prop_keys 中包含 'color',那么接下来使用 next(ax._get_lines.prop_cycler)['color'] 来执行与答案中所建议的 color_cycle 相同的操作。我不确定是否可以仅获取颜色的可迭代对象,需要进一步探索。 - J Richard Snape
1
@endolith 当然没问题(谢谢)- 但我觉得它最好放在Joe已经非常好并且被接受的答案中。如果他周一之前没有时间把它放进去,我会在那里发布一个适当的答案,而不是可怕的“评论中的答案”。 - J Richard Snape
@JoeKington,请你更新Matplotlib 3.8版本,将它们移除掉。 - undefined
显示剩余6条评论

43

以下是一种在1.5中有效的方法,希望它是兼容未来的,因为它不依赖于前缀带有下划线的方法:

colors = plt.rcParams["axes.prop_cycle"].by_key()["color"]

这将为您提供按照当前样式定义的颜色列表。


1
适用于2.0.2版本。 - KevinG
2
这正是我需要的,谢谢。适用于matplotlib 3.4.3。 - amzon-ex
1
适用于matplotlib 3.5.2。 - Timbus Calin

19
注意:在最新版本的matplotlib(>= 1.5)中,_get_lines已经更改。您现在需要在Python 2或3中使用next(ax._get_lines.prop_cycler)['color'](或Python 2中的ax._get_lines.prop_cycler.next()['color']),以从颜色循环中获取下一个颜色。
在可能的情况下,请使用@joe-kington回答底部所示的更直接的方法。由于_get_lines不是API面向的,因此它未来可能会再次以不向后兼容的方式更改。

你如何避免查询颜色循环器的下一个元素会改变循环器的状态? - MB-F

6
当然,这样做可以解决问题。
#rainbow

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0,2*np.pi)
ax= plt.subplot(1,1,1)
ax.plot(np.sin(x))
ax.plot(np.cos(x))

rainbow = ax._get_lines.color_cycle
print rainbow
for i, color in enumerate(rainbow):
    if i<10:
        print color,

给予:

<itertools.cycle object at 0x034CB288>
r c m y k b g r c m

这是matplotlib使用的itertools函数:itertools.cycle 编辑:感谢评论,似乎不可能复制迭代器。一个想法是将一个完整的周期转储出来,并跟踪您正在使用的值,让我回到那里。
编辑2:好了,这将为您提供下一个颜色并创建一个新的迭代器,该迭代器的行为就像没有调用next一样。这不保留着色的顺序,只保留下一个颜色值,具体顺序请自行处理。
这将给出以下输出,请注意,图中的陡峭程度对应于索引,例如第一个g是最底部的图形等等。
#rainbow

import matplotlib.pyplot as plt
import numpy as np
import collections
import itertools

x = np.linspace(0,2*np.pi)
ax= plt.subplot(1,1,1)


def create_rainbow():
    rainbow = [ax._get_lines.color_cycle.next()]
    while True:
        nextval = ax._get_lines.color_cycle.next()
        if nextval not in rainbow:
            rainbow.append(nextval)
        else:
            return rainbow

def next_color(axis_handle=ax):
    rainbow = create_rainbow()
    double_rainbow = collections.deque(rainbow)
    nextval = ax._get_lines.color_cycle.next()
    double_rainbow.rotate(-1)
    return nextval, itertools.cycle(double_rainbow)


for i in range(1,10):
    nextval, ax._get_lines.color_cycle = next_color(ax)
    print "Next color is: ", nextval
    ax.plot(i*(x))


plt.savefig("SO_rotate_color.png")
plt.show()

控制台

Next color is:  g
Next color is:  c
Next color is:  y
Next color is:  b
Next color is:  r
Next color is:  m
Next color is:  k
Next color is:  g
Next color is:  c

Rotate color


谢谢!只是为了澄清,它似乎不会返回一个副本,而是返回对实际循环的引用。因此,调用 rainbow.next() 实际上会改变下一个图形的样子。 - mwaskom

5
我想在@Andi上面说的话上补充一点。由于matplotlib 1.5中color_cycle已被弃用,因此您必须使用prop_cycler,但是Andi的解决方法(ax._get_lines.prop_cycler.next()['color'])对我来说返回了以下错误:

AttributeError: 'itertools.cycle' object has no attribute 'next'

对我有效的代码是:next(ax._get_lines.prop_cycler),这实际上与@joe-kington的原始响应相差不远。
就个人而言,在制作twinx()轴时遇到了这个问题,它会重置颜色循环器。因为我正在使用style.use('ggplot'),所以需要一种使颜色正确循环的方法。可能有更简单/更好的方法来做到这一点,所以请随时纠正我。

请使用 next(ax._get_lines.prop_cycler) 作为一种可能的替代方案。 - UmNyobe
@Carson 你如何避免调用 next(ax._get_lines.prop_cycler) 实际上改变颜色循环器的状态? - MB-F
这实际上是我试图做的事情 - 改变prop_cycler的状态。如果您阅读此问题的已接受答案,您将看到没有办法访问prop_cycler的状态,因为它是可迭代的,必须改变其状态。 - Carson
你指出的错误是Python 2 / Python 3之间的差异。感谢您指出这一点,我已相应地编辑了我的答案。 - Andi

1

由于matplotlib使用了itertools.cycle,我们实际上可以浏览整个颜色循环,然后将迭代器恢复到其先前的状态:

def list_from_cycle(cycle):
    first = next(cycle)
    result = [first]
    for current in cycle:
        if current == first:
            break
        result.append(current)

    # Reset iterator state:
    for current in cycle:
        if current == result[-1]:
            break
    return result

这应该返回列表而不改变迭代器的状态。
在matplotlib >= 1.5中使用它:
>>> list_from_cycle(ax._get_lines.prop_cycler)
[{'color': 'r'}, {'color': 'g'}, {'color': 'b'}]

或者使用 matplotlib < 1.5:
>>> list_from_cycle(ax._get_lines.color_cycle)
['r', 'g', 'b']

1
我能找到的最简单的方法是不必遍历整个循环器,只需使用ax1.lines[-1].get_color()即可。

0

如何访问颜色(和完整样式)循环?

当前状态存储在ax._get_lines.prop_cycler中。 没有内置方法来公开通用的itertools.cycle的“基本列表”,特别是对于ax._get_lines.prop_cycler(见下文)。

我在这里发布了一些函数以获取itertools.cycle上的信息。 然后可以使用其中之一

style_cycle = ax._get_lines.prop_cycler
curr_style = get_cycle_state(style_cycle)  # <-- my (non-builtin) function
curr_color = curr_style['color']

获取当前颜色而不改变循环的状态


简而言之

颜色(和完整样式)循环存储在哪里?

样式循环存储在两个不同的位置,一个是用于默认,另一个是用于当前轴(假设import matplotlib.pyplot as pltax是轴处理程序):

default_prop_cycler = plt.rcParams['axes.prop_cycle']
current_prop_cycle = ax._get_lines.prop_cycler

请注意这些具有不同的类。 默认设置为“基本周期设置”,它不知道任何轴的当前状态,而当前设置则知道要遵循的周期及其当前状态:

print('type(default_prop_cycler) =', type(default_prop_cycler))
print('type(current_prop_cycle) =', type(current_prop_cycle))

[]: type(default_prop_cycler) = <class 'cycler.Cycler'>
[]: type(current_prop_cycle) = <class 'itertools.cycle'>

默认循环可能有几个键(属性)要循环,而且只能获取颜色:

print('default_prop_cycler.keys =', default_prop_cycler.keys)
default_prop_cycler2 = plt.rcParams['axes.prop_cycle'].by_key()
print(default_prop_cycler2)
print('colors =', default_prop_cycler2['color'])

[]: default_prop_cycler.keys = {'color', 'linestyle'}
[]: {'color': ['r', 'g', 'b', 'y'], 'linestyle': ['-', '--', ':', '-.']}
[]: colors = ['r', 'g', 'b', 'y']

甚至可以在定义了custom_prop_cycler之后,更改给定axes使用的cycler

ax.set_prop_cycle(custom_prop_cycler)

但是没有内置的方法来公开泛型 itertools.cycle 的“基础列表”,特别是针对 ax._get_lines.prop_cycler


-1

最小工作示例

我已经遇到过这个问题很多次了。 这是Andis answer的最小工作示例。 输入图像描述

代码

import numpy as np
import matplotlib.pyplot as plt


xs = np.arange(10)


fig, ax = plt.subplots()

for ii in range(3):
    color = next(ax._get_lines.prop_cycler)['color']
    lbl = 'line {:d}, color {:}'.format(ii, color)
    ys = np.random.rand(len(xs))
    ax.plot(xs, ys, color=color, label=lbl)
ax.legend()

-1
在matplotlib 2.2.3版本中,_get_lines属性上有一个get_next_color()方法:
import from matplotlib import pyplot as plt
fig, ax = plt.subplots()
next_color = ax._get_lines.get_next_color()

get_next_color() 返回一个 HTML 颜色字符串,并推进颜色循环迭代器。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接