|
1 |
| -def compute_anchors(position, vertical): |
| 1 | +import plotly.graph_objects as go |
| 2 | +from numpy import argmax, argmin, mean |
| 3 | + |
| 4 | + |
| 5 | +def compute_anchors_for_line(position, vertical): |
2 | 6 | xanchor = None
|
3 | 7 | yanchor = None
|
4 | 8 | if vertical:
|
5 |
| - if position in ["n", "sw", "se"]: |
| 9 | + if position in ["n", "top", "sw", "bottom left", "se", "bottom right"]: |
6 | 10 | yanchor = "bottom"
|
7 |
| - if position in ["nw", "ne", "s"]: |
| 11 | + if position in ["nw", "top left", "ne", "top right", "s", "bottom"]: |
8 | 12 | yanchor = "top"
|
9 |
| - if position in ["w", "e"]: |
| 13 | + if position in ["w", "left", "e", "right"]: |
10 | 14 | yanchor = "middle"
|
11 |
| - if position in ["ne", "e", "se"]: |
| 15 | + if position in ["ne", "top right", "e", "right", "se", "bottom right"]: |
12 | 16 | xanchor = "left"
|
13 |
| - if position in ["sw", "w", "nw"]: |
| 17 | + if position in ["sw", "bottom left", "w", "left", "nw", "top left"]: |
14 | 18 | xanchor = "right"
|
15 |
| - if position in ["n", "s"]: |
| 19 | + if position in ["n", "top", "s", "bottom"]: |
16 | 20 | xanchor = "center"
|
17 | 21 | else:
|
18 |
| - if position in ["nw", "n", "ne"]: |
| 22 | + if position in ["nw", "top left", "n", "top", "ne", "top right"]: |
19 | 23 | yanchor = "bottom"
|
20 |
| - if position in ["se", "s", "sw"]: |
| 24 | + if position in ["se", "bottom right", "s", "bottom", "sw", "bottom left"]: |
21 | 25 | yanchor = "top"
|
22 |
| - if position in ["w", "e"]: |
| 26 | + if position in ["w", "left", "e", "right"]: |
23 | 27 | yanchor = "middle"
|
24 |
| - if position in ["e", "sw", "nw"]: |
| 28 | + if position in ["e", "right", "sw", "bottom left", "nw", "top left"]: |
25 | 29 | xanchor = "left"
|
26 |
| - if position in ["ne", "se", "w"]: |
| 30 | + if position in ["ne", "top right", "se", "bottom right", "w", "left"]: |
27 | 31 | xanchor = "right"
|
28 |
| - if position in ["n", "s"]: |
| 32 | + if position in ["n", "top", "s", "bottom"]: |
29 | 33 | xanchor = "center"
|
30 | 34 | return xanchor, yanchor
|
31 | 35 |
|
32 | 36 |
|
33 |
| -def compute_coord(annotype, position): |
34 |
| - # If annotype is vline, this is y value, if hline, this is x value |
35 |
| - if annotype == "vline": |
36 |
| - if position in ["nw", "n", "ne"]: |
| 37 | +def compute_coord_for_line(position, vertical): |
| 38 | + # If vertical, this is y value, otherwise, this is x value |
| 39 | + if vertical: |
| 40 | + if position in ["nw", "top left", "n", "top", "ne", "top right"]: |
37 | 41 | return 1
|
38 |
| - if position in ["w", "e"]: |
| 42 | + if position in ["w", "left", "e", "right"]: |
39 | 43 | return 0.5
|
40 |
| - if position in ["sw", "s", "se"]: |
| 44 | + if position in ["sw", "bottom left", "s", "bottom", "se", "bottom right"]: |
41 | 45 | return 0
|
42 |
| - if annotype == "hline": |
43 |
| - if position in ["nw", "sw", "w", "nw"]: |
| 46 | + else: |
| 47 | + if position in [ |
| 48 | + "nw", |
| 49 | + "top left", |
| 50 | + "sw", |
| 51 | + "bottom left", |
| 52 | + "w", |
| 53 | + "left", |
| 54 | + "nw", |
| 55 | + "top left", |
| 56 | + ]: |
44 | 57 | return 0
|
45 |
| - if position in ["n", "s"]: |
| 58 | + if position in ["n", "top", "s", "bottom"]: |
46 | 59 | return 0.5
|
47 |
| - if position in ["ne", "e", "se"]: |
| 60 | + if position in ["ne", "top right", "e", "right", "se", "bottom right"]: |
48 | 61 | return 1
|
| 62 | + |
| 63 | + |
| 64 | +def annotation_params_for_line(shape_type, shape_args, position): |
| 65 | + # all x0, x1, y0, y1 are used to place the annotation, that way it could |
| 66 | + # work with a slanted line |
| 67 | + # even with a slanted line, there are the horizontal and vertical |
| 68 | + # conventions of placing a shape |
| 69 | + x0 = shape_args["x0"] |
| 70 | + x1 = shape_args["x1"] |
| 71 | + y0 = shape_args["y0"] |
| 72 | + y1 = shape_args["y1"] |
| 73 | + X = [x0, x1] |
| 74 | + Y = [y0, y1] |
| 75 | + R = "right" |
| 76 | + T = "top" |
| 77 | + L = "left" |
| 78 | + C = "center" |
| 79 | + B = "bottom" |
| 80 | + M = "middle" |
| 81 | + aY = max(Y) |
| 82 | + iY = min(Y) |
| 83 | + mY = mean(Y) |
| 84 | + aaY = argmax(Y) |
| 85 | + aiY = argmin(Y) |
| 86 | + aX = max(X) |
| 87 | + iX = min(X) |
| 88 | + mX = mean(X) |
| 89 | + aaX = argmax(X) |
| 90 | + aiX = argmin(X) |
| 91 | + |
| 92 | + def _d(xanchor, yanchor, x, y): |
| 93 | + return dict(xanchor=xanchor, yanchor=yanchor, x=x, y=y) |
| 94 | + |
| 95 | + if shape_type == "vline": |
| 96 | + if position == "top left": |
| 97 | + return _d(R, T, X[aaY], aY) |
| 98 | + if position == "top right": |
| 99 | + return _d(L, T, X[aaY], aY) |
| 100 | + if position == "top": |
| 101 | + return _d(C, B, X[aaY], aY) |
| 102 | + if position == "bottom left": |
| 103 | + return _d(R, B, X[aiY], iY) |
| 104 | + if position == "bottom right": |
| 105 | + return _d(L, B, X[aiY], iY) |
| 106 | + if position == "bottom": |
| 107 | + return _d(C, T, X[aiY], iY) |
| 108 | + if position == "left": |
| 109 | + return _d(R, M, eX, eY) |
| 110 | + if position == "right": |
| 111 | + return _d(L, M, eX, eY) |
| 112 | + return _d(L, T, X[aaY], aY) |
| 113 | + if shape_type == "hline": |
| 114 | + if position == "top left": |
| 115 | + return _d(L, B, iX, Y[aiX]) |
| 116 | + if position == "top right": |
| 117 | + return _d(R, B, aX, Y[aaX]) |
| 118 | + if position == "top": |
| 119 | + return _d(C, B, mX, mY) |
| 120 | + if position == "bottom left": |
| 121 | + return _d(L, T, iX, Y[aiX]) |
| 122 | + if position == "bottom right": |
| 123 | + return _d(R, T, aX, Y[aaX]) |
| 124 | + if position == "bottom": |
| 125 | + return _d(C, T, mX, mY) |
| 126 | + if position == "left": |
| 127 | + return _d(R, M, iX, Y[aiX]) |
| 128 | + if position == "right": |
| 129 | + return _d(L, M, aX, Y[aaX]) |
| 130 | + return _d(R, B, aX, Y[aaX]) |
| 131 | + |
| 132 | + |
| 133 | +def annotation_params_for_rect(shape_type, shape_args, position): |
| 134 | + x0 = shape_args["x0"] |
| 135 | + x1 = shape_args["x1"] |
| 136 | + y0 = shape_args["y0"] |
| 137 | + y1 = shape_args["y1"] |
| 138 | + |
| 139 | + def _d(xanchor, yanchor, x, y): |
| 140 | + return dict(xanchor=xanchor, yanchor=yanchor, x=x, y=y) |
| 141 | + |
| 142 | + if position == "inside top left": |
| 143 | + return _d("left", "top", min([x0, x1]), max([y0, y1])) |
| 144 | + if position == "inside top right": |
| 145 | + return _d("right", "top", max([x0, x1]), max([y0, y1])) |
| 146 | + if position == "inside top": |
| 147 | + return _d("center", "top", mean([x0, x1]), max([y0, y1])) |
| 148 | + if position == "inside bottom left": |
| 149 | + return _d("left", "bottom", min([x0, x1]), min([y0, y1])) |
| 150 | + if position == "inside bottom right": |
| 151 | + return _d("right", "bottom", max([x0, x1]), min([y0, y1])) |
| 152 | + if position == "inside bottom": |
| 153 | + return _d("center", "bottom", mean([x0, x1]), min([y0, y1])) |
| 154 | + if position == "inside left": |
| 155 | + return _d("left", "middle", min([x0, x1]), mean([y0, y1])) |
| 156 | + if position == "inside right": |
| 157 | + return _d("right", "middle", max([x0, x1]), mean([y0, y1])) |
| 158 | + if position == "inside": |
| 159 | + # TODO: Do we want this? |
| 160 | + return _d("center", "middle", mean([x0, x1]), mean([y0, y1])) |
| 161 | + if position == "outside top left": |
| 162 | + return _d("right", "bottom", min([x0, x1]), max([y0, y1])) |
| 163 | + if position == "outside top right": |
| 164 | + return _d("left", "bottom", max([x0, x1]), max([y0, y1])) |
| 165 | + if position == "outside top": |
| 166 | + return _d("center", "bottom", mean([x0, x1]), max([y0, y1])) |
| 167 | + if position == "outside bottom left": |
| 168 | + return _d("right", "top", min([x0, x1]), min([y0, y1])) |
| 169 | + if position == "outside bottom right": |
| 170 | + return _d("left", "top", max([x0, x1]), min([y0, y1])) |
| 171 | + if position == "outside bottom": |
| 172 | + return _d("center", "top", mean([x0, x1]), min([y0, y1])) |
| 173 | + if position == "outside left": |
| 174 | + return _d("right", "middle", min([x0, x1]), mean([y0, y1])) |
| 175 | + if position == "outside right": |
| 176 | + return _d("left", "middle", max([x0, x1]), mean([y0, y1])) |
| 177 | + # default is inside top right |
| 178 | + return _d("right", "top", max([x0, x1]), max([y0, y1])) |
| 179 | + |
| 180 | + |
| 181 | +def axis_spanning_shape_annotation(annotation, shape_type, shape_args, kwargs): |
| 182 | + """ |
| 183 | + annotation: a go.layout.Annotation object, a dict describing an annotation, or None |
| 184 | + shape_type: one of 'vline', 'hline', 'vrect', 'hrect' and determines how the |
| 185 | + x, y, xanchor, and yanchor values are set. |
| 186 | + shape_args: the parameters used to draw the shape, which are used to place the annotation |
| 187 | + kwargs: a dictionary that was the kwargs of a |
| 188 | + _process_multiple_axis_spanning_shapes spanning shapes call. Items in this |
| 189 | + dict whose keys start with 'annotation_' will be extracted and the keys with |
| 190 | + the 'annotation_' part stripped off will be used to assign properties of the |
| 191 | + new annotation. |
| 192 | +
|
| 193 | + Property precedence: |
| 194 | + The annotation's x, y, xanchor, and yanchor properties are set based on the |
| 195 | + shape_type argument. Each property already specified in the annotation or |
| 196 | + through kwargs will be left as is (not replaced by the value computed using |
| 197 | + shape_type). Note that the xref and yref properties will in general get |
| 198 | + overwritten if the result of this function is passed to an add_annotation |
| 199 | + called with the row and col parameters specified. |
| 200 | + """ |
| 201 | + # Force to go.layout.Annotation, no matter if it is that already, a dict or None |
| 202 | + annotation = go.layout.Annotation(annotation) |
| 203 | + # set properties based on annotation_ prefixed kwargs |
| 204 | + prefix = "annotation_" |
| 205 | + len_prefix = len(prefix) |
| 206 | + annotation_keys = filter(lambda k: k.startswith(prefix), kwargs.keys()) |
| 207 | + for k in annotation_keys: |
| 208 | + subk = k[len_prefix:] |
| 209 | + annotation[subk] = kwargs[k] |
| 210 | + # set x, y, xanchor, yanchor based on shape_type and position |
| 211 | + annotation_position = None |
| 212 | + if "annotation_position" in kwargs.keys(): |
| 213 | + annotation_position = kwargs["annotation_position"] |
0 commit comments