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