Отримати стан кольорового циклу matplotlib


89

Чи можна запитати поточний стан колірного циклу matplotlib? Іншими словами, чи існує функція, get_cycle_stateяка буде поводитись наступним чином?

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

Де я очікую, що стан буде індексом наступного кольору, який буде використаний у графіку. Як варіант, якщо він повертає наступний колір ("r" для циклу за замовчуванням у прикладі вище), це теж буде добре.


Це можна зробити навіть без зміни поточного стану, див. Відповідь нижче
sancho.s ReinstateMonicaCellio

Відповіді:


107

Доступ до ітератора колірного циклу

Не існує методу "орієнтованого на користувача" (він же "загальнодоступний") для доступу до базового ітератора, але ви можете отримати до нього доступ за допомогою "приватних" (за домовленістю) методів. Однак ви не зможете отримати стан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і дає adict властивості замість лише кольору.

Загалом, ви зробите щось на зразок:

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об'єкт , який використовується, як ми могли б визначити його поточний стан. Однак сам 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?
ендоліт

6
Я думаю, що (майже) еквівалент є ітерабельним, ax._get_lines.prop_cyclerви можете створити щось подібне, if 'color' in ax._get_lines._prop_keys:за яким next(ax._get_lines.prop_cycler)['color']слід виконати еквівалент того, що пропонується color_cycleу відповіді, я вважаю. Я не впевнений, чи можете ви отримати ітерацію лише кольорів, мені потрібно було б вивчити більше.
J Richard Snape

1
@endolith Звичайно (і спасибі) - але я просто відчув, що було б краще сидіти у вже дуже хорошій і прийнятій відповіді Джо. Якщо він не обійдеться докласти його до понеділка - я наклею там правильну відповідь замість страшних "відповідей у ​​коментарях"
Дж. Річард Снейп,

25

Ось спосіб, який працює у версії 1.5, і, сподіваємось, буде надійним у майбутньому, оскільки він не покладається на методи, додані підкресленнями:

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

Це дасть вам список кольорів, визначених для поточного стилю.


1
Працює в 2.0.2.
KevinG

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_linesAPI не стоїть перед ним, він може змінитися в майбутньому, не сумісно із зворотною стороною.


Як уникнути того, щоб запит щодо наступного елемента кольорового циклера міняв стан циклера?
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без зміни його стану, оскільки це ітерація.
Карсон

Помилка, на яку ви вказуєте, полягає в різниці між Python 2 / Python 3. Дякую, що вказали на це, я відповідно відредагував свою відповідь.
Анді

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

0

Найпростіший спосіб це можливо , я міг би знайти , не роблячи весь цикл через велосипедист знаходиться ax1.lines[-1].get_color().


-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-рядок кольору та просуває ітератор циклу кольорів.


-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.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.