@@ -262,8 +262,8 @@ def _axis_labels_title(data, axis=None, units=True):
262262
263263def standardize_1d (self , func , * args , ** kwargs ):
264264 """
265- Interprets positional arguments for the "1d" plotting methods
266- %(methods)s . This also optionally modifies the x axis label, y axis label,
265+ Interpret positional arguments for the "1d" plotting methods so usage is
266+ consistent . This also optionally modifies the x axis label, y axis label,
267267 title, and axis ticks if a `~xarray.DataArray`, `~pandas.DataFrame`, or
268268 `~pandas.Series` is passed.
269269
@@ -280,6 +280,10 @@ def standardize_1d(self, func, *args, **kwargs):
280280 See also
281281 --------
282282 cycle_changer
283+
284+ Note
285+ ----
286+ This function wraps the 1d plotting methods: %(methods)s.
283287 """
284288 # Sanitize input
285289 # TODO: Add exceptions for methods other than 'hist'?
@@ -311,8 +315,10 @@ def standardize_1d(self, func, *args, **kwargs):
311315 # Auto x coords
312316 y = ys [0 ] # test the first y input
313317 if x is None :
314- axis = 1 if (name in ('hist' , 'boxplot' , 'violinplot' ) or any (
315- kwargs .get (s , None ) for s in ('means' , 'medians' ))) else 0
318+ axis = int (
319+ name in ('hist' , 'boxplot' , 'violinplot' )
320+ or any (kwargs .get (s , None ) for s in ('means' , 'medians' ))
321+ )
316322 x , _ = _axis_labels_title (y , axis = axis )
317323 x = _to_array (x )
318324 if x .ndim != 1 :
@@ -452,8 +458,8 @@ def _standardize_latlon(x, y):
452458
453459def standardize_2d (self , func , * args , order = 'C' , globe = False , ** kwargs ):
454460 """
455- Interprets positional arguments for the "2d" plotting methods
456- %(methods)s . This also optionally modifies the x axis label, y axis label,
461+ Interpret positional arguments for the "2d" plotting methods so usage is
462+ consistent . This also optionally modifies the x axis label, y axis label,
457463 title, and axis ticks if a `~xarray.DataArray`, `~pandas.DataFrame`, or
458464 `~pandas.Series` is passed.
459465
@@ -484,6 +490,10 @@ def standardize_2d(self, func, *args, order='C', globe=False, **kwargs):
484490 See also
485491 --------
486492 cmap_changer
493+
494+ Note
495+ ----
496+ This function wraps the 2d plotting methods: %(methods)s.
487497 """
488498 # Sanitize input
489499 name = func .__name__
@@ -587,10 +597,7 @@ def standardize_2d(self, func, *args, order='C', globe=False, **kwargs):
587597 f'Input arrays must be 2d, instead got shape { Z .shape } .'
588598 )
589599 elif Z .shape [1 ] == xlen and Z .shape [0 ] == ylen :
590- if all (
591- z .ndim == 1 and z .size > 1
592- and _is_number (z ) for z in (x , y )
593- ):
600+ if all (z .ndim == 1 and z .size > 1 and _is_number (z ) for z in (x , y )):
594601 x = edges (x )
595602 y = edges (y )
596603 else :
@@ -610,6 +617,7 @@ def standardize_2d(self, func, *args, order='C', globe=False, **kwargs):
610617 f'Z centers { Z .shape } or '
611618 f'Z borders { tuple (i + 1 for i in Z .shape )} .'
612619 )
620+
613621 # Optionally re-order
614622 # TODO: Double check this
615623 if order == 'F' :
@@ -632,33 +640,27 @@ def standardize_2d(self, func, *args, order='C', globe=False, **kwargs):
632640 f'Input arrays must be 2d, instead got shape { Z .shape } .'
633641 )
634642 elif Z .shape [1 ] == xlen - 1 and Z .shape [0 ] == ylen - 1 :
635- if all (
636- z .ndim == 1 and z .size > 1
637- and _is_number (z ) for z in (x , y )
638- ):
643+ if all (z .ndim == 1 and z .size > 1 and _is_number (z ) for z in (x , y )):
639644 x = (x [1 :] + x [:- 1 ]) / 2
640645 y = (y [1 :] + y [:- 1 ]) / 2
641646 else :
642647 if (
643648 x .ndim == 2 and x .shape [0 ] > 1 and x .shape [1 ] > 1
644649 and _is_number (x )
645650 ):
646- x = 0.25 * (
647- x [:- 1 , :- 1 ] + x [:- 1 , 1 :] + x [1 :, :- 1 ] + x [1 :, 1 :]
648- )
651+ x = 0.25 * (x [:- 1 , :- 1 ] + x [:- 1 , 1 :] + x [1 :, :- 1 ] + x [1 :, 1 :])
649652 if (
650653 y .ndim == 2 and y .shape [0 ] > 1 and y .shape [1 ] > 1
651654 and _is_number (y )
652655 ):
653- y = 0.25 * (
654- y [:- 1 , :- 1 ] + y [:- 1 , 1 :] + y [1 :, :- 1 ] + y [1 :, 1 :]
655- )
656+ y = 0.25 * (y [:- 1 , :- 1 ] + y [:- 1 , 1 :] + y [1 :, :- 1 ] + y [1 :, 1 :])
656657 elif Z .shape [1 ] != xlen or Z .shape [0 ] != ylen :
657658 raise ValueError (
658659 f'Input shapes x { x .shape } and y { y .shape } '
659660 f'must match Z centers { Z .shape } '
660661 f'or Z borders { tuple (i + 1 for i in Z .shape )} .'
661662 )
663+
662664 # Optionally re-order
663665 # TODO: Double check this
664666 if order == 'F' :
@@ -1819,13 +1821,31 @@ def cycle_changer(
18191821 key = 'edgecolors'
18201822 kw [key ] = value
18211823
1822- # Get x coordinates
1823- x_col , y_first = x , ys [0 ] # samples
1824+ # Add x coordinates as pi chart labels by default
18241825 if name in ('pie' ,):
1825- kw ['labels' ] = _not_none (labels , x_col ) # TODO: move to pie wrapper?
1826+ kw ['labels' ] = _not_none (labels , x ) # TODO: move to pie wrapper?
1827+
1828+ # Step size for grouped bar plots
1829+ # WARNING: This will fail for non-numeric non-datetime64 singleton
1830+ # datatypes but this is good enough for vast majority of most cases.
1831+ x_test = np .atleast_1d (_to_ndarray (x ))
1832+ if len (x_test ) >= 2 :
1833+ x_step = x_test [1 :] - x_test [:- 1 ]
1834+ x_step = np .concatenate ((x_step , x_step [- 1 :]))
1835+ elif x_test .dtype == np .datetime64 :
1836+ x_step = np .timedelta64 (1 , 'D' )
1837+ else :
1838+ x_step = np .array (0.5 )
1839+ if np .issubdtype (x_test .dtype , np .datetime64 ):
1840+ x_step = x_step .astype ('timedelta64[ns]' ) # avoid int timedelta truncation
1841+
1842+ # Get x coordinates for bar plot
1843+ x_col , y_first = x , ys [0 ] # samples
18261844 if name in ('bar' ,): # adjust
18271845 if not stacked :
1828- x_col = x + (i - ncols / 2 + 0.5 ) * width / ncols
1846+ scale = i - 0.5 * (ncols - 1 ) # offset from true coordinate
1847+ scale = width * scale / ncols
1848+ x_col = x + x_step * scale
18291849 elif stacked and y_first .ndim > 1 :
18301850 key = 'x' if barh else 'bottom'
18311851 kw [key ] = _to_indexer (y_first )[:, :i ].sum (axis = 1 )
0 commit comments