diff --git a/plotly/shapeannotation.py b/plotly/shapeannotation.py index a2323ed02d4..92e1e03b504 100644 --- a/plotly/shapeannotation.py +++ b/plotly/shapeannotation.py @@ -4,7 +4,7 @@ def _mean(x): if len(x) == 0: raise ValueError("x must have positive length") - return float(sum(x)) / len(x) + return float(x[0] + x[1]) / 2 # Only ever called with [x0, x1] def _argmin(x): @@ -30,10 +30,10 @@ def _prepare_position(position, prepend_inside=False): if position is None: position = "top right" pos_str = position - position = set(position.split(" ")) - if prepend_inside: - position = _add_inside_to_position(position) - return position, pos_str + pos_set = set(position.split(" ")) + if prepend_inside and not ("inside" in pos_set or "outside" in pos_set): + pos_set.add("inside") + return pos_set, pos_str def annotation_params_for_line(shape_type, shape_args, position): @@ -107,62 +107,44 @@ def annotation_params_for_rect(shape_type, shape_args, position): y0 = shape_args["y0"] y1 = shape_args["y1"] - position, pos_str = _prepare_position(position, prepend_inside=True) - if position == set(["inside", "top", "left"]): - return _df_anno("left", "top", min([x0, x1]), max([y0, y1])) - if position == set(["inside", "top", "right"]): - return _df_anno("right", "top", max([x0, x1]), max([y0, y1])) - if position == set(["inside", "top"]): - return _df_anno("center", "top", _mean([x0, x1]), max([y0, y1])) - if position == set(["inside", "bottom", "left"]): - return _df_anno("left", "bottom", min([x0, x1]), min([y0, y1])) - if position == set(["inside", "bottom", "right"]): - return _df_anno("right", "bottom", max([x0, x1]), min([y0, y1])) - if position == set(["inside", "bottom"]): - return _df_anno("center", "bottom", _mean([x0, x1]), min([y0, y1])) - if position == set(["inside", "left"]): - return _df_anno("left", "middle", min([x0, x1]), _mean([y0, y1])) - if position == set(["inside", "right"]): - return _df_anno("right", "middle", max([x0, x1]), _mean([y0, y1])) - if position == set(["inside"]): - # TODO: Do we want this? - return _df_anno("center", "middle", _mean([x0, x1]), _mean([y0, y1])) - if position == set(["outside", "top", "left"]): + pos_set, pos_str = _prepare_position(position, prepend_inside=True) + key = frozenset(pos_set) + + # Table-driven direct hits handle most cases + fn = _INSIDE_OUTSIDE_TABLE.get(key, None) + if fn is not None: + return fn(x0, x1, y0, y1, shape_type) + + # Special cases for "outside" positions that depend on shape_type + if key == frozenset(["outside", "top", "left"]): return _df_anno( "right" if shape_type == "vrect" else "left", "bottom" if shape_type == "hrect" else "top", - min([x0, x1]), - max([y0, y1]), + min(x0, x1), + max(y0, y1), ) - if position == set(["outside", "top", "right"]): + if key == frozenset(["outside", "top", "right"]): return _df_anno( "left" if shape_type == "vrect" else "right", "bottom" if shape_type == "hrect" else "top", - max([x0, x1]), - max([y0, y1]), + max(x0, x1), + max(y0, y1), ) - if position == set(["outside", "top"]): - return _df_anno("center", "bottom", _mean([x0, x1]), max([y0, y1])) - if position == set(["outside", "bottom", "left"]): + if key == frozenset(["outside", "bottom", "left"]): return _df_anno( "right" if shape_type == "vrect" else "left", "top" if shape_type == "hrect" else "bottom", - min([x0, x1]), - min([y0, y1]), + min(x0, x1), + min(y0, y1), ) - if position == set(["outside", "bottom", "right"]): + if key == frozenset(["outside", "bottom", "right"]): return _df_anno( "left" if shape_type == "vrect" else "right", "top" if shape_type == "hrect" else "bottom", - max([x0, x1]), - min([y0, y1]), + max(x0, x1), + min(y0, y1), ) - if position == set(["outside", "bottom"]): - return _df_anno("center", "top", _mean([x0, x1]), min([y0, y1])) - if position == set(["outside", "left"]): - return _df_anno("right", "middle", min([x0, x1]), _mean([y0, y1])) - if position == set(["outside", "right"]): - return _df_anno("left", "middle", max([x0, x1]), _mean([y0, y1])) + raise ValueError("Invalid annotation position %s" % (pos_str,)) @@ -244,3 +226,19 @@ def split_dict_by_key_prefix(d, prefix): else: no_prefix[k] = d[k] return (no_prefix, with_prefix) + +_INSIDE_OUTSIDE_TABLE = { + frozenset(["inside", "top", "left"]): lambda a,b,c,d,_: _df_anno("left", "top", min(a, b), max(c, d)), + frozenset(["inside", "top", "right"]): lambda a,b,c,d,_: _df_anno("right", "top", max(a, b), max(c, d)), + frozenset(["inside", "top"]): lambda a,b,c,d,_: _df_anno("center", "top", _mean((a, b)), max(c, d)), + frozenset(["inside", "bottom", "left"]): lambda a,b,c,d,_: _df_anno("left", "bottom", min(a, b), min(c, d)), + frozenset(["inside", "bottom", "right"]):lambda a,b,c,d,_: _df_anno("right", "bottom", max(a, b), min(c, d)), + frozenset(["inside", "bottom"]): lambda a,b,c,d,_: _df_anno("center", "bottom", _mean((a, b)), min(c, d)), + frozenset(["inside", "left"]): lambda a,b,c,d,_: _df_anno("left", "middle", min(a, b), _mean((c, d))), + frozenset(["inside", "right"]): lambda a,b,c,d,_: _df_anno("right", "middle", max(a, b), _mean((c, d))), + frozenset(["inside"]): lambda a,b,c,d,_: _df_anno("center", "middle", _mean((a, b)), _mean((c, d))), + frozenset(["outside", "top"]): lambda a,b,c,d,_: _df_anno("center", "bottom", _mean((a, b)), max(c, d)), + frozenset(["outside", "bottom"]): lambda a,b,c,d,_: _df_anno("center", "top", _mean((a, b)), min(c, d)), + frozenset(["outside", "left"]): lambda a,b,c,d,_: _df_anno("right", "middle", min(a, b), _mean((c, d))), + frozenset(["outside", "right"]): lambda a,b,c,d,_: _df_anno("left", "middle", max(a, b), _mean((c, d))), +}