Skip to content

Commit 43c392b

Browse files
committed
Fix bar position bug in grouped bar plots
1 parent e9e5651 commit 43c392b

File tree

1 file changed

+40
-48
lines changed

1 file changed

+40
-48
lines changed

proplot/axes/plot.py

Lines changed: 40 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,8 @@
7979
If the latter, the levels should be monotonically increasing or
8080
decreasing (note that decreasing levels will only work with ``pcolor``
8181
plots, not ``contour`` plots). Default is :rc:`image.levels`.
82-
83-
Since this function also wraps `~matplotlib.axes.Axes.pcolor` and
84-
`~matplotlib.axes.Axes.pcolormesh`, this means they now
85-
accept the `levels` keyword arg. You can now discretize your
86-
colors in a ``pcolor`` plot just like with ``contourf``.
82+
Note this means you can now discretize your colormap colors in a
83+
``pcolor`` plot just like with ``contourf``.
8784
values : int or list of float, optional
8885
The number of level centers, or a list of level centers. If provided,
8986
levels are inferred using `~proplot.utils.edges`. This will override
@@ -1757,23 +1754,28 @@ def cycle_changer(
17571754
proplot.constructor.Cycle
17581755
proplot.constructor.Colors
17591756
"""
1760-
# Parse input
1761-
cycle_kw = cycle_kw or {}
1762-
legend_kw = legend_kw or {}
1763-
colorbar_kw = colorbar_kw or {}
1764-
1765-
# Test input
1757+
# Parse input args
17661758
# NOTE: Requires standardize_1d wrapper before reaching this. Also note
17671759
# that the 'x' coordinates are sometimes ignored below.
17681760
name = func.__name__
17691761
if not args:
17701762
return func(self, *args, **kwargs)
1771-
barh = name == 'bar' and kwargs.get('orientation', None) == 'horizontal'
1763+
barh = stacked = False
1764+
if name in ('bar', 'fill_between', 'fill_betweenx'):
1765+
stacked = kwargs.pop('stacked', False)
1766+
if name in ('bar',):
1767+
barh = kwargs.get('orientation', None) == 'horizontal'
1768+
if barh:
1769+
kwargs.setdefault('x', 0)
17721770
x, y, *args = args
1773-
if len(args) >= 1 and 'fill_between' in name:
1771+
ys = (y,)
1772+
if len(args) >= 1 and name in ('fill_between', 'fill_betweenx'):
17741773
ys, args = (y, args[0]), args[1:]
1775-
else:
1776-
ys = (y,)
1774+
if name in ('pie',): # add x coordinates as default pie chart labels
1775+
kwargs['labels'] = _not_none(labels, x) # TODO: move to pie wrapper?
1776+
cycle_kw = cycle_kw or {}
1777+
legend_kw = legend_kw or {}
1778+
colorbar_kw = colorbar_kw or {}
17771779

17781780
# Determine and temporarily set cycler
17791781
# NOTE: Axes cycle has no getter, only set_prop_cycle, which sets a
@@ -1850,13 +1852,25 @@ def cycle_changer(
18501852
if labels is None or isinstance(labels, str):
18511853
labels = [labels] * ncols
18521854

1853-
# Handle stacked bar plots
1854-
stacked = kwargs.pop('stacked', False)
1855-
if name in ('bar',):
1856-
width = kwargs.pop('width', 0.8)
1857-
kwargs['height' if barh else 'width'] = (
1858-
width if stacked else width / ncols
1859-
)
1855+
# Get step size for bar plots
1856+
# WARNING: This will fail for non-numeric non-datetime64 singleton
1857+
# datatypes but this is good enough for vast majority of most cases.
1858+
if name in ('bar',) and not stacked:
1859+
x_test = np.atleast_1d(_to_ndarray(x))
1860+
if len(x_test) >= 2:
1861+
x_step = x_test[1:] - x_test[:-1]
1862+
x_step = np.concatenate((x_step, x_step[-1:]))
1863+
elif x_test.dtype == np.datetime64:
1864+
x_step = np.timedelta64(1, 'D')
1865+
else:
1866+
x_step = np.array(0.5)
1867+
if np.issubdtype(x_test.dtype, np.datetime64):
1868+
# Avoid integer timedelta truncation
1869+
x_step = x_step.astype('timedelta64[ns]')
1870+
key = 'height' if barh else 'width'
1871+
width = kwargs.pop(key, 0.8)
1872+
width = width * x_step / ncols
1873+
kwargs[key] = width
18601874

18611875
# Plot susccessive columns
18621876
objs = []
@@ -1876,31 +1890,12 @@ def cycle_changer(
18761890
key = 'edgecolors'
18771891
kw[key] = value
18781892

1879-
# Add x coordinates as pi chart labels by default
1880-
if name in ('pie',):
1881-
kw['labels'] = _not_none(labels, x) # TODO: move to pie wrapper?
1882-
1883-
# Step size for grouped bar plots
1884-
# WARNING: This will fail for non-numeric non-datetime64 singleton
1885-
# datatypes but this is good enough for vast majority of most cases.
1886-
x_test = np.atleast_1d(_to_ndarray(x))
1887-
if len(x_test) >= 2:
1888-
x_step = x_test[1:] - x_test[:-1]
1889-
x_step = np.concatenate((x_step, x_step[-1:]))
1890-
elif x_test.dtype == np.datetime64:
1891-
x_step = np.timedelta64(1, 'D')
1892-
else:
1893-
x_step = np.array(0.5)
1894-
if np.issubdtype(x_test.dtype, np.datetime64):
1895-
x_step = x_step.astype('timedelta64[ns]') # avoid int timedelta truncation
1896-
18971893
# Get x coordinates for bar plot
18981894
x_col, y_first = x, ys[0] # samples
18991895
if name in ('bar',): # adjust
19001896
if not stacked:
1901-
scale = i - 0.5 * (ncols - 1) # offset from true coordinate
1902-
scale = width * scale / ncols
1903-
x_col = x + x_step * scale
1897+
offset = width * (i - 0.5 * (ncols - 1))
1898+
x_col = x + offset
19041899
elif stacked and y_first.ndim > 1:
19051900
key = 'x' if barh else 'bottom'
19061901
kw[key] = _to_indexer(y_first)[:, :i].sum(axis=1)
@@ -1915,7 +1910,7 @@ def cycle_changer(
19151910
# WARNING: If stacked=True then we always *ignore* second
19161911
# argument passed to fill_between. Warning should be issued
19171912
# by fill_between_wrapper in this case.
1918-
if stacked and 'fill_between' in name:
1913+
if stacked and name in ('fill_between', 'fill_betweenx'):
19191914
ys_col = tuple(
19201915
y_first if y_first.ndim == 1
19211916
else _to_indexer(y_first)[:, :ii].sum(axis=1)
@@ -1949,15 +1944,12 @@ def cycle_changer(
19491944

19501945
# Build coordinate arguments
19511946
x_ys_col = ()
1952-
if barh: # special, use kwargs only!
1947+
if barh: # special case, use kwargs only!
19531948
kw.update({'bottom': x_col, 'width': ys_col[0]})
1954-
kw.setdefault('x', kwargs.get('bottom', 0)) # required
19551949
elif name in ('pie', 'hist', 'boxplot', 'violinplot'):
19561950
x_ys_col = ys_col
19571951
else: # has x-coordinates, and maybe more than one y
19581952
x_ys_col = (x_col, *ys_col)
1959-
1960-
# Call plotting function
19611953
obj = func(self, *x_ys_col, *args, **kw)
19621954
if isinstance(obj, (list, tuple)) and len(obj) == 1:
19631955
obj = obj[0]

0 commit comments

Comments
 (0)