18
18
19
19
from matplotlib .externals import six
20
20
21
+ import operator
21
22
import os
22
23
import warnings
23
24
from matplotlib .fontconfig_pattern import parse_fontconfig_pattern
24
25
from matplotlib .colors import is_color_like
25
26
26
- from cycler import cycler , Cycler
27
+ # Don't let the original cycler collide with our validating cycler
28
+ from cycler import Cycler , cycler as ccycler
27
29
28
30
#interactive_bk = ['gtk', 'gtkagg', 'gtkcairo', 'qt4agg',
29
31
# 'tkagg', 'wx', 'wxagg', 'cocoaagg', 'webagg']
@@ -579,6 +581,7 @@ def __call__(self, s):
579
581
580
582
validate_grid_axis = ValidateInStrings ('axes.grid.axis' , ['x' , 'y' , 'both' ])
581
583
584
+
582
585
_prop_validators = {
583
586
'color' : validate_colorlist ,
584
587
'linewidth' : validate_floatlist ,
@@ -607,24 +610,86 @@ def __call__(self, s):
607
610
'ms' : 'markersize' ,
608
611
}
609
612
610
- def _cycler_wrap ( prop , vals ):
613
+ def cycler ( * args , ** kwargs ):
611
614
"""
612
- This is an internal wrapper around :func:`cycler`
613
- that will be used in :func:`validate_cycler` to normalize
614
- and validate property inputs.
615
+ Creates a :class:`cycler.Cycler` object much like :func:`cycler.cycler`,
616
+ but includes input validation.
617
+
618
+ cyl(arg)
619
+ cyl(label, itr)
620
+ cyl(label1=itr1[, label2=itr2[, ...]])
621
+
622
+ Form 1 simply copies a given `Cycler` object.
623
+
624
+ Form 2 composes a `Cycler` as an inner product of the
625
+ pairs of keyword arguments. In other words, all of the
626
+ iterables are cycled simultaneously, as if through zip().
627
+
628
+ Form 3 creates a `Cycler` from a label and an iterable.
629
+ This is useful for when the label cannot be a keyword argument
630
+ (e.g., an integer or a name that has a space in it).
631
+
632
+ Parameters
633
+ ----------
634
+ arg : Cycler
635
+ Copy constructor for Cycler.
636
+
637
+ label : name
638
+ The property key. In the 2-arg form of the function,
639
+ the label can be any hashable object. In the keyword argument
640
+ form of the function, it must be a valid python identifier.
641
+
642
+ itr : iterable
643
+ Finite length iterable of the property values.
644
+
645
+ Returns
646
+ -------
647
+ cycler : Cycler
648
+ New :class:`cycler.Cycler` for the given properties
615
649
616
650
"""
617
- norm_prop = _prop_aliases .get (prop , prop )
618
- validator = _prop_validators .get (norm_prop , None )
619
- if validator is None :
620
- raise TypeError ("Unknown artist property: %s" % prop )
621
- vals = validator (vals )
622
- # We will normalize the property names as well to reduce
623
- # the amount of alias handling code elsewhere.
624
- return cycler (norm_prop , vals )
651
+ if args and kwargs :
652
+ raise TypeError ("cyl() can only accept positional OR keyword "
653
+ "arguments -- not both." )
654
+ elif not args and not kwargs :
655
+ raise TypeError ("cyl() must have positional OR keyword arguments" )
656
+
657
+ if len (args ) == 1 :
658
+ if not isinstance (args [0 ], Cycler ):
659
+ raise TypeError ("If only one positional argument given, it must "
660
+ " be a Cycler instance." )
661
+
662
+ c = args [0 ]
663
+ unknowns = c .keys - (set (_prop_validators .keys ()) |
664
+ set (_prop_aliases .keys ()))
665
+ if unknowns :
666
+ # This is about as much validation I can do
667
+ raise TypeError ("Unknown artist properties: %s" % unknowns )
668
+ else :
669
+ return Cycler (c )
670
+ elif len (args ) == 2 :
671
+ pairs = [(args [0 ], args [1 ])]
672
+ elif len (args ) > 2 :
673
+ raise TypeError ("No more than 2 positional arguments allowed" )
674
+ else :
675
+ pairs = six .iteritems (kwargs )
676
+
677
+ validated = []
678
+ for prop , vals in pairs :
679
+ norm_prop = _prop_aliases .get (prop , prop )
680
+ validator = _prop_validators .get (norm_prop , None )
681
+ if validator is None :
682
+ raise TypeError ("Unknown artist property: %s" % prop )
683
+ vals = validator (vals )
684
+ # We will normalize the property names as well to reduce
685
+ # the amount of alias handling code elsewhere.
686
+ validated .append ((norm_prop , vals ))
687
+
688
+ return reduce (operator .add , (ccycler (k , v ) for k , v in validated ))
689
+
625
690
626
691
def validate_cycler (s ):
627
- 'return a cycler object from a string repr or the object itself'
692
+ 'return a Cycler object from a string repr or the object itself'
628
693
if isinstance (s , six .string_types ):
629
694
try :
630
695
# TODO: We might want to rethink this...
@@ -645,7 +710,7 @@ def validate_cycler(s):
645
710
# someone managed to hack the cycler module. But, if
646
711
# someone does that, this wouldn't make anything
647
712
# worse because we have to import the module anyway.
648
- s = eval (s , {'cycler' : _cycler_wrap })
713
+ s = eval (s , {'cycler' : cycler })
649
714
except BaseException as e :
650
715
raise ValueError ("'%s' is not a valid cycler construction: %s" %
651
716
(s , e ))
@@ -851,7 +916,7 @@ def validate_cycler(s):
851
916
# This entry can be either a cycler object or a
852
917
# string repr of a cycler-object, which gets eval()'ed
853
918
# to create the object.
854
- 'axes.prop_cycle' : [cycler ('color' , 'bgrcmyk' ),
919
+ 'axes.prop_cycle' : [ccycler ('color' , 'bgrcmyk' ),
855
920
validate_cycler ],
856
921
'axes.xmargin' : [0 , ValidateInterval (0 , 1 ,
857
922
closedmin = True ,
0 commit comments