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