1616import matplotlib as mpl
1717from . import (_path , artist , cbook , cm , colors as mcolors , docstring ,
1818 lines as mlines , path as mpath , transforms )
19+ import warnings
1920
2021
2122@cbook ._define_aliases ({
@@ -868,6 +869,7 @@ def draw(self, renderer):
868869class PathCollection (_CollectionWithSizes ):
869870 """
870871 This is the most basic :class:`Collection` subclass.
872+ A :class:`PathCollection` is e.g. created by a :meth:`~.Axes.scatter` plot.
871873 """
872874 @docstring .dedent_interpd
873875 def __init__ (self , paths , sizes = None , ** kwargs ):
@@ -890,6 +892,133 @@ def set_paths(self, paths):
890892 def get_paths (self ):
891893 return self ._paths
892894
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+
8931022
8941023class PolyCollection (_CollectionWithSizes ):
8951024 @docstring .dedent_interpd
0 commit comments