如何为多色线创建基本图例?

Posted

技术标签:

【中文标题】如何为多色线创建基本图例?【英文标题】:How to create a basic legend to a multicolored line? 【发布时间】:2019-08-26 16:24:45 【问题描述】:

我目前正在完成一个更大的项目,最后一部分是在彩色线条的图中添加一个简单的图例。该行仅包含两种不同的颜色。

下图显示了创建时的情节。

下一张图片显示了相同的图,分辨率更高。

该图显示了地球和火星之间的距离随时间的变化。在 3 月到 8 月,这条线是橙色的,其他月份是蓝色的。图例应该出现在绘图右上角的一个简单框中,显示每个使用颜色的标签。像this 这样的东西会很好。

绘图的数据来自我命名为master_array 的巨大矩阵。在显示此问题所涉及的情节之前,它包含一些任务所必需的更多信息。 对于我正在努力解决的情节而言,重要的是第 0、1 和 6 列包含日期、相关日期的行星之间的距离,在第 6 列中,我设置了一个标志来确定给定点是否属于“三月到八月” ' 设置与否(0 用于 9 月至 2 月/“冬季”,1 用于 3 月至 8 月/“夏季”)。 master_array 是一个 numpy 数组,dtype 是 float64。它包含大约 45k 个数据点。

看起来像:

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

在较大的脚本中还有另一个函数(散点图)来标记曲线的最小值和最大值,但我想这在这里并不那么重要。

我已经尝试this 生成了一个图例,它显示了一个垂直颜色条和一个标签,以及this question 的答案中描述的两个选项,因为它看起来更像我的目标但无法做到它适用于我的情况。

也许我应该补充一点,我只是 python 的初学者,这是我的第一个项目,所以我不熟悉 matplotlib 的更深层次的功能,这可能是我无法自定义上述答案的原因让它在我的情况下工作。


更新

感谢用户 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) 行,以实现我根据this answer 寻找的内容。

所以我尝试了其他一些更改:

我使用plt.style.use('classic') 来获得我需要的图例,但这让我失去了前面提到的plt.style.use('seaborn-whitegrid') 的漂亮风格。此外,我根据提到的answer 禁用了我之前添加的colorbar 行。

这是我得到的:

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

【问题讨论】:

LineCollection.set_label() 对您有帮助吗?请注意,您之后必须调用 ax.legend() 你能再精确一点吗?当我添加这两行(包括一个参数)时,我得到TypeError: set_label() missing 1 required positional argument: 's' 好吧,set_label 是设置标签的函数调用,所以你必须在图例中给它你想要的标签作为字符串。 lc.set_label('a label for the legend')ax.legend() 然后将根据线条的外观及其标签生成图例。我从来没有使用过 LineCollections,所以我不知道会发生什么。 他的风格没有改变;所以可能你运行一些你需要关闭的交互式会话。至于为什么删除颜色条会删除您的颜色,我不知道。在原始帖子中也是如此,但在我的任何答案中都没有;需要检查差异。 好的,有一个funny bug。我更新了 original answer 以使其正常工作,即使您删除了颜色条。 【参考方案1】:

要在 matplotlib 中获得多色图,请标记您的图,然后调用 legend() 函数。以下示例代码取自link,但由于链接中断,这是帖子..

这里使用的图表是一条线,但同样的原理也适用于其他图表类型,从这个other SO answer可以看到

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

此代码将显示以下图像(图表内带有图例)

希望对你有帮助

【讨论】:

【参考方案2】:

所以这里是如何为多色线创建基本图例的答案,包含每种使用的颜色的多个标签,并且在绘图旁边不显示颜色条(标准颜色条,图例中没有任何内容;请参阅更新原始问题有关问题的更多信息):

感谢许多有用的 cmets,我想出为 LineCollection() 添加一个规范,以避免在通过禁用 fig.colorbar() 删除颜色条时出现单色线(另见 this) 要添加的附加参数(在本例中为“norm”)是norm=plt.Normalize(z.min(), z.max()),其中z 是包含负责段不同颜色的信息的数组。请注意,z 只需要为每种不同的颜色保存一个元素。这就是为什么我将 swFilter 数组(每个数据点包含一个标志)包装到 np.unique() 中。

要在不接触plt.style.use() 的框中获得正确的图例,我只需将正确的参数添加到ax.legend()。就我而言,一个简单的frameon=True 完成了这项工作。

结果如下:

代码如下:

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())是绘图被水平压缩,即使绘图右侧有很多可用空间(颜色条所在的位置)调用时出现)。

这需要另一个修复,但目前我不知道如何解决。因此,如果有人知道如何防止绘图标题和轴的描述在窗口模式下被截断,如果您发表评论,我将不胜感激。

【讨论】:

以上是关于如何为多色线创建基本图例?的主要内容,如果未能解决你的问题,请参考以下文章

如何为 matplotlib 图例命名

如何为每组线添加单个图例标签

如何为图形级功能编辑 seaborn 图例标题和标签

如何在 matplotlib 中为 3D 条形图创建图例?

如何绘制和导出具有平滑边缘的多色线

如何为幻灯片添加图例? (解压器)