Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit809c130

Browse files
legend-for-scatter
1 parent0984694 commit809c130

File tree

4 files changed

+270
-6
lines changed

4 files changed

+270
-6
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
Legend for scatter
2+
------------------
3+
4+
A new method for creating legends for scatter plots has been introduced.
5+
Previously, in order to obtain a legend for a:meth:`~Axes.scatter` plot, one
6+
could either plot several scatters, each with an individual label, or create
7+
proxy artists to show in the legend manually.
8+
Now,:class:`~.collections.PathCollection` provides a method
9+
:meth:`~.collections.PathCollection.legend_elements` to obtain the handles and labels
10+
for a scatter plot in an automated way. This makes creating a legend for a
11+
scatter plot as easy as::
12+
13+
scatter = plt.scatter([1,2,3], [4,5,6], c=[7,2,3])
14+
plt.legend(*scatter.legend_elements())
15+
16+
An example can be found in
17+
:ref:`automatedlegendcreation`.

‎examples/lines_bars_and_markers/scatter_with_legend.py

Lines changed: 95 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,113 @@
33
Scatter plots with a legend
44
===========================
55
6-
Also demonstrates how transparency of the markers
7-
can be adjusted by giving ``alpha`` a value between
8-
0 and 1.
6+
To create a scatter plot with a legend one may use a loop and create one
7+
`~.Axes.scatter` plot per item to appear in the legend and set the ``label``
8+
accordingly.
9+
10+
The following also demonstrates how transparency of the markers
11+
can be adjusted by giving ``alpha`` a value between 0 and 1.
912
"""
1013

14+
importnumpyasnp
15+
np.random.seed(19680801)
1116
importmatplotlib.pyplotasplt
12-
fromnumpy.randomimportrand
1317

1418

1519
fig,ax=plt.subplots()
1620
forcolorin ['red','green','blue']:
1721
n=750
18-
x,y=rand(2,n)
19-
scale=200.0*rand(n)
22+
x,y=np.random.rand(2,n)
23+
scale=200.0*np.random.rand(n)
2024
ax.scatter(x,y,c=color,s=scale,label=color,
2125
alpha=0.3,edgecolors='none')
2226

2327
ax.legend()
2428
ax.grid(True)
2529

2630
plt.show()
31+
32+
33+
##############################################################################
34+
# .. _automatedlegendcreation:
35+
#
36+
# Automated legend creation
37+
# -------------------------
38+
#
39+
# Another option for creating a legend for a scatter is to use the
40+
# :class:`~matplotlib.collections.PathCollection`'s
41+
# :meth:`~.PathCollection.legend_elements` method.
42+
# It will automatically try to determine a useful number of legend entries
43+
# to be shown and return a tuple of handles and labels. Those can be passed
44+
# to the call to :meth:`~.axes.Axes.legend`.
45+
46+
47+
N=45
48+
x,y=np.random.rand(2,N)
49+
c=np.random.randint(1,5,size=N)
50+
s=np.random.randint(10,220,size=N)
51+
52+
fig,ax=plt.subplots()
53+
54+
scatter=ax.scatter(x,y,c=c,s=s)
55+
56+
# produce a legend with the unique colors from the scatter
57+
legend1=ax.legend(*scatter.legend_elements(),loc=3,title="Classes")
58+
ax.add_artist(legend1)
59+
60+
# produce a legend with a cross section of sizes from the scatter
61+
handles,labels=scatter.legend_elements(prop="sizes",alpha=0.6)
62+
legend2=ax.legend(handles,labels,loc=1,title="Sizes")
63+
64+
plt.show()
65+
66+
67+
##############################################################################
68+
# Further arguments to the :meth:`~.PathCollection.legend_elements` method
69+
# can be used to steer how many legend entries are to be created and how they
70+
# should be labeled. The following shows how to use some of them.
71+
#
72+
73+
volume=np.random.rayleigh(27,size=40)
74+
amount=np.random.poisson(10,size=40)
75+
ranking=np.random.normal(size=40)
76+
price=np.random.uniform(1,10,size=40)
77+
78+
fig,ax=plt.subplots()
79+
80+
# Because the price is much too small when being provided as size for ``s``,
81+
# we normalize it to some useful point sizes, s=0.3*(price*3)**2
82+
scatter=ax.scatter(volume,amount,c=ranking,s=0.3*(price*3)**2,
83+
vmin=-3,vmax=3,cmap="Spectral")
84+
85+
# Produce a legend for the ranking (colors). Even though there are 40 different
86+
# rankings, we only want to show 5 of them in the legend.
87+
legend1=ax.legend(*scatter.legend_elements(num=5),loc=2,title="Ranking")
88+
ax.add_artist(legend1)
89+
90+
# Produce a legend for the price (sizes). Because we want to show the prices
91+
# in dollars, we use the *func* argument to supply the inverse of the function
92+
# used to calculate the sizes from above. The *fmt* ensures to show the price
93+
# in dollars. Note how we target at 5 elements here, but obtain only 4 in the
94+
# created legend due to the automatic round prices that are chosen for us.
95+
kw=dict(prop="sizes",num=5,color=scatter.cmap(0.7),fmt="\$ {x:.2f}",
96+
func=lambdas:np.sqrt(s/.3)/3)
97+
legend2=ax.legend(*scatter.legend_elements(**kw),loc=4,title="Price")
98+
99+
plt.show()
100+
101+
#############################################################################
102+
#
103+
# ------------
104+
#
105+
# References
106+
# """"""""""
107+
#
108+
# The usage of the following functions and methods is shown in this example:
109+
110+
importmatplotlib
111+
matplotlib.axes.Axes.scatter
112+
matplotlib.pyplot.scatter
113+
matplotlib.axes.Axes.legend
114+
matplotlib.pyplot.legend
115+
matplotlib.collections.PathCollection.legend_elements

‎lib/matplotlib/collections.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -877,6 +877,7 @@ def draw(self, renderer):
877877
classPathCollection(_CollectionWithSizes):
878878
"""
879879
This is the most basic :class:`Collection` subclass.
880+
A :class:`PathCollection` is e.g. created by a :meth:`~.Axes.scatter` plot.
880881
"""
881882
@docstring.dedent_interpd
882883
def__init__(self,paths,sizes=None,**kwargs):
@@ -899,6 +900,121 @@ def set_paths(self, paths):
899900
defget_paths(self):
900901
returnself._paths
901902

903+
deflegend_elements(self,prop="colors",num="auto",
904+
fmt=None,func=lambdax:x,**kwargs):
905+
"""
906+
Creates legend handles and labels for a PathCollection. This is useful
907+
for obtaining a legend for a :meth:`~.Axes.scatter` plot. E.g.::
908+
909+
scatter = plt.scatter([1,2,3], [4,5,6], c=[7,2,3])
910+
plt.legend(*scatter.legend_elements())
911+
912+
Also see the :ref:`automatedlegendcreation` example.
913+
914+
Parameters
915+
----------
916+
prop : string, optional, default *"colors"*
917+
Can be *"colors"* or *"sizes"*. In case of *"colors"*, the legend
918+
handles will show the different colors of the collection. In case
919+
of "sizes", the legend will show the different sizes.
920+
num : int or None or string "auto", optional (default "auto")
921+
Target number of elements to create.
922+
If None, use all unique elements of the mappable array. If an
923+
integer, target to use *num* elements in the normed range.
924+
If *"auto"*, try to determine which option better suits the nature
925+
of the data.
926+
The number of created elements may slightly deviate from *num* due
927+
to a `~.ticker.Locator` being used to find useful locations.
928+
fmt : string, `~matplotlib.ticker.Formatter`, or None (default)
929+
The format or formatter to use for the labels. If a string must be
930+
a valid input for a `~.StrMethodFormatter`. If None (the default),
931+
use a `~.ScalarFormatter`.
932+
func : function, default *lambda x: x*
933+
Function to calculate the labels. Often the size (or color)
934+
argument to :meth:`~.Axes.scatter` will have been pre-processed
935+
by the user using a function *s = f(x)* to make the markers
936+
visible; e.g. *size = np.log10(x)*. Providing the inverse of this
937+
function here allows that pre-processing to be inverted, so that
938+
the legend labels have the correct values;
939+
e.g. *func = np.exp(x, 10)*.
940+
kwargs : further parameters
941+
Allowed kwargs are *color* and *size*. E.g. it may be useful to
942+
set the color of the markers if *prop="sizes"* is used; similarly
943+
to set the size of the markers if *prop="colors"* is used.
944+
Any further parameters are passed onto the `.Line2D` instance.
945+
This may be useful to e.g. specify a different *markeredgecolor* or
946+
*alpha* for the legend handles.
947+
948+
Returns
949+
-------
950+
tuple (handles, labels)
951+
with *handles* being a list of `.Line2D` objects
952+
and *labels* a list of strings of the same length.
953+
"""
954+
handles= []
955+
labels= []
956+
hasarray=self.get_array()isnotNone
957+
iffmtisNone:
958+
fmt=mpl.ticker.ScalarFormatter(useOffset=False,useMathText=True)
959+
eliftype(fmt)==str:
960+
fmt=mpl.ticker.StrMethodFormatter(fmt)
961+
fmt.create_dummy_axis()
962+
963+
ifprop=="colors"andhasarray:
964+
u=np.unique(self.get_array())
965+
size=kwargs.pop("size",mpl.rcParams["lines.markersize"])
966+
elifprop=="sizes":
967+
u=np.unique(self.get_sizes())
968+
color=kwargs.pop("color","k")
969+
else:
970+
warnings.warn("Invalid prop provided, or collection without "
971+
"array used.")
972+
returnhandles,labels
973+
974+
fmt.set_bounds(func(u).min(),func(u).max())
975+
ifnum=="auto":
976+
num=9
977+
iflen(u)<=num:
978+
num=None
979+
ifnumisNone:
980+
values=u
981+
label_values=func(values)
982+
else:
983+
num=int(num)
984+
ifprop=="colors"andhasarray:
985+
arr=self.get_array()
986+
elifprop=="sizes":
987+
arr=self.get_sizes()
988+
loc=mpl.ticker.MaxNLocator(nbins=num,min_n_ticks=num-1,
989+
steps=[1,2,2.5,3,5,6,8,10])
990+
label_values=loc.tick_values(func(arr).min(),func(arr).max())
991+
cond= (label_values>=func(arr).min())& \
992+
(label_values<=func(arr).max())
993+
label_values=label_values[cond]
994+
xarr=np.linspace(arr.min(),arr.max(),256)
995+
values=np.interp(label_values,func(xarr),xarr)
996+
997+
kw=dict(markeredgewidth=self.get_linewidths()[0],
998+
alpha=self.get_alpha())
999+
kw.update(kwargs)
1000+
1001+
forval,labinzip(values,label_values):
1002+
ifprop=="colors"andhasarray:
1003+
color=self.cmap(self.norm(val))
1004+
elifprop=="sizes":
1005+
size=np.sqrt(val)
1006+
ifnp.isclose(size,0.0):
1007+
continue
1008+
h=mlines.Line2D([0], [0],ls="",color=color,ms=size,
1009+
marker=self.get_paths()[0],**kw)
1010+
handles.append(h)
1011+
ifhasattr(fmt,"set_locs"):
1012+
fmt.set_locs(label_values)
1013+
l=fmt(lab)
1014+
labels.append(l)
1015+
1016+
returnhandles,labels
1017+
9021018

9031019
classPolyCollection(_CollectionWithSizes):
9041020
@docstring.dedent_interpd

‎lib/matplotlib/tests/test_collections.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -669,3 +669,45 @@ def test_scatter_post_alpha():
669669
# this needs to be here to update internal state
670670
fig.canvas.draw()
671671
sc.set_alpha(.1)
672+
673+
674+
deftest_pathcollection_legend_elements():
675+
np.random.seed(19680801)
676+
x,y=np.random.rand(2,10)
677+
y=np.random.rand(10)
678+
c=np.random.randint(0,5,size=10)
679+
s=np.random.randint(10,300,size=10)
680+
681+
fig,ax=plt.subplots()
682+
sc=ax.scatter(x,y,c=c,s=s,cmap="jet",marker="o",linewidths=0)
683+
684+
h,l=sc.legend_elements(fmt="{x:g}")
685+
assertlen(h)==5
686+
assert_array_equal(np.array(l).astype(float),np.arange(5))
687+
colors=np.array([line.get_color()forlineinh])
688+
colors2=sc.cmap(np.arange(5)/4)
689+
assert_array_equal(colors,colors2)
690+
l1=ax.legend(h,l,loc=1)
691+
692+
h,l=sc.legend_elements(num=9)
693+
assertlen(h)==9
694+
l2=ax.legend(h,l,loc=2)
695+
696+
h,l=sc.legend_elements(prop="sizes",alpha=0.5,color="red")
697+
alpha=np.array([line.get_alpha()forlineinh])
698+
assert_array_equal(alpha,0.5)
699+
color=np.array([line.get_markerfacecolor()forlineinh])
700+
assert_array_equal(color,"red")
701+
l3=ax.legend(h,l,loc=4)
702+
703+
h,l=sc.legend_elements(prop="sizes",num=4,fmt="{x:.2f}",
704+
func=lambdax:2*x)
705+
actsizes= [line.get_markersize()forlineinh]
706+
labeledsizes=np.sqrt(np.array(l).astype(float)/2)
707+
assert_array_almost_equal(actsizes,labeledsizes)
708+
l4=ax.legend(h,l,loc=3)
709+
710+
forlin [l1,l2,l3,l4]:
711+
ax.add_artist(l)
712+
713+
fig.canvas.draw()

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp