Python 为图例中的点设置固定大小
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/24706125/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Setting a fixed size for points in legend
提问by Gabriel
I'm making some scatter plots and I want to set the size of the points in the legend to a fixed, equal value.
我正在制作一些散点图,我想将图例中点的大小设置为固定的相等值。
Right now I have this:
现在我有这个:
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()
which produces this:
产生这个:
The sizes of the points in the legend are scaled but not the same. How can I fix the sizes of the points in the legend to an equal value without affecting the sizes in the scatter
plot?
图例中点的大小按比例缩放但不相同。如何在不影响图中大小的情况下将图例中点的大小固定为相等的值scatter
?
采纳答案by DrV
I had a look into the source code of matplotlib
. Bad news is that there does not seem to be any simple way of setting equal sizes of points in the legend. It is especially difficult with scatter plots (wrong: see the update below). There are essentially two alternatives:
我查看了matplotlib
. 坏消息是,似乎没有任何简单的方法可以在图例中设置相同大小的点。散点图尤其困难(错误:请参阅下面的更新)。基本上有两种选择:
- Change the
maplotlib
code - Add a transform into the
PathCollection
objects representing the dots in the image. The transform (scaling) has to take the original size into account.
- 更改
maplotlib
代码 - 将变换添加到
PathCollection
表示图像中点的对象中。变换(缩放)必须考虑原始大小。
Neither of these is very much fun, though #1 seems to be easier. The scatter
plots are especially challenging in this respect.
这些都不是很有趣,尽管#1 似乎更容易。该scatter
地块在这方面尤其具有挑战性。
However, I have a hack which does probably what you want:
但是,我有一个 hack 可能可以满足您的需求:
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()
This gives:
这给出:
Which seems to be what you wanted.
这似乎是你想要的。
The changes:
变化:
scatter
changed into aplot
, which changes the marker scaling (hence thesqrt
) and makes it impossible to use changing marker size (if that was intended)- the marker size changed manually to be 6 points for both markers in the legend
scatter
更改为 aplot
,这会更改标记缩放(因此是sqrt
),并且无法使用更改标记大小(如果这是有意的)- 图例中两个标记的标记大小手动更改为 6 磅
As you can see, this utilizes hidden underscore properties (_legmarker
) and is bug-ugly. It may break down at any update in matplotlib
.
如您所见,这利用了隐藏的下划线属性 ( _legmarker
) 并且非常丑陋。它可能会在matplotlib
.
Update
更新
Haa, I found it. A better hack:
哈哈,我找到了。一个更好的黑客:
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()
Now the _sizes
(another underscore property) does the trick. No need to touch the source, even though this is quite a hack. But now you can use everything scatter
offers.
现在_sizes
(另一个下划线属性)可以解决问题。无需触及源代码,即使这是一个相当大的黑客攻击。但是现在您可以使用所有scatter
优惠。
回答by Steven C. Howell
I did not have much success using @DrV's solution though perhaps my use case is unique. Because of the density of points, I am using the smallest marker size, i.e. plt.plot(x, y, '.', ms=1, ...)
, and want the legend symbols larger.
我使用@DrV 的解决方案并没有取得太大的成功,尽管我的用例可能是独一无二的。由于点的密度,我使用最小的标记大小,即plt.plot(x, y, '.', ms=1, ...)
,并希望图例符号更大。
I followed the recommendation I found on the matplotlib forums:
我遵循了在matplotlib 论坛上找到的建议:
- plot the data (no labels)
- record axes limit (
xlimits = plt.xlim()
) - plot fake data far away from real data with legend-appropriate symbol colors and sizes
- restore axes limits (
plt.xlim(xlimits)
) - create legend
- 绘制数据(无标签)
- 记录轴限制 (
xlimits = plt.xlim()
) - 使用适合图例的符号颜色和大小绘制远离真实数据的虚假数据
- 恢复轴限制 (
plt.xlim(xlimits)
) - 创造传奇
Here is how it turned out (for this the dots are actually less important that the lines):
Hope this helps someone else.
希望这对其他人有帮助。
回答by Bruno Morais
Similarly to the answer, assuming you want all the markers with the same size:
与答案类似,假设您希望所有标记都具有相同的大小:
lgnd = plt.legend(loc="lower left", scatterpoints=1, fontsize=10)
for handle in lgnd.legendHandles:
handle.set_sizes([6.0])
With MatPlotlib 2.0.0
使用 MatPlotlib 2.0.0
回答by Jeremy Bancroft Brown
You can make a Line2D object that resembles your chosen markers, except with a different marker size of your choosing, and use that to construct the legend. This is nice because it doesn't require placing an object in your axes (potentially triggering a resize event), and it doesn't require use of any hidden attributes. The only real downside is that you have to construct the legend explicitly from lists of objects and labels, but this is a well-documented matplotlib feature so it feels pretty safe to use.
您可以制作一个类似于您选择的标记的 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()
回答by ImportanceOfBeingErnest
Just another alternative here. This has the advantage that it would not use any "private" methods and works even with other objects than scatters present in the legend. The key is to map the scatter PathCollection
to a HandlerPathCollection
with an updating function being set to it.
这里只是另一种选择。这样做的优点是它不会使用任何“私有”方法,并且甚至可以与图例中存在的散点之外的其他对象一起使用。关键是将 scatter 映射PathCollection
到 aHandlerPathCollection
并为其设置更新函数。
def update(handle, orig):
handle.update_from(orig)
handle.set_sizes([64])
plt.legend(handler_map={PathCollection : HandlerPathCollection(update_func=update)})
Complete code example:
完整代码示例:
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()