Получить состояние цветового цикла matplotlib

89

Можно ли запросить текущее состояние цветового цикла matplotlib? Другими словами, есть ли функция, get_cycle_stateкоторая будет вести себя следующим образом?

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

Где я ожидаю, что состояние будет индексом следующего цвета, который будет использоваться в графике. В качестве альтернативы, если бы он вернул следующий цвет («r» для цикла по умолчанию в приведенном выше примере), это тоже было бы хорошо.

Мваском
источник
Это можно сделать, даже не меняя текущего состояния, см. Ответ ниже
sancho.s ReinstateMonicaCellio

Ответы:

109

Доступ к итератору цветового цикла

Не существует "ориентированного на пользователя" (также известного как "общедоступный") метода для доступа к базовому итератору, но вы можете получить к нему доступ через "частные" (по соглашению) методы. Однако вы не можете получить состояние, iteratorне изменив его.

Установка цветового цикла

Небольшое отступление: вы можете установить цикл цвет / свойство различными способами (например, ax.set_color_cycleв версиях <1.5 или ax.set_prop_cyclerв> = 1.5). Взгляните на пример здесь для версии 1.5 или выше или на предыдущий стиль здесь .

Доступ к базовому итератору

Однако, хотя нет общедоступного метода для доступа к итерируемому объекту, вы можете получить к нему доступ для данного объекта осей ( ax) через _get_linesэкземпляр вспомогательного класса. ax._get_lines- это прикосновение, название которого сбивает с толку, но это закулисный механизм, который позволяет plotкоманде обрабатывать все странные и разнообразные способы plotвызова. Среди прочего, это то, что отслеживает, какие цвета назначать автоматически. Точно так же ax._get_patches_for_fillможно контролировать циклическое переключение цветов заливки по умолчанию и свойств патча.

В любом случае, итеративный цветовой цикл предназначен ax._get_lines.color_cycleдля линий и ax._get_patches_for_fill.color_cycleдля участков. В matplotlib> = 1.5 это было изменено, чтобы использовать cyclerбиблиотеку , и итерация вызывается prop_cyclerвместо color_cycleи дает dictсвойство of вместо только цвета.

В общем, вы бы сделали что-то вроде:

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

Однако этот объект является «голым» iterator. Мы можем легко получить следующий элемент (например next_color = next(color_cycle), но это означает , что следующий цвет после этого является то , что будет построено. По дизайну, нет никакого способа , чтобы получить текущее состояние итератора , не изменяя его.

v1.5Было бы неплохо получить используемый cyclerобъект уровня In или выше, поскольку мы можем вывести его текущее состояние. Однако сам cyclerобъект нигде недоступен (публично или приватно). Вместо этого доступен только itertools.cycleэкземпляр, созданный из cyclerобъекта. В любом случае, нет никакого способа добраться до базового состояния циклера цвета / свойства.

Вместо этого сопоставьте цвет ранее нанесенного элемента

В вашем случае это похоже на то, что вы хотите сопоставить цвет с тем, что только что было нанесено. Вместо того, чтобы пытаться определить, каким будет цвет / свойство, установите цвет / и т. Д. Вашего нового элемента на основе свойств того, что отображается на графике.

Например, в описанном вами случае я бы сделал что-то вроде этого:

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

введите описание изображения здесь

Это не единственный способ, но в данном случае он чище, чем попытка получить цвет нанесенной линии заранее.

Джо Кингтон
источник
Я пишу несколько высокоуровневых функций для автоматизации общих задач построения графиков в моей области работы. Часто это влечет за собой рисование нескольких объектов matplotlib, и я хотел бы, чтобы несколько объектов имели общий цвет в цветовом цикле, поэтому мне не всегда нужно указывать аргумент цвета, если я чувствую себя ленивым.
mwaskom
12
Если вы просто хотите подобрать цвет определенного объекта, вы всегда можете выбрать его цвет. например, line, = ax.plot(x, y)а затем используйте, line.get_color()чтобы получить цвет ранее нанесенной линии.
Джо Кингтон
3
Вроде ax._get_lines.color_cycleв 1.5 уже нет?
endolith
6
Я думаю, что (почти) эквивалент - это итерация, у ax._get_lines.prop_cyclerвас может быть такая конструкция, if 'color' in ax._get_lines._prop_keys:за которой next(ax._get_lines.prop_cycler)['color']следует выполнение эквивалента того, что предлагается color_cycleв ответе, я считаю. Я не уверен, можно ли получить итерацию только по цветам, мне нужно изучить больше.
Джей Ричард Снейп
1
@endolith Конечно (и спасибо), но я просто почувствовал, что было бы лучше сидеть в уже очень хорошем и принятом ответе Джо. Если он не дойдет до понедельника - я поставлю правильный ответ вместо ужасного «ответа в комментариях»
Дж. Ричард Снейп,
25

Вот способ, который работает в версии 1.5, который, мы надеемся, будет перспективным, поскольку он не полагается на методы, добавленные с подчеркиванием:

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

Это даст вам список цветов, определенных для текущего стиля.

Майк ДеПалатис
источник
1
Работает в 2.0.2.
KevinG 08
16

Примечание: в последних версиях matplotlib (> = 1.5) _get_linesбыли внесены изменения. Теперь вам нужно использовать next(ax._get_lines.prop_cycler)['color']в Python 2 или 3 (или ax._get_lines.prop_cycler.next()['color']в Python 2 ), чтобы получить следующий цвет из цветового цикла.

По возможности используйте более прямой подход, показанный в нижней части ответа @joe-kington. Поскольку _get_linesон не ориентирован на API, он может снова измениться не обратно совместимым образом в будущем.

И я
источник
Как избежать того, чтобы запрос следующего элемента циклического устройства цвета изменил его состояние?
kazemakase
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

Вот функция itertools, которую использует matplotlib itertools.cycle

Изменить: Спасибо за комментарий, похоже, что итератор скопировать невозможно. Идея заключалась бы в том, чтобы сбросить полный цикл и отслеживать, какое значение вы используете, позвольте мне вернуться к этому.

Edit2: Хорошо, это даст вам следующий цвет и создаст новый итератор, который ведет себя так, как если бы следующий не был вызван. Это не сохраняет порядок окраски, просто следующее значение цвета, я оставляю это вам.

Это дает следующий результат, обратите внимание, что крутизна графика соответствует индексу, например, первый 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

Повернуть цвет

Арынак
источник
Благодарность! Чтобы уточнить, это не похоже на то, что возвращает копию, а скорее ссылку на фактический цикл. Итак, вызов rainbow.next () на самом деле изменит то, как будет выглядеть следующий график.
mwaskom
5

Я просто хочу добавить к тому, что @Andi сказал выше. Поскольку color_cycleон устарел в matplotlib 1.5, вы должны использовать prop_cycler, однако, решение Andi ( ax._get_lines.prop_cycler.next()['color']) вернуло мне эту ошибку:

AttributeError: объект 'itertools.cycle' не имеет атрибута '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)самом деле изменял состояние цветового циклера?
kazemakase
На самом деле это то, что я пытался сделать - изменить состояние файла 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']
Freidrichen
источник
0

Самый простой способ, который я мог найти, не выполняя весь цикл через циклер, - это ax1.lines[-1].get_color().

Акима AKBA5439
источник
-1

В matplotlib версии 2.2.3 для свойства есть get_next_color()метод _get_lines:

import from matplotlib import pyplot as plt
fig, ax = plt.subplots()
next_color = ax._get_lines.get_next_color()

get_next_color() возвращает цветовую строку HTML и продвигает итератор цветового цикла.

Jkokorian
источник
-1

Как получить доступ к циклу цвета (и полного стиля)?

Текущее состояние сохраняется в 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']

чтобы получить текущий цвет без изменения состояния цикла .


TL; DR

Где хранится цикл цвета (и полного стиля)?

Цикл стилей хранится в двух разных местах, одно по умолчанию , а другое для текущих осей (при условии, что import matplotlib.pyplot as pltи axявляется обработчиком оси):

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

Можно даже изменить cyclerиспользование для данного axes, после определения этого custom_prop_cyclerс помощью

ax.set_prop_cycle(custom_prop_cycler)

Но не существует встроенных методов для отображения «базового списка» для универсального itertools.cycle, и в частности для ax._get_lines.prop_cycler.

sancho.s ReinstateMonicaCellio
источник