1717import matplotlib .font_manager as font_manager
1818import matplotlib .text as text
1919import matplotlib .cbook as cbook
20- import matplotlib .mathtext as mathtext
2120import matplotlib .patches as mpatches
22- import matplotlib .texmanager as texmanager
2321import matplotlib .transforms as mtransforms
2422
2523# Import needed for adding manual selection capability to clabel
@@ -51,7 +49,7 @@ class ContourLabeler:
5149"""Mixin to provide labelling capability to `.ContourSet`."""
5250
5351def clabel (self ,levels = None ,* ,
54- fontsize = None ,inline = True ,inline_spacing = 5 ,fmt = '%1.3f' ,
52+ fontsize = None ,inline = True ,inline_spacing = 5 ,fmt = None ,
5553colors = None ,use_clabeltext = False ,manual = False ,
5654rightside_up = True ,zorder = None ):
5755"""
@@ -92,14 +90,17 @@ def clabel(self, levels=None, *,
9290 This spacing will be exact for labels at locations where the
9391 contour is straight, less so for labels on curved contours.
9492
95- fmt : str or dict,default: '%1.3f'
96- A format string for the label.
93+ fmt :`.Formatter` or str orcallable or dict,optional
94+ How the levels are formatted:
9795
98- Alternatively, this can be a dictionary matching contour levels
99- with arbitrary strings to use for each contour level (i.e.,
100- fmt[level]=string), or it can be any callable, such as a
101- `.Formatter` instance, that returns a string when called with a
102- numeric contour level.
96+ - If a `.Formatter`, it is used to format all levels at once, using
97+ its `.Formatter.format_ticks` method.
98+ - If a str, it is interpreted as a %-style format string.
99+ - If a callable, it is called with one level at a time and should
100+ return the corresponding label.
101+ - If a dict, it should directly map levels to labels.
102+
103+ The default is to use a standard `.ScalarFormatter`.
103104
104105 manual : bool or iterable, default: False
105106 If ``True``, contour labels will be placed manually using
@@ -144,6 +145,9 @@ def clabel(self, levels=None, *,
144145# labels method (case of automatic label placement) or
145146# `BlockingContourLabeler` (case of manual label placement).
146147
148+ if fmt is None :
149+ fmt = ticker .ScalarFormatter (useOffset = False )
150+ fmt .create_dummy_axis ()
147151self .labelFmt = fmt
148152self ._use_clabeltext = use_clabeltext
149153# Detect if manual selection is desired and remove from argument list.
@@ -243,20 +247,12 @@ def get_label_width(self, lev, fmt, fsize):
243247 """
244248if not isinstance (lev ,str ):
245249lev = self .get_text (lev ,fmt )
246- lev ,ismath = text .Text ()._preprocess_math (lev )
247- if ismath == 'TeX' :
248- lw ,_ ,_ = (texmanager .TexManager ()
249- .get_text_width_height_descent (lev ,fsize ))
250- elif ismath :
251- if not hasattr (self ,'_mathtext_parser' ):
252- self ._mathtext_parser = mathtext .MathTextParser ('agg' )
253- _ ,_ ,_ ,_ ,_ ,img ,_ = self ._mathtext_parser .parse (
254- lev ,dpi = 72 ,prop = self .labelFontProps )
255- _ ,lw = np .shape (img )# at dpi=72, the units are PostScript points
256- else :
257- # width is much less than "font size"
258- lw = len (lev )* fsize * 0.6
259- return lw
250+ fig = self .axes .figure
251+ width = (text .Text (0 ,0 ,lev ,figure = fig ,
252+ size = fsize ,fontproperties = self .labelFontProps )
253+ .get_window_extent (fig .canvas .get_renderer ()).width )
254+ width *= 72 / fig .dpi
255+ return width
260256
261257def set_label_props (self ,label ,text ,color ):
262258"""Set the label properties - color, fontsize, text."""
@@ -269,13 +265,14 @@ def get_text(self, lev, fmt):
269265"""Get the text of the label."""
270266if isinstance (lev ,str ):
271267return lev
268+ elif isinstance (fmt ,dict ):
269+ return fmt .get (lev ,'%1.3f' )
270+ elif callable (getattr (fmt ,"format_ticks" ,None )):
271+ return fmt .format_ticks ([* self .labelLevelList ,lev ])[- 1 ]
272+ elif callable (fmt ):
273+ return fmt (lev )
272274else :
273- if isinstance (fmt ,dict ):
274- return fmt .get (lev ,'%1.3f' )
275- elif callable (fmt ):
276- return fmt (lev )
277- else :
278- return fmt % lev
275+ return fmt % lev
279276
280277def locate_label (self ,linecontour ,labelwidth ):
281278"""
@@ -564,23 +561,14 @@ def labels(self, inline, inline_spacing):
564561paths = con .get_paths ()
565562for segNum ,linepath in enumerate (paths ):
566563lc = linepath .vertices # Line contour
567- slc0 = trans .transform (lc )# Line contour in screen coords
568-
569- # For closed polygons, add extra point to avoid division by
570- # zero in print_label and locate_label. Other than these
571- # functions, this is not necessary and should probably be
572- # eventually removed.
573- if _is_closed_polygon (lc ):
574- slc = np .row_stack ([slc0 ,slc0 [1 :2 ]])
575- else :
576- slc = slc0
564+ slc = trans .transform (lc )# Line contour in screen coords
577565
578566# Check if long enough for a label
579567if self .print_label (slc ,lw ):
580568x ,y ,ind = self .locate_label (slc ,lw )
581569
582570rotation ,new = self .calc_label_rot_and_inline (
583- slc0 ,ind ,lw ,lc if inline else None ,inline_spacing )
571+ slc ,ind ,lw ,lc if inline else None ,inline_spacing )
584572
585573# Actually add the label
586574add_label (x ,y ,rotation ,lev ,cvalue )