diff --git a/lib/mpl_toolkits/axes_grid1/axes_divider.py b/lib/mpl_toolkits/axes_grid1/axes_divider.py index 2722a3fe507c..680ad9bf3e92 100644 --- a/lib/mpl_toolkits/axes_grid1/axes_divider.py +++ b/lib/mpl_toolkits/axes_grid1/axes_divider.py @@ -583,39 +583,67 @@ def get_subplotspec(self): return None -class HBoxDivider(SubplotDivider): +# Helper for HBoxDivider/VBoxDivider. +# The variable names are written for a horizontal layout, but the calculations +# work identically for vertical layouts (and likewise for the helpers below). +def _determine_karray(summed_widths, equal_heights, total_width, max_height): + n = len(equal_heights) + eq_rs, eq_as = np.asarray(equal_heights).T + sm_rs, sm_as = np.asarray(summed_widths).T + A = np.zeros((n + 1, n + 1)) + B = np.zeros(n + 1) + np.fill_diagonal(A[:n, :n], eq_rs) + A[:n, -1] = -1 + A[-1, :-1] = sm_rs + B[:n] = -eq_as + B[-1] = total_width - sum(sm_as) + # A @ K = B: This solves for {k_0, ..., k_{N-1}, H} so that + # eq_r_i * k_i + eq_a_i = H for all i: all axes have the same height + # sum(sm_r_i * k_i + sm_a_i) = total_summed_width: fixed total width + # (foo_r_i * k_i + foo_a_i will end up being the size of foo.) + karray_and_height = np.linalg.solve(A, B) + karray = karray_and_height[:-1] + height = karray_and_height[-1] + if height > max_height: # Additionally, upper-bound the height. + karray = (max_height - eq_as) / eq_rs + return karray + + +# Helper for HBoxDivider/VBoxDivider (see above re: variable naming). +def _calc_offsets(summed_sizes, karray): + offsets = [0.] + for (r, a), k in zip(summed_sizes, karray): + offsets.append(offsets[-1] + r*k + a) + return offsets + + +# Helper for HBoxDivider/VBoxDivider (see above re: variable naming). +def _locate(x, y, w, h, summed_widths, equal_heights, fig_w, fig_h, anchor): + karray = _determine_karray( + summed_widths, equal_heights, + total_width=fig_w * w, max_height=fig_h * h) + ox = _calc_offsets(summed_widths, karray) + + ww = (ox[-1] - ox[0]) / fig_w + h0_r, h0_a = equal_heights[0] + hh = (karray[0]*h0_r + h0_a) / fig_h + pb = mtransforms.Bbox.from_bounds(x, y, w, h) + pb1 = mtransforms.Bbox.from_bounds(x, y, ww, hh) + pb1_anchored = pb1.anchored(anchor, pb) + x0, y0 = pb1_anchored.x0, pb1_anchored.y0 + + return x0, y0, ox, hh - @staticmethod - def _determine_karray(equivalent_sizes, appended_sizes, - max_equivalent_size, - total_appended_size): - n = len(equivalent_sizes) - eq_rs, eq_as = np.asarray(equivalent_sizes).T - ap_rs, ap_as = np.asarray(appended_sizes).T - A = np.zeros((n + 1, n + 1)) - B = np.zeros(n + 1) - np.fill_diagonal(A[:n, :n], eq_rs) - A[:n, -1] = -1 - A[-1, :-1] = ap_rs - B[:n] = -eq_as - B[-1] = total_appended_size - sum(ap_as) - # A @ K = B: This solves for {k_0, ..., k_{N-1}, H} so that - # eq_r_i * k_i + eq_a_i = H for all i: all axes have the same height - # sum(ap_r_i * k_i + ap_a_i) = total_appended_size: fixed total width - # (foo_r_i * k_i + foo_a_i will end up being the size of foo.) - karray_H = np.linalg.solve(A, B) - karray = karray_H[:-1] - H = karray_H[-1] - if H > max_equivalent_size: # Additionally, upper-bound the height. - karray = (max_equivalent_size - eq_as) / eq_rs - return karray - @staticmethod - def _calc_offsets(appended_sizes, karray): - offsets = [0.] - for (r, a), k in zip(appended_sizes, karray): - offsets.append(offsets[-1] + r*k + a) - return offsets +class HBoxDivider(SubplotDivider): + """ + A `SubplotDivider` for laying out axes horizontally, while ensuring that + they have equal heights. + + Examples + -------- + .. plot:: gallery/axes_grid1/demo_axes_hbox_divider.py + """ def new_locator(self, nx, nx1=None): """ @@ -631,52 +659,25 @@ def new_locator(self, nx, nx1=None): """ return AxesLocator(self, nx, 0, nx1, None) - def _locate(self, x, y, w, h, - y_equivalent_sizes, x_appended_sizes, - figW, figH): - equivalent_sizes = y_equivalent_sizes - appended_sizes = x_appended_sizes - - max_equivalent_size = figH * h - total_appended_size = figW * w - karray = self._determine_karray(equivalent_sizes, appended_sizes, - max_equivalent_size, - total_appended_size) - - ox = self._calc_offsets(appended_sizes, karray) - - ww = (ox[-1] - ox[0]) / figW - ref_h = equivalent_sizes[0] - hh = (karray[0]*ref_h[0] + ref_h[1]) / figH - pb = mtransforms.Bbox.from_bounds(x, y, w, h) - pb1 = mtransforms.Bbox.from_bounds(x, y, ww, hh) - pb1_anchored = pb1.anchored(self.get_anchor(), pb) - x0, y0 = pb1_anchored.x0, pb1_anchored.y0 - - return x0, y0, ox, hh - def locate(self, nx, ny, nx1=None, ny1=None, axes=None, renderer=None): # docstring inherited - figW, figH = self._fig.get_size_inches() + fig_w, fig_h = self._fig.get_size_inches() x, y, w, h = self.get_position_runtime(axes, renderer) - - y_equivalent_sizes = self.get_vertical_sizes(renderer) - x_appended_sizes = self.get_horizontal_sizes(renderer) - x0, y0, ox, hh = self._locate(x, y, w, h, - y_equivalent_sizes, x_appended_sizes, - figW, figH) + summed_ws = self.get_horizontal_sizes(renderer) + equal_hs = self.get_vertical_sizes(renderer) + x0, y0, ox, hh = _locate( + x, y, w, h, summed_ws, equal_hs, fig_w, fig_h, self.get_anchor()) if nx1 is None: nx1 = nx + 1 - - x1, w1 = x0 + ox[nx] / figW, (ox[nx1] - ox[nx]) / figW + x1, w1 = x0 + ox[nx] / fig_w, (ox[nx1] - ox[nx]) / fig_w y1, h1 = y0, hh - return mtransforms.Bbox.from_bounds(x1, y1, w1, h1) -class VBoxDivider(HBoxDivider): +class VBoxDivider(SubplotDivider): """ - The Divider class whose rectangle area is specified as a subplot geometry. + A `SubplotDivider` for laying out axes vertically, while ensuring that they + have equal widths. """ def new_locator(self, ny, ny1=None): @@ -695,20 +696,16 @@ def new_locator(self, ny, ny1=None): def locate(self, nx, ny, nx1=None, ny1=None, axes=None, renderer=None): # docstring inherited - figW, figH = self._fig.get_size_inches() + fig_w, fig_h = self._fig.get_size_inches() x, y, w, h = self.get_position_runtime(axes, renderer) - - x_equivalent_sizes = self.get_horizontal_sizes(renderer) - y_appended_sizes = self.get_vertical_sizes(renderer) - y0, x0, oy, ww = self._locate(y, x, h, w, - x_equivalent_sizes, y_appended_sizes, - figH, figW) + summed_hs = self.get_vertical_sizes(renderer) + equal_ws = self.get_horizontal_sizes(renderer) + y0, x0, oy, ww = _locate( + y, x, h, w, summed_hs, equal_ws, fig_h, fig_w, self.get_anchor()) if ny1 is None: ny1 = ny + 1 - x1, w1 = x0, ww - y1, h1 = y0 + oy[ny] / figH, (oy[ny1] - oy[ny]) / figH - + y1, h1 = y0 + oy[ny] / fig_h, (oy[ny1] - oy[ny]) / fig_h return mtransforms.Bbox.from_bounds(x1, y1, w1, h1)