From c6700995c9eb1993e7c41d868e9b55ee6e99bc31 Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Wed, 28 Aug 2019 11:08:18 -0500 Subject: [PATCH 01/30] Add where="between"/"edges" to step and step="between" to fill_between --- .../option_between_for_step_fill_between.rst | 17 +++ .../lines_bars_and_markers/filled_step.py | 26 +++- examples/lines_bars_and_markers/step_demo.py | 16 +++ lib/matplotlib/axes/_axes.py | 82 +++++++++--- lib/matplotlib/axes/_base.py | 7 +- lib/matplotlib/cbook/__init__.py | 118 +++++++++++++++++- lib/matplotlib/lines.py | 38 ++++-- .../test_lines/drawstyle_variants.png | Bin 10946 -> 15434 bytes lib/matplotlib/tests/test_axes.py | 64 ++++++++++ lib/matplotlib/tests/test_lines.py | 17 ++- 10 files changed, 351 insertions(+), 34 deletions(-) create mode 100644 doc/users/next_whats_new/option_between_for_step_fill_between.rst diff --git a/doc/users/next_whats_new/option_between_for_step_fill_between.rst b/doc/users/next_whats_new/option_between_for_step_fill_between.rst new file mode 100644 index 000000000000..b4f9a0465eff --- /dev/null +++ b/doc/users/next_whats_new/option_between_for_step_fill_between.rst @@ -0,0 +1,17 @@ +step() and fill_between() take a new option where/step="between" +------------------------------------------------------------------------ + +Previously one would need to trick step() and fill_between() to plot +data where x has one point than y, typically when plotting pre-binned +histograms. + +step() now takes where="between" for x, y satisfying either +len(x) + 1 = len(y) or len(x) = len(y) + 1. Plotting a step line "between" +specified edges in either direction. Convenience option where="edges" is +added to close the shape. + +fill_between() now takes step="between" for x, y satisfying +len(x) + 1 = len(y). Plotting fill "between" specified edges. + +fill_betweenx() now takes step="between" for x, y satisfying +len(x) = len(y) + 1. Plotting fill "between" specified edges. diff --git a/examples/lines_bars_and_markers/filled_step.py b/examples/lines_bars_and_markers/filled_step.py index 0427c8c31579..f8d95846a8f1 100644 --- a/examples/lines_bars_and_markers/filled_step.py +++ b/examples/lines_bars_and_markers/filled_step.py @@ -1,9 +1,9 @@ """ ========================= -Hatch-filled histograms +Filled histograms ========================= -Hatching capabilities for plotting histograms. +Filled histograms and hatching capabilities for plotting histograms. """ import itertools @@ -15,6 +15,28 @@ import matplotlib.ticker as mticker from cycler import cycler +############################################################################### +# Plain filled steps + +fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(9, 4.5), tight_layout=True) +ax1.fill_between([0, 1, 2, 3], [1, 2, 3], step='between') +ax2.fill_betweenx([0, 1, 2, 3], [0, 1, 2], step='between') +ax1.set_ylabel('counts') +ax1.set_xlabel('edges') +ax2.set_xlabel('counts') +ax2.set_ylabel('edges') + +fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(9, 4.5), tight_layout=True) +ax1.fill_between([0, 1, 2, 3], [1, 2, 3], [0, 1, 0], step='between') +ax2.fill_betweenx([0, 1, 2, 3], [1, 2, 3], [0, 1, 0], step='between') +ax1.set_ylabel('counts') +ax1.set_xlabel('edges') +ax2.set_xlabel('counts') +ax2.set_ylabel('edges') + + +############################################################################### +# Hatches def filled_hist(ax, edges, values, bottoms=None, orientation='v', **kwargs): diff --git a/examples/lines_bars_and_markers/step_demo.py b/examples/lines_bars_and_markers/step_demo.py index 12006b197e53..7f56bc7e5cd3 100644 --- a/examples/lines_bars_and_markers/step_demo.py +++ b/examples/lines_bars_and_markers/step_demo.py @@ -29,6 +29,22 @@ plt.legend(title='Parameter where:') plt.show() +# Plotting with where='between'/'edges' +values = np.array([6, 14, 32, 37, 48, 32, 21, 4]) # hist +edges = np.array([1., 2., 3., 4., 5., 6., 7., 8., 9.]) # bins +fig, axes = plt.subplots(3, 2) +axes = axes.flatten() +axes[0].step(edges, values, where='between') +axes[1].step(values, edges, where='between') +axes[2].step(edges, values, where='edges') +axes[3].step(values, edges, where='edges') +axes[4].step(edges, values, where='edges') +axes[4].semilogy() +axes[5].step(edges, values, where='edges') +axes[5].semilogy() + +fig.show() + ############################################################################# # # ------------ diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 40e48fd5512f..32907278408d 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -2079,7 +2079,8 @@ def step(self, x, y, *args, where='pre', data=None, **kwargs): An object with labelled data. If given, provide the label names to plot in *x* and *y*. - where : {'pre', 'post', 'mid'}, optional, default 'pre' + where : {'pre', 'post', 'mid', 'between', 'edges'}, optional + Default 'pre' Define where the steps should be placed: - 'pre': The y value is continued constantly to the left from @@ -2089,6 +2090,10 @@ def step(self, x, y, *args, where='pre', data=None, **kwargs): every *x* position, i.e. the interval ``[x[i], x[i+1])`` has the value ``y[i]``. - 'mid': Steps occur half-way between the *x* positions. + - 'between': Expects abs(len(x)-len(y)) == 1, steps have value y[i] + on the interval ``[x[i], x[i+1])`` + - 'edges': Expects abs(len(x)-len(y)) == 1, steps have value y[i] + on interval ``[x[i], x[i+1]), shape is closed at x[0], x[-1]`` Returns ------- @@ -2104,7 +2109,17 @@ def step(self, x, y, *args, where='pre', data=None, **kwargs): ----- .. [notes section required to get data note injection right] """ - cbook._check_in_list(('pre', 'post', 'mid'), where=where) + cbook._check_in_list(('pre', 'post', 'mid', 'between', 'edges'), + where=where) + + if where in ['between', 'edges']: + if len(x) == len(y) or abs(len(x)-len(y)) > 1: + raise ValueError(f"When plotting with 'between' or 'edges'" + f"input sizes have to be have to satisfy " + f"len(x) + 1 == len(y) or " + f"len(x) == len(y) + 1 but x " + f"and y have size {len(x)} and {len(y)}") + kwargs['drawstyle'] = 'steps-' + where return self.plot(x, y, *args, data=data, **kwargs) @@ -5089,7 +5104,7 @@ def fill_between(self, x, y1, y2=0, where=None, interpolate=False, Setting *interpolate* to *True* will calculate the actual intersection point and extend the filled region up to this point. - step : {'pre', 'post', 'mid'}, optional + step : {'pre', 'post', 'mid', 'between'}, optional Define *step* if the filling should be a step function, i.e. constant in between *x*. The value determines where the step will occur: @@ -5101,6 +5116,8 @@ def fill_between(self, x, y1, y2=0, where=None, interpolate=False, every *x* position, i.e. the interval ``[x[i], x[i+1])`` has the value ``y[i]``. - 'mid': Steps occur half-way between the *x* positions. + - 'between': Expects abs(len(x)-len(y)) == 1, steps have value y[i] + on the interval ``[x[i], x[i+1])`` Other Parameters ---------------- @@ -5124,17 +5141,29 @@ def fill_between(self, x, y1, y2=0, where=None, interpolate=False, .. [notes section required to get data note injection right] """ + + cbook._check_in_list((None, 'pre', 'post', 'mid', 'between'), + step=step) + if not rcParams['_internal.classic_mode']: kwargs = cbook.normalize_kwargs(kwargs, mcoll.Collection) if not any(c in kwargs for c in ('color', 'facecolor')): kwargs['facecolor'] = \ self._get_patches_for_fill.get_next_color() + if step == 'between': + if not len(x) == len(y1) + 1: + raise ValueError(f"When plotting with 'between' " + f"input sizes have to be have to satisfy " + f"len(x) == len(y1) + 1, but x " + f"and y1 have size {len(x)} and {len(y1)}") + # Handle united data, such as dates self._process_unit_info(xdata=x, ydata=y1, kwargs=kwargs) self._process_unit_info(ydata=y2) # Convert the arrays so we can work with them + x = ma.masked_invalid(self.convert_xunits(x)) y1 = ma.masked_invalid(self.convert_yunits(y1)) y2 = ma.masked_invalid(self.convert_yunits(y2)) @@ -5154,10 +5183,15 @@ def fill_between(self, x, y1, y2=0, where=None, interpolate=False, message="The parameter where must have the same size as x " "in fill_between(). This will become an error in " "future versions of Matplotlib.") - where = where & ~functools.reduce(np.logical_or, - map(np.ma.getmask, [x, y1, y2])) - x, y1, y2 = np.broadcast_arrays(np.atleast_1d(x), y1, y2) + y_arrays = [y1, y2] + + get_masks = cbook.pad_arrays(list(map(np.atleast_1d, + map(np.ma.getmask, + [x, *y_arrays]))), False) + where = where & ~functools.reduce(np.logical_or, get_masks) + + y1, y2 = np.broadcast_arrays(*y_arrays) polys = [] for ind0, ind1 in cbook.contiguous_regions(where): @@ -5217,8 +5251,9 @@ def get_interp_point(ind): collection = mcoll.PolyCollection(polys, **kwargs) # now update the datalim and autoscale - XY1 = np.array([x[where], y1[where]]).T - XY2 = np.array([x[where], y2[where]]).T + y1, y2 = cbook.pad_arrays([y1, y2, where], 0)[0:2] + XY1 = np.array(cbook.pad_arrays([x[where], y1[where]], 0)).T + XY2 = np.array(cbook.pad_arrays([x[where], y2[where]], 0)).T self.dataLim.update_from_data_xy(XY1, self.ignore_existing_data_limits, updatex=True, updatey=True) self.ignore_existing_data_limits = False @@ -5278,7 +5313,7 @@ def fill_betweenx(self, y, x1, x2=0, where=None, Setting *interpolate* to *True* will calculate the actual intersection point and extend the filled region up to this point. - step : {'pre', 'post', 'mid'}, optional + step : {'pre', 'post', 'mid', 'between'}, optional Define *step* if the filling should be a step function, i.e. constant in between *y*. The value determines where the step will occur: @@ -5290,6 +5325,8 @@ def fill_betweenx(self, y, x1, x2=0, where=None, every *x* position, i.e. the interval ``[x[i], x[i+1])`` has the value ``y[i]``. - 'mid': Steps occur half-way between the *x* positions. + - 'between': Expects abs(len(x)-len(y)) == 1, steps have value x[i] + on the interval ``[y[i], y[i+1])`` Other Parameters ---------------- @@ -5313,12 +5350,23 @@ def fill_betweenx(self, y, x1, x2=0, where=None, .. [notes section required to get data note injection right] """ + + cbook._check_in_list((None, 'pre', 'post', 'mid', 'between'), + step=step) + if not rcParams['_internal.classic_mode']: kwargs = cbook.normalize_kwargs(kwargs, mcoll.Collection) if not any(c in kwargs for c in ('color', 'facecolor')): kwargs['facecolor'] = \ self._get_patches_for_fill.get_next_color() + if step == 'between': + if not len(y) == len(x1) + 1: + raise ValueError(f"When plotting with 'between' " + f"input sizes have to be have to satisfy " + f"len(y) == len(x1) + 1, but y " + f"and x1 have size {len(y)} and {len(x1)}") + # Handle united data, such as dates self._process_unit_info(ydata=y, xdata=x1, kwargs=kwargs) self._process_unit_info(xdata=x2) @@ -5343,10 +5391,15 @@ def fill_betweenx(self, y, x1, x2=0, where=None, message="The parameter where must have the same size as y " "in fill_between(). This will become an error in " "future versions of Matplotlib.") - where = where & ~functools.reduce(np.logical_or, - map(np.ma.getmask, [y, x1, x2])) - y, x1, x2 = np.broadcast_arrays(np.atleast_1d(y), x1, x2) + x_arrays = [x1, x2] + + get_masks = cbook.pad_arrays(list(map(np.atleast_1d, + map(np.ma.getmask, + [y, *x_arrays]))), False) + where = where & ~functools.reduce(np.logical_or, get_masks) + + x1, x2 = np.broadcast_arrays(*x_arrays) polys = [] for ind0, ind1 in cbook.contiguous_regions(where): @@ -5405,8 +5458,9 @@ def get_interp_point(ind): collection = mcoll.PolyCollection(polys, **kwargs) # now update the datalim and autoscale - X1Y = np.array([x1[where], y[where]]).T - X2Y = np.array([x2[where], y[where]]).T + x1, x2 = cbook.pad_arrays([x1, x2, where], 0)[0:2] + X1Y = np.array(cbook.pad_arrays([x1[where], y[where]], 0)).T + X2Y = np.array(cbook.pad_arrays([x2[where], y[where]], 0)).T self.dataLim.update_from_data_xy(X1Y, self.ignore_existing_data_limits, updatex=True, updatey=True) self.ignore_existing_data_limits = False diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 9e36ee9ae9eb..142e4d1b619e 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -338,9 +338,10 @@ def _plot_args(self, tup, kwargs): if self.axes.yaxis is not None: self.axes.yaxis.update_units(y) - if x.shape[0] != y.shape[0]: - raise ValueError(f"x and y must have same first dimension, but " - f"have shapes {x.shape} and {y.shape}") + if not kwargs.get('drawstyle') in ['steps-between', 'steps-edges']: + if x.shape[0] != y.shape[0]: + raise ValueError(f"x and y must have same first dimension, but" + f" have shapes {x.shape} and {y.shape}") if x.ndim > 2 or y.ndim > 2: raise ValueError(f"x and y can be no greater than 2-D, but have " f"shapes {x.shape} and {y.shape}") diff --git a/lib/matplotlib/cbook/__init__.py b/lib/matplotlib/cbook/__init__.py index a6a163843a6a..64d421be1fc3 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -1460,6 +1460,32 @@ def violin_stats(X, method, points=100, quantiles=None): return vpstats +def pad_arrays(v, padval=np.nan): + """ + Pad list of arrays of varying lengths to the same size with specified + value + + Parameters + ---------- + v : iterable + List of arrays to be padded to the largest len. All elements must + support iteration + + padval : scalar, bool or NaN, defaul NaN + value to pad missing values with + + Returns + ------- + out : array + Array of input arrays padded to the same len by specified padval + + Examples + -------- + >>> a, b, c = pad_arrays([1,2,3,4], [1,2,3], [1]) + """ + return np.array(list(itertools.zip_longest(*v, fillvalue=padval))).T + + def pts_to_prestep(x, *args): """ Convert continuous line to pre-steps. @@ -1498,6 +1524,93 @@ def pts_to_prestep(x, *args): return steps +def pts_to_betweenstep(x, *args): + """ + Convert continuous line to between-steps. + + Given a set of ``N`` edges and ``N - 1`` values padded with Nan to size N, + converts to ``2N - 2`` points, which when connected linearly give + a step function connecting step edges at a specified value + + Parameters + ---------- + x : array + The x location of the step edges. May be empty. + + y1, ..., yp : array + y arrays to be turned into steps; must have length as ``x`` +/- 1. + Returns + ------- + out : array + The x and y values converted to steps in the same order as the input; + can be unpacked as ``x_out, y1_out, ..., yp_out``. If the input is + length ``N``, each of these arrays will be length ``2N - 2``. For + ``N=0``, the length will be 0. + + """ + args = np.array(args) + step_length = max(2 * max(len(x), len(args[0])) - 2, 0) + steps = np.zeros((1 + len(args), step_length)) + + if len(x) == len(args[0]) + 1: + steps[0, ::len(steps[0])-1] = x[::len(x)-1] + steps[0, 2::2] = x[1:-1] + steps[0, 1:-1:2] = x[1:-1] + steps[1:, 0::2] = args + steps[1:, 1::2] = args + + elif len(x) + 1 == len(args[0]): + steps[0, 0::2] = x + steps[0, 1::2] = x + steps[1:, ::len(steps[0])-1] = args[:, ::len(args[0])-1] + steps[1:, 1:-1:2] = args[:, 1:-1] + steps[1:, 2:-1:2] = args[:, 1:-1] + else: + raise ValueError(f"Expects abs(len(x)-len(y)) == 1") + + return steps + + +def pts_to_betweenstep_edges(x, *args): + """ + Convert continuous line to between-steps, adding edges + + Given a set of ``N`` edges and ``N - 1`` values padded with Nan to size N, + converts to ``2N`` points, which when connected linearly give + a step function connecting step edges at a specified value, with the first + and last edge going to 0 + + Parameters + ---------- + x : array + The x location of the step edges. May be empty. + + y1, ..., yp : array + y arrays to be turned into steps; must have length as ``x`` +/- 1. + Returns + ------- + out : array + The x and y values converted to steps in the same order as the input; + can be unpacked as ``x_out, y1_out, ..., yp_out``. If the input is + length ``N``, each of these arrays will be length ``2N``. For + ``N=0``, the length will be 0. + + """ + + steps = pts_to_betweenstep(x, *args) + edge_steps = np.zeros((steps.shape[0], steps.shape[1]+2)) + + edge_steps[:, 1:-1] = steps + if len(x) == len(args[0]) + 1: + edge_steps[0, ::len(edge_steps[0])-1] = steps[0, ::len(steps[0])-1] + elif len(x) + 1 == len(args[0]): + edge_steps[1:, ::len(edge_steps[0])-1] = steps[1:, ::len(steps[0])-1] + else: + raise ValueError(f"Expects abs(len(x)-len(y)) == 1") + + return edge_steps + + def pts_to_poststep(x, *args): """ Convert continuous line to post-steps. @@ -1513,7 +1626,6 @@ def pts_to_poststep(x, *args): y1, ..., yp : array y arrays to be turned into steps; all must be the same length as ``x``. - Returns ------- out : array @@ -1576,7 +1688,9 @@ def pts_to_midstep(x, *args): 'steps': pts_to_prestep, 'steps-pre': pts_to_prestep, 'steps-post': pts_to_poststep, - 'steps-mid': pts_to_midstep} + 'steps-mid': pts_to_midstep, + 'steps-between': pts_to_betweenstep, + 'steps-edges': pts_to_betweenstep_edges} def index_of(y): diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index c67c6f35f8e1..85b8637e09b4 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -232,6 +232,8 @@ class Line2D(Artist): 'steps-mid': '_draw_steps_mid', 'steps-pre': '_draw_steps_pre', 'steps-post': '_draw_steps_post', + 'steps-between': '_draw_steps_between', + 'steps-edges': '_draw_steps_edges', } _drawStyles_s = { @@ -621,8 +623,8 @@ def set_picker(self, p): def get_window_extent(self, renderer): bbox = Bbox([[0, 0], [0, 0]]) trans_data_to_xy = self.get_transform().transform - bbox.update_from_data_xy(trans_data_to_xy(self.get_xydata()), - ignore=True) + bbox.update_from_data_xy(trans_data_to_xy( + cbook.pad_arrays(self.get_xydata(), np.nan)), ignore=True) # correct for marker size, if any if self._marker: ms = (self._markersize / 72.0 * self.figure.dpi) * 0.5 @@ -673,8 +675,13 @@ def recache(self, always=False): else: y = self._y - self._xy = np.column_stack(np.broadcast_arrays(x, y)).astype(float) - self._x, self._y = self._xy.T # views + if self._drawstyle in ["steps-between", "steps-edges"]: + # Done separately, as x and y could have different length + self._x, self._y = x, y + self._xy = np.array([self._x, self._y]) + else: + self._xy = np.column_stack(np.broadcast_arrays(x, y)).astype(float) + self._x, self._y = self._xy.T # views self._subslice = False if (self.axes and len(x) > 1000 and self._is_sorted(x) and @@ -696,7 +703,11 @@ def recache(self, always=False): interpolation_steps = self._path._interpolation_steps else: interpolation_steps = 1 - xy = STEP_LOOKUP_MAP[self._drawstyle](*self._xy.T) + + if self._drawstyle in ["steps-between", "steps-edges"]: + xy = STEP_LOOKUP_MAP[self._drawstyle](*self._xy) + else: + xy = STEP_LOOKUP_MAP[self._drawstyle](*self._xy.T) self._path = Path(np.asarray(xy).T, _interpolation_steps=interpolation_steps) self._transformed_path = None @@ -711,7 +722,17 @@ def _transform_path(self, subslice=None): """ # Masked arrays are now handled by the Path class itself if subslice is not None: - xy = STEP_LOOKUP_MAP[self._drawstyle](*self._xy[subslice, :].T) + if self._drawstyle in ["steps-between", "steps-edges"]: + xy_asym_sliced = [row[subslice] for row in self._xy] + # If not asym after slice, crop as original + if len(set(map(len, xy_asym_sliced))): + if len(self._xy[0]) > len(self._xy[1]): + xy_asym_sliced[1] = xy_asym_sliced[1][:-1] + else: + xy_asym_sliced[0] = xy_asym_sliced[0][:-1] + xy = STEP_LOOKUP_MAP[self._drawstyle](*xy_asym_sliced) + else: + xy = STEP_LOOKUP_MAP[self._drawstyle](*self._xy[subslice, :].T) _path = Path(np.asarray(xy).T, _interpolation_steps=self._path._interpolation_steps) else: @@ -759,7 +780,8 @@ def draw(self, renderer): x0, x1 = self.axes.get_xbound() i0 = self._x_filled.searchsorted(x0, 'left') i1 = self._x_filled.searchsorted(x1, 'right') - subslice = slice(max(i0 - 1, 0), i1 + 1) + #subslice = slice(max(i0 - 1, 0), i1 + 1) + subslice = slice(max(i0 - 2, 0), i1 + 2) self.ind_offset = subslice.start self._transform_path(subslice) else: @@ -832,7 +854,7 @@ def draw(self, renderer): self.recache() self._transform_path(subslice) tpath, affine = (self._get_transformed_path() - .get_transformed_points_and_affine()) + .get_transformed_points_and_affine()) else: tpath, affine = (self._get_transformed_path() .get_transformed_points_and_affine()) diff --git a/lib/matplotlib/tests/baseline_images/test_lines/drawstyle_variants.png b/lib/matplotlib/tests/baseline_images/test_lines/drawstyle_variants.png index 8b6d97b7b4079f2e38ab981e63f4ea10fdc5b8e0..f5286e834299db75d6bba36a1ef8d7cdc24f448f 100644 GIT binary patch literal 15434 zcmeHu2UL^U+HOD@b#!3PSP>BCAjX1%fG7z?$5BKOR1Anzr9`Df2?0V2j*c7)K?%|W zN{dnj1f&LK1VTxqm(U3yB?J;$AS8ExpmUsZ&i|kJ?_GD@wXQB(V!rJC?YBJd^E}`E zb4GgKe7*f^7!3A}{^=htz+kI(!eHW;)_w(EiSqdACir7D_6PloYrzlSwf6r2|6X_V zv^f?Alez-^5__Rbxei`D?|$-4i*{Eu{|1w3TA4EiJxob;#199dg}? zuYOeJ`Nc-!OIo7dUuKMEbK1ihMnVWF#Dm=X3 zETc9=(IADW#;!eYn_M@edHE;t3kqYcnZ^VHq4`h%-qy?OXsT&?3Zmk*n%?aTL!+_> zj*Go~l4KCRd!+c8kGFTqHza#o+nsmr+(A{IzL2S;U&5_zRbqRnYJ~aD=Fk>qX!Blf z@7B@U(Qry!tYAvMb@-P4C{HUWC@6_aC5Vb6ooeg#OetE~hUrG;6%`>u;cZLr5nCcM zV^p2y5dIB|jmZ>dwP!mhgp8%f>W-47afb#-;+Ry}FjZ*U<~ zi+7-=?g-qeb$6Yk+tLh8E%u3E2(#O%Wfwfow^uY#!bxWggfxxe�o6jaBvJ&B@#< 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 z%0A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 literal 10946 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 diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 01110ae26855..c686bcd4013f 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -1084,6 +1084,29 @@ def test_fill_between_interpolate_decreasing(): ax.set_ylim(800, 600) +@check_figures_equal() +def test_fill_between_histlike(fig_test, fig_ref): + x, y = [0, 1, 2, 3, 4, 5], [1, 2, 3, 4, 5] + y2 = np.zeros(len(y))-5 + # Test + fig_test, test_axes = plt.subplots(2, 2) + test_axes = test_axes.flatten() + test_axes[0].fill_between(x, y, step='between') + test_axes[1].fill_betweenx(x, x1=y, step='between') + test_axes[2].fill_between(x, y, y2=y2, step='between') + test_axes[3].fill_betweenx(x, x1=y, x2=y2, step='between') + + # Ref + fig_ref, ref_axes = plt.subplots(2, 2) + ref_axes = ref_axes.flatten() + ref_axes[0].fill_between(x, np.r_[y, y[-1]], step='post') + ref_axes[1].fill_betweenx(x, np.r_[y, y[-1]], step='post') + ref_axes[2].fill_between(x, np.r_[y, y[-1]], + np.r_[y2, y2[-1]], step='post') + ref_axes[3].fill_betweenx(x, np.r_[y, y[-1]], + np.r_[y2, y2[-1]], step='post') + + # test_symlog and test_symlog2 used to have baseline images in all three # formats, but the png and svg baselines got invalidated by the removal of # minor tick overstriking. @@ -3700,6 +3723,47 @@ def test_step_linestyle(): ax.set_ylim([-1, 7]) +@check_figures_equal() +def test_step_histlike(fig_test, fig_ref): + import matplotlib.lines as mlines + y = np.array([6, 14, 32, 37, 48, 32, 21, 4]) # hist + x = np.array([1., 2., 3., 4., 5., 6., 7., 8., 9.]) # bins + # Test + fig_test, test_axes = plt.subplots(3, 2) + test_axes = test_axes.flatten() + test_axes[0].step(x, y, where='between') + test_axes[1].step(y, x, where='between') + test_axes[2].step(x, y, where='edges') + test_axes[3].step(y, x, where='edges') + test_axes[4].step(x, y, where='edges') + test_axes[4].semilogy() + test_axes[5].step(x, y, where='edges') + test_axes[5].semilogy() + # Ref + fig_ref, ref_axes = plt.subplots(4, 2) + ref_axes = ref_axes.flatten() + ref_axes[0].plot(x, np.r_[y, y[-1]], drawstyle='steps-post') + ref_axes[1].plot(np.r_[y[0], y], x, drawstyle='steps-post') + + ref_axes[2].plot(x, np.r_[y, y[-1]], drawstyle='steps-post') + ref_axes[2].add_line(mlines.Line2D([x[0], x[0]], [0, y[0]])) + ref_axes[2].add_line(mlines.Line2D([x[-1], x[-1]], [0, y[-1]])) + + ref_axes[3].plot(np.r_[y[0], y], x, drawstyle='steps-post') + ref_axes[3].add_line(mlines.Line2D([0, y[0]], [x[0], x[0]])) + ref_axes[3].add_line(mlines.Line2D([0, y[-1]], [x[-1], x[-1]])) + + ref_axes[4].plot(x, np.r_[y, y[-1]], drawstyle='steps-post') + ref_axes[4].add_line(mlines.Line2D([x[0], x[0]], [0, y[0]])) + ref_axes[4].add_line(mlines.Line2D([x[-1], x[-1]], [0, y[-1]])) + ref_axes[4].semilogy() + + ref_axes[5].plot(np.r_[y[0], y], x, drawstyle='steps-post') + ref_axes[5].add_line(mlines.Line2D([0, y[0]], [x[0], x[0]])) + ref_axes[5].add_line(mlines.Line2D([0, y[-1]], [x[-1], x[-1]])) + ref_axes[5].semilogx() + + @image_comparison(['mixed_collection'], remove_text=True) def test_mixed_collection(): from matplotlib import patches diff --git a/lib/matplotlib/tests/test_lines.py b/lib/matplotlib/tests/test_lines.py index 517b467e0994..d8389567f383 100644 --- a/lib/matplotlib/tests/test_lines.py +++ b/lib/matplotlib/tests/test_lines.py @@ -106,15 +106,22 @@ def test_valid_linestyles(): @image_comparison(['drawstyle_variants.png'], remove_text=True) def test_drawstyle_variants(): - fig, axs = plt.subplots(6) - dss = ["default", "steps-mid", "steps-pre", "steps-post", "steps", None] + fig, axs = plt.subplots(4, 2) + dss = ["default", "steps", "steps-mid", "steps-pre", "steps-post", + "steps-between", "steps-edges", None] # We want to check that drawstyles are properly handled even for very long # lines (for which the subslice optimization is on); however, we need # to zoom in so that the difference between the drawstyles is actually # visible. - for ax, ds in zip(axs.flat, dss): - ax.plot(range(2000), drawstyle=ds) - ax.set(xlim=(0, 2), ylim=(0, 2)) + for ax, ds in zip(axs.T.flat, dss): + x = np.arange(1, 1200) + if ds in ["steps-between", "steps-edges"]: + ax.plot(x, x[:-1], drawstyle=ds) + ax.plot(x[:-1], x, drawstyle=ds) + else: + ax.plot(x, x+1, drawstyle=ds) + ax.plot(x+1, x, drawstyle=ds) + ax.set(xlim=(0, 4), ylim=(0, 4)) def test_valid_drawstyles(): From f9d9d431e609ba20e64835e897520cc066b47780 Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Wed, 28 Aug 2019 13:24:18 -0500 Subject: [PATCH 02/30] Protect for scalars in fill_between --- lib/matplotlib/axes/_axes.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 32907278408d..0743c81fb4c0 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -5191,7 +5191,10 @@ def fill_between(self, x, y1, y2=0, where=None, interpolate=False, [x, *y_arrays]))), False) where = where & ~functools.reduce(np.logical_or, get_masks) - y1, y2 = np.broadcast_arrays(*y_arrays) + if np.ma.getdata(y1).shape != (): + y1, y2 = np.broadcast_arrays(*y_arrays) + else: + x, y1, y2 = np.broadcast_arrays(*[x, *y_arrays]) polys = [] for ind0, ind1 in cbook.contiguous_regions(where): From 723dcdd6816a7d3f0323af1dcd186458aa161ac6 Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Wed, 28 Aug 2019 18:49:12 -0500 Subject: [PATCH 03/30] Fix fill_between input handling --- lib/matplotlib/axes/_axes.py | 78 +++++++++++------- lib/matplotlib/cbook/__init__.py | 47 +++++++++++ .../test_axes/fill_between_interpolate.pdf | Bin 5187 -> 5038 bytes .../test_axes/fill_between_interpolate.png | Bin 34822 -> 34768 bytes 4 files changed, 94 insertions(+), 31 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 0743c81fb4c0..a6453af177c2 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -5151,13 +5151,6 @@ def fill_between(self, x, y1, y2=0, where=None, interpolate=False, kwargs['facecolor'] = \ self._get_patches_for_fill.get_next_color() - if step == 'between': - if not len(x) == len(y1) + 1: - raise ValueError(f"When plotting with 'between' " - f"input sizes have to be have to satisfy " - f"len(x) == len(y1) + 1, but x " - f"and y1 have size {len(x)} and {len(y1)}") - # Handle united data, such as dates self._process_unit_info(xdata=x, ydata=y1, kwargs=kwargs) self._process_unit_info(ydata=y2) @@ -5184,21 +5177,26 @@ def fill_between(self, x, y1, y2=0, where=None, interpolate=False, "in fill_between(). This will become an error in " "future versions of Matplotlib.") - y_arrays = [y1, y2] + if step == 'between': + y1, y2, where = cbook.broadcaster([y1, y2, where], + def_size=x.size - 1) + else: + y1, y2, where = cbook.broadcaster([y1, y2, where], + def_size=x.size) get_masks = cbook.pad_arrays(list(map(np.atleast_1d, - map(np.ma.getmask, - [x, *y_arrays]))), False) - where = where & ~functools.reduce(np.logical_or, get_masks) + map(np.ma.getmask, + [y1, y2]))), False) - if np.ma.getdata(y1).shape != (): - y1, y2 = np.broadcast_arrays(*y_arrays) - else: - x, y1, y2 = np.broadcast_arrays(*[x, *y_arrays]) + where = where & ~functools.reduce(np.logical_or, get_masks) polys = [] - for ind0, ind1 in cbook.contiguous_regions(where): - xslice = x[ind0:ind1] + pad_where = cbook.pad_arrays([where, x], False)[0].astype(bool) + for ind0, ind1 in cbook.contiguous_regions(pad_where): + if step == 'between': + xslice = x[ind0:ind1+1] + else: + xslice = x[ind0:ind1] y1slice = y1[ind0:ind1] y2slice = y2[ind0:ind1] if step is not None: @@ -5254,9 +5252,14 @@ def get_interp_point(ind): collection = mcoll.PolyCollection(polys, **kwargs) # now update the datalim and autoscale - y1, y2 = cbook.pad_arrays([y1, y2, where], 0)[0:2] - XY1 = np.array(cbook.pad_arrays([x[where], y1[where]], 0)).T - XY2 = np.array(cbook.pad_arrays([x[where], y2[where]], 0)).T + # For between pad with mean value + if step == 'between': + y1, y2 = cbook.pad_arrays([y1, y2, x], + np.mean([np.mean(y1.flatten()), + np.mean(y2.flatten())]))[:-1] + where = cbook.pad_arrays([where, x], 1)[0].astype(bool) + XY1 = np.array([x[where], y1[where]]).T + XY2 = np.array([x[where], y2[where]]).T self.dataLim.update_from_data_xy(XY1, self.ignore_existing_data_limits, updatex=True, updatey=True) self.ignore_existing_data_limits = False @@ -5391,22 +5394,30 @@ def fill_betweenx(self, y, x1, x2=0, where=None, if where.size != y.size: cbook.warn_deprecated( "3.2", - message="The parameter where must have the same size as y " + message="The parameter where must have the same size as x " "in fill_between(). This will become an error in " "future versions of Matplotlib.") - x_arrays = [x1, x2] + if step == 'between': + x1, x2, where = cbook.broadcaster([x1, x2, where], + def_size=y.size - 1) + else: + x1, x2, where = cbook.broadcaster([x1, x2, where], + def_size=y.size) get_masks = cbook.pad_arrays(list(map(np.atleast_1d, - map(np.ma.getmask, - [y, *x_arrays]))), False) - where = where & ~functools.reduce(np.logical_or, get_masks) + map(np.ma.getmask, + [x1, x2]))), False) - x1, x2 = np.broadcast_arrays(*x_arrays) + where = where & ~functools.reduce(np.logical_or, get_masks) polys = [] - for ind0, ind1 in cbook.contiguous_regions(where): - yslice = y[ind0:ind1] + pad_where = cbook.pad_arrays([where, y], False)[0].astype(bool) + for ind0, ind1 in cbook.contiguous_regions(pad_where): + if step == 'between': + yslice = y[ind0:ind1+1] + else: + yslice = y[ind0:ind1] x1slice = x1[ind0:ind1] x2slice = x2[ind0:ind1] if step is not None: @@ -5461,9 +5472,14 @@ def get_interp_point(ind): collection = mcoll.PolyCollection(polys, **kwargs) # now update the datalim and autoscale - x1, x2 = cbook.pad_arrays([x1, x2, where], 0)[0:2] - X1Y = np.array(cbook.pad_arrays([x1[where], y[where]], 0)).T - X2Y = np.array(cbook.pad_arrays([x2[where], y[where]], 0)).T + # For between pad with mean value + if step == 'between': + x1, x2 = cbook.pad_arrays([x1, x2, y], + np.mean([np.mean(x1.flatten()), + np.mean(x2.flatten())]))[:-1] + where = cbook.pad_arrays([where, y], 1)[0].astype(bool) + X1Y = np.array([x1[where], y[where]]).T + X2Y = np.array([x2[where], y[where]]).T self.dataLim.update_from_data_xy(X1Y, self.ignore_existing_data_limits, updatex=True, updatey=True) self.ignore_existing_data_limits = False diff --git a/lib/matplotlib/cbook/__init__.py b/lib/matplotlib/cbook/__init__.py index 64d421be1fc3..414b759ec861 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -1483,9 +1483,56 @@ def pad_arrays(v, padval=np.nan): -------- >>> a, b, c = pad_arrays([1,2,3,4], [1,2,3], [1]) """ + if len(set([k.shape[0] for k in v])) == 1: + return v return np.array(list(itertools.zip_longest(*v, fillvalue=padval))).T +def broadcaster(v, def_size=1): + """ + Broadcast arrays together, if all arrays are of size 1, can specify size + to expand to + + Parameters + ---------- + v : iterable + List of arrays to be padded to the largest len. All elements must + support iteration + + def_size : scalar + Size to with to broadcaset to if all broadcasted arrays have size 1. + + Returns + ------- + out : array + Array of input arrays padded to the same len by specified padval + """ + + def get_lens(v): + return np.array([np.atleast_1d(k).size for k in v]) + + def have_lens(v): + return (get_lens(v) > 1) + + if np.sum((have_lens([*v]))) == 0: + vb = np.broadcast_arrays(v[0]*np.ones(def_size), *v[1:]) + elif np.sum((have_lens([*v]))) == 1: + vb = np.broadcast_arrays(*v) + else: + if len(set(get_lens([*v]))) == 1: + vb = v + elif (len(set(get_lens([*v]))) == 2 and + 1 in set(get_lens([*v]))): + vb = np.broadcast_arrays(*v) + else: + raise ValueError(f"Cannot broadcast arrays where more " + f"than one of them have different value " + f"of size > 1. " + f"Attempting to broadcast arrays of " + f"sizes {get_lens([*v])}.") + return vb + + def pts_to_prestep(x, *args): """ Convert continuous line to pre-steps. diff --git a/lib/matplotlib/tests/baseline_images/test_axes/fill_between_interpolate.pdf b/lib/matplotlib/tests/baseline_images/test_axes/fill_between_interpolate.pdf index eeb8969fa702ea20bca210c3c2945aee6edc5580..98c3e8cde324b0559ba7eddc7698c181c379cec8 100644 GIT binary patch delta 3616 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 delta 3762 zcmV;j4o&f{C&MVPz5##Rt{t}xecxZPzhJD}=<*n&fQvpg?L!}mJ`}F&Ht;dfIB0&q zOCwT4YIshLFF@nK4FYG&8cIX1St1uzfB)g;?{Dw!{saE~`Tp1Y-|*)@?*9G?|GvFB z{Cc?UCF9R`$Db(%{FkFY|8=|l^Y-F9O#OfvuWlvWL-bp5%{71DpY(N0a_Aim#ag$D zUwDB3^RV@wyEHMLNh#wAzn6{2T)Y4H=Kh!a`}>RULSNfs|6c6B@8!iG|NhfYZ+?7# zfBoV9;`jdk;r095v7UEZ*c1N22K8_32TNsa&*!cBl2XH1bz2Md9z)zh$#w59UiU3F zJR91!M^|g^V{Lz{d%nlGZ>hT0r?s@fPciw;#pr#v*6LO&R=`WS)>`O3@eZaFme}2v zoli7aw&L;hp0KB!Ltn%Duz7?b!W%iJz0%n3YVqY5u6}n;&#hZ7uCy$z=CXP3_FCln z#c!>Y=91WG30up#`Fy@lahvmdDowm~j$1@zlJw3Fv$=n^V`27Ua@!uVZ_?Xg-)i;I zCqjgb2w?H@cJnoES%=O8H?BX8&G+pcEBgjtV42X$7Gemo9fNs;r!j~SlX7#-byzauxNxMLO~pW$8wCNt-@g;x2?I7$5`7UJR341n?lOR zDgaGE3_^bnTP<#E8NzoA+OXo9ywjmMsHORnx>Zb2A>3kR^K(TZ(^(A4tLg)IUq*^7-veB3@d z7sVUJ2$MAfFXyYrg!#4B7=@1%PT89Zr|foxQ;vU+0xAVkA(bPppvu@#SY;F`uui;5 zv<`HJwr>lugx0paGD&m^j$Eh&^s((LmqZ^ow~$Hn1-sE~5(7N3(n$>Xu2wpU5#V3x zq??Zmo%FF{(8)PhglSrpVVdj9FwLFHF!3wNFwIw&VVX{oVVdrgVVaJ=hABD3LWe1N z4|{(Li6jpXtR#|x+x$u*_{_Jwl1M@z{X!xMAHlwmNQwZ|LLw;vc`J$Z1F)?mf^sPV1%-eNGiaEK_Wa?lE^eFNrabIB*Gh&B*Jf` zNQ7QpkqG-pp_9F-(8+FB=;Zh)bW$J{Iyryh3Z0A%g-%A1Lg&QW1)bmDKRNg5S=x_p zISIOnBxwHSFDE{~@SBcdGu6OZ43_I}g$TNR2fcN^#0T$t=rRrrgt#2J3f_C~G0r_Q zsUGn}u)%0y9mJQ?^71%hcZ|t(QruEMQ)Eg6XaE^Y?z97Joo!T>wRe8bOFo7*kH~)s zru7-&?5Gor?17Bns-|v`JrEc)$$OB2k69lFm!#&|Lu4%=AIqWOe`4LvJFSK~PBPkI zz!wmbbK@kVSLq*raL$d*jCbyNKx%M$5Y7p3=MI0BUAPemBVOA3kwKEf1HORR>VAAw z92tNgUwaK>@exAB5gcp95fc@XBPM@-1V>DuNseGdN{*nz+RT7(V(*>9(~_#~Ao2cQ zDyhS_-?|56_LouCbXUnN5oFSzRoNt{Gj`vrw6nrNJxVG;tr_?U`V{IjO2?5ruP*7w z^M{5tg`>Hy#L?WL#L;{gi6ee#iKFQpiKFR7iKFRniK8>LFocB29k zJ6r*YBcp&sAykCKu~vjMQ6YZ`Y2rr^(gd0$Bu3f2lh zDK)@!AtZ3@<`zQ22Lo3wgtVi4aw{S2u0OUA(vEDst%UUCpu)(sDKRovml&D*lo;U` zk{FpUE-^AaBr!65DKT<-eI=Vu&d|dB4_cwRjpXdb@810P->={N>D7PB`|A&eHuY6% zQQs25FO&#=Q@1`v-TF=Ca9^*4^ey}PGTGNRdFqqosjoG0y8Ze8mSspm%IejaJsqR> zW3-5@PLkPjvYS(88_Ou~Z5_Z83`}Lg)*uYA!c;X({lipBOijfSUrd$8)N^d*$I^(N zNHin>>;{O{DlwZXX7_)^Y}42c9joK>LrVoE8N=wiwudH zOkD2yJS_KoFy|R}gT}1!jA4N`>99$6AN?*kL|yxNYi%?fA~CP=Qm%3QXBrEkp&_My0s*pvm9I{XeRHj;Eec^SDj`4vDElfe{~X zgB$aXO2{KGOiLx?6LJhX7pM^O;BYsKqfrQX93wlQDNl`|NOA>7QW5t0AyVBQjtg(J zEb4;baz1}*(6ig#D<9u$s&V)|ScB>cU?1NDzO;6KCnopoBe|6C`bM2{0G9&3>ld>w zqEet&S9R7AR0??Ct3583o*a~Jm^P&!=IU2RaPklL?c*qYFvey*P2+c7NEY_68GCMCluAa>#|fi?&Y8c2JtzJO_ni1EkUu>r zV?J%ln9s$pG3QiYf!`QZ7uFQ`&&d}*j1yA%fpcF4e$IUr_&N7gIDdLjI%3+Cj+m=o z9l?Lut_1&eY$m`GnVkPh0CV~)fzIi#9vj)OdUhngYKgOXDZx)OPw=xj1%6&vfuA?1 zz|U`@z)x?iz|Wq!2K^(Cmi1cIdD7j)wW>#$cixEdUGn3yhXpG52&~MS7YS7TEc8AnJN(}REiN;p;Cmx3YBD@%c=D2AvT6YqqvZQTGsi_J$o){Zogc}Ju>43E3+jI( zVbrLPWa3MHBn(9Pkz6i9ouBS`4@w=ZBy4qQVv10tYjtj$0vO3!0A4z82_34I@5Sp> z&q&qkg0Eli7%2*6RZB52t6GYyFK65Hhsc_Q&RkbQhj%EU!>=NsLvJjh!;X>AVFya+ zu%jh(I3N-_m)lQ@d`J;fq`-=pVh?{-#1zl4B(@9=NlZUJBr&}XNn-klCP_?@4@+W- zd{`1wS@D&1EHmc!Lr_{3ZfH^uiKB z>==n4_Mk)%yILZMV#Fr!=2I7@`%)H^e^Q3zEocKS!x3}9(8{TF&d9L*PzCu%1Nm2sB#x-JE|P#S0pn0hO)BHQXHt& zsL%b;YSak=wHkFML9Ireb5N^MXD7DQ@GY_WLW$Mq$}OYSsGv%%Mull^HL9e5I*lrM zphlxgGJZuW%h#C}o%fl(bS`lb%^ys0-4fvqQT)LylkIxith~({-L8M;*P7oF1}p)= z5*jQ)!q6=Yb;A-tY_-JFRh~G_rGRPc{8mT6(5xS9aobeEO}*Sy)=drmqSPA|w^^B6 z6}(mH8-jo#8(8XsDMOfo#S;gujESkk+lu{FKYuVQI=l2Ui$t@WGz-s5d1}?LR;_E* z#8&NWmDtxp+^W~D!rp(B0G>McWK8UqfZ05_%G(dN4sHtOrnGL0@RnSE5%rA%(kwKs zg48Npul21_1>424RaIN{_eWfKp+@%FsGj#-RHJ*H@<=0`F2bCXAAA0}mtD?H;?Owz z`)Y;m7`QfwD-(ml>0$aYpQlSN(gacdVr7sHS)m`(3SD`TR)~M37Axa3AuIG_TA>Rs z(h7Z6ixnd#rRN3lP^u#BX6q z%^IfGiC3{j^U!Ha&LUgO#BK|xS{2u8#BXU!&LUgO#0xa@g8WBfl`fyi%iH(2{{agq zw|fd@Ze(+Ga%Hok51RsiFGF%=VRUJ4ZbV^pWgsX-Ix;XZH!v|VH83zVGBh?VFgGVK zFefPrFGF%=VRUbDASi8NbZ~5MbZlv2ATlm6E--RqGA?j$b96H>F*Yk_W;bFvH8N%} zW-K6RbaZe!FE4FjbZ~5MbZlv2E^l&YDGD!8a&KgHV`Xw6C~aXCbZ~5MbZlv2AaG=6 cAYx%-Yh`X^DYHBhXaNd1GB^q)B}Gq03eDjotN;K2 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/fill_between_interpolate.png b/lib/matplotlib/tests/baseline_images/test_axes/fill_between_interpolate.png index 59c32a9084d7d2470b9766f77dddadde4dfa60ac..db3f7efd011a909466898b3a77f6033688321bf4 100644 GIT binary patch delta 15193 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 Sb0UGCTQ@ZoG1tk=p8PMtA$uGE delta 15267 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 Ve*B4rCkgzhC~MxtC|UgT{{V_frJVo( From 4e464ebac1f14a37894502956afb83012725d4a8 Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Wed, 28 Aug 2019 18:57:56 -0500 Subject: [PATCH 04/30] clean up --- lib/matplotlib/axes/_axes.py | 9 +-------- lib/matplotlib/cbook/__init__.py | 5 +++-- lib/matplotlib/lines.py | 3 +-- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index a6453af177c2..8cdf59a75cab 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -5366,13 +5366,6 @@ def fill_betweenx(self, y, x1, x2=0, where=None, kwargs['facecolor'] = \ self._get_patches_for_fill.get_next_color() - if step == 'between': - if not len(y) == len(x1) + 1: - raise ValueError(f"When plotting with 'between' " - f"input sizes have to be have to satisfy " - f"len(y) == len(x1) + 1, but y " - f"and x1 have size {len(y)} and {len(x1)}") - # Handle united data, such as dates self._process_unit_info(ydata=y, xdata=x1, kwargs=kwargs) self._process_unit_info(xdata=x2) @@ -5394,7 +5387,7 @@ def fill_betweenx(self, y, x1, x2=0, where=None, if where.size != y.size: cbook.warn_deprecated( "3.2", - message="The parameter where must have the same size as x " + message="The parameter where must have the same size as y " "in fill_between(). This will become an error in " "future versions of Matplotlib.") diff --git a/lib/matplotlib/cbook/__init__.py b/lib/matplotlib/cbook/__init__.py index 414b759ec861..986702705c9b 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -1526,8 +1526,8 @@ def have_lens(v): vb = np.broadcast_arrays(*v) else: raise ValueError(f"Cannot broadcast arrays where more " - f"than one of them have different value " - f"of size > 1. " + f"than one of them have different size, " + f"for sizes > 1. " f"Attempting to broadcast arrays of " f"sizes {get_lens([*v])}.") return vb @@ -1673,6 +1673,7 @@ def pts_to_poststep(x, *args): y1, ..., yp : array y arrays to be turned into steps; all must be the same length as ``x``. + Returns ------- out : array diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 85b8637e09b4..7c1e4cfdb8a9 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -624,7 +624,7 @@ def get_window_extent(self, renderer): bbox = Bbox([[0, 0], [0, 0]]) trans_data_to_xy = self.get_transform().transform bbox.update_from_data_xy(trans_data_to_xy( - cbook.pad_arrays(self.get_xydata(), np.nan)), ignore=True) + cbook.pad_arrays(self.get_xydata())), ignore=True) # correct for marker size, if any if self._marker: ms = (self._markersize / 72.0 * self.figure.dpi) * 0.5 @@ -780,7 +780,6 @@ def draw(self, renderer): x0, x1 = self.axes.get_xbound() i0 = self._x_filled.searchsorted(x0, 'left') i1 = self._x_filled.searchsorted(x1, 'right') - #subslice = slice(max(i0 - 1, 0), i1 + 1) subslice = slice(max(i0 - 2, 0), i1 + 2) self.ind_offset = subslice.start self._transform_path(subslice) From ce9fbe489168bf28f3fd856b9308f280b62b77d7 Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Wed, 28 Aug 2019 20:06:28 -0500 Subject: [PATCH 05/30] Add svg --- .../test_axes/fill_between_interpolate.svg | 166 +++++++++--------- 1 file changed, 83 insertions(+), 83 deletions(-) diff --git a/lib/matplotlib/tests/baseline_images/test_axes/fill_between_interpolate.svg b/lib/matplotlib/tests/baseline_images/test_axes/fill_between_interpolate.svg index 35a003c97c21..5d02afc717dd 100644 --- a/lib/matplotlib/tests/baseline_images/test_axes/fill_between_interpolate.svg +++ b/lib/matplotlib/tests/baseline_images/test_axes/fill_between_interpolate.svg @@ -1,7 +1,7 @@ - + - + +" id="mc63e59a608" style="stroke:#000000;stroke-width:0.5;"/> - + - + - + - + - + - + - + - + - + @@ -572,92 +572,92 @@ L 0 4 +" id="m556f96d829" style="stroke:#000000;stroke-width:0.5;"/> - + +" id="m27e32ca04a" style="stroke:#000000;stroke-width:0.5;"/> - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -673,7 +673,7 @@ z " style="fill:#ffffff;"/> - - - - - - - - - - - - - - - + - + - + - + - + - + - + - + - + - + @@ -1153,72 +1153,72 @@ L 518.4 231.709091 - + - + - + - + - + - + - + - + - + - + - + - + @@ -1226,15 +1226,15 @@ L 518.4 231.709091 - + - + - + $t4Qr zV8ap4F*E=Qz&zlXmzQ5s4738M-x=tX)I1>15@LXRQGRKG0?3=r!3z2=sb!hTsX^{Q ztAV_bqQt!7g2bZKyyQxl&mro87U}z@rer2M_9cIV`aLYljSKK;(T zTyoi<;!)(&gqcFI7eee0yy!Nny0~`Vf$#41*Ww@i50$QM$`904SpTcA;eYXu-RBOi z=>7lRt2Vfu-Jq7Wd$N?Sk4f3n87m7p<|)5ewsIyzqaZKy|LJP8iVtT!|CM$@KmD)g z|10h6-QC-L@6URCp>OhQhJ88@9Danr}k?2VM-^$K&FJ7O-X+Y(G2^TKsa z*U7l1i5|6Gtx*;2S~>mB+@#9wrxjkVad~vtApY{lXIWk>N-VA2oc=)v)=HerNXkn~ z{Pg|nEhaI+`qh)dC1=H(wg#=N$-et>zLV&O+_}rHrCfVm5Lj9L;cM9T%}rf?hvnSN z*VM_!L~OU)WBvc%$C5gmv%g-N?r+jOf7QKG_Sd2l31yEf8;!5Xu8YvEaBOPWG*iX< zU8i*6!JRqglMi1_OMT;UeUthDog*$PQ+B+pQoG9jc3eE zaZT?T+MgQY1Fm~pO)M?`#~?KIl+8TXWhN4?oPCS_2~AH^4wqOWCbZ*@!HmWxP&db@C3VXN+H%Ed?qVph*;3M1YenD2Oqn5WWk}f#RQh1p3o27E1VJSW zh9Iaggt^c;zZ96+f!2Cwrhp0=aM2S4DhD8CC8YR7_|6bXGeWeYC^e1CK*7+M%K#1( z%uG#7<;9`bACxU=N9!$*8*a+xCG%-UHU=X5pqQuOc)FN>8 Date: Thu, 29 Aug 2019 10:20:05 -0500 Subject: [PATCH 07/30] Fallback to post-step, when x,y equal len --- lib/matplotlib/cbook/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/cbook/__init__.py b/lib/matplotlib/cbook/__init__.py index 986702705c9b..ea7c33998abe 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -1613,7 +1613,8 @@ def pts_to_betweenstep(x, *args): steps[1:, 1:-1:2] = args[:, 1:-1] steps[1:, 2:-1:2] = args[:, 1:-1] else: - raise ValueError(f"Expects abs(len(x)-len(y)) == 1") + # Fall back to steps-post (legend drawing) + steps = pts_to_poststep(x, *args) return steps @@ -1653,7 +1654,8 @@ def pts_to_betweenstep_edges(x, *args): elif len(x) + 1 == len(args[0]): edge_steps[1:, ::len(edge_steps[0])-1] = steps[1:, ::len(steps[0])-1] else: - raise ValueError(f"Expects abs(len(x)-len(y)) == 1") + # Fall back to steps-post (legend drawing) + edge_steps = pts_to_poststep(x, *args) return edge_steps From aab6c1bec7df7f7a9c1a14f4a3176872dff2930d Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Mon, 16 Sep 2019 10:54:42 +0300 Subject: [PATCH 08/30] Protect "edges" from Nan values --- lib/matplotlib/cbook/__init__.py | 38 ++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/lib/matplotlib/cbook/__init__.py b/lib/matplotlib/cbook/__init__.py index ea7c33998abe..b033a1a85d58 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -1644,20 +1644,40 @@ def pts_to_betweenstep_edges(x, *args): ``N=0``, the length will be 0. """ - steps = pts_to_betweenstep(x, *args) - edge_steps = np.zeros((steps.shape[0], steps.shape[1]+2)) - edge_steps[:, 1:-1] = steps - if len(x) == len(args[0]) + 1: - edge_steps[0, ::len(edge_steps[0])-1] = steps[0, ::len(steps[0])-1] - elif len(x) + 1 == len(args[0]): - edge_steps[1:, ::len(edge_steps[0])-1] = steps[1:, ::len(steps[0])-1] - else: + # Extra steps to plot edges where values are missing (Nan). + nan_cols = np.nonzero(np.isnan(np.sum(steps, axis=0)))[0] + nan_cols[1::2] = nan_cols[1::2]+1 + pad_steps = [] + + xlike = len(x) == len(args[0]) + 1 + ylike = len(x) + 1 == len(args[0]) + if not (xlike or ylike): # Fall back to steps-post (legend drawing) edge_steps = pts_to_poststep(x, *args) + return edge_steps + + for part in np.split(steps, nan_cols, axis=1): + if not np.isnan(np.sum(part)): + pad_part = np.zeros((2, part.shape[-1]+2)) + pad_part[:, 1:-1] = part + if xlike: + pad_part[:, 0] = part[:, 0] + pad_part[1, 0] = 0 + pad_part[:, -1] = part[:, -1] + pad_part[1, -1] = 0 + else: + pad_part[:, 0] = 0 + pad_part[1, 0] = part[1, 0] + pad_part[:, -1] = 0 + pad_part[1, -1] = part[1, -1] + + pad_steps.append(pad_part) + else: + pad_steps.append(part) - return edge_steps + return np.hstack(pad_steps) def pts_to_poststep(x, *args): From babca7e25cc806e3dd823a11ec5b7dbc125472ee Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Thu, 19 Sep 2019 10:52:27 +0300 Subject: [PATCH 09/30] Fix subslicing, add test at edges --- lib/matplotlib/lines.py | 6 +++--- .../test_lines/drawstyle_variants.png | Bin 15434 -> 15375 bytes .../drawstyle_variants_at_edges.png | Bin 0 -> 7306 bytes lib/matplotlib/tests/test_lines.py | 12 ++++++++++++ 4 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 lib/matplotlib/tests/baseline_images/test_lines/drawstyle_variants_at_edges.png diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 7c1e4cfdb8a9..1c5e8bacf0ba 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -725,8 +725,8 @@ def _transform_path(self, subslice=None): if self._drawstyle in ["steps-between", "steps-edges"]: xy_asym_sliced = [row[subslice] for row in self._xy] # If not asym after slice, crop as original - if len(set(map(len, xy_asym_sliced))): - if len(self._xy[0]) > len(self._xy[1]): + if len(set(map(len, xy_asym_sliced))) != 2: + if len(self._xy[0]) < len(self._xy[1]): xy_asym_sliced[1] = xy_asym_sliced[1][:-1] else: xy_asym_sliced[0] = xy_asym_sliced[0][:-1] @@ -780,7 +780,7 @@ def draw(self, renderer): x0, x1 = self.axes.get_xbound() i0 = self._x_filled.searchsorted(x0, 'left') i1 = self._x_filled.searchsorted(x1, 'right') - subslice = slice(max(i0 - 2, 0), i1 + 2) + subslice = slice(max(i0 - 1, 0), i1 + 1) self.ind_offset = subslice.start self._transform_path(subslice) else: diff --git a/lib/matplotlib/tests/baseline_images/test_lines/drawstyle_variants.png b/lib/matplotlib/tests/baseline_images/test_lines/drawstyle_variants.png index f5286e834299db75d6bba36a1ef8d7cdc24f448f..1bc4ebc5714f5b9399aa3117794bd0eecaa4000f 100644 GIT binary patch literal 15375 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 literal 15434 zcmeHu2UL^U+HOD@b#!3PSP>BCAjX1%fG7z?$5BKOR1Anzr9`Df2?0V2j*c7)K?%|W zN{dnj1f&LK1VTxqm(U3yB?J;$AS8ExpmUsZ&i|kJ?_GD@wXQB(V!rJC?YBJd^E}`E zb4GgKe7*f^7!3A}{^=htz+kI(!eHW;)_w(EiSqdACir7D_6PloYrzlSwf6r2|6X_V zv^f?Alez-^5__Rbxei`D?|$-4i*{Eu{|1w3TA4EiJxob;#199dg}? zuYOeJ`Nc-!OIo7dUuKMEbK1ihMnVWF#Dm=X3 zETc9=(IADW#;!eYn_M@edHE;t3kqYcnZ^VHq4`h%-qy?OXsT&?3Zmk*n%?aTL!+_> zj*Go~l4KCRd!+c8kGFTqHza#o+nsmr+(A{IzL2S;U&5_zRbqRnYJ~aD=Fk>qX!Blf z@7B@U(Qry!tYAvMb@-P4C{HUWC@6_aC5Vb6ooeg#OetE~hUrG;6%`>u;cZLr5nCcM zV^p2y5dIB|jmZ>dwP!mhgp8%f>W-47afb#-;+Ry}FjZ*U<~ zi+7-=?g-qeb$6Yk+tLh8E%u3E2(#O%Wfwfow^uY#!bxWggfxxe�o6jaBvJ&B@#< z)m((b$VYl4dNx_|NlW?|Jq6XgMp|0`V1fMvz5csR-#(l5V4u;Md1*8<-w+<6Ks-&? zgcpowvt}}lw?x^)RGj8>zG=EpMD}GnksEFeKBdvgv>KaOL06CBR?Elko@V54VO~n$ zI;Q^f<@VQJ@NygsvfZz%syt4!CTqL{>C)dmdoh+V7E0{vvuEZdQs8`D5qyMWOw`UB zb%&K1nj8y|&iC_axPg06dG8yVNnDhr`}tuXIc47~*|x`<*NFvO*z~RKsquD_B@RV# z2fq!snWGgsUV6h+J=yavI02fzZyhoX@^BiA%YSy`+IyAT`E41H_0G2$gy$1(Drorl#`R31+RI-5JGZ+F$z zOCRrOF7Rpo@ZmO_H5?ngL2`Gd51GxveYla^a3goOoQ%AD=AN_WLwi)@hIq1MeVxBI zedz6d#w&fNCtpM8QQYkqAQ$k)aXI{zL7 zQWFJ{-{7}2M#^;}Vd+_9S=dqcuh%|y%4Az!J)A`r{IZecS*m89TkYWnv-RG)Q^mN; ze-Ug%L4kH&Pci7!@OP=DI!u~aK;-j)GA-^0Hf#MB_q-4rH+{FE$UM?2n3}QJZB5s! zNPJGa=D@8=Iof)gw)WOreLvGe6rmeBv{7R9(2GD&LIo5IU~++Lgzq=(OOuo=htg%-46}i%bISaQcIC6}4i^G*|NA&Qo!6n0#GZ80Ca5xnC86w?uf4ZqtLzYE;&*k$` z1$O=d#VCib)~sn{GA(c$ZuH+?gXkIdn)KivDgM5o{{gEGlShuZ!)`BvoB4@S(kxyb zv=Kg4hU?QWdziMQT_T~omudyo4+%;AchwlOgY{8Id55%Orl&7PClLz^LmX;SjLwr^ z3j40I7jj2*OQ`WbV(*BHAI7nB$qiW^E@&iQAFfCcRD#>Z@O5k+c@gbVMCNpJyEX(# zCgEK6BRx2$dk*>WQ;!Hy+bQbV_P2)pYZMd|Hp74R9v#tlH{EkW?DqD8rCEAp(DUc3 zm^9_W7ufqKOr}BF(Y89zu9&`{^6NwmwLCv1#<;hmkhfllHO;(4CdCf<*T9;B#l<%l zaZ17GbVYn4+R?>vmXt_Eb6^9LrrDOU=s8HTi>v)m@!WSL(5Cut!N|W4$KG`P%1m-- zrjn`?jI3P>6MLa7v3lPJS9lNKXxK22&C=Sw*s;i+k@wTCON}nzF6__2GU&zq?U_}9 zbm2L=?v%h?zI}-&(t|zwYSXuSJ|;&?I>UQjzvNjFdjxfK5AH_eG=v;(`VP|n@$>rU zmZei%=K`z&(T97BUlJ~`hE_ii*AG@-s{>;1mCuCcE3yCtf-_u~Q_L#i!R`!{#acldjJcYOx791%qu4da)%1X5Xc{bC_ zX2}b0x(WGtB3K!@gTEvg*CK6}=xGQC6w$9C z3st!);5sFs?E690W)(yyPlwfjCkluDl&Kq$06;$DK7hPJ=1}zMVtW#Sh3JJzsnCqDdzorWl>>>YOX{ zE4aZ!fhged>*5$cDOH)*Fscx7Qy6)k{M8=KalUibgV`Rf}&OJ&|x?F`p!=SnsdkWMo0w z7<|x^I%t~u)HL<*NZb6pSC&YGQq{XXqS7o|GLjd)?~Ix!RLqYajQ|Zk#je(At}EPk z0!wa~`dQiiOht5es31;6it#UGvt-9(wdYu*Xr0KRYic&azHlt-odVXCQGjQRaA;#h zA1g&Bjj~Xtt*RPQT3Xs~eg>cz=!>k1{9P7|IhlLvp%Y~+S3FX%Zz8C7Cza21^I}0{ zCJ<8)-{W#JOO$Pjrkt)5J@QCBCBqZNT+PhdK*CJ9rsx+QFR`+yM-`?OD*mq6v+*pM zIVAZF4m1QGXRZacx!tI{(FBJW{df=Q+D0Y&j*%^}0uv&GLuJijrye{6Z6P4;gxD2p z?>B4~$H#`-(b{1|e+wewh+Z_mBAEIFt}WEZkKM=AMK!jt92nk_7Q^CY35fwJm`_Xg zO9M+rWu6fVIi&pJ_a1JNvt^}gxbNlN^GJjRpSW%+S@3|DQKbowA)O3_)+&hK^zBfw zjO)ixVvVw~Yw;oc1l9#U{KSe!assiODNRrJ?1>@~b@mbjqq_7I6SKicD-ipzz#qh} z7=hSjCCIZk1!MekupR?0n6Xa8T#_>0Q9FP5fm6n5?CVI;n;G3%4BRH?>RIpL z5#0UFFvE=!cQprYuL&Uthd~3Jrhu3;%Xm$W3H0n)H8+Uu5m`d1*LP3Z{`eNw_#k>X z*m_1!)2>|_w6n4e=Z;0pWE3QuAX+lmAockymaz~ztq8jmBfT!@W>|OC>SZjRo@n#a zv!1TqI;g$BhqT_fR7LNA2h{-5ikJXPHL^+cNBU(IwQ#=KZEyyNo1ZJhJ$^v|BxXG_)?Wy>$u{4UR9`twNgC7idO) zTv&i+X!Cz*gR8#2bxq{K<$2R<7L5ot);^t1``cGO{sY^kJ*0TC3w*2Y(sgeCRyfBG zGi)%JN8+7>s%NtdH44P9e38(uz=zR=Z^l|XAT@d28CGP6R6`97Q`}G9%wjvN$3(k1 z?$PCTx8bl9g<9{=saWLGkaa5I$*F|%=i_g5VU4+wj9t_vCb;^f>zjdl>Y1)X+@SE` ziYb}p;FB+&mU5eVxpnvrrIxXHApdgu>&xl-l@*nh`_Gt!X?njp8x3+L#iYs1%&d9c zD4IvCGZzX6QI#)R4(l%BU-_i2Rn7l0L7OUQaJcTmdAGJJDEM>NiQ+l>5|c*d4`7D$ zg9#V9JsOzV-tPgtP@7pZ@#M*q>AE|{&xby~H9Goz`>W@pn`WAtpDJ0H;vOtSf_CaV zanqYPn@QMMp+QU%O$$K*xB-stC+^$3Yi6|{6NGPd0ph|exKH$W61wL*k0j58nRW{V zN0>&EQg&1QKf(vHK57=iODesl#`DuhVL5T);&Zu3YZndDV73|; zjs;vU;M`v1wsQy?E47U}!pBrYr9Nh=Sj}yIpc5kvb5xU9{lc=Si=K}WdO(rQ>Ii00 z=7j3XIxH!NLfk=EE%xiRH>-BE%^hp$d8KngCJ=g`-}#hZWuX&d<0s7Q z0C7J~326FIR(7ngs0c0x5czfW?VA;Udg&sdRe*?eWFL0ok$n6CK&=De_nQd(6=YvH z0mjM^Hai{hmEj27Q?$#>5_|HVhUDO=MJB!eVKDt9#WM8p{ub72bj;9qsoNt*SIt#b zonBa2*mve4@i$<7Ba;>l&fp4fEB8(Lj_@M~@&V+Yf4_HUS!f5_L%1|VFy@Uqwpiu6 zg8Mf|o+`EU;{J>uji;E({S6lJGq`af@TYVjo1e&#{5 z<`vpH(B5H=ni8w4x_l;}s%w)kaM~hQx@UMMCY-9t!#IN(Ao;1dIIpR6wp74uA=G^M z_#u(a5={;fsw}*_uY&%nqOj@P2y`iRU=iw=*93a_Zj(4Pcmv`=cxA?(BK5Hhi*Nuk ze8-Y8NsCiZO`)XI4E#qFUCHYqqNGd~%n!u?LZD0MBDG#|Zm0QnSEnHfR^6`!XQ9d9 zDFEn1=@OiJT|H=W%!z^n6fJhBV8ltJo@!+E6s1 zjk9M#^+?KMR;Cc4i2h?-#AkGi$aCnqr6OJ{T7c^Y>vD#pL%skBC$CqlLipaLC$v;* z1|^r!Qb>%T<~zhOuuTyEOvw%n_9Sp@o+?G_Uqm}Fgl`Buu@Hu=9lPb^Q71e{Z? z{9j<9YTu=-y7}t&qs;2sQ=i9?si~<44WSN1@y07UjQYse&$?XmQzegAt^{xPmGXPj zv$LjhYa;G^TJt$=?9hjT)$3ufJz%(m!5*AH2Kch8o4$p?o^6v@z5H_D747LfEGoq$ zEYTWoA9AgMzGz~RX)eE>syJo6Gn)U}PnWTcR*TL@|s`=%& z%nqP5UK)fI=;#?-`ba5wK3((}#a6nf^X z!+d+Ku%5)QX8}!BzYL0NYimQL_vqP%t}d&Gvic?2yKps)WHrY4S-Ji+i&io!^~Sfm z$VxMK;wUdly2wvSe`tJAf9Rt^s>u%XWK)V#dl;*VRcjU-*=?7?cs+q>acHjI9&@Wr zCq#-BqKVWqm{fHCB7RrG+Uru#sf5wBi-6I(kgqjEyFjT-0)*J_0#x7aOtYBei!Qrz zyX1h$SEJLXGC|zA;zvnIM!^PujQsB;?n*xo0Qvl{IAoY3vvKdQiH(YLdP-~ zoQlW4IN<5}5+UA1#2?DdVj>ClfIxLl)i5ACA37x!DSqL^s#U8-CMIIt)9SA!)?d3A z`r>l*5qM3w89BbYurS5woN~0MGo3r9}qi+#_i;k$Ww2s}#mL%|z#3PrmaG(UOZ`d^jL0>j_8+)J8NB;QPbg-rvQ+=TA<{ zc9FX6im&ayB`sk{D~@=Vx65;Wv1Xe0C*ktnzllfNeh)^(9TR@vV8Cn`H~i7?!XM5y z_|c=p%*@RA9|t26%G@KMrt@Ev<3BykmEvuF2?y)_00A-N+;|$|RQjVX9aG%u7mB=o z+l{=d&Puer8#zsN5$CHnoChlb^g7_UNyNk3gVeVNJ^1dt*~*~-p6iqgPOct>yL%@0 zcNofBh1dUN#^%G*&hxkj-lI{K3GPs*Nd&#Ox7XI&$0wC|UIrfX*tzWkUzv1PU@$aU zP(GqrNaD zBVVGIy|&(5;@mc=?R;^nn~Kcu_x)$sQKbeVa98N?&5HXfWhIi!p#{?vFa1TC32N{#m>OoTge)Qk zDYlzl54|iclZp_l)%}y9?C-W3H*zmu&%I0{1_cM3g(fRur)@3r-G$5^c?tWkt*lV+ zXT)U+dOg6RMWf%kKU$79Wf=|U(-;h<`BsF4M;CQ}bRMR9oq3-(`{uPx-|jFacYj=d z;S((J$L~pSW%zuOeDp`3QjE-xs{aAsEw_U4?h>TFDj2Y?AZSOC06LmV;~_1|rI&18EtfB*4Pk%!B|;$kXMtaep-dAWJ+4OH`?WFRDj zs+z56SxuM-?fxO{{;$BB{l-?=kAf7JmR1!^H@ubgv~0c}QgcwIirHhmG@#m7!jY?5qkG9O zHY+O&Gz?DM5p0m=Le=Ps_Epoywi^+p3oRG6h7Y}go9VVGFDBG*XFW?xMz`@R%<@>P zIED7~6_6GA#_}130tbD2GesW^r%q24UYbFW8N}4ciVApZMFl*(ma8__(maB%jw#Wi z$@sPH*-mtpP0tgKy3fI8q7c^ApHvnGGiBByLW!<^D(n4`PWG0i693x?3TQXUxo zHk1k&C-?af=zm|@>aOT{ksQA-9fCk!n~x7|l1RMwt4g$^;+M$azP>~N%9U`9zfx86 z;mv*6z(%Zlu5wgXkR7DIS3QFu2_}>91#9uN${HJyKzdGgG zVNC?d@+XPEI8;H^RG?Mlp%|@!BKHHpm$I>`bx1B6l8I96n&@q8YxSl(b06RC?vAsw z)6YXiC%J+fkPH!wkeoX)p(Fv>*&?)SiwQY+nS!veLUf8;HKBmIDq*kwEr)+-v5+Y_!94l)w-GZ z_3i-91{)ijllQ9+K}raOcHP!(`5(kunO**v(5n5lpGotfkX?59!6-SgSs5@6wgX1j zJJKtd81&wbx6|&_)=huL2*zjpO6k~zWGEB@hKTW7LeNx%nStx+uzL6EE=CS7D1SUN z@d9YZ?$TC<|CT)oG=FAKXDqGP$C2N{lAr+ldpDo zGYj@X6ZMyT&w@5*hGs&kJQWsbM)`Nd(4YOs-_2dnlzP;5ZlX&+1a#;JB=<+b!DxH? z#L;b_*#SD~6IAo#8rFv2$E&qQBuHiQ9zQ}mr|rOxPPbKYCr3jm2hE2TWDi_9Uex7k zCifFD*Qqs`N~Ml&E7MZZ(uzT)nxf?FIwBK}?Zx6&Sc+LTGY60=N>+vH54RjhK}f2s zVFTP|k?&ep{d9khJuM_<03ui-u4Db6PeAkk{9i7tUydXH4n-~JXxV?dwx%lJ(Q54J zvB7Py-LY-Q4&XvcO(7lvzN}}V1qeL@KsVTrG$iRGlt0GC#@;C3CB1E16&U3}(m~dz zI4w{-38G^MOWai|<;-wVtn8YvxU>0YqBpx}K0V0CQvR=Y02-y(QAbqA7!K(vvHy+r z%0A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 diff --git a/lib/matplotlib/tests/baseline_images/test_lines/drawstyle_variants_at_edges.png b/lib/matplotlib/tests/baseline_images/test_lines/drawstyle_variants_at_edges.png new file mode 100644 index 0000000000000000000000000000000000000000..98d68ef9387ff4611dc38feed6c43023f6b7d070 GIT binary patch literal 7306 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 literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/test_lines.py b/lib/matplotlib/tests/test_lines.py index d8389567f383..852948a1c0a5 100644 --- a/lib/matplotlib/tests/test_lines.py +++ b/lib/matplotlib/tests/test_lines.py @@ -124,6 +124,18 @@ def test_drawstyle_variants(): ax.set(xlim=(0, 4), ylim=(0, 4)) +@image_comparison(['drawstyle_variants_at_edges.png'], remove_text=True) +def test_drawstyle_variants_at_edges(): + fig, axs = plt.subplots(3, 2) + dss = ["steps-between", "steps-edges"] + for row, lims in zip(axs, [(-1, 3), (3, 6), (1196, 1200)]): + for ax, ds in zip(row.T.flat, dss): + x = np.arange(1, 1200) + ax.plot(x, x[:-1], drawstyle=ds) + ax.plot(x[:-1], x, drawstyle=ds) + ax.set(xlim=lims, ylim=lims) + + def test_valid_drawstyles(): line = mlines.Line2D([], []) with pytest.raises(ValueError): From 5f70d070078d957ac6a39d98b1d92d1395acc4bf Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Thu, 19 Sep 2019 11:16:02 +0300 Subject: [PATCH 10/30] Switch broadcasting back to np --- lib/matplotlib/axes/_axes.py | 18 +++++++------ lib/matplotlib/cbook/__init__.py | 45 -------------------------------- 2 files changed, 10 insertions(+), 53 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 2260f71d5630..d3ae877a904a 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -5178,11 +5178,12 @@ def fill_between(self, x, y1, y2=0, where=None, interpolate=False, "future versions of Matplotlib.") if step == 'between': - y1, y2, where = cbook.broadcaster([y1, y2, where], - def_size=x.size - 1) + pad_size = x.size - 1 else: - y1, y2, where = cbook.broadcaster([y1, y2, where], - def_size=x.size) + pad_size = x.size + y1 = np.broadcast_to(y1, pad_size, subok=True) + y2 = np.broadcast_to(y2, pad_size, subok=True) + where = np.broadcast_to(where, pad_size, subok=True) get_masks = cbook.pad_arrays(list(map(np.atleast_1d, map(np.ma.getmask, @@ -5393,11 +5394,12 @@ def fill_betweenx(self, y, x1, x2=0, where=None, "future versions of Matplotlib.") if step == 'between': - x1, x2, where = cbook.broadcaster([x1, x2, where], - def_size=y.size - 1) + pad_size = y.size - 1 else: - x1, x2, where = cbook.broadcaster([x1, x2, where], - def_size=y.size) + pad_size = y.size + x1 = np.broadcast_to(x1, pad_size, subok=True) + x2 = np.broadcast_to(x2, pad_size, subok=True) + where = np.broadcast_to(where, pad_size, subok=True) get_masks = cbook.pad_arrays(list(map(np.atleast_1d, map(np.ma.getmask, diff --git a/lib/matplotlib/cbook/__init__.py b/lib/matplotlib/cbook/__init__.py index b033a1a85d58..02eacf617439 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -1488,51 +1488,6 @@ def pad_arrays(v, padval=np.nan): return np.array(list(itertools.zip_longest(*v, fillvalue=padval))).T -def broadcaster(v, def_size=1): - """ - Broadcast arrays together, if all arrays are of size 1, can specify size - to expand to - - Parameters - ---------- - v : iterable - List of arrays to be padded to the largest len. All elements must - support iteration - - def_size : scalar - Size to with to broadcaset to if all broadcasted arrays have size 1. - - Returns - ------- - out : array - Array of input arrays padded to the same len by specified padval - """ - - def get_lens(v): - return np.array([np.atleast_1d(k).size for k in v]) - - def have_lens(v): - return (get_lens(v) > 1) - - if np.sum((have_lens([*v]))) == 0: - vb = np.broadcast_arrays(v[0]*np.ones(def_size), *v[1:]) - elif np.sum((have_lens([*v]))) == 1: - vb = np.broadcast_arrays(*v) - else: - if len(set(get_lens([*v]))) == 1: - vb = v - elif (len(set(get_lens([*v]))) == 2 and - 1 in set(get_lens([*v]))): - vb = np.broadcast_arrays(*v) - else: - raise ValueError(f"Cannot broadcast arrays where more " - f"than one of them have different size, " - f"for sizes > 1. " - f"Attempting to broadcast arrays of " - f"sizes {get_lens([*v])}.") - return vb - - def pts_to_prestep(x, *args): """ Convert continuous line to pre-steps. From 46c22631bae8890b9d236791bf1633499c885cb5 Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Thu, 19 Sep 2019 11:42:11 +0300 Subject: [PATCH 11/30] Improve example --- .../option_between_for_step_fill_between.rst | 2 +- examples/lines_bars_and_markers/step_demo.py | 26 +++++++++---------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/doc/users/next_whats_new/option_between_for_step_fill_between.rst b/doc/users/next_whats_new/option_between_for_step_fill_between.rst index b4f9a0465eff..5eed3090b495 100644 --- a/doc/users/next_whats_new/option_between_for_step_fill_between.rst +++ b/doc/users/next_whats_new/option_between_for_step_fill_between.rst @@ -2,7 +2,7 @@ step() and fill_between() take a new option where/step="between" ------------------------------------------------------------------------ Previously one would need to trick step() and fill_between() to plot -data where x has one point than y, typically when plotting pre-binned +data where x has one more point than y, typically when plotting pre-binned histograms. step() now takes where="between" for x, y satisfying either diff --git a/examples/lines_bars_and_markers/step_demo.py b/examples/lines_bars_and_markers/step_demo.py index 7f56bc7e5cd3..acbe2d9ca49e 100644 --- a/examples/lines_bars_and_markers/step_demo.py +++ b/examples/lines_bars_and_markers/step_demo.py @@ -30,21 +30,19 @@ plt.show() # Plotting with where='between'/'edges' -values = np.array([6, 14, 32, 37, 48, 32, 21, 4]) # hist -edges = np.array([1., 2., 3., 4., 5., 6., 7., 8., 9.]) # bins -fig, axes = plt.subplots(3, 2) -axes = axes.flatten() -axes[0].step(edges, values, where='between') -axes[1].step(values, edges, where='between') -axes[2].step(edges, values, where='edges') -axes[3].step(values, edges, where='edges') -axes[4].step(edges, values, where='edges') -axes[4].semilogy() -axes[5].step(edges, values, where='edges') -axes[5].semilogy() - -fig.show() +x = np.arange(0, 7, 1) +y = np.array([2, 3, 4, 5, 4, 3]) +fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5)) +ax1.step(x, y + 2, where='between', label='between') +ax1.step(x, y, where='edges', label='edges') +ax1.legend(title='Parameter where:') + +ax2.step(y + 2, x, where='between', label='between') +ax2.step(y, x, where='edges', label='edges') +ax2.legend(title='Parameter where:') + +plt.show() ############################################################################# # # ------------ From 5264deaf951f5f79ac3cccc14efdc84b1f79c722 Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Fri, 20 Sep 2019 09:41:33 +0300 Subject: [PATCH 12/30] Update docs --- .../option_between_for_step_fill_between.rst | 29 +++++++++++-------- .../lines_bars_and_markers/filled_step.py | 2 ++ lib/matplotlib/lines.py | 6 +++- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/doc/users/next_whats_new/option_between_for_step_fill_between.rst b/doc/users/next_whats_new/option_between_for_step_fill_between.rst index 5eed3090b495..f305bc987e92 100644 --- a/doc/users/next_whats_new/option_between_for_step_fill_between.rst +++ b/doc/users/next_whats_new/option_between_for_step_fill_between.rst @@ -1,17 +1,22 @@ -step() and fill_between() take a new option where/step="between" +New drawstyles for steps - "steps-between", "steps-edges" ------------------------------------------------------------------------ +They are asymmetrical such that abs(len(x) - len(y)) == 1. Typically +to enable plotting histograms with step() and fill_between(). -Previously one would need to trick step() and fill_between() to plot -data where x has one more point than y, typically when plotting pre-binned -histograms. + .. plot:: -step() now takes where="between" for x, y satisfying either -len(x) + 1 = len(y) or len(x) = len(y) + 1. Plotting a step line "between" -specified edges in either direction. Convenience option where="edges" is -added to close the shape. + import numpy as np + import matplotlib.pyplot as plt -fill_between() now takes step="between" for x, y satisfying -len(x) + 1 = len(y). Plotting fill "between" specified edges. + x = np.arange(0,7,1) + y = np.array([2,3,4,5,4,3]) -fill_betweenx() now takes step="between" for x, y satisfying -len(x) = len(y) + 1. Plotting fill "between" specified edges. + fig, ax = plt.subplots(constrained_layout=True) + + ax.plot(x, y + 2, drawstyle='steps-between') + ax.plot(x, y, drawstyle='steps-edges') + + plt.show() + +See :doc:`/gallery/lines_bars_and_markers/step_demo` and +:doc:`/gallery/lines_bars_and_markers/filled_step` for examples. diff --git a/examples/lines_bars_and_markers/filled_step.py b/examples/lines_bars_and_markers/filled_step.py index f8d95846a8f1..db9bae312df2 100644 --- a/examples/lines_bars_and_markers/filled_step.py +++ b/examples/lines_bars_and_markers/filled_step.py @@ -18,6 +18,8 @@ ############################################################################### # Plain filled steps +# sphinx_gallery_thumbnail_number = 2 + fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(9, 4.5), tight_layout=True) ax1.fill_between([0, 1, 2, 3], [1, 2, 3], step='between') ax2.fill_betweenx([0, 1, 2, 3], [0, 1, 2], step='between') diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 1c5e8bacf0ba..c2a285ac9caf 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -1085,7 +1085,7 @@ def set_drawstyle(self, drawstyle): Parameters ---------- drawstyle : {'default', 'steps', 'steps-pre', 'steps-mid', \ -'steps-post'}, default: 'default' +'steps-post', 'steps-between', 'steps-edges'}, default: 'default' For 'default', the points are connected with straight lines. The steps variants connect the points with step-like lines, @@ -1097,6 +1097,10 @@ def set_drawstyle(self, drawstyle): - 'steps-mid': The step is halfway between the points. - 'steps-post: The step is at the end of the line segment, i.e. the line will be at the y-value of the point to the left. + - 'between': Expects abs(len(x)-len(y)) == 1, steps have value y[i] + on the interval ``[x[i], x[i+1])`` + - 'edges': Expects abs(len(x)-len(y)) == 1, steps have value y[i] + on interval ``[x[i], x[i+1]), shape is closed at x[0], x[-1]`` - 'steps' is equal to 'steps-pre' and is maintained for backward-compatibility. From 7ed54a67614e25cc5342ebf908d50d5ba8c134f4 Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Sat, 28 Sep 2019 15:03:39 +0200 Subject: [PATCH 13/30] Clean for speedup --- lib/matplotlib/axes/_axes.py | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index d3ae877a904a..355cedaeaf66 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -5192,8 +5192,7 @@ def fill_between(self, x, y1, y2=0, where=None, interpolate=False, where = where & ~functools.reduce(np.logical_or, get_masks) polys = [] - pad_where = cbook.pad_arrays([where, x], False)[0].astype(bool) - for ind0, ind1 in cbook.contiguous_regions(pad_where): + for ind0, ind1 in cbook.contiguous_regions(where): if step == 'between': xslice = x[ind0:ind1+1] else: @@ -5253,12 +5252,11 @@ def get_interp_point(ind): collection = mcoll.PolyCollection(polys, **kwargs) # now update the datalim and autoscale - # For between pad with mean value + # For between pad last value if step == 'between': - y1, y2 = cbook.pad_arrays([y1, y2, x], - np.mean([np.mean(y1.flatten()), - np.mean(y2.flatten())]))[:-1] - where = cbook.pad_arrays([where, x], 1)[0].astype(bool) + y1 = np.r_[y1, y1[-1]] + y2 = np.r_[y2, y2[-1]] + where = np.r_[where, where[-1]] XY1 = np.array([x[where], y1[where]]).T XY2 = np.array([x[where], y2[where]]).T self.dataLim.update_from_data_xy(XY1, self.ignore_existing_data_limits, @@ -5408,8 +5406,7 @@ def fill_betweenx(self, y, x1, x2=0, where=None, where = where & ~functools.reduce(np.logical_or, get_masks) polys = [] - pad_where = cbook.pad_arrays([where, y], False)[0].astype(bool) - for ind0, ind1 in cbook.contiguous_regions(pad_where): + for ind0, ind1 in cbook.contiguous_regions(where): if step == 'between': yslice = y[ind0:ind1+1] else: @@ -5468,12 +5465,11 @@ def get_interp_point(ind): collection = mcoll.PolyCollection(polys, **kwargs) # now update the datalim and autoscale - # For between pad with mean value + # For between pad last value if step == 'between': - x1, x2 = cbook.pad_arrays([x1, x2, y], - np.mean([np.mean(x1.flatten()), - np.mean(x2.flatten())]))[:-1] - where = cbook.pad_arrays([where, y], 1)[0].astype(bool) + x1 = np.r_[x1, x1[-1]] + x2 = np.r_[x2, x2[-1]] + where = np.r_[where, where[-1]] X1Y = np.array([x1[where], y[where]]).T X2Y = np.array([x2[where], y[where]]).T self.dataLim.update_from_data_xy(X1Y, self.ignore_existing_data_limits, From b0a6244cc073cb566c9e8ffb5b42f401c4fb713f Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Sat, 28 Sep 2019 20:14:17 +0200 Subject: [PATCH 14/30] Pad where True --- lib/matplotlib/axes/_axes.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 355cedaeaf66..87227ad2d3e1 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -5188,7 +5188,6 @@ def fill_between(self, x, y1, y2=0, where=None, interpolate=False, get_masks = cbook.pad_arrays(list(map(np.atleast_1d, map(np.ma.getmask, [y1, y2]))), False) - where = where & ~functools.reduce(np.logical_or, get_masks) polys = [] @@ -5256,7 +5255,7 @@ def get_interp_point(ind): if step == 'between': y1 = np.r_[y1, y1[-1]] y2 = np.r_[y2, y2[-1]] - where = np.r_[where, where[-1]] + where = np.r_[where, True] XY1 = np.array([x[where], y1[where]]).T XY2 = np.array([x[where], y2[where]]).T self.dataLim.update_from_data_xy(XY1, self.ignore_existing_data_limits, @@ -5469,7 +5468,7 @@ def get_interp_point(ind): if step == 'between': x1 = np.r_[x1, x1[-1]] x2 = np.r_[x2, x2[-1]] - where = np.r_[where, where[-1]] + where = np.r_[where, True] X1Y = np.array([x1[where], y[where]]).T X2Y = np.array([x2[where], y[where]]).T self.dataLim.update_from_data_xy(X1Y, self.ignore_existing_data_limits, From 65f02829d33b02a0fee0305821fe49f5e40b4782 Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Thu, 2 Jan 2020 22:23:22 +0100 Subject: [PATCH 15/30] Undo redundant padding --- lib/matplotlib/axes/_axes.py | 19 ++++++++----------- lib/matplotlib/cbook/__init__.py | 9 ++++++++- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 0b8dc7ede757..3573b7b547ea 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -5133,7 +5133,7 @@ def fill_between(self, x, y1, y2=0, where=None, interpolate=False, every *x* position, i.e. the interval ``[x[i], x[i+1])`` has the value ``y[i]``. - 'mid': Steps occur half-way between the *x* positions. - - 'between': Expects abs(len(x)-len(y)) == 1, steps have value y[i] + - 'between': Expects len(x) = len(y) + 1, steps have value y[i] on the interval ``[x[i], x[i+1])`` Other Parameters @@ -5202,10 +5202,9 @@ def fill_between(self, x, y1, y2=0, where=None, interpolate=False, y2 = np.broadcast_to(y2, pad_size, subok=True) where = np.broadcast_to(where, pad_size, subok=True) - get_masks = cbook.pad_arrays(list(map(np.atleast_1d, - map(np.ma.getmask, - [y1, y2]))), False) - where = where & ~functools.reduce(np.logical_or, get_masks) + _get_masks = list(map(np.atleast_1d, + map(np.ma.getmask, [y1, y2]))) + where = where & ~functools.reduce(np.logical_or, _get_masks) polys = [] for ind0, ind1 in cbook.contiguous_regions(where): @@ -5346,7 +5345,7 @@ def fill_betweenx(self, y, x1, x2=0, where=None, every *x* position, i.e. the interval ``[x[i], x[i+1])`` has the value ``y[i]``. - 'mid': Steps occur half-way between the *x* positions. - - 'between': Expects abs(len(x)-len(y)) == 1, steps have value x[i] + - 'between': Expects len(y) = len(x) + 1, steps have value x[i] on the interval ``[y[i], y[i+1])`` Other Parameters @@ -5415,11 +5414,9 @@ def fill_betweenx(self, y, x1, x2=0, where=None, x2 = np.broadcast_to(x2, pad_size, subok=True) where = np.broadcast_to(where, pad_size, subok=True) - get_masks = cbook.pad_arrays(list(map(np.atleast_1d, - map(np.ma.getmask, - [x1, x2]))), False) - - where = where & ~functools.reduce(np.logical_or, get_masks) + _get_masks = list(map(np.atleast_1d, + map(np.ma.getmask, [x1, x2]))) + where = where & ~functools.reduce(np.logical_or, _get_masks) polys = [] for ind0, ind1 in cbook.contiguous_regions(where): diff --git a/lib/matplotlib/cbook/__init__.py b/lib/matplotlib/cbook/__init__.py index 7fee6e136ccf..1d07899d2746 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -1520,7 +1520,14 @@ def pad_arrays(v, padval=np.nan): Examples -------- - >>> a, b, c = pad_arrays([1,2,3,4], [1,2,3], [1]) + >>> a, b, c = pad_arrays([np.array([1,2,3,4]), np.array([1,2,3]), + np.array([1])]) + >>> a + array([1., 2., 3., 4.]) + >>> b + array([ 1., 2., 3., nan]) + >>> c + array([ 1., nan, nan, nan]) """ if len(set([k.shape[0] for k in v])) == 1: return v From 9cc16c1f0ebdc714e2d6ba00a35dc0036f941d1f Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Thu, 2 Jan 2020 22:25:02 +0100 Subject: [PATCH 16/30] paddding not needed --- lib/matplotlib/cbook/__init__.py | 35 -------------------------------- lib/matplotlib/lines.py | 4 ++-- 2 files changed, 2 insertions(+), 37 deletions(-) diff --git a/lib/matplotlib/cbook/__init__.py b/lib/matplotlib/cbook/__init__.py index 1d07899d2746..bd0f20c63524 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -1499,41 +1499,6 @@ def violin_stats(X, method, points=100, quantiles=None): return vpstats -def pad_arrays(v, padval=np.nan): - """ - Pad list of arrays of varying lengths to the same size with specified - value - - Parameters - ---------- - v : iterable - List of arrays to be padded to the largest len. All elements must - support iteration - - padval : scalar, bool or NaN, defaul NaN - value to pad missing values with - - Returns - ------- - out : array - Array of input arrays padded to the same len by specified padval - - Examples - -------- - >>> a, b, c = pad_arrays([np.array([1,2,3,4]), np.array([1,2,3]), - np.array([1])]) - >>> a - array([1., 2., 3., 4.]) - >>> b - array([ 1., 2., 3., nan]) - >>> c - array([ 1., nan, nan, nan]) - """ - if len(set([k.shape[0] for k in v])) == 1: - return v - return np.array(list(itertools.zip_longest(*v, fillvalue=padval))).T - - def pts_to_prestep(x, *args): """ Convert continuous line to pre-steps. diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 201be965ccc6..61eefe487f56 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -628,8 +628,8 @@ def set_picker(self, p): def get_window_extent(self, renderer): bbox = Bbox([[0, 0], [0, 0]]) trans_data_to_xy = self.get_transform().transform - bbox.update_from_data_xy(trans_data_to_xy( - cbook.pad_arrays(self.get_xydata())), ignore=True) + bbox.update_from_data_xy( + trans_data_to_xy(self.get_xydata()), ignore=True) # correct for marker size, if any if self._marker: ms = (self._markersize / 72.0 * self.figure.dpi) * 0.5 From 814f1803259bf90104fb98a937174e571fb6a19e Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Fri, 3 Jan 2020 23:59:45 +0100 Subject: [PATCH 17/30] flake --- lib/matplotlib/axes/_axes.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 3573b7b547ea..696de3e1b7da 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -5193,11 +5193,10 @@ def fill_between(self, x, y1, y2=0, where=None, interpolate=False, message="The parameter where must have the same size as x " "in fill_between(). This will become an error in " "future versions of Matplotlib.") - + pad_size = x.size if step == 'between': - pad_size = x.size - 1 - else: - pad_size = x.size + pad_size -= 1 + y1 = np.broadcast_to(y1, pad_size, subok=True) y2 = np.broadcast_to(y2, pad_size, subok=True) where = np.broadcast_to(where, pad_size, subok=True) @@ -5406,10 +5405,10 @@ def fill_betweenx(self, y, x1, x2=0, where=None, "in fill_between(). This will become an error in " "future versions of Matplotlib.") + pad_size = y.size if step == 'between': - pad_size = y.size - 1 - else: - pad_size = y.size + pad_size -= 1 + x1 = np.broadcast_to(x1, pad_size, subok=True) x2 = np.broadcast_to(x2, pad_size, subok=True) where = np.broadcast_to(where, pad_size, subok=True) From 8b08a8e59c06e751b6a04a2c33f885b2f593f66f Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Sat, 4 Jan 2020 00:38:57 +0100 Subject: [PATCH 18/30] flake --- lib/matplotlib/axes/_axes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 23aed7d3fc59..023deada31fc 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -5368,7 +5368,7 @@ def fill_betweenx(self, y, x1, x2=0, where=None, x2 = np.broadcast_to(x2, pad_size, subok=True) where = np.broadcast_to(where, pad_size, subok=True) - _get_masks = list(map(np.atleast_1d, + _get_masks = list(map(np.atleast_1d, map(np.ma.getmask, [x1, x2]))) where = where & ~functools.reduce(np.logical_or, _get_masks) From 3b73e4fd234ec12bc09e56e308fba2dcb8e82733 Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Mon, 6 Jan 2020 14:52:50 +0100 Subject: [PATCH 19/30] Simplify between-step --- lib/matplotlib/axes/_axes.py | 1 + lib/matplotlib/cbook/__init__.py | 71 +++++++++++++++++++++++++++----- lib/matplotlib/lines.py | 9 ++-- 3 files changed, 66 insertions(+), 15 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 023deada31fc..ecc03f4b730d 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -5364,6 +5364,7 @@ def fill_betweenx(self, y, x1, x2=0, where=None, if step == 'between': pad_size -= 1 + # Broadcast scalar values x1 = np.broadcast_to(x1, pad_size, subok=True) x2 = np.broadcast_to(x2, pad_size, subok=True) where = np.broadcast_to(where, pad_size, subok=True) diff --git a/lib/matplotlib/cbook/__init__.py b/lib/matplotlib/cbook/__init__.py index 53912d619759..1746cf603e25 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -1505,6 +1505,38 @@ def violin_stats(X, method, points=100, quantiles=None): return vpstats +def pad_arrays(*v, padval=np.nan): + """ + Pad list of arrays of varying lengths to the same size with specified + value. Useful for padding asymetrical arrays + Parameters + ---------- + v : iterable + List of arrays to be padded to the largest len. All elements must + support iteration + padval : scalar, bool or NaN, defaul NaN + value to pad missing values with + Returns + ------- + out : array + Array of input arrays padded to the same len by specified padval + Examples + -------- + >>> a, b, c = pad_arrays(np.array([1,2,3,4]), np.array([1,2,3]), + np.array([1])) + >>> a + np.array([1, 2, 3, 4]) + >>> b + np.array([1, 2, 3, np.nan] + >>> c + np.array([1, np.nan, np.nan, np.nan]) + """ + + if len(set([k.shape[0] for k in v])) == 1: + return v + return np.array(list(itertools.zip_longest(*v, fillvalue=padval)), dtype=float).T + + def pts_to_prestep(x, *args): """ Convert continuous line to pre-steps. @@ -1571,19 +1603,35 @@ def pts_to_betweenstep(x, *args): step_length = max(2 * max(len(x), len(args[0])) - 2, 0) steps = np.zeros((1 + len(args), step_length)) + def __between__(steps, sl0, sl1, _x, _args): + # Be agnostic whether xlike or ylike + if _x.flatten().shape != x.shape: + if _x.flatten().shape[-1] == _x.shape[-1]: + _x = _x[0] + steps[sl0, ::steps.shape[-1]-1] = _x[::_x.shape[-1]-1] + steps[sl0, 2::2] = _x[1:-1] + steps[sl0, 1:-1:2] = _x[1:-1] + steps[sl1, 0::2] = _args + steps[sl1, 1::2] = _args + return steps + if len(x) == len(args[0]) + 1: - steps[0, ::len(steps[0])-1] = x[::len(x)-1] - steps[0, 2::2] = x[1:-1] - steps[0, 1:-1:2] = x[1:-1] - steps[1:, 0::2] = args - steps[1:, 1::2] = args + steps = __between__(steps, 0, slice(1, None), + x, args) + # steps[0, ::len(steps[0])-1] = x[::len(x)-1] + # steps[0, 2::2] = x[1:-1] + # steps[0, 1:-1:2] = x[1:-1] + # steps[1:, 0::2] = args + # steps[1:, 1::2] = args elif len(x) + 1 == len(args[0]): - steps[0, 0::2] = x - steps[0, 1::2] = x - steps[1:, ::len(steps[0])-1] = args[:, ::len(args[0])-1] - steps[1:, 1:-1:2] = args[:, 1:-1] - steps[1:, 2:-1:2] = args[:, 1:-1] + steps = __between__(steps, slice(1, None), 0, + args, x) + # steps[0, 0::2] = x + # steps[0, 1::2] = x + # steps[1:, ::len(steps[0])-1] = args[:, ::len(args[0])-1] + # steps[1:, 1:-1:2] = args[:, 1:-1] + # steps[1:, 2:-1:2] = args[:, 1:-1] else: # Fall back to steps-post (legend drawing) steps = pts_to_poststep(x, *args) @@ -1617,7 +1665,7 @@ def pts_to_betweenstep_edges(x, *args): """ steps = pts_to_betweenstep(x, *args) - + print("XXX") # Extra steps to plot edges where values are missing (Nan). nan_cols = np.nonzero(np.isnan(np.sum(steps, axis=0)))[0] nan_cols[1::2] = nan_cols[1::2]+1 @@ -1625,6 +1673,7 @@ def pts_to_betweenstep_edges(x, *args): xlike = len(x) == len(args[0]) + 1 ylike = len(x) + 1 == len(args[0]) + print(x,args) if not (xlike or ylike): # Fall back to steps-post (legend drawing) edge_steps = pts_to_poststep(x, *args) diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 4caade14ca41..3d75f2956de5 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -609,7 +609,7 @@ def set_picker(self, p): Parameters ---------- p : float or callable[[Artist, Event], Tuple[bool, dict]] - If a float, it is used as the pick radius in points. + If a float, it is used as the pick radius in poits. """ if callable(p): self._contains = p @@ -620,8 +620,9 @@ def set_picker(self, p): def get_window_extent(self, renderer): bbox = Bbox([[0, 0], [0, 0]]) trans_data_to_xy = self.get_transform().transform - bbox.update_from_data_xy( - trans_data_to_xy(self.get_xydata()), ignore=True) + padded_xy = np.column_stack( + cbook.pad_arrays(*self.get_xydata())).astype(float) + bbox.update_from_data_xy(trans_data_to_xy(padded_xy), ignore=True) # correct for marker size, if any if self._marker: ms = (self._markersize / 72.0 * self.figure.dpi) * 0.5 @@ -673,7 +674,7 @@ def recache(self, always=False): y = self._y if self._drawstyle in ["steps-between", "steps-edges"]: - # Done separately, as x and y could have different length + # Account for varying x, y length self._x, self._y = x, y self._xy = np.array([self._x, self._y]) else: From 0d9dec7ef715f792306b85cabf35755cb1049af5 Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Mon, 6 Jan 2020 16:55:04 +0100 Subject: [PATCH 20/30] flake --- lib/matplotlib/cbook/__init__.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/cbook/__init__.py b/lib/matplotlib/cbook/__init__.py index 1746cf603e25..6fd60bd33ab6 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -1534,7 +1534,8 @@ def pad_arrays(*v, padval=np.nan): if len(set([k.shape[0] for k in v])) == 1: return v - return np.array(list(itertools.zip_longest(*v, fillvalue=padval)), dtype=float).T + padded_v = list(itertools.zip_longest(*v, fillvalue=padval)) + return np.array(padded_v, dtype=float).T def pts_to_prestep(x, *args): @@ -1603,7 +1604,7 @@ def pts_to_betweenstep(x, *args): step_length = max(2 * max(len(x), len(args[0])) - 2, 0) steps = np.zeros((1 + len(args), step_length)) - def __between__(steps, sl0, sl1, _x, _args): + def __between__(steps, sl0, sl1, _x, _args): # Be agnostic whether xlike or ylike if _x.flatten().shape != x.shape: if _x.flatten().shape[-1] == _x.shape[-1]: @@ -1612,7 +1613,7 @@ def __between__(steps, sl0, sl1, _x, _args): steps[sl0, 2::2] = _x[1:-1] steps[sl0, 1:-1:2] = _x[1:-1] steps[sl1, 0::2] = _args - steps[sl1, 1::2] = _args + steps[sl1, 1::2] = _args return steps if len(x) == len(args[0]) + 1: @@ -1665,7 +1666,6 @@ def pts_to_betweenstep_edges(x, *args): """ steps = pts_to_betweenstep(x, *args) - print("XXX") # Extra steps to plot edges where values are missing (Nan). nan_cols = np.nonzero(np.isnan(np.sum(steps, axis=0)))[0] nan_cols[1::2] = nan_cols[1::2]+1 @@ -1673,7 +1673,6 @@ def pts_to_betweenstep_edges(x, *args): xlike = len(x) == len(args[0]) + 1 ylike = len(x) + 1 == len(args[0]) - print(x,args) if not (xlike or ylike): # Fall back to steps-post (legend drawing) edge_steps = pts_to_poststep(x, *args) From 72598d6783425042ce9b78d0a4bb1160fa83622f Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Tue, 7 Jan 2020 00:07:06 +0100 Subject: [PATCH 21/30] Try to fix docs --- lib/matplotlib/cbook/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/matplotlib/cbook/__init__.py b/lib/matplotlib/cbook/__init__.py index 71ef42138657..6bda39b8ac31 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -1509,17 +1509,21 @@ def pad_arrays(*v, padval=np.nan): """ Pad list of arrays of varying lengths to the same size with specified value. Useful for padding asymetrical arrays + Parameters ---------- v : iterable List of arrays to be padded to the largest len. All elements must support iteration + padval : scalar, bool or NaN, defaul NaN value to pad missing values with + Returns ------- out : array Array of input arrays padded to the same len by specified padval + Examples -------- >>> a, b, c = pad_arrays(np.array([1,2,3,4]), np.array([1,2,3]), From bc3b19bf81e200cad68e1113e9381bd09846ee42 Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Tue, 7 Jan 2020 10:17:57 +0100 Subject: [PATCH 22/30] flake --- lib/matplotlib/cbook/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/cbook/__init__.py b/lib/matplotlib/cbook/__init__.py index 6bda39b8ac31..29813eb9ff0d 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -1515,7 +1515,7 @@ def pad_arrays(*v, padval=np.nan): v : iterable List of arrays to be padded to the largest len. All elements must support iteration - + padval : scalar, bool or NaN, defaul NaN value to pad missing values with From 80c348b0ea64927cf7a0392f25298a2d8c8e485b Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Tue, 7 Jan 2020 21:17:23 +0100 Subject: [PATCH 23/30] no np.r_ --- lib/matplotlib/axes/_axes.py | 44 +++++++++++++++---------------- lib/matplotlib/cbook/__init__.py | 34 +++++++++--------------- lib/matplotlib/tests/test_axes.py | 24 ++++++++--------- 3 files changed, 46 insertions(+), 56 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index ce971effc1dd..c8931cb5b1e1 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -5125,11 +5125,11 @@ def fill_between(self, x, y1, y2=0, where=None, interpolate=False, self._get_patches_for_fill.get_next_color() # Handle united data, such as dates - self._process_unit_info(xdata=x, ydata=y1, kwargs=kwargs) + self._process_unit_info(xdata=x, kwargs=kwargs) + self._process_unit_info(ydata=y1) self._process_unit_info(ydata=y2) # Convert the arrays so we can work with them - x = ma.masked_invalid(self.convert_xunits(x)) y1 = ma.masked_invalid(self.convert_yunits(y1)) y2 = ma.masked_invalid(self.convert_yunits(y2)) @@ -5139,8 +5139,12 @@ def fill_between(self, x, y1, y2=0, where=None, interpolate=False, raise ValueError('Input passed into argument "%r"' % name + 'is not 1-dimensional.') + pad_size = x.size + if step == 'between': + pad_size -= 1 + if where is None: - where = True + where = np.ones(pad_size).astype(bool) else: where = np.asarray(where, dtype=bool) if where.size != x.size and step != 'between': @@ -5149,16 +5153,13 @@ def fill_between(self, x, y1, y2=0, where=None, interpolate=False, message="The parameter where must have the same size as x " "in fill_between(). This will become an error in " "future versions of Matplotlib.") - pad_size = x.size - if step == 'between': - pad_size -= 1 + # Broadcast scalar values y1 = np.broadcast_to(y1, pad_size, subok=True) y2 = np.broadcast_to(y2, pad_size, subok=True) where = np.broadcast_to(where, pad_size, subok=True) - _get_masks = list(map(np.atleast_1d, - map(np.ma.getmask, [y1, y2]))) + _get_masks = list(map(np.ma.getmask, [y1, y2])) where = where & ~functools.reduce(np.logical_or, _get_masks) polys = [] @@ -5224,9 +5225,9 @@ def get_interp_point(ind): # now update the datalim and autoscale # For between pad last value if step == 'between': - y1 = np.r_[y1, y1[-1]] - y2 = np.r_[y2, y2[-1]] - where = np.r_[where, True] + y1 = np.append(y1, y1[-1]) + y2 = np.append(y2, y2[-1]) + where = np.append(where, True) XY1 = np.array([x[where], y1[where]]).T XY2 = np.array([x[where], y2[where]]).T self.dataLim.update_from_data_xy(XY1, self.ignore_existing_data_limits, @@ -5349,8 +5350,12 @@ def fill_betweenx(self, y, x1, x2=0, where=None, raise ValueError('Input passed into argument "%r"' % name + 'is not 1-dimensional.') + pad_size = y.size + if step == 'between': + pad_size -= 1 + if where is None: - where = True + where = np.ones(pad_size).astype(bool) else: where = np.asarray(where, dtype=bool) if where.size != y.size and step != 'between': @@ -5359,18 +5364,13 @@ def fill_betweenx(self, y, x1, x2=0, where=None, message="The parameter where must have the same size as y " "in fill_between(). This will become an error in " "future versions of Matplotlib.") - - pad_size = y.size - if step == 'between': - pad_size -= 1 - + # Broadcast scalar values x1 = np.broadcast_to(x1, pad_size, subok=True) x2 = np.broadcast_to(x2, pad_size, subok=True) where = np.broadcast_to(where, pad_size, subok=True) - _get_masks = list(map(np.atleast_1d, - map(np.ma.getmask, [x1, x2]))) + _get_masks = list(map(np.ma.getmask, [x1, x2])) where = where & ~functools.reduce(np.logical_or, _get_masks) polys = [] @@ -5435,9 +5435,9 @@ def get_interp_point(ind): # now update the datalim and autoscale # For between pad last value if step == 'between': - x1 = np.r_[x1, x1[-1]] - x2 = np.r_[x2, x2[-1]] - where = np.r_[where, True] + x1 = np.append(x1, x1[-1]) + x2 = np.append(x2, x2[-1]) + where = np.append(where, True) X1Y = np.array([x1[where], y[where]]).T X2Y = np.array([x2[where], y[where]]).T self.dataLim.update_from_data_xy(X1Y, self.ignore_existing_data_limits, diff --git a/lib/matplotlib/cbook/__init__.py b/lib/matplotlib/cbook/__init__.py index 29813eb9ff0d..a1aff6678ffe 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -1608,6 +1608,9 @@ def pts_to_betweenstep(x, *args): step_length = max(2 * max(len(x), len(args[0])) - 2, 0) steps = np.zeros((1 + len(args), step_length)) + xlike = len(x) == len(args[0]) + 1 + ylike = len(x) + 1 == len(args[0]) + def __between__(steps, sl0, sl1, _x, _args): # Be agnostic whether xlike or ylike if _x.flatten().shape != x.shape: @@ -1620,25 +1623,14 @@ def __between__(steps, sl0, sl1, _x, _args): steps[sl1, 1::2] = _args return steps - if len(x) == len(args[0]) + 1: + if xlike: steps = __between__(steps, 0, slice(1, None), x, args) - # steps[0, ::len(steps[0])-1] = x[::len(x)-1] - # steps[0, 2::2] = x[1:-1] - # steps[0, 1:-1:2] = x[1:-1] - # steps[1:, 0::2] = args - # steps[1:, 1::2] = args - - elif len(x) + 1 == len(args[0]): + elif ylike: steps = __between__(steps, slice(1, None), 0, args, x) - # steps[0, 0::2] = x - # steps[0, 1::2] = x - # steps[1:, ::len(steps[0])-1] = args[:, ::len(args[0])-1] - # steps[1:, 1:-1:2] = args[:, 1:-1] - # steps[1:, 2:-1:2] = args[:, 1:-1] else: - # Fall back to steps-post (legend drawing) + # Fall back to steps-post (e.g. legend drawing) steps = pts_to_poststep(x, *args) return steps @@ -1669,19 +1661,18 @@ def pts_to_betweenstep_edges(x, *args): ``N=0``, the length will be 0. """ + xlike = len(x) == len(args[0]) + 1 + ylike = len(x) + 1 == len(args[0]) + if not (xlike or ylike): + # Fall back to steps-post (e.g. legend drawing) + return pts_to_poststep(x, *args) + steps = pts_to_betweenstep(x, *args) # Extra steps to plot edges where values are missing (Nan). nan_cols = np.nonzero(np.isnan(np.sum(steps, axis=0)))[0] nan_cols[1::2] = nan_cols[1::2]+1 pad_steps = [] - xlike = len(x) == len(args[0]) + 1 - ylike = len(x) + 1 == len(args[0]) - if not (xlike or ylike): - # Fall back to steps-post (legend drawing) - edge_steps = pts_to_poststep(x, *args) - return edge_steps - for part in np.split(steps, nan_cols, axis=1): if not np.isnan(np.sum(part)): pad_part = np.zeros((2, part.shape[-1]+2)) @@ -1696,7 +1687,6 @@ def pts_to_betweenstep_edges(x, *args): pad_part[1, 0] = part[1, 0] pad_part[:, -1] = 0 pad_part[1, -1] = part[1, -1] - pad_steps.append(pad_part) else: pad_steps.append(part) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 5b13667f16b7..05f1aa08083a 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -1122,12 +1122,12 @@ def test_fill_between_histlike(fig_test, fig_ref): # Ref fig_ref, ref_axes = plt.subplots(2, 2) ref_axes = ref_axes.flatten() - ref_axes[0].fill_between(x, np.r_[y, y[-1]], step='post') - ref_axes[1].fill_betweenx(x, np.r_[y, y[-1]], step='post') - ref_axes[2].fill_between(x, np.r_[y, y[-1]], - np.r_[y2, y2[-1]], step='post') - ref_axes[3].fill_betweenx(x, np.r_[y, y[-1]], - np.r_[y2, y2[-1]], step='post') + ref_axes[0].fill_between(x, np.append(y, y[-1]), step='post') + ref_axes[1].fill_betweenx(x, np.append(y, y[-1]), step='post') + ref_axes[2].fill_between(x, np.append(y, y[-1]), + np.append(y2, y2[-1]), step='post') + ref_axes[3].fill_betweenx(x, np.append(y, y[-1]), + np.append(y2, y2[-1]), step='post') @image_comparison(['fill_between_step_between.pdf'], remove_text=True) @@ -3990,23 +3990,23 @@ def test_step_histlike(fig_test, fig_ref): # Ref fig_ref, ref_axes = plt.subplots(4, 2) ref_axes = ref_axes.flatten() - ref_axes[0].plot(x, np.r_[y, y[-1]], drawstyle='steps-post') - ref_axes[1].plot(np.r_[y[0], y], x, drawstyle='steps-post') + ref_axes[0].plot(x, np.append(y, y[-1]), drawstyle='steps-post') + ref_axes[1].plot(np.append(y[0], y), x, drawstyle='steps-post') - ref_axes[2].plot(x, np.r_[y, y[-1]], drawstyle='steps-post') + ref_axes[2].plot(x, np.append(y, y[-1]), drawstyle='steps-post') ref_axes[2].add_line(mlines.Line2D([x[0], x[0]], [0, y[0]])) ref_axes[2].add_line(mlines.Line2D([x[-1], x[-1]], [0, y[-1]])) - ref_axes[3].plot(np.r_[y[0], y], x, drawstyle='steps-post') + ref_axes[3].plot(np.append(y[0], y), x, drawstyle='steps-post') ref_axes[3].add_line(mlines.Line2D([0, y[0]], [x[0], x[0]])) ref_axes[3].add_line(mlines.Line2D([0, y[-1]], [x[-1], x[-1]])) - ref_axes[4].plot(x, np.r_[y, y[-1]], drawstyle='steps-post') + ref_axes[4].plot(x, np.append(y, y[-1]), drawstyle='steps-post') ref_axes[4].add_line(mlines.Line2D([x[0], x[0]], [0, y[0]])) ref_axes[4].add_line(mlines.Line2D([x[-1], x[-1]], [0, y[-1]])) ref_axes[4].semilogy() - ref_axes[5].plot(np.r_[y[0], y], x, drawstyle='steps-post') + ref_axes[5].plot(np.append(y[0], y), x, drawstyle='steps-post') ref_axes[5].add_line(mlines.Line2D([0, y[0]], [x[0], x[0]])) ref_axes[5].add_line(mlines.Line2D([0, y[-1]], [x[-1], x[-1]])) ref_axes[5].semilogx() From 023ebe35070ca1ccd0b7c311c04c5dfa79987c9f Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Wed, 8 Jan 2020 00:23:54 +0100 Subject: [PATCH 24/30] whitespace --- lib/matplotlib/axes/_axes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index c8931cb5b1e1..47499d94bf83 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -5142,7 +5142,7 @@ def fill_between(self, x, y1, y2=0, where=None, interpolate=False, pad_size = x.size if step == 'between': pad_size -= 1 - + if where is None: where = np.ones(pad_size).astype(bool) else: @@ -5364,7 +5364,7 @@ def fill_betweenx(self, y, x1, x2=0, where=None, message="The parameter where must have the same size as y " "in fill_between(). This will become an error in " "future versions of Matplotlib.") - + # Broadcast scalar values x1 = np.broadcast_to(x1, pad_size, subok=True) x2 = np.broadcast_to(x2, pad_size, subok=True) From 290be4ad45ca34768790da6f99b91a64e6514fc1 Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Mon, 13 Jan 2020 10:38:19 +0100 Subject: [PATCH 25/30] Fix typos --- lib/matplotlib/lines.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 3d75f2956de5..3c8f0a9df891 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -609,7 +609,7 @@ def set_picker(self, p): Parameters ---------- p : float or callable[[Artist, Event], Tuple[bool, dict]] - If a float, it is used as the pick radius in poits. + If a float, it is used as the pick radius in points. """ if callable(p): self._contains = p @@ -851,7 +851,7 @@ def draw(self, renderer): self.recache() self._transform_path(subslice) tpath, affine = (self._get_transformed_path() - .get_transformed_points_and_affine()) + .get_transformed_points_and_affine()) else: tpath, affine = (self._get_transformed_path() .get_transformed_points_and_affine()) From 46791b7d3624c95d1354dcc7e9f592e4dcf94937 Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Fri, 6 Mar 2020 11:52:10 +0100 Subject: [PATCH 26/30] Externalize polycollection prep into cbook --- lib/matplotlib/axes/_axes.py | 119 ++----------------------------- lib/matplotlib/cbook/__init__.py | 99 +++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 115 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 47499d94bf83..408259969783 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -5162,64 +5162,8 @@ def fill_between(self, x, y1, y2=0, where=None, interpolate=False, _get_masks = list(map(np.ma.getmask, [y1, y2])) where = where & ~functools.reduce(np.logical_or, _get_masks) - polys = [] - for ind0, ind1 in cbook.contiguous_regions(where): - if step == 'between': - xslice = x[ind0:ind1+1] - else: - xslice = x[ind0:ind1] - y1slice = y1[ind0:ind1] - y2slice = y2[ind0:ind1] - if step is not None: - step_func = cbook.STEP_LOOKUP_MAP["steps-" + step] - xslice, y1slice, y2slice = step_func(xslice, y1slice, y2slice) - - if not len(xslice): - continue - - N = len(xslice) - X = np.zeros((2 * N + 2, 2), float) - - if interpolate: - def get_interp_point(ind): - im1 = max(ind - 1, 0) - x_values = x[im1:ind + 1] - diff_values = y1[im1:ind + 1] - y2[im1:ind + 1] - y1_values = y1[im1:ind + 1] - - if len(diff_values) == 2: - if np.ma.is_masked(diff_values[1]): - return x[im1], y1[im1] - elif np.ma.is_masked(diff_values[0]): - return x[ind], y1[ind] - - diff_order = diff_values.argsort() - diff_root_x = np.interp( - 0, diff_values[diff_order], x_values[diff_order]) - x_order = x_values.argsort() - diff_root_y = np.interp(diff_root_x, x_values[x_order], - y1_values[x_order]) - return diff_root_x, diff_root_y - - start = get_interp_point(ind0) - end = get_interp_point(ind1) - else: - # the purpose of the next two lines is for when y2 is a - # scalar like 0 and we want the fill to go all the way - # down to 0 even if none of the y1 sample points do - start = xslice[0], y2slice[0] - end = xslice[-1], y2slice[-1] - - X[0] = start - X[N + 1] = end - - X[1:N + 1, 0] = xslice - X[1:N + 1, 1] = y1slice - X[N + 2:, 0] = xslice[::-1] - X[N + 2:, 1] = y2slice[::-1] - - polys.append(X) - + polys = cbook._get_fillbetween_polys(x, y1, y2, where, step=step, + interpolate=interpolate, dir='y') collection = mcoll.PolyCollection(polys, **kwargs) # now update the datalim and autoscale @@ -5373,63 +5317,8 @@ def fill_betweenx(self, y, x1, x2=0, where=None, _get_masks = list(map(np.ma.getmask, [x1, x2])) where = where & ~functools.reduce(np.logical_or, _get_masks) - polys = [] - for ind0, ind1 in cbook.contiguous_regions(where): - if step == 'between': - yslice = y[ind0:ind1+1] - else: - yslice = y[ind0:ind1] - x1slice = x1[ind0:ind1] - x2slice = x2[ind0:ind1] - if step is not None: - step_func = cbook.STEP_LOOKUP_MAP["steps-" + step] - yslice, x1slice, x2slice = step_func(yslice, x1slice, x2slice) - - if not len(yslice): - continue - - N = len(yslice) - Y = np.zeros((2 * N + 2, 2), float) - if interpolate: - def get_interp_point(ind): - im1 = max(ind - 1, 0) - y_values = y[im1:ind + 1] - diff_values = x1[im1:ind + 1] - x2[im1:ind + 1] - x1_values = x1[im1:ind + 1] - - if len(diff_values) == 2: - if np.ma.is_masked(diff_values[1]): - return x1[im1], y[im1] - elif np.ma.is_masked(diff_values[0]): - return x1[ind], y[ind] - - diff_order = diff_values.argsort() - diff_root_y = np.interp( - 0, diff_values[diff_order], y_values[diff_order]) - y_order = y_values.argsort() - diff_root_x = np.interp(diff_root_y, y_values[y_order], - x1_values[y_order]) - return diff_root_x, diff_root_y - - start = get_interp_point(ind0) - end = get_interp_point(ind1) - else: - # the purpose of the next two lines is for when x2 is a - # scalar like 0 and we want the fill to go all the way - # down to 0 even if none of the x1 sample points do - start = x2slice[0], yslice[0] - end = x2slice[-1], yslice[-1] - - Y[0] = start - Y[N + 1] = end - - Y[1:N + 1, 0] = x1slice - Y[1:N + 1, 1] = yslice - Y[N + 2:, 0] = x2slice[::-1] - Y[N + 2:, 1] = yslice[::-1] - - polys.append(Y) - + polys = cbook._get_fillbetween_polys(y, x1, x2, where, step=step, + interpolate=interpolate, dir='x') collection = mcoll.PolyCollection(polys, **kwargs) # now update the datalim and autoscale diff --git a/lib/matplotlib/cbook/__init__.py b/lib/matplotlib/cbook/__init__.py index a1aff6678ffe..5a767d1ee2e4 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -1580,6 +1580,105 @@ def pts_to_prestep(x, *args): return steps +def get_fillbetween_polys(x, y1, y2, where, step=None, + interpolate=False, dir='x'): + """ + A helper function for fill_between and fill_betweenx. + Converts x, y1, y2 or y, x1, x2 arrays into vertices to fill + polycollection. + + Parameters + ---------- + x (y): array + Base array for fill_between (x-like) + + y1 (x1): array + Upper edge boundary for fill_between plot (y1-like) + + y2 (x2): array + Lower edge boundary for fill_between plot (y2-like) + + where: array + Bool array. See `~.axes.Axes.fill_between` + + step : {'pre', 'post', 'mid', 'between'}, optional + See `~.axes.Axes.fill_between` + + interpolate : bool, default: False + See `~.axes.Axes.fill_between` + + dir : {'x', 'y}, optional, default: 'x' + Return vertex collection for `~.axes.Axes.fill_between` or + `~.axes.Axes.fill_betweenx` + + Returns + ------- + out : array + ``Nx2`` array of verstices + """ + polys = [] + for ind0, ind1 in contiguous_regions(where): + if step == 'between': + xslice = x[ind0:ind1+1] + else: + xslice = x[ind0:ind1] + y1slice = y1[ind0:ind1] + y2slice = y2[ind0:ind1] + if step is not None: + step_func = STEP_LOOKUP_MAP["steps-" + step] + xslice, y1slice, y2slice = step_func(xslice, y1slice, y2slice) + + if not len(xslice): + continue + + N = len(xslice) + X = np.zeros((2 * N + 2, 2), float) + + if interpolate: + def get_interp_point(ind): + im1 = max(ind - 1, 0) + x_values = x[im1:ind + 1] + diff_values = y1[im1:ind + 1] - y2[im1:ind + 1] + y1_values = y1[im1:ind + 1] + + if len(diff_values) == 2: + if np.ma.is_masked(diff_values[1]): + return x[im1], y1[im1] + elif np.ma.is_masked(diff_values[0]): + return x[ind], y1[ind] + + diff_order = diff_values.argsort() + diff_root_x = np.interp( + 0, diff_values[diff_order], x_values[diff_order]) + x_order = x_values.argsort() + diff_root_y = np.interp(diff_root_x, x_values[x_order], + y1_values[x_order]) + return diff_root_x, diff_root_y + + start = get_interp_point(ind0) + end = get_interp_point(ind1) + else: + # the purpose of the next two lines is for when y2 is a + # scalar like 0 and we want the fill to go all the way + # down to 0 even if none of the y1 sample points do + start = xslice[0], y2slice[0] + end = xslice[-1], y2slice[-1] + + X[0] = start + X[N + 1] = end + + X[1:N + 1, 0] = xslice + X[1:N + 1, 1] = y1slice + X[N + 2:, 0] = xslice[::-1] + X[N + 2:, 1] = y2slice[::-1] + + if dir == 'y': + polys.append(X) + elif dir == 'x': + polys.append(X.T[::-1].T) + return polys + + def pts_to_betweenstep(x, *args): """ Convert continuous line to between-steps. From 1989735c4dbf4578bf3a78bd6ecde9cd2adcb697 Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Fri, 6 Mar 2020 12:17:13 +0100 Subject: [PATCH 27/30] Fix name --- lib/matplotlib/cbook/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/cbook/__init__.py b/lib/matplotlib/cbook/__init__.py index 5a767d1ee2e4..68e940e16706 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -1580,7 +1580,7 @@ def pts_to_prestep(x, *args): return steps -def get_fillbetween_polys(x, y1, y2, where, step=None, +def _get_fillbetween_polys(x, y1, y2, where, step=None, interpolate=False, dir='x'): """ A helper function for fill_between and fill_betweenx. From c26aa110cc486f83fa2b30845f7cbb7986508b3f Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Fri, 6 Mar 2020 14:45:53 +0100 Subject: [PATCH 28/30] flake --- lib/matplotlib/cbook/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/cbook/__init__.py b/lib/matplotlib/cbook/__init__.py index 002b0d08f0ad..54cc15b5e5c0 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -1609,7 +1609,7 @@ def pts_to_prestep(x, *args): def _get_fillbetween_polys(x, y1, y2, where, step=None, - interpolate=False, dir='x'): + interpolate=False, dir='x'): """ A helper function for fill_between and fill_betweenx. Converts x, y1, y2 or y, x1, x2 arrays into vertices to fill @@ -1636,7 +1636,7 @@ def _get_fillbetween_polys(x, y1, y2, where, step=None, See `~.axes.Axes.fill_between` dir : {'x', 'y}, optional, default: 'x' - Return vertex collection for `~.axes.Axes.fill_between` or + Return vertex collection for `~.axes.Axes.fill_between` or `~.axes.Axes.fill_betweenx` Returns From b4cae0d1ec4127396569473af8cc49a14a41b6a8 Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Sat, 7 Mar 2020 18:12:47 +0100 Subject: [PATCH 29/30] private --- lib/matplotlib/cbook/__init__.py | 4 ++-- lib/matplotlib/lines.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/cbook/__init__.py b/lib/matplotlib/cbook/__init__.py index 54cc15b5e5c0..d33dba67f83e 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -1533,7 +1533,7 @@ def violin_stats(X, method, points=100, quantiles=None): return vpstats -def pad_arrays(*v, padval=np.nan): +def _pad_arrays(*v, padval=np.nan): """ Pad list of arrays of varying lengths to the same size with specified value. Useful for padding asymetrical arrays @@ -1554,7 +1554,7 @@ def pad_arrays(*v, padval=np.nan): Examples -------- - >>> a, b, c = pad_arrays(np.array([1,2,3,4]), np.array([1,2,3]), + >>> a, b, c = _pad_arrays(np.array([1,2,3,4]), np.array([1,2,3]), np.array([1])) >>> a np.array([1, 2, 3, 4]) diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index aaea9db414e8..3832f0c5773a 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -616,7 +616,7 @@ def get_window_extent(self, renderer): bbox = Bbox([[0, 0], [0, 0]]) trans_data_to_xy = self.get_transform().transform padded_xy = np.column_stack( - cbook.pad_arrays(*self.get_xydata())).astype(float) + cbook._pad_arrays(*self.get_xydata())).astype(float) bbox.update_from_data_xy(trans_data_to_xy(padded_xy), ignore=True) # correct for marker size, if any if self._marker: From 3fd3b434029b41b0447d44ae49bf72b041d0ce7c Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Sat, 7 Mar 2020 22:02:50 +0100 Subject: [PATCH 30/30] private --- lib/matplotlib/cbook/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/cbook/__init__.py b/lib/matplotlib/cbook/__init__.py index d33dba67f83e..534a189ed653 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -1707,7 +1707,7 @@ def get_interp_point(ind): return polys -def pts_to_betweenstep(x, *args): +def _pts_to_betweenstep(x, *args): """ Convert continuous line to between-steps. @@ -1763,7 +1763,7 @@ def __between__(steps, sl0, sl1, _x, _args): return steps -def pts_to_betweenstep_edges(x, *args): +def _pts_to_betweenstep_edges(x, *args): """ Convert continuous line to between-steps, adding edges @@ -1794,7 +1794,7 @@ def pts_to_betweenstep_edges(x, *args): # Fall back to steps-post (e.g. legend drawing) return pts_to_poststep(x, *args) - steps = pts_to_betweenstep(x, *args) + steps = _pts_to_betweenstep(x, *args) # Extra steps to plot edges where values are missing (Nan). nan_cols = np.nonzero(np.isnan(np.sum(steps, axis=0)))[0] nan_cols[1::2] = nan_cols[1::2]+1 @@ -1900,8 +1900,8 @@ def pts_to_midstep(x, *args): 'steps-pre': pts_to_prestep, 'steps-post': pts_to_poststep, 'steps-mid': pts_to_midstep, - 'steps-between': pts_to_betweenstep, - 'steps-edges': pts_to_betweenstep_edges} + 'steps-between': _pts_to_betweenstep, + 'steps-edges': _pts_to_betweenstep_edges} def index_of(y):