|
10 | 10 | import json
|
11 | 11 | from itertools import product, cycle, chain
|
12 | 12 | from functools import reduce
|
| 13 | +import re |
13 | 14 |
|
14 | 15 |
|
15 | 16 | def multi_index(*kwargs):
|
@@ -49,6 +50,118 @@ def extract_axis_titles(fig):
|
49 | 50 | return (r_titles, c_titles)
|
50 | 51 |
|
51 | 52 |
|
| 53 | +def make_subplots_all_secondary_y(rows, cols): |
| 54 | + """ |
| 55 | + Get subplots like make_subplots but all also have secondary y-axes. |
| 56 | + """ |
| 57 | + grid_ref_shape = [rows, cols] |
| 58 | + specs = [ |
| 59 | + [dict(secondary_y=True) for __ in range(grid_ref_shape[1])] |
| 60 | + for _ in range(grid_ref_shape[0]) |
| 61 | + ] |
| 62 | + fig = make_subplots(*grid_ref_shape, specs=specs) |
| 63 | + return fig |
| 64 | + |
| 65 | + |
| 66 | +def parse_axis_ref(ax): |
| 67 | + """ Find the axis letter, optional number, and domain of axis. """ |
| 68 | + # TODO: can this be obtained via codegen? |
| 69 | + pat = re.compile("([xy])(axis)?([0-9]*)( domain)?") |
| 70 | + matches = pat.match(ax) |
| 71 | + if matches is None: |
| 72 | + raise ValueError('Axis "%s" cannot be parsed.' % (ax,)) |
| 73 | + return (matches[1], matches[3], matches[4]) |
| 74 | + |
| 75 | + |
| 76 | +def norm_axis_ref(ax): |
| 77 | + """ normalize ax so it is in the format: yaxis, yaxis2, xaxis7 etc. """ |
| 78 | + al, an, _ = parse_axis_ref(ax) |
| 79 | + return al + "axis" + an |
| 80 | + |
| 81 | + |
| 82 | +def axis_pair_to_row_col(fig, axpair): |
| 83 | + """ |
| 84 | + returns the row and column of the subplot having the axis pair and whether it is a |
| 85 | + secondary y |
| 86 | + """ |
| 87 | + if "paper" in axpair: |
| 88 | + raise ValueError('Cannot find row and column of "paper" axis reference.') |
| 89 | + naxpair = tuple([norm_axis_ref(ax) for ax in axpair]) |
| 90 | + nrows, ncols = fig_grid_ref_shape(fig) |
| 91 | + row = None |
| 92 | + col = None |
| 93 | + for r, c in multi_index(nrows, ncols): |
| 94 | + for sp in fig._grid_ref[r][c]: |
| 95 | + if naxpair == sp.layout_keys: |
| 96 | + row = r + 1 |
| 97 | + col = c + 1 |
| 98 | + if row is None or col is None: |
| 99 | + raise ValueError("Could not find subplot containing axes (%s,%s)." % nax) |
| 100 | + secondary_y = False |
| 101 | + yax = naxpair[1] |
| 102 | + if fig.layout[yax]["side"] == "right": |
| 103 | + secondary_y = True |
| 104 | + return (row, col, secondary_y) |
| 105 | + |
| 106 | + |
| 107 | +def find_subplot_axes(fig, row, col, secondary_y=False): |
| 108 | + """ |
| 109 | + Returns 2-tuple containing (xaxis,yaxis) at specified row, col and secondary y-axis. |
| 110 | + """ |
| 111 | + nrows, ncols = fig_grid_ref_shape(fig) |
| 112 | + try: |
| 113 | + sps = fig._grid_ref[row - 1][col - 1] |
| 114 | + except IndexError: |
| 115 | + raise IndexError( |
| 116 | + "Figure does not have a subplot at the requested row or column." |
| 117 | + ) |
| 118 | + |
| 119 | + def _check_is_secondary_y(sp): |
| 120 | + xax, yax = sp.layout_keys |
| 121 | + # TODO: It may not be totally accurate to assume if an y-axis' "side" is |
| 122 | + # "right" than it is a secondary y axis... |
| 123 | + return fig.layout[yax]["side"] == "right" |
| 124 | + |
| 125 | + # find the secondary y axis |
| 126 | + err_msg = ( |
| 127 | + "Could not find a y-axis " "at the subplot in the requested row or column." |
| 128 | + ) |
| 129 | + filter_fun = lambda sp: not _check_is_secondary_y(sp) |
| 130 | + if secondary_y: |
| 131 | + err_msg = ( |
| 132 | + "Could not find a secondary y-axis " |
| 133 | + "at the subplot in the requested row or column." |
| 134 | + ) |
| 135 | + filter_fun = _check_is_secondary_y |
| 136 | + try: |
| 137 | + sp = list(filter(filter_fun, sps))[0] |
| 138 | + except (IndexError, TypeError): |
| 139 | + # Catch IndexError if the list is empty, catch TypeError if sps isn't |
| 140 | + # iterable (e.g., is None) |
| 141 | + raise IndexError(err_msg) |
| 142 | + return sp.layout_keys |
| 143 | + |
| 144 | + |
| 145 | +def map_axis_pair(old_fig, new_fig, axpair, make_axis_ref=True): |
| 146 | + """ |
| 147 | + Find the axes on the new figure that will give the same subplot and |
| 148 | + possibly secondary y axis as on the old figure. This can only |
| 149 | + work if the axis pair is ("paper","paper") or the axis pair corresponds to a |
| 150 | + subplot on the old figure the new figure has corresponding rows, |
| 151 | + columns and secondary y-axes. |
| 152 | + if make_axis_ref is True, axis is removed from the resulting strings, e.g., xaxis2 -> x2 |
| 153 | + """ |
| 154 | + if axpair == ("paper", "paper"): |
| 155 | + return ax |
| 156 | + row, col, secondary_y = axis_pair_to_row_col(old_fig, axpair) |
| 157 | + newaxpair = find_subplot_axes(new_fig, row, col, secondary_y) |
| 158 | + axpair_extras = [" domain" if ax.endswith("domain") else "" for ax in axpair] |
| 159 | + newaxpair = tuple(ax + extra for ax, extra in zip(newaxpair, axpair_extras)) |
| 160 | + if make_axis_ref: |
| 161 | + newaxpair = tuple(ax.replace("axis", "") for ax in newaxpair) |
| 162 | + return newaxpair |
| 163 | + |
| 164 | + |
52 | 165 | def px_simple_combine(fig0, fig1, fig1_secondary_y=False):
|
53 | 166 | """
|
54 | 167 | Combines two figures by just using the layout of the first figure and
|
|
0 commit comments