Как создать базовую легенду для разноцветной линии? - PullRequest
2 голосов
/ 04 апреля 2019

В настоящее время я заканчиваю большой проект, и последняя часть - добавить простую легенду к сюжету многоцветной линии. Строка содержит только два разных цвета.

На следующем рисунке показан график при создании. https://drive.google.com/open?id=1VYehsd6ByqdpZcS0u7Uh7OjklNqMgs_H

На следующем изображении показан тот же график с более высоким разрешением. enter image description here

График отображает расстояние между Землей и Марсом во времени. Для месяцев с марта по август линия оранжевая, для других месяцев - синяя. Легенда должна появиться в простом поле в верхнем правом углу графика, на котором должна быть метка для каждого используемого цвета. Что-то вроде , это было бы неплохо.

Данные для графика взяты из огромной матрицы, которую я назвал master_array. Он содержит намного больше информации, которая необходима для некоторых задач, прежде чем показывать сюжет, к которому относится этот вопрос. Для сюжета, с которым я борюсь, важны столбцы 0, 1 и 6, которые содержат дату, расстояние между планетами на соответствующую дату, а в столбце 6 я установил флаг, чтобы определить, принадлежит ли данная точка «март-август». 'установлено или нет (0 для сентября-февраля / "зима", 1 для марта-августа / "лето"). master_array является массивом numpy, dtype - float64. Содержит около 45 тыс. Точек данных.

Похоже:

In [3]: master_array
Out[3]: 
array([[ 1.89301010e+07,  1.23451036e+00, -8.10000000e+00, ...,
         1.00000000e+00,  1.00000000e+00,  1.89300000e+03],
       [ 1.89301020e+07,  1.24314818e+00, -8.50000000e+00, ...,
         2.00000000e+00,  1.00000000e+00,  1.89300000e+03],
       [ 1.89301030e+07,  1.25179997e+00, -9.70000000e+00, ...,
         3.00000000e+00,  1.00000000e+00,  1.89300000e+03],
       ...,
       [ 2.01903100e+07,  1.84236878e+00,  7.90000000e+00, ...,
         1.00000000e+01,  3.00000000e+00,  2.01900000e+03],
       [ 2.01903110e+07,  1.85066892e+00,  5.50000000e+00, ...,
         1.10000000e+01,  3.00000000e+00,  2.01900000e+03],
       [ 2.01903120e+07,  1.85894904e+00,  9.40000000e+00, ...,
         1.20000000e+01,  3.00000000e+00,  2.01900000e+03]])

Это функция для получения графика, который я описал в начале:

def md_plot3(dt64=np.array, md=np.array, swFilter=np.array):
    """ noch nicht fertig """
    y, m, d = dt64.astype(int) // np.c_[[10000, 100, 1]] % np.c_[[10000, 100, 100]]
    dt64 = y.astype('U4').astype('M8') + (m-1).astype('m8[M]') + (d-1).astype('m8[D]')

    cmap = ListedColormap(['b','darkorange'])

    plt.figure('zeitlich-global betrachtet')
    plt.title("Marsdistanz unter Berücksichtigung der Halbjahre der steigenden und sinkenden Temperaturen",
              loc='left', wrap=True)
    plt.xlabel("Zeit in Jahren\n")
    plt.xticks(rotation = 45)
    plt.ylabel("Marsdistanz in AE\n(1 AE = 149.597.870,7 km)")
#    plt.legend(loc='upper right', frameon=True) # worked formerly
    ax=plt.gca()
    plt.style.use('seaborn-whitegrid')

#convert dates to numbers first
    inxval = mdates.date2num(dt64)
    points = np.array([inxval, md]).T.reshape(-1,1,2)
    segments = np.concatenate([points[:-1],points[1:]], axis=1)

    lc = LineCollection(segments, cmap=cmap, linewidth=3)
# set color to s/w values
    lc.set_array(swFilter)
    ax.add_collection(lc)

    loc = mdates.AutoDateLocator()
    ax.xaxis.set_major_locator(loc)
    ax.xaxis.set_major_formatter(mdates.AutoDateFormatter(loc))

    ax.autoscale_view()

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

Я уже попробовал это , в результате чего появилась легенда, показывающая вертикальную цветную полосу и только одну метку, а также оба варианта, описанные в ответах на этот вопрос , потому что это больше похоже на то, что Я стремлюсь, но не могу заставить это работать для моего случая.

Может быть, я должен добавить, что я только начинающий в Python, это мой первый проект, поэтому я не знаком с более глубокой функциональностью matplotlib, что, вероятно, является причиной, почему я не могу настроить упомянутые ответы на заставить его работать в моем случае.


UPDATE

Благодаря помощи пользователя ImportanceOfBeingErnest я сделал несколько улучшений:

import matplotlib.dates as mdates
from matplotlib.collections import LineCollection
from matplotlib.colors import ListedColormap
from matplotlib.lines import Line2D

def md_plot4(dt64=np.array, md=np.array, swFilter=np.array):
    y, m, d = dt64.astype(int) // np.c_[[10000, 100, 1]] % np.c_[[10000, 100, 100]]
    dt64 = y.astype('U4').astype('M8') + (m-1).astype('m8[M]') + (d-1).astype('m8[D]')

    z = np.unique(swFilter)

    cmap = ListedColormap(['b','darkorange'])

    fig = plt.figure('Test')
    plt.title("Test", loc='left', wrap=True)
    plt.xlabel("Zeit in Jahren\n")
    plt.xticks(rotation = 45)
    plt.ylabel("Marsdistanz in AE\n(1 AE = 149.597.870,7 km)")
#    plt.legend(loc='upper right', frameon=True) # worked formerly
    ax=plt.gca()
    plt.style.use('seaborn-whitegrid')
    #plt.style.use('classic')

#convert dates to numbers first
    inxval = mdates.date2num(dt64)
    points = np.array([inxval, md]).T.reshape(-1,1,2)
    segments = np.concatenate([points[:-1],points[1:]], axis=1)

    lc = LineCollection(segments, array=z, cmap=plt.cm.get_cmap(cmap), 
                        linewidth=3)
# set color to s/w values
    lc.set_array(swFilter)
    ax.add_collection(lc)
    fig.colorbar(lc)


    loc = mdates.AutoDateLocator()
    ax.xaxis.set_major_locator(loc)
    ax.xaxis.set_major_formatter(mdates.AutoDateFormatter(loc))

    ax.autoscale_view()

    def make_proxy(zvalue, scalar_mappable, **kwargs):
        color = scalar_mappable.cmap(scalar_mappable.norm(zvalue))
        return Line2D([0, 1], [0, 1], color=color, **kwargs)
    proxies = [make_proxy(item, lc, linewidth=2) for item in z]
    ax.legend(proxies, ['Winter', 'Summer'])


    plt.show()

md_plot4(dt64, md, swFilter)

+ Что в этом хорошего:

Ну, это показывает легенду и показывает правильные цвета в соответствии с ярлыками.

-Что еще нужно оптимизировать:

1) Легенда не находится в рамке, и «линии» легенды мешают нижним слоям сюжета. Как заявил пользователь ImportanceOfBeingErnest, это вызвано использованием plt.style.use('seaborn-whitegrid'). Так что, если есть способ использовать plt.style.use('seaborn-whitegrid') вместе со стилем легенды plt.style.use('classic'), это может помочь. 2) Большим вопросом является цветовая полоса. Я добавил строку fig.colorbar(lc) к исходному коду, чтобы добиться того, что искал, согласно этого ответа .

Итак, я попробовал некоторые другие изменения:

Я использовал plt.style.use('classic'), чтобы получить легенду так, как мне нужно, но это стоило мне хорошего стиля plt.style.use('seaborn-whitegrid'), как упоминалось ранее. Более того, я отключил добавленную ранее строку colorbar в соответствии с упомянутым ответом .

Вот что я получил:

more

import matplotlib.dates as mdates
from matplotlib.collections import LineCollection
from matplotlib.colors import ListedColormap
from matplotlib.lines import Line2D

def md_plot4(dt64=np.array, md=np.array, swFilter=np.array):
    y, m, d = dt64.astype(int) // np.c_[[10000, 100, 1]] % np.c_[[10000, 100, 100]]
    dt64 = y.astype('U4').astype('M8') + (m-1).astype('m8[M]') + (d-1).astype('m8[D]')

    z = np.unique(swFilter)

    cmap = ListedColormap(['b','darkorange'])

    #fig =
    plt.figure('Test')
    plt.title("Test", loc='left', wrap=True)
    plt.xlabel("Zeit in Jahren\n")
    plt.xticks(rotation = 45)
    plt.ylabel("Marsdistanz in AE\n(1 AE = 149.597.870,7 km)")
#    plt.legend(loc='upper right', frameon=True) # worked formerly
    ax=plt.gca()
    #plt.style.use('seaborn-whitegrid')
    plt.style.use('classic')

#convert dates to numbers first
    inxval = mdates.date2num(dt64)
    points = np.array([inxval, md]).T.reshape(-1,1,2)
    segments = np.concatenate([points[:-1],points[1:]], axis=1)

    lc = LineCollection(segments, array=z, cmap=plt.cm.get_cmap(cmap), 
                        linewidth=3)
# set color to s/w values
    lc.set_array(swFilter)
    ax.add_collection(lc)
    #fig.colorbar(lc)


    loc = mdates.AutoDateLocator()
    ax.xaxis.set_major_locator(loc)
    ax.xaxis.set_major_formatter(mdates.AutoDateFormatter(loc))

    ax.autoscale_view()

    def make_proxy(zvalue, scalar_mappable, **kwargs):
        color = scalar_mappable.cmap(scalar_mappable.norm(zvalue))
        return Line2D([0, 1], [0, 1], color=color, **kwargs)
    proxies = [make_proxy(item, lc, linewidth=2) for item in z]
    ax.legend(proxies, ['Winter', 'Summer'])


    plt.show()

md_plot4(dt64, md, swFilter)

+ Что в этом хорошего:

Показывает легенду так, как мне нужно.

Цветная полоса больше не отображается.

-Что стоит оптимизировать:

Сюжет больше не разноцветный.

Ни одна из легенд не существует.

Стиль classic - это не то, что я искал, как я объяснял ранее ...


Так что, если у кого-то есть хороший совет, пожалуйста, дайте мне знать!

Я использую numpy версию 1.16.2 и matplotlib версию 3.0.3

Ответы [ 2 ]

0 голосов
/ 05 апреля 2019

Итак, вот ответ, как создать базовую легенду для многоцветной линии, содержащей несколько меток для каждого используемого цвета и не отображая цветную полосу рядом с графиком (стандартная цветовая полоса, внутри легенды ничего нет; см. Обновление исходного вопроса длябольше информации о проблемах):

Благодаря большому количеству полезных комментариев я решил добавить норму в LineCollection(), чтобы избежать удаления одноцветной линии при удалении цветовой шкалы путем отключения fig.colorbar() (также см. this ) Дополнительный аргумент (в данном случае «норма») для добавления был norm=plt.Normalize(z.min(), z.max()), где z - массив, содержащий информацию, отвечающую за различные цвета сегментов.Обратите внимание, что z должен содержать только один элемент для каждого цвета.Вот почему я обернул свой массив swFilter, состоящий из одного флага на точку данных, в np.unique().

Чтобы получить правильную легенду внутри поля, не касающегося plt.style.use(), мне просто нужно было добавить правильные аргументы в ax.legend().В моем случае простой frameon=True сделал работу.

Результат следующий: plot of multicolored line showing no colorbar but a boxed legend

Вот код:

import matplotlib.dates as mdates
from matplotlib.collections import LineCollection
from matplotlib.colors import ListedColormap
from matplotlib.lines import Line2D

def md_plot4(dt64=np.array, md=np.array, swFilter=np.array):
    y, m, d = dt64.astype(int) // np.c_[[10000, 100, 1]] % np.c_[[10000, 100, 100]]
    dt64 = y.astype('U4').astype('M8') + (m-1).astype('m8[M]') + (d-1).astype('m8[D]')

    z = np.unique(swFilter)

    cmap = ListedColormap(['b','darkorange'])

    #fig =
    plt.figure('Test')
    plt.title("Marsdistanz unter Berücksichtigung der Halbjahre der steigenden und sinkenden Temperaturen\n",
              loc='left', wrap=True)
    plt.xlabel("Zeit in Jahren\n")
    plt.xticks(rotation = 45)
    plt.ylabel("Marsdistanz in AE\n(1 AE = 149.597.870,7 km)")
    plt.tight_layout()
    ax=plt.gca()
    plt.style.use('seaborn-whitegrid')

#convert dates to numbers first
    inxval = mdates.date2num(dt64)
    points = np.array([inxval, md]).T.reshape(-1,1,2)
    segments = np.concatenate([points[:-1],points[1:]], axis=1)

    lc = LineCollection(segments, array=z, cmap=plt.cm.get_cmap(cmap),
                        linewidth=3, norm=plt.Normalize(z.min(), z.max()))
# set color to s/w values
    lc.set_array(swFilter)
    ax.add_collection(lc)

    loc = mdates.AutoDateLocator()
    ax.xaxis.set_major_locator(loc)
    ax.xaxis.set_major_formatter(mdates.AutoDateFormatter(loc))

    ax.autoscale_view()

    def make_proxy(zvalue, scalar_mappable, **kwargs):
        color = scalar_mappable.cmap(scalar_mappable.norm(zvalue))
        return Line2D([0, 1], [0, 1], color=color, **kwargs)
    proxies = [make_proxy(item, lc, linewidth=2) for item in z]
    ax.legend(proxies, ['Halbjahr der sinkenden \nTemperaturen',
                        'Halbjahr der steigenden \nTemperaturen'], frameon=True)

    plt.show()

md_plot4(dt64, md, swFilter)

Обратите внимание, что я добавил plt.tight_layout(), чтобы заголовок графика и описание осей отображались без каких-либо срезов в режиме окна.

Новый выпуск сейчас (в результате добавления tight_layout()) заключается в том, что график сжимается по горизонтали, даже если на правой стороне графика доступно много места (место, где при вызове должна появиться цветовая полоса).

Это требует другого исправления, но в настоящее время я не знаю как.Так что, если кто-нибудь знает, как предотвратить обрезание заголовка и описания осей в оконном режиме, я был бы очень признателен, если вы оставите комментарий.

0 голосов
/ 05 апреля 2019

Чтобы получить многоцветный график в matplotlib, пометьте его и затем вызовите функцию legend(). Следующий пример кода взят из ссылки , но когда ссылки прерываются, вот пост ..

Используемая здесь диаграмма - это линия, но тот же принцип применим к другим типам диаграмм, как вы можете видеть из этого другого ответа SO

import matplotlib.pyplot as plt
import numpy as np

y = [2,4,6,8,10,12,14,16,18,20]
y2 = [10,11,12,13,14,15,16,17,18,19]
x = np.arange(10)
fig = plt.figure()
ax = plt.subplot(111)
ax.plot(x, y, label='$y = numbers')
ax.plot(x, y2, label='$y2 = other numbers')
plt.title('Legend inside')
ax.legend()
plt.show()

Этот код покажет следующее изображение (с легендой внутри диаграммы)

legend

Надеюсь, это поможет

...