Dapatkan status siklus warna matplotlib

89

Apakah mungkin untuk menanyakan status saat ini dari siklus warna matplotlib? Dengan kata lain, apakah ada fungsi get_cycle_stateyang akan berperilaku seperti berikut?

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

Di mana saya mengharapkan status menjadi indeks warna berikutnya yang akan digunakan dalam plot. Atau, jika mengembalikan warna berikutnya ("r" untuk siklus default dalam contoh di atas), itu juga bagus.

mwaskom.dll
sumber
Hal ini dapat dilakukan, bahkan tanpa mengubah keadaan saat ini, lihat jawaban di bawah
sancho.s ReinstateMonicaCellio

Jawaban:

109

Mengakses iterator siklus warna

Tidak ada metode "menghadap pengguna" (alias "publik") untuk mengakses iterator yang mendasarinya, tetapi Anda dapat mengaksesnya melalui metode "pribadi" (menurut konvensi). Namun, Anda tidak bisa mendapatkan keadaan iteratortanpa mengubahnya.

Mengatur siklus warna

Singkatnya: Anda dapat menyetel siklus warna / properti dalam berbagai cara (misalnya ax.set_color_cycledalam versi <1.5 atau ax.set_prop_cyclerdi> = 1.5). Lihat contoh di sini untuk versi 1.5 atau yang lebih baru , atau gaya sebelumnya di sini .

Mengakses iterator yang mendasari

Namun, meskipun tidak ada metode publik untuk mengakses iterable, Anda bisa mengaksesnya untuk objek sumbu tertentu ( ax) melalui _get_linesinstance kelas helper. ax._get_linesadalah sentuhan dengan nama yang membingungkan, tetapi mesin di belakang layarlah yang memungkinkan plotperintah untuk memproses semua cara aneh dan beragam yang plotdapat dipanggil. Antara lain, itulah yang melacak warna apa yang akan ditetapkan secara otomatis. Demikian pula, ada ax._get_patches_for_filluntuk mengontrol perputaran melalui warna isian default dan properti tambalan.

Bagaimanapun, siklus warna yang dapat diulang ax._get_lines.color_cycleuntuk garis dan ax._get_patches_for_fill.color_cycleuntuk tambalan. Pada matplotlib> = 1.5, ini telah diubah untuk menggunakan cyclerperpustakaan , dan iterable dipanggil prop_cyclersebagai ganti color_cycledan menghasilkan dictproperti alih-alih hanya warna.

Secara keseluruhan, Anda akan melakukan sesuatu seperti:

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

Anda tidak dapat melihat status file iterator

Namun, objek ini adalah "telanjang" iterator. Kita bisa dengan mudah mendapatkan item berikutnya (misalnya next_color = next(color_cycle), tapi itu berarti warna berikutnya setelah itu akan diplot. Secara desain, tidak ada cara untuk mendapatkan status iterator saat ini tanpa mengubahnya.

Dalam v1.5atau lebih besar, alangkah baiknya untuk mendapatkan cyclerobjek yang digunakan, karena kita bisa menyimpulkan keadaannya saat ini. Namun, cyclerobjek itu sendiri tidak dapat diakses (secara publik atau pribadi) di mana pun. Sebaliknya, hanya itertools.cycleinstance yang dibuat dari cyclerobjek yang dapat diakses. Apa pun itu, tidak ada cara untuk mencapai status yang mendasari pengendara sepeda warna / properti.

Cocokkan warna item yang sebelumnya diplot

Dalam kasus Anda, sepertinya Anda ingin mencocokkan warna sesuatu yang baru saja diplot. Alih-alih mencoba menentukan warna / propertinya, tetapkan warna / dll dari item baru Anda berdasarkan properti dari apa yang diplot.

Misalnya, dalam kasus yang Anda jelaskan, saya akan melakukan sesuatu seperti ini:

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

masukkan deskripsi gambar di sini

Ini bukan satu-satunya cara, tetapi lebih bersih daripada mencoba mendapatkan warna garis yang diplot sebelumnya, dalam kasus ini.

Joe Kington
sumber
Saya menulis beberapa fungsi tingkat yang lebih tinggi untuk mengotomatiskan tugas-tugas penggambaran umum untuk domain pekerjaan saya. Seringkali ini memerlukan menggambar beberapa objek matplotlib, dan saya ingin beberapa objek berbagi warna dalam siklus warna, jadi saya tidak selalu harus memberikan argumen warna jika saya merasa malas.
mwaskom
12
Jika Anda hanya ingin mencocokkan warna objek tertentu, Anda selalu dapat mengambil warnanya. misalnya line, = ax.plot(x, y)dan kemudian gunakan line.get_color()untuk mendapatkan warna dari garis yang telah diplot sebelumnya.
Joe Kington
3
Sepertinya sudah ax._get_lines.color_cycletidak ada lagi di 1,5?
endolith
6
Saya pikir padanan (dekat) adalah iterable ax._get_lines.prop_cyclerAnda dapat memiliki konstruksi seperti if 'color' in ax._get_lines._prop_keys:diikuti oleh next(ax._get_lines.prop_cycler)['color']untuk melakukan padanan dari apa yang disarankan color_cycledalam jawaban, saya percaya. Saya tidak yakin apakah Anda bisa mendapatkan iterable hanya warna, saya perlu menjelajahi lebih banyak.
J Richard Snape
1
@endolith Tentu (dan terima kasih) - tetapi saya hanya merasa akan lebih baik duduk di jawaban Joe yang sudah sangat baik dan diterima. Jika dia tidak sempat memasukkannya pada hari Senin - saya akan menempelkan jawaban yang tepat di sana alih-alih "jawaban-dalam-komentar" yang ditakuti
J Richard Snape
25

Berikut adalah cara yang berhasil di 1.5 yang diharapkan akan terbukti di masa depan karena tidak bergantung pada metode yang diawali dengan garis bawah:

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

Ini akan memberi Anda daftar warna yang ditentukan agar gaya saat ini.

Mike DePalatis
sumber
1
Bekerja di 2.0.2.
KevinG
16

Catatan: Di matplotlib versi terbaru (> = 1.5) _get_linestelah berubah. Anda sekarang perlu untuk menggunakan next(ax._get_lines.prop_cycler)['color']Python 2 atau 3 (atau ax._get_lines.prop_cycler.next()['color']di Python 2 ) untuk mendapatkan warna berikutnya dari siklus warna.

Jika memungkinkan, gunakan pendekatan yang lebih langsung yang ditunjukkan di bagian bawah jawaban @ joe-kington. Karena _get_linestidak menghadap API, ini mungkin berubah lagi dengan cara yang tidak kompatibel ke belakang di masa mendatang.

Dan saya
sumber
Bagaimana Anda menghindari kueri elemen selanjutnya dari color cycler mengubah status cycler?
kazemakase
6

Tentu, ini akan berhasil.

#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,

Memberikan:

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

Berikut adalah fungsi itertools yang digunakan matplotlib itertools.cycle

Sunting: Terima kasih atas komentarnya, tampaknya iterator tidak dapat disalin. Ide akan membuang siklus penuh dan melacak nilai mana yang Anda gunakan, izinkan saya kembali ke sana.

Sunting2: Baiklah, ini akan memberi Anda warna berikutnya dan membuat iterator baru yang berperilaku seolah-olah berikutnya tidak dipanggil. Ini tidak mempertahankan urutan pewarnaan, hanya nilai warna berikutnya, saya serahkan kepada Anda.

Ini memberikan hasil sebagai berikut, perhatikan bahwa kecuraman di plot sesuai dengan indeks, misalnya g pertama adalah grafik paling bawah dan seterusnya.

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

Menghibur

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

Putar warna

arynaq
sumber
Terima kasih! Hanya untuk memperjelas, sepertinya itu tidak mengembalikan salinan, melainkan referensi ke siklus yang sebenarnya. Jadi, memanggil rainbow.next () sebenarnya juga akan mengubah tampilan plot berikutnya.
mwaskom
5

Saya hanya ingin menambahkan apa yang dikatakan @Andi di atas. Karena color_cyclesudah tidak digunakan lagi di matplotlib 1.5, Anda harus menggunakan prop_cycler, namun solusi Andi ( ax._get_lines.prop_cycler.next()['color']) mengembalikan kesalahan ini untuk saya:

AttributeError: objek 'itertools.cycle' tidak memiliki atribut 'berikutnya'

Kode yang berhasil untuk saya adalah next(ax._get_lines.prop_cycler):, yang sebenarnya tidak jauh dari respons asli @ joe-kington.

Secara pribadi, saya mengalami masalah ini saat membuat sumbu twinx (), yang mengatur ulang color cycler. Saya membutuhkan cara untuk membuat siklus warna dengan benar karena saya sedang menggunakannya style.use('ggplot'). Mungkin ada cara yang lebih mudah / lebih baik untuk melakukan ini, jadi silakan koreksi saya.

Carson
sumber
tolong format posting Anda sebagai jawaban , bukan sebagai bahan diskusi (selain itu adalah komentar), poin utama Anda adalah bahwa itu next(ax._get_lines.prop_cycler)adalah alternatif yang memungkinkan.
UmNyobe
@Carson Bagaimana Anda menghindari panggilan yang next(ax._get_lines.prop_cycler)benar-benar mengubah status siklus warna?
kazemakase
Sebenarnya itulah yang saya coba lakukan - mengubah status prop_cycler. Jika Anda membaca jawaban yang diterima untuk pertanyaan ini, Anda akan melihat bahwa tidak ada cara untuk mengakses status prop_cyclertanpa mengubah statusnya karena ini dapat diulang.
Carson
Kesalahan yang Anda tunjukkan adalah perbedaan Python 2 / Python 3. Terima kasih telah menunjukkan hal ini, saya mengedit jawaban saya.
Andi
1

Karena matplotlib menggunakan, itertools.cyclekita sebenarnya dapat melihat seluruh siklus warna dan kemudian mengembalikan iterator ke keadaan sebelumnya:

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

Ini harus mengembalikan daftar tanpa mengubah status iterator.

Gunakan dengan matplotlib> = 1.5:

>>> list_from_cycle(ax._get_lines.prop_cycler)
[{'color': 'r'}, {'color': 'g'}, {'color': 'b'}]

atau dengan matplotlib <1.5:

>>> list_from_cycle(ax._get_lines.color_cycle)
['r', 'g', 'b']
freidrichen.dll
sumber
0

Cara paling sederhana yang bisa saya temukan tanpa melakukan seluruh putaran melalui pengendara sepeda adalah ax1.lines[-1].get_color().

Akim AKBA5439
sumber
-1

Di matplotlib versi 2.2.3 ada get_next_color()metode di _get_linesproperti:

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

get_next_color() mengembalikan string warna html, dan memajukan iterator siklus warna.

jkokorian.dll
sumber
-1

Bagaimana cara mengakses siklus warna (dan gaya lengkap)?

Status saat ini disimpan di ax._get_lines.prop_cycler. Tidak ada metode bawaan untuk mengekspos "daftar dasar" untuk generik itertools.cycle, dan khususnya untuk ax._get_lines.prop_cycler(lihat di bawah).

Saya telah memposting di sini beberapa fungsi untuk mendapatkan info tentang a itertools.cycle. Seseorang kemudian bisa menggunakan

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

untuk mendapatkan warna saat ini tanpa mengubah status siklus .


TL; DR

Di mana siklus warna (dan gaya lengkap) disimpan?

Siklus gaya disimpan di dua tempat berbeda, satu untuk default , dan satu lagi untuk sumbu saat ini (dengan asumsi import matplotlib.pyplot as pltdan axmerupakan pengendali sumbu):

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

Perhatikan bahwa ini memiliki kelas yang berbeda. Standarnya adalah "setelan siklus dasar" dan tidak mengetahui status saat ini untuk sumbu mana pun, sedangkan arus mengetahui siklus yang akan diikuti dan statusnya saat ini:

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

Siklus default mungkin memiliki beberapa kunci (properti) untuk diputar, dan seseorang hanya bisa mendapatkan warna:

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

Seseorang bahkan bisa mengubah cyclerto use for a given axes, setelah mendefinisikannya custom_prop_cycler, dengan

ax.set_prop_cycle(custom_prop_cycler)

Tetapi tidak ada metode built-in untuk mengekspos "daftar dasar" untuk generik itertools.cycle, dan khususnya untuk ax._get_lines.prop_cycler.

sancho.s ReinstateMonicaCellio
sumber