@@ -2397,8 +2397,10 @@ def _parse_level_count(
23972397 )
23982398 try :
23992399 levels = locator .tick_values (vmin , vmax )
2400+ except TypeError : # e.g. due to datetime arrays
2401+ return None , kwargs
24002402 except RuntimeError : # too-many-ticks error
2401- levels = np .linspace (vmin , vmax , levels ) # TODO: _autolev used N+ 1
2403+ levels = np .linspace (vmin , vmax , levels ) # TODO: _autolev used N + 1
24022404
24032405 # Possibly trim levels far outside of 'vmin' and 'vmax'
24042406 # NOTE: This part is mostly copied from matplotlib _autolev
@@ -2468,48 +2470,58 @@ def _parse_level_list(
24682470 kwargs
24692471 Unused arguments.
24702472 """
2471- # Rigorously check user input levels and values
2473+ # Helper function that restricts levels
2474+ # NOTE: This should have no effect if levels were generated automatically.
2475+ # However want to apply these to manual-input levels as well.
2476+ def _restrict_levels (levels ):
2477+ if nozero :
2478+ levels = levels [levels != 0 ]
2479+ if positive :
2480+ levels = levels [levels >= 0 ]
2481+ if negative :
2482+ levels = levels [levels <= 0 ]
2483+ return levels
2484+
2485+ # Helper function to sanitize input levels
24722486 # NOTE: Include special case where color levels are referenced by string labels
2487+ def _sanitize_levels (key , array , minsize ):
2488+ if np .iterable (array ):
2489+ array , _ = pcolors ._sanitize_levels (array , minsize )
2490+ if isinstance (norm , (mcolors .BoundaryNorm , pcolors .SegmentedNorm )):
2491+ if array is not None :
2492+ warnings ._warn_proplot (
2493+ f'Ignoring { key } ={ array } . Using norm={ norm !r} { key } instead.'
2494+ )
2495+ if key == 'levels' :
2496+ array = _not_none (levels = array , norm_boundaries = norm .boundaries )
2497+ else :
2498+ array = None
2499+ return array
2500+
2501+ # Parse input arguments and resolve incompatibilities
2502+ vmin = vmax = None
24732503 levels = _not_none (N = N , levels = levels , norm_kw_levs = norm_kw .pop ('levels' , None ))
2474- min_levels = _not_none (min_levels , 2 ) # q for contour plots
24752504 if positive and negative :
2476- negative = False
24772505 warnings ._warn_proplot (
24782506 'Incompatible args positive=True and negative=True. Using former.'
24792507 )
2508+ negative = False
24802509 if levels is not None and values is not None :
24812510 warnings ._warn_proplot (
24822511 f'Incompatible args levels={ levels !r} and values={ values !r} . Using former.' # noqa: E501
24832512 )
2484- for key , points in (('levels' , levels ), ('values' , values )):
2485- if points is None :
2486- continue
2487- if isinstance (norm , (mcolors .BoundaryNorm , pcolors .SegmentedNorm )):
2488- warnings ._warn_proplot (
2489- f'Ignoring { key } ={ points } . Instead using norm={ norm !r} boundaries.'
2490- )
2491- if not np .iterable (points ):
2492- continue
2493- if len (points ) < min_levels :
2494- raise ValueError (
2495- f'Invalid { key } ={ points } . Must be at least length { min_levels } .'
2496- )
2497- if isinstance (norm , (mcolors .BoundaryNorm , pcolors .SegmentedNorm )):
2498- levels , values = norm .boundaries , None
2499- else :
2500- levels = _not_none (levels , rc ['cmap.levels' ])
2513+ values = None
2514+ levels = _sanitize_levels ('levels' , levels , _not_none (min_levels , 2 ))
2515+ levels = _not_none (levels , rc ['cmap.levels' ])
2516+ values = _sanitize_levels ('values' , values , 1 )
25012517
25022518 # Infer level edges from level centers if possible
25032519 # NOTE: The only way for user to manually impose BoundaryNorm is by
25042520 # passing one -- users cannot create one using Norm constructor key.
2505- if isinstance (values , Integral ):
2506- levels = values + 1
2507- elif values is None :
2521+ if values is None :
25082522 pass
2509- elif not np .iterable (values ):
2510- raise ValueError (f'Invalid values={ values !r} .' )
2511- elif len (values ) == 0 :
2512- levels = [] # weird but why not
2523+ elif isinstance (values , Integral ):
2524+ levels = values + 1
25132525 elif len (values ) == 1 :
25142526 levels = [values [0 ] - 1 , values [0 ] + 1 ] # weird but why not
25152527 elif norm is not None and norm not in ('segments' , 'segmented' ):
@@ -2519,16 +2531,16 @@ def _parse_level_list(
25192531 convert = constructor .Norm (norm , ** norm_kw )
25202532 levels = convert .inverse (utils .edges (convert (values )))
25212533 else :
2522- # Try to generate levels so SegmentedNorm will place 'values' ticks at the
2523- # center of each segment. edges() gives wrong result unless spacing is even.
2534+ # Generate levels so that ticks will be centered between edges
25242535 # Solve: (x1 + x2) / 2 = y --> x2 = 2 * y - x1 with arbitrary starting x1.
2536+ print ('hi!!!' , values )
25252537 descending = values [1 ] < values [0 ]
25262538 if descending : # e.g. [100, 50, 20, 10, 5, 2, 1] successful if reversed
25272539 values = values [::- 1 ]
25282540 levels = [1.5 * values [0 ] - 0.5 * values [1 ]] # arbitrary starting point
25292541 for value in values :
25302542 levels .append (2 * value - levels [- 1 ])
2531- if np .any (np .diff (levels ) < 0 ):
2543+ if np .any (np .diff (levels ) < 0 ): # never happens for evenly spaced levels
25322544 levels = utils .edges (values )
25332545 if descending : # then revert back below
25342546 levels = levels [::- 1 ]
@@ -2541,39 +2553,40 @@ def _parse_level_list(
25412553 pop = _pop_params (kwargs , self ._parse_level_count , ignore_internal = True )
25422554 if pop :
25432555 warnings ._warn_proplot (f'Ignoring unused keyword arg(s): { pop } ' )
2544- elif not skip_autolev :
2556+ if not np . iterable ( levels ) and not skip_autolev :
25452557 levels , kwargs = self ._parse_level_count (
25462558 * args , levels = levels , norm = norm , norm_kw = norm_kw , extend = extend ,
25472559 negative = negative , positive = positive , ** kwargs
25482560 )
25492561
2550- # Determine default norm
2551- # NOTE: DiscreteNorm does not currently support vmin and vmax different
2552- # from level list minimum and maximum.
2553- if levels is not None :
2554- if len (levels ) == 1 : # use central colormap color
2555- vmin , vmax = levels [0 ] - 1 , levels [0 ] + 1
2556- elif len (levels ) > 1 : # use minimum and maximum
2557- vmin , vmax = np .min (levels ), np .max (levels )
2558- if not np .allclose (levels [1 ] - levels [0 ], np .diff (levels )):
2559- norm = _not_none (norm , 'segmented' )
2560- if np .iterable (levels ) and norm in ('segments' , 'segmented' ):
2561- norm_kw ['levels' ] = levels
2562-
2563- # Determine default colorbar locator
2564- # NOTE: Always show all segmented levels in case distribution is uneven
2562+ # Determine default colorbar locator and norm and apply filters
2563+ # NOTE: DiscreteNorm does not currently support vmin and
2564+ # vmax different from level list minimum and maximum.
2565+ # NOTE: The level restriction should have no effect if levels were generated
2566+ # automatically. However want to apply these to manual-input levels as well.
25652567 locator = values if np .iterable (values ) else levels
2566- if locator is not None and np .iterable (locator ):
2568+ if np .iterable (locator ):
2569+ locator = _restrict_levels (locator )
25672570 if norm in ('segments' , 'segmented' ) or isinstance (norm , pcolors .SegmentedNorm ): # noqa: E501
25682571 locator = mticker .FixedLocator (locator )
25692572 else :
25702573 locator = pticker .DiscreteLocator (locator )
25712574 guides ._guide_kw_to_arg ('colorbar' , kwargs , locator = locator )
2575+ if np .iterable (levels ):
2576+ levels = _restrict_levels (levels )
2577+ if len (levels ) == 0 : # skip
2578+ pass
2579+ elif len (levels ) == 1 : # use central colormap color
2580+ vmin , vmax = levels [0 ] - 1 , levels [0 ] + 1
2581+ else : # use minimum and maximum
2582+ vmin , vmax = np .min (levels ), np .max (levels )
2583+ if not np .allclose (levels [1 ] - levels [0 ], np .diff (levels )):
2584+ norm = _not_none (norm , 'segmented' )
2585+ if norm in ('segments' , 'segmented' ):
2586+ norm_kw ['levels' ] = levels
25722587
25732588 # Filter the level boundaries
2574- # NOTE: This should have no effect if levels were generated automatically.
2575- # However want to apply these to manual-input levels as well.
2576- if levels is not None and np .iterable (levels ):
2589+ if np .iterable (levels ):
25772590 if nozero :
25782591 levels = levels [levels != 0 ]
25792592 if positive :
0 commit comments