@@ -2397,8 +2397,10 @@ def _parse_level_count(
2397
2397
)
2398
2398
try :
2399
2399
levels = locator .tick_values (vmin , vmax )
2400
+ except TypeError : # e.g. due to datetime arrays
2401
+ return None , kwargs
2400
2402
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
2402
2404
2403
2405
# Possibly trim levels far outside of 'vmin' and 'vmax'
2404
2406
# NOTE: This part is mostly copied from matplotlib _autolev
@@ -2468,48 +2470,58 @@ def _parse_level_list(
2468
2470
kwargs
2469
2471
Unused arguments.
2470
2472
"""
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
2472
2486
# 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
2473
2503
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
2475
2504
if positive and negative :
2476
- negative = False
2477
2505
warnings ._warn_proplot (
2478
2506
'Incompatible args positive=True and negative=True. Using former.'
2479
2507
)
2508
+ negative = False
2480
2509
if levels is not None and values is not None :
2481
2510
warnings ._warn_proplot (
2482
2511
f'Incompatible args levels={ levels !r} and values={ values !r} . Using former.' # noqa: E501
2483
2512
)
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 )
2501
2517
2502
2518
# Infer level edges from level centers if possible
2503
2519
# NOTE: The only way for user to manually impose BoundaryNorm is by
2504
2520
# 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 :
2508
2522
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
2513
2525
elif len (values ) == 1 :
2514
2526
levels = [values [0 ] - 1 , values [0 ] + 1 ] # weird but why not
2515
2527
elif norm is not None and norm not in ('segments' , 'segmented' ):
@@ -2519,16 +2531,16 @@ def _parse_level_list(
2519
2531
convert = constructor .Norm (norm , ** norm_kw )
2520
2532
levels = convert .inverse (utils .edges (convert (values )))
2521
2533
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
2524
2535
# Solve: (x1 + x2) / 2 = y --> x2 = 2 * y - x1 with arbitrary starting x1.
2536
+ print ('hi!!!' , values )
2525
2537
descending = values [1 ] < values [0 ]
2526
2538
if descending : # e.g. [100, 50, 20, 10, 5, 2, 1] successful if reversed
2527
2539
values = values [::- 1 ]
2528
2540
levels = [1.5 * values [0 ] - 0.5 * values [1 ]] # arbitrary starting point
2529
2541
for value in values :
2530
2542
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
2532
2544
levels = utils .edges (values )
2533
2545
if descending : # then revert back below
2534
2546
levels = levels [::- 1 ]
@@ -2541,39 +2553,40 @@ def _parse_level_list(
2541
2553
pop = _pop_params (kwargs , self ._parse_level_count , ignore_internal = True )
2542
2554
if pop :
2543
2555
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 :
2545
2557
levels , kwargs = self ._parse_level_count (
2546
2558
* args , levels = levels , norm = norm , norm_kw = norm_kw , extend = extend ,
2547
2559
negative = negative , positive = positive , ** kwargs
2548
2560
)
2549
2561
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.
2565
2567
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 )
2567
2570
if norm in ('segments' , 'segmented' ) or isinstance (norm , pcolors .SegmentedNorm ): # noqa: E501
2568
2571
locator = mticker .FixedLocator (locator )
2569
2572
else :
2570
2573
locator = pticker .DiscreteLocator (locator )
2571
2574
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
2572
2587
2573
2588
# 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 ):
2577
2590
if nozero :
2578
2591
levels = levels [levels != 0 ]
2579
2592
if positive :
0 commit comments