16
16
import matplotlib as mpl
17
17
from . import (_path , artist , cbook , cm , colors as mcolors , docstring ,
18
18
lines as mlines , path as mpath , transforms )
19
+ import warnings
19
20
20
21
21
22
@cbook ._define_aliases ({
@@ -868,6 +869,7 @@ def draw(self, renderer):
868
869
class PathCollection (_CollectionWithSizes ):
869
870
"""
870
871
This is the most basic :class:`Collection` subclass.
872
+ A :class:`PathCollection` is e.g. created by a :meth:`~.Axes.scatter` plot.
871
873
"""
872
874
@docstring .dedent_interpd
873
875
def __init__ (self , paths , sizes = None , ** kwargs ):
@@ -890,6 +892,133 @@ def set_paths(self, paths):
890
892
def get_paths (self ):
891
893
return self ._paths
892
894
895
+ def legend_elements (self , prop = "colors" , num = "auto" ,
896
+ fmt = None , func = lambda x : x , ** kwargs ):
897
+ """
898
+ Creates legend handles and labels for a PathCollection. This is useful
899
+ for obtaining a legend for a :meth:`~.Axes.scatter` plot. E.g.::
900
+
901
+ scatter = plt.scatter([1,2,3], [4,5,6], c=[7,2,3])
902
+ plt.legend(*scatter.legend_elements())
903
+
904
+ Also see the :ref:`automatedlegendcreation` example.
905
+
906
+ Parameters
907
+ ----------
908
+ prop : string, optional, default *"colors"*
909
+ Can be *"colors"* or *"sizes"*. In case of *"colors"*, the legend
910
+ handles will show the different colors of the collection. In case
911
+ of "sizes", the legend will show the different sizes.
912
+ num : int, None, "auto" (default), array-like, or `~.ticker.Locator`,
913
+ optional
914
+ Target number of elements to create.
915
+ If None, use all unique elements of the mappable array. If an
916
+ integer, target to use *num* elements in the normed range.
917
+ If *"auto"*, try to determine which option better suits the nature
918
+ of the data.
919
+ The number of created elements may slightly deviate from *num* due
920
+ to a `~.ticker.Locator` being used to find useful locations.
921
+ If a list or array, use exactly those elements for the legend.
922
+ Finally, a `~.ticker.Locator` can be provided.
923
+ fmt : string, `~matplotlib.ticker.Formatter`, or None (default)
924
+ The format or formatter to use for the labels. If a string must be
925
+ a valid input for a `~.StrMethodFormatter`. If None (the default),
926
+ use a `~.ScalarFormatter`.
927
+ func : function, default *lambda x: x*
928
+ Function to calculate the labels. Often the size (or color)
929
+ argument to :meth:`~.Axes.scatter` will have been pre-processed
930
+ by the user using a function *s = f(x)* to make the markers
931
+ visible; e.g. *size = np.log10(x)*. Providing the inverse of this
932
+ function here allows that pre-processing to be inverted, so that
933
+ the legend labels have the correct values;
934
+ e.g. *func = np.exp(x, 10)*.
935
+ kwargs : further parameters
936
+ Allowed kwargs are *color* and *size*. E.g. it may be useful to
937
+ set the color of the markers if *prop="sizes"* is used; similarly
938
+ to set the size of the markers if *prop="colors"* is used.
939
+ Any further parameters are passed onto the `.Line2D` instance.
940
+ This may be useful to e.g. specify a different *markeredgecolor* or
941
+ *alpha* for the legend handles.
942
+
943
+ Returns
944
+ -------
945
+ tuple (handles, labels)
946
+ with *handles* being a list of `.Line2D` objects
947
+ and *labels* a matching list of strings.
948
+ """
949
+ handles = []
950
+ labels = []
951
+ hasarray = self .get_array () is not None
952
+ if fmt is None :
953
+ fmt = mpl .ticker .ScalarFormatter (useOffset = False , useMathText = True )
954
+ elif isinstance (fmt , str ):
955
+ fmt = mpl .ticker .StrMethodFormatter (fmt )
956
+ fmt .create_dummy_axis ()
957
+
958
+ if prop == "colors" :
959
+ if not hasarray :
960
+ warnings .warn ("Collection without array used. Make sure to "
961
+ "specify the values to be colormapped via the "
962
+ "`c` argument." )
963
+ return handles , labels
964
+ u = np .unique (self .get_array ())
965
+ size = kwargs .pop ("size" , mpl .rcParams ["lines.markersize" ])
966
+ elif prop == "sizes" :
967
+ u = np .unique (self .get_sizes ())
968
+ color = kwargs .pop ("color" , "k" )
969
+ else :
970
+ raise ValueError ("Valid values for `prop` are 'colors' or "
971
+ f"'sizes'. You supplied '{ prop } ' instead." )
972
+
973
+ fmt .set_bounds (func (u ).min (), func (u ).max ())
974
+ if num == "auto" :
975
+ num = 9
976
+ if len (u ) <= num :
977
+ num = None
978
+ if num is None :
979
+ values = u
980
+ label_values = func (values )
981
+ else :
982
+ if prop == "colors" :
983
+ arr = self .get_array ()
984
+ elif prop == "sizes" :
985
+ arr = self .get_sizes ()
986
+ if isinstance (num , mpl .ticker .Locator ):
987
+ loc = num
988
+ elif np .iterable (num ):
989
+ loc = mpl .ticker .FixedLocator (num )
990
+ else :
991
+ num = int (num )
992
+ loc = mpl .ticker .MaxNLocator (nbins = num , min_n_ticks = num - 1 ,
993
+ steps = [1 , 2 , 2.5 , 3 , 5 , 6 , 8 , 10 ])
994
+ label_values = loc .tick_values (func (arr ).min (), func (arr ).max ())
995
+ cond = ((label_values >= func (arr ).min ()) &
996
+ (label_values <= func (arr ).max ()))
997
+ label_values = label_values [cond ]
998
+ xarr = np .linspace (arr .min (), arr .max (), 256 )
999
+ values = np .interp (label_values , func (xarr ), xarr )
1000
+
1001
+ kw = dict (markeredgewidth = self .get_linewidths ()[0 ],
1002
+ alpha = self .get_alpha ())
1003
+ kw .update (kwargs )
1004
+
1005
+ for val , lab in zip (values , label_values ):
1006
+ if prop == "colors" :
1007
+ color = self .cmap (self .norm (val ))
1008
+ elif prop == "sizes" :
1009
+ size = np .sqrt (val )
1010
+ if np .isclose (size , 0.0 ):
1011
+ continue
1012
+ h = mlines .Line2D ([0 ], [0 ], ls = "" , color = color , ms = size ,
1013
+ marker = self .get_paths ()[0 ], ** kw )
1014
+ handles .append (h )
1015
+ if hasattr (fmt , "set_locs" ):
1016
+ fmt .set_locs (label_values )
1017
+ l = fmt (lab )
1018
+ labels .append (l )
1019
+
1020
+ return handles , labels
1021
+
893
1022
894
1023
class PolyCollection (_CollectionWithSizes ):
895
1024
@docstring .dedent_interpd
0 commit comments