11
11
"""
12
12
13
13
from enum import Enum , auto
14
- from matplotlib import cbook , docstring
14
+ from numbers import Number
15
+
16
+ from matplotlib import _api , docstring
15
17
16
18
17
19
class _AutoStringNameEnum (Enum ):
@@ -28,12 +30,12 @@ def _deprecate_case_insensitive_join_cap(s):
28
30
s_low = s .lower ()
29
31
if s != s_low :
30
32
if s_low in ['miter' , 'round' , 'bevel' ]:
31
- cbook .warn_deprecated (
33
+ _api .warn_deprecated (
32
34
"3.3" , message = "Case-insensitive capstyles are deprecated "
33
35
"since %(since)s and support for them will be removed "
34
36
"%(removal)s; please pass them in lowercase." )
35
37
elif s_low in ['butt' , 'round' , 'projecting' ]:
36
- cbook .warn_deprecated (
38
+ _api .warn_deprecated (
37
39
"3.3" , message = "Case-insensitive joinstyles are deprecated "
38
40
"since %(since)s and support for them will be removed "
39
41
"%(removal)s; please pass them in lowercase." )
@@ -206,3 +208,260 @@ def demo():
206
208
207
209
docstring .interpd .update ({'JoinStyle' : JoinStyle .input_description ,
208
210
'CapStyle' : CapStyle .input_description })
211
+
212
+
213
+ #: Maps short codes for line style to their full name used by backends.
214
+ _ls_mapper = {'' : 'None' , ' ' : 'None' , 'none' : 'None' ,
215
+ '-' : 'solid' , '--' : 'dashed' , '-.' : 'dashdot' , ':' : 'dotted' }
216
+ _deprecated_lineStyles = {
217
+ '-' : '_draw_solid' ,
218
+ '--' : '_draw_dashed' ,
219
+ '-.' : '_draw_dash_dot' ,
220
+ ':' : '_draw_dotted' ,
221
+ 'None' : '_draw_nothing' ,
222
+ ' ' : '_draw_nothing' ,
223
+ '' : '_draw_nothing' ,
224
+ }
225
+
226
+
227
+ class NamedLineStyle (str , _AutoStringNameEnum ):
228
+ """
229
+ Describe if the line is solid or dashed, and the dash pattern, if any.
230
+
231
+ All lines in Matplotlib are considered either solid or "dashed". Some
232
+ common dashing patterns are built-in, and are sufficient for a majority of
233
+ uses:
234
+
235
+ =============================== =================
236
+ Linestyle Description
237
+ =============================== =================
238
+ ``'-'`` or ``'solid'`` solid line
239
+ ``'--'`` or ``'dashed'`` dashed line
240
+ ``'-.'`` or ``'dashdot'`` dash-dotted line
241
+ ``':'`` or ``'dotted'`` dotted line
242
+ ``'None'`` or ``' '`` or ``''`` draw nothing
243
+ =============================== =================
244
+
245
+ However, for more fine-grained control, one can directly specify the
246
+ dashing pattern by specifying::
247
+
248
+ (offset, onoffseq)
249
+
250
+ where ``onoffseq`` is an even length tuple specifying the lengths of each
251
+ subsequent dash and space, and ``offset`` controls at which point in this
252
+ pattern the start of the line will begin (to allow you to e.g. prevent
253
+ corners from happening to land in between dashes).
254
+
255
+ For example, (5, 2, 1, 2) describes a sequence of 5 point and 1 point
256
+ dashes separated by 2 point spaces.
257
+
258
+ Setting ``onoffseq`` to ``None`` results in a solid *LineStyle*.
259
+
260
+ The default dashing patterns described in the table above are themselves
261
+ all described in this notation, and can therefore be customized by editing
262
+ the appropriate ``lines.*_pattern`` *rc* parameter, as described in
263
+ :doc:`/tutorials/introductory/customizing`.
264
+
265
+ .. plot::
266
+ :alt: Demo of possible LineStyle's.
267
+
268
+ from matplotlib._types import LineStyle
269
+ LineStyle.demo()
270
+
271
+ .. note::
272
+
273
+ In addition to directly taking a ``linestyle`` argument,
274
+ `~.lines.Line2D` exposes a ``~.lines.Line2D.set_dashes`` method that
275
+ can be used to create a new *LineStyle* by providing just the
276
+ ``onoffseq``, but does not let you customize the offset. This method is
277
+ called when using the keyword *dashes* to the cycler , as shown in
278
+ :doc:`property_cycle </tutorials/intermediate/color_cycle>`.
279
+ """
280
+ solid = auto ()
281
+ dashed = auto ()
282
+ dotted = auto ()
283
+ dashdot = auto ()
284
+ none = auto ()
285
+ custom = auto ()
286
+
287
+ class LineStyle (str ):
288
+
289
+ def __init__ (self , ls , scale = 1 ):
290
+ """
291
+ Parameters
292
+ ----------
293
+ ls : str or dash tuple
294
+ A description of the dashing pattern of the line. Allowed string
295
+ inputs are {'-', 'solid', '--', 'dashed', '-.', 'dashdot', ':',
296
+ 'dotted', '', ' ', 'None', 'none'}. Alternatively, the dash tuple
297
+ (``offset``, ``onoffseq``) can be specified directly in points.
298
+ scale : float
299
+ Uniformly scale the internal dash sequence length by a constant
300
+ factor.
301
+ """
302
+
303
+ self ._linestyle_spec = ls
304
+ if isinstance (ls , str ):
305
+ if ls in [' ' , '' , 'None' ]:
306
+ ls = 'none'
307
+ if ls in _ls_mapper :
308
+ ls = _ls_mapper [ls ]
309
+ Enum .__init__ (self )
310
+ offset , onoffseq = None , None
311
+ else :
312
+ try :
313
+ offset , onoffseq = ls
314
+ except ValueError : # not enough/too many values to unpack
315
+ raise ValueError ('LineStyle should be a string or a 2-tuple, '
316
+ 'instead received: ' + str (ls ))
317
+ if offset is None :
318
+ _api .warn_deprecated (
319
+ "3.3" , message = "Passing the dash offset as None is deprecated "
320
+ "since %(since)s and support for it will be removed "
321
+ "%(removal)s; pass it as zero instead." )
322
+ offset = 0
323
+
324
+ if onoffseq is not None :
325
+ # normalize offset to be positive and shorter than the dash cycle
326
+ dsum = sum (onoffseq )
327
+ if dsum :
328
+ offset %= dsum
329
+ if len (onoffseq ) % 2 != 0 :
330
+ raise ValueError ('LineStyle onoffseq must be of even length.' )
331
+ if not all (isinstance (elem , Number ) for elem in onoffseq ):
332
+ raise ValueError ('LineStyle onoffseq must be list of floats.' )
333
+ self ._us_offset = offset
334
+ self ._us_onoffseq = onoffseq
335
+
336
+ def __hash__ (self ):
337
+ if self == LineStyle .custom :
338
+ return (self ._us_offset , tuple (self ._us_onoffseq )).__hash__ ()
339
+ return _AutoStringNameEnum .__hash__ (self )
340
+
341
+
342
+ def get_dashes (self , lw = 1 ):
343
+ """
344
+ Get the (scaled) dash sequence for this `.LineStyle`.
345
+ """
346
+ # defer lookup until draw time
347
+ if self ._us_offset is None or self ._us_onoffseq is None :
348
+ self ._us_offset , self ._us_onoffseq = \
349
+ LineStyle ._get_dash_pattern (self .name )
350
+ # normalize offset to be positive and shorter than the dash cycle
351
+ dsum = sum (self ._us_onoffseq )
352
+ self ._us_offset %= dsum
353
+ return self ._scale_dashes (self ._us_offset , self ._us_onoffseq , lw )
354
+
355
+ @staticmethod
356
+ def _scale_dashes (offset , dashes , lw ):
357
+ from . import rcParams
358
+ if not rcParams ['lines.scale_dashes' ]:
359
+ return offset , dashes
360
+ scaled_offset = offset * lw
361
+ scaled_dashes = ([x * lw if x is not None else None for x in dashes ]
362
+ if dashes is not None else None )
363
+ return scaled_offset , scaled_dashes
364
+
365
+ @staticmethod
366
+ def _get_dash_pattern (style ):
367
+ """Convert linestyle string to explicit dash pattern."""
368
+ # import must be local for validator code to live here
369
+ from . import rcParams
370
+ # un-dashed styles
371
+ if style in ['solid' , 'None' ]:
372
+ offset = 0
373
+ dashes = None
374
+ # dashed styles
375
+ elif style in ['dashed' , 'dashdot' , 'dotted' ]:
376
+ offset = 0
377
+ dashes = tuple (rcParams ['lines.{}_pattern' .format (style )])
378
+ return offset , dashes
379
+
380
+ @staticmethod
381
+ def from_dashes (seq ):
382
+ """
383
+ Create a `.LineStyle` from a dash sequence (i.e. the ``onoffseq``).
384
+
385
+ The dash sequence is a sequence of floats of even length describing
386
+ the length of dashes and spaces in points.
387
+
388
+ Parameters
389
+ ----------
390
+ seq : sequence of floats (on/off ink in points) or (None, None)
391
+ If *seq* is empty or ``(None, None)``, the `.LineStyle` will be
392
+ solid.
393
+ """
394
+ if seq == (None , None ) or len (seq ) == 0 :
395
+ return LineStyle ('-' )
396
+ else :
397
+ return LineStyle ((0 , seq ))
398
+
399
+ @staticmethod
400
+ def demo ():
401
+ import numpy as np
402
+ import matplotlib .pyplot as plt
403
+
404
+ linestyle_str = [
405
+ ('solid' , 'solid' ), # Same as (0, ()) or '-'
406
+ ('dotted' , 'dotted' ), # Same as (0, (1, 1)) or '.'
407
+ ('dashed' , 'dashed' ), # Same as '--'
408
+ ('dashdot' , 'dashdot' )] # Same as '-.'
409
+
410
+ linestyle_tuple = [
411
+ ('loosely dotted' , (0 , (1 , 10 ))),
412
+ ('dotted' , (0 , (1 , 1 ))),
413
+ ('densely dotted' , (0 , (1 , 1 ))),
414
+
415
+ ('loosely dashed' , (0 , (5 , 10 ))),
416
+ ('dashed' , (0 , (5 , 5 ))),
417
+ ('densely dashed' , (0 , (5 , 1 ))),
418
+
419
+ ('loosely dashdotted' , (0 , (3 , 10 , 1 , 10 ))),
420
+ ('dashdotted' , (0 , (3 , 5 , 1 , 5 ))),
421
+ ('densely dashdotted' , (0 , (3 , 1 , 1 , 1 ))),
422
+
423
+ ('dashdotdotted' , (0 , (3 , 5 , 1 , 5 , 1 , 5 ))),
424
+ ('loosely dashdotdotted' , (0 , (3 , 10 , 1 , 10 , 1 , 10 ))),
425
+ ('densely dashdotdotted' , (0 , (3 , 1 , 1 , 1 , 1 , 1 )))]
426
+
427
+ def plot_linestyles (ax , linestyles , title ):
428
+ X , Y = np .linspace (0 , 100 , 10 ), np .zeros (10 )
429
+ yticklabels = []
430
+
431
+ for i , (name , linestyle ) in enumerate (linestyles ):
432
+ ax .plot (X , Y + i , linestyle = linestyle , linewidth = 1.5 ,
433
+ color = 'black' )
434
+ yticklabels .append (name )
435
+
436
+ ax .set_title (title )
437
+ ax .set (ylim = (- 0.5 , len (linestyles )- 0.5 ),
438
+ yticks = np .arange (len (linestyles )),
439
+ yticklabels = yticklabels )
440
+ ax .tick_params (left = False , bottom = False , labelbottom = False )
441
+ for spine in ax .spines .values ():
442
+ spine .set_visible (False )
443
+
444
+ # For each line style, add a text annotation with a small offset
445
+ # from the reference point (0 in Axes coords, y tick value in Data
446
+ # coords).
447
+ for i , (name , linestyle ) in enumerate (linestyles ):
448
+ ax .annotate (repr (linestyle ),
449
+ xy = (0.0 , i ), xycoords = ax .get_yaxis_transform (),
450
+ xytext = (- 6 , - 12 ), textcoords = 'offset points' ,
451
+ color = "blue" , fontsize = 8 , ha = "right" ,
452
+ family = "monospace" )
453
+
454
+ ax0 , ax1 = (plt .figure (figsize = (10 , 8 ))
455
+ .add_gridspec (2 , 1 , height_ratios = [1 , 3 ])
456
+ .subplots ())
457
+
458
+ plot_linestyles (ax0 , linestyle_str [::- 1 ], title = 'Named linestyles' )
459
+ plot_linestyles (ax1 , linestyle_tuple [::- 1 ],
460
+ title = 'Parametrized linestyles' )
461
+
462
+ plt .tight_layout ()
463
+ plt .show ()
464
+
465
+
466
+ LineStyle ._ls_mapper = _ls_mapper
467
+ LineStyle ._deprecated_lineStyles = _deprecated_lineStyles
0 commit comments