Source code for whitecanvas.backend.plotly.text

from __future__ import annotations
import numpy as np
from numpy.typing import NDArray

from whitecanvas.types import Alignment, FacePattern, LineStyle
from whitecanvas.protocols import TextProtocol, check_protocol
from whitecanvas.utils.normalize import arr_color, as_color_array, rgba_str_color
from ._base import PlotlyLayer


[docs]@check_protocol(TextProtocol) class Texts(PlotlyLayer): def __init__( self, x: NDArray[np.floating], y: NDArray[np.floating], text: list[str] ): ntexts = len(text) self._props = { "x": x, "y": y, "mode": "markers", "marker": { "color": "rgba(0, 0, 0, 0)", "size": 10, "symbol": "circle", }, "text": text, "textposition": ["bottom left"] * ntexts, "textfont_color": ["black"] * ntexts, "textfont_size": np.full(ntexts, 10), "textfont_family": ["Arial"] * ntexts, "type": "scatter", "showlegend": False, "visible": True, } # TODO: plotly does not support text rotation. # It seems that rotation (and background) can be implemented with # fig.add_annotation, but since it is a layout instead of a trace, # we need additional work to make it work. self._rotation = 0 ##### BaseProtocol ##### def _plt_get_visible(self) -> bool: return self.visible def _plt_set_visible(self, visible: bool): self.visible = visible ##### TextProtocol ##### def _plt_get_text(self) -> str: return self._props["text"] def _plt_set_text(self, text: str): self._props["text"] = text def _plt_get_text_color(self): return arr_color(self._props["textfont_color"]) def _plt_set_text_color(self, color): color = as_color_array(color, len(self._props["text"])) self._props["textfont_color"] = [rgba_str_color(c) for c in color] def _plt_get_text_size(self) -> NDArray[np.floating]: return self._props["textfont_size"] def _plt_set_text_size(self, size: NDArray[np.floating]): if np.isscalar(size): size = np.full(len(self._props["text"]), size) self._props["textfont_size"] = size def _plt_get_text_position( self, ) -> tuple[NDArray[np.floating], NDArray[np.floating]]: return self._props["x"], self._props["y"] def _plt_set_text_position( self, position: tuple[NDArray[np.floating], NDArray[np.floating]] ): self._props["x"], self._props["y"] = position def _plt_get_text_anchor(self) -> list[Alignment]: return [_norm_alignment(p) for p in self._props["textposition"]] def _plt_set_text_anchor(self, anc: Alignment | list[Alignment]): if isinstance(anc, Alignment): self._props["textposition"] = [anc.value.replace("_", " ")] * len( self._props["textposition"] ) else: self._props["textposition"] = [a.value.replace("_", " ") for a in anc] def _plt_get_text_rotation(self) -> float: return self._rotation def _plt_set_text_rotation(self, rotation: float): if np.isscalar(rotation): rotation = np.full(len(self._props["text"]), rotation) self._rotation = rotation def _plt_get_text_fontfamily(self) -> list[str]: return self._props["textfont_family"] def _plt_set_text_fontfamily(self, fontfamily: list[str]): if isinstance(fontfamily, str): fontfamily = [fontfamily] * len(self._props["textfont_family"]) self._props["textfont_family"] = fontfamily ##### HasFaces ##### def _plt_get_face_color(self): return np.zeros((len(self._props["text"]), 4)) def _plt_set_face_color(self, color): pass def _plt_get_face_pattern(self) -> FacePattern: return [FacePattern.SOLID] * len(self._props["text"]) def _plt_set_face_pattern(self, pattern: FacePattern): pass ##### HasEdges ##### def _plt_get_edge_color(self): return np.zeros((len(self._props["text"]), 4)) def _plt_set_edge_color(self, color): pass def _plt_get_edge_width(self) -> float: return np.full(len(self._props["text"]), 0.0) def _plt_set_edge_width(self, width: float): pass def _plt_get_edge_style(self) -> LineStyle: return [LineStyle.SOLID] * len(self._props["text"]) def _plt_set_edge_style(self, style: LineStyle): pass
def _norm_alignment(s: str) -> Alignment: if s == "center": return Alignment.CENTER va, ha = s.lower().split(" ") return Alignment(f"{va}_{ha}")