@@ -877,6 +877,7 @@ def draw(self, renderer):
877
877
class PathCollection (_CollectionWithSizes ):
878
878
"""
879
879
This is the most basic :class:`Collection` subclass.
880
+ A :class:`PathCollection` is e.g. created by a :meth:`~.Axes.scatter` plot.
880
881
"""
881
882
@docstring .dedent_interpd
882
883
def __init__ (self , paths , sizes = None , ** kwargs ):
@@ -899,6 +900,118 @@ def set_paths(self, paths):
899
900
def get_paths (self ):
900
901
return self ._paths
901
902
903
+ def legend_items (self , mode = "colors" , useall = "auto" , num = 10 ,
904
+ fmt = None , func = lambda x : 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_items())
911
+
912
+ Also see the :ref:`automatedlegendcreation` example.
913
+
914
+ Parameters
915
+ ----------
916
+ mode : 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
+ useall : bool or string "auto", optional (default "auto")
921
+ If True, use all unique elements of the mappable array. If False,
922
+ target to use *num* elements in the normed range. If *"auto"*, try
923
+ to determine which option better suits the nature of the data.
924
+ num : int or None, optional (default 10)
925
+ Target number of elements to create in case of *useall=False*.
926
+ The number of created elements may slightly deviate from that 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 shown labels. This converts the initial
934
+ values for color or size and needs to take a numpy array as input.
935
+ Note that if e.g. the :meth:`~.Axes.scatter`'s *s* parameter has
936
+ been calculated from some values *x* via a function
937
+ *f* as *s = f(x)*, you need to supply the inverse of that function
938
+ *f_inv* here, *func = f_inv*.
939
+ kwargs : further parameters
940
+ Allowed kwargs are *color* and *size*. E.g. it may be useful to
941
+ set the color of the markers if *mode="sizes"* is used; similarly
942
+ to set the size of the markers if *mode="colors"* is used.
943
+ Any further parameters are passed onto the `.Line2D` instance.
944
+ This may be useful to e.g. specify a different *markeredgecolor* or
945
+ *alpha* for the legend handles.
946
+
947
+ Returns
948
+ -------
949
+ tuple (handles, labels)
950
+ with *handles* being a list of `.Line2D` objects
951
+ and *labels* a list of strings of the same length.
952
+ """
953
+ handles = []
954
+ labels = []
955
+ hasarray = self .get_array () is not None
956
+ if fmt is None :
957
+ fmt = mpl .ticker .ScalarFormatter (useOffset = False , useMathText = True )
958
+ elif type (fmt ) == str :
959
+ fmt = mpl .ticker .StrMethodFormatter (fmt )
960
+ fmt .create_dummy_axis ()
961
+
962
+ if mode == "colors" and hasarray :
963
+ u = np .unique (self .get_array ())
964
+ size = kwargs .pop ("size" , mpl .rcParams ["lines.markersize" ])
965
+ elif mode == "sizes" :
966
+ u = np .unique (self .get_sizes ())
967
+ color = kwargs .pop ("color" , "k" )
968
+ else :
969
+ warnings .warn ("Invalid mode provided, or collections without "
970
+ "array used." )
971
+
972
+ fmt .set_bounds (func (u ).min (), func (u ).max ())
973
+ if useall == "auto" :
974
+ useall = False
975
+ if len (u ) <= num :
976
+ useall = True
977
+ if useall :
978
+ values = u
979
+ label_values = func (values )
980
+ else :
981
+ if mode == "colors" and hasarray :
982
+ arr = self .get_array ()
983
+ elif mode == "sizes" :
984
+ arr = self .get_sizes ()
985
+ loc = mpl .ticker .MaxNLocator (nbins = num , min_n_ticks = num - 1 ,
986
+ steps = [1 , 2 , 2.5 , 3 , 5 , 6 , 8 , 10 ])
987
+ label_values = loc .tick_values (func (arr ).min (), func (arr ).max ())
988
+ cond = (label_values >= func (arr ).min ()) & \
989
+ (label_values <= func (arr ).max ())
990
+ label_values = label_values [cond ]
991
+ xarr = np .linspace (arr .min (), arr .max (), 256 )
992
+ values = np .interp (label_values , func (xarr ), xarr )
993
+
994
+ kw = dict (markeredgewidth = self .get_linewidths ()[0 ],
995
+ alpha = self .get_alpha ())
996
+ kw .update (kwargs )
997
+
998
+ for val , lab in zip (values , label_values ):
999
+ if mode == "colors" and hasarray :
1000
+ color = self .cmap (self .norm (val ))
1001
+ elif mode == "sizes" :
1002
+ size = np .sqrt (val )
1003
+ if np .isclose (size , 0.0 ):
1004
+ continue
1005
+ h = mlines .Line2D ([0 ], [0 ], ls = "" , color = color , ms = size ,
1006
+ marker = self .get_paths ()[0 ], ** kw )
1007
+ handles .append (h )
1008
+ if hasattr (fmt , "set_locs" ):
1009
+ fmt .set_locs (label_values )
1010
+ l = fmt (lab )
1011
+ labels .append (l )
1012
+
1013
+ return handles , labels
1014
+
902
1015
903
1016
class PolyCollection (_CollectionWithSizes ):
904
1017
@docstring .dedent_interpd
0 commit comments