为图例中的点设置固定大小

85 投票
6 回答
106381 浏览
提问于 2025-04-18 13:00

我正在制作一些散点图,想把图例中点的大小设置成一个固定的、相等的值。

现在我有这个:

import matplotlib.pyplot as plt
import numpy as np

def rand_data():
    return np.random.uniform(low=0., high=1., size=(100,))

# Generate data.
x1, y1 = [rand_data() for i in range(2)]
x2, y2 = [rand_data() for i in range(2)]


plt.figure()
plt.scatter(x1, y1, marker='o', label='first', s=20., c='b')
plt.scatter(x2, y2, marker='o', label='second', s=35., c='r')
# Plot legend.
plt.legend(loc="lower left", markerscale=2., scatterpoints=1, fontsize=10)
plt.show()

这段代码生成了这个效果:

这里输入图片描述

现在图例中的点的大小是按比例缩放的,但并不相同。我该如何把图例中点的大小固定成相等的值,而不影响散点图中的点的大小呢?

6 个回答

10

这里有一个不同的选择。这个方法的好处是,它不需要使用任何“私有”方法,并且即使在图例中有其他对象(不仅仅是散点图)时也能正常工作。关键在于将散点图的 PathCollection 映射到 HandlerPathCollection,并为它设置一个更新函数。

def update(handle, orig):
    handle.update_from(orig)
    handle.set_sizes([64])

plt.legend(handler_map={PathCollection : HandlerPathCollection(update_func=update)})

完整的代码示例:

import matplotlib.pyplot as plt
import numpy as np; np.random.seed(42)
from matplotlib.collections import PathCollection
from matplotlib.legend_handler import HandlerPathCollection, HandlerLine2D

colors = ["limegreen", "crimson", "indigo"]
markers = ["o", "s", r"$\clubsuit$"]
labels = ["ABC", "DEF", "XYZ"]
plt.plot(np.linspace(0,1,8), np.random.rand(8), marker="o", markersize=22, label="A line")
for i,(c,m,l) in enumerate(zip(colors,markers,labels)):
    plt.scatter(np.random.rand(8),np.random.rand(8), 
                c=c, marker=m, s=10+np.exp(i*2.9), label=l)

def updatescatter(handle, orig):
    handle.update_from(orig)
    handle.set_sizes([64])

def updateline(handle, orig):
    handle.update_from(orig)
    handle.set_markersize(8)

plt.legend(handler_map={PathCollection : HandlerPathCollection(update_func=updatescatter),
                        plt.Line2D : HandlerLine2D(update_func = updateline)})

plt.show()

在这里输入图片描述

13

我使用@DrV的解决方案时没什么成功,可能是因为我的情况比较特殊。由于数据点很多,我使用了最小的标记大小,也就是 plt.plot(x, y, '.', ms=1, ...),但我希望图例中的符号能大一些。

我按照在matplotlib论坛上找到的建议进行了操作:

  1. 先绘制数据(不加标签)
  2. 记录坐标轴的范围(xlimits = plt.xlim()
  3. 在离真实数据很远的地方绘制一些假数据,使用适合图例的符号颜色和大小
  4. 恢复坐标轴的范围(plt.xlim(xlimits)
  5. 创建图例

最后的效果是这样的(在这个例子中,点其实没有线那么重要): enter image description here

希望这能对其他人有所帮助。

24

你可以创建一个Line2D对象,它的外观和你选择的标记类似,只不过你可以自定义标记的大小,然后用这个对象来制作图例。这种做法很好,因为它不需要在你的坐标轴中放置一个对象(这样可能会引发重新调整大小的事件),而且也不需要使用任何隐藏的属性。唯一的缺点是,你需要从对象和标签的列表中明确构建图例,但这是一个在matplotlib中有详细文档说明的功能,所以使用起来还是比较安全的。

from matplotlib.lines import Line2D
import matplotlib.pyplot as plt
import numpy as np

def rand_data():
    return np.random.uniform(low=0., high=1., size=(100,))

# Generate data.
x1, y1 = [rand_data() for i in range(2)]
x2, y2 = [rand_data() for i in range(2)]

plt.figure()
plt.scatter(x1, y1, marker='o', label='first', s=20., c='b')
plt.scatter(x2, y2, marker='o', label='second', s=35., c='r')

# Create dummy Line2D objects for legend
h1 = Line2D([0], [0], marker='o', markersize=np.sqrt(20), color='b', linestyle='None')
h2 = Line2D([0], [0], marker='o', markersize=np.sqrt(20), color='r', linestyle='None')

# Set axes limits
plt.gca().set_xlim(-0.2, 1.2)
plt.gca().set_ylim(-0.2, 1.2)

# Plot legend.
plt.legend([h1, h2], ['first', 'second'], loc="lower left", markerscale=2,
           scatterpoints=1, fontsize=10)
plt.show()

resulting figure

75

和之前的回答类似,假设你想让所有的标记(marker)大小都一样:

lgnd = plt.legend(loc="lower left", scatterpoints=1, fontsize=10)
for handle in lgnd.legend_handles:
    handle.set_sizes([6.0])

这是在使用 MatPlotlib 2.0.0 的情况下。

107

我查看了一下matplotlib的源代码。坏消息是,似乎没有简单的方法可以让图例中的点大小相等。特别是在散点图中,这个问题更难解决(错误:请看下面的更新)。基本上有两个选择:

  1. 修改matplotlib的代码
  2. 在表示图中点的PathCollection对象中添加一个变换。这个变换(缩放)需要考虑到原始大小。

不过这两种方法都不太好玩,虽然第一种似乎更简单。散点图在这方面尤其具有挑战性。

不过,我有一个小技巧,可能能实现你想要的效果:

import matplotlib.pyplot as plt
import numpy as np

def rand_data():
    return np.random.uniform(low=0., high=1., size=(100,))

# Generate data.
x1, y1 = [rand_data() for i in range(2)]
x2, y2 = [rand_data() for i in range(2)]

plt.figure()
plt.plot(x1, y1, 'o', label='first', markersize=np.sqrt(20.), c='b')
plt.plot(x2, y2, 'o', label='second', markersize=np.sqrt(35.), c='r')
# Plot legend.
lgnd = plt.legend(loc="lower left", numpoints=1, fontsize=10)

#change the marker size manually for both lines
lgnd.legendHandles[0]._legmarker.set_markersize(6)
lgnd.legendHandles[1]._legmarker.set_markersize(6)
plt.show()

这样做会得到:

这里输入图片描述

看起来这就是你想要的效果。

具体的改动:

  • scatter改成了plot,这改变了标记的缩放(所以用了sqrt),并且使得无法使用变化的标记大小(如果这是你想要的)
  • 手动把图例中两个标记的大小都改成了6个点

如你所见,这个方法利用了隐藏的下划线属性(_legmarker),看起来非常丑陋。它可能在matplotlib更新时崩溃。

更新

哈哈,我找到了。一个更好的小技巧:

import matplotlib.pyplot as plt
import numpy as np

def rand_data():
    return np.random.uniform(low=0., high=1., size=(100,))

# Generate data.
x1, y1 = [rand_data() for i in range(2)]
x2, y2 = [rand_data() for i in range(2)]

plt.figure()
plt.scatter(x1, y1, marker='o', label='first', s=20., c='b')
plt.scatter(x2, y2, marker='o', label='second', s=35., c='r')
# Plot legend.
lgnd = plt.legend(loc="lower left", scatterpoints=1, fontsize=10)
lgnd.legendHandles[0]._sizes = [30]
lgnd.legendHandles[1]._sizes = [30]
plt.show()

现在_sizes(另一个下划线属性)解决了这个问题。你不需要修改源代码,尽管这仍然是个小技巧。但现在你可以使用scatter提供的所有功能。

这里输入图片描述

撰写回答