from __future__ import annotations
import numpy as np
from numpy.typing import NDArray
import matplotlib.pyplot as plt
from matplotlib.text import Text as mplText
from matplotlib.artist import Artist
from whitecanvas.backend.matplotlib._base import MplLayer
from whitecanvas.types import Alignment, FacePattern, LineStyle
from whitecanvas.protocols import TextProtocol, check_protocol
[docs]class Texts(Artist, MplLayer):
def __init__(
self, x: NDArray[np.floating], y: NDArray[np.floating], text: list[str]
):
super().__init__()
self._children = []
for x0, y0, text0 in zip(x, y, text):
self._children.append(
mplText(
x0, y0, text0, verticalalignment="baseline",
horizontalalignment="left", clip_on=False,
color=np.array([0, 0, 0, 1], dtype=np.float32),
) # fmt: skip
)
[docs] def draw(self, renderer):
for child in self.get_children():
child.draw(renderer)
[docs] def get_children(self) -> list[mplText]:
return self._children
[docs] def set_visible(self, visible: bool):
for child in self.get_children():
child.set_visible(visible)
super().set_visible(visible)
[docs] def set_transform(self, transform):
for child in self.get_children():
child.set_transform(transform)
super().set_transform(transform)
def _plt_get_text(self) -> list[str]:
return [child.get_text() for child in self.get_children()]
def _plt_set_text(self, text: list[str]):
for child, text0 in zip(self.get_children(), text):
child.set_text(str(text0))
def _plt_get_text_color(self):
return np.stack([child.get_color() for child in self.get_children()], axis=0)
def _plt_set_text_color(self, color):
for child, color0 in zip(self.get_children(), color):
child.set_color(color0)
def _plt_get_text_size(self) -> NDArray[np.float32]:
return np.array(
[child.get_fontsize() for child in self.get_children()], dtype=np.float32
)
def _plt_set_text_size(self, size: NDArray[np.float32]):
for child, size0 in zip(self.get_children(), size):
child.set_fontsize(size0)
def _plt_get_text_position(self) -> tuple[NDArray[np.float32], NDArray[np.float32]]:
ar = np.array([child.get_position() for child in self.get_children()])
return ar[:, 0], ar[:, 1]
def _plt_set_text_position(
self, position: tuple[NDArray[np.float32], NDArray[np.float32]]
):
x, y = position
for child, x0, y0 in zip(self.get_children(), x, y):
child.set_position((x0, y0))
def _plt_get_text_anchor(self) -> Alignment:
out = []
for child in self.get_children():
va = child.get_verticalalignment()
ha = child.get_horizontalalignment()
if (aln := _ALIGNMENTS.get((va, ha))) is None:
v = _VERTICAL_ALIGNMENTS[va]
h = _HORIZONTAL_ALIGNMENTS[ha]
aln = Alignment.merge(v, h)
_ALIGNMENTS[(va, ha)] = aln
_ALIGNMENTS_INV[aln] = (va, ha)
out.append(aln)
return out
def _plt_set_text_anchor(self, anc: Alignment | list[Alignment]):
"""Set the text position."""
if isinstance(anc, Alignment):
v, h = anc.split()
va = _VERTICAL_ALIGNMENTS_INV[v]
ha = _HORIZONTAL_ALIGNMENTS_INV[h]
for child in self.get_children():
child.set_verticalalignment(va)
child.set_horizontalalignment(ha)
else:
for child, anc0 in zip(self.get_children(), anc):
v, h = anc0.split()
va = _VERTICAL_ALIGNMENTS_INV[v]
ha = _HORIZONTAL_ALIGNMENTS_INV[h]
child.set_verticalalignment(va)
child.set_horizontalalignment(ha)
def _plt_get_text_rotation(self) -> NDArray[np.float32]:
return np.array(
[child.get_rotation() for child in self.get_children()], dtype=np.float32
)
def _plt_set_text_rotation(self, rotation: NDArray[np.float32]):
for child, rotation0 in zip(self.get_children(), rotation):
child.set_rotation(rotation0)
def _plt_get_text_fontfamily(self) -> list[str]:
return [child.get_fontfamily() for child in self.get_children()]
def _plt_set_text_fontfamily(self, fontfamily: list[str]):
for child, fontfamily0 in zip(self.get_children(), fontfamily):
child.set_fontfamily(fontfamily0)
##### HasFaces #####
@staticmethod
def _set_bbox_props(child: mplText, **bbox_props):
if child.get_bbox_patch() is None:
child.set_bbox(bbox_props)
else:
child.get_bbox_patch().update(bbox_props)
def _plt_get_face_color(self):
out = []
for child in self.get_children():
patch = child.get_bbox_patch()
if patch is None:
out.append(np.array([0, 0, 0, 0]))
else:
out.append(np.asarray(patch.get_facecolor()))
return np.stack(out, axis=0)
def _plt_set_face_color(self, color):
for child, color0 in zip(self.get_children(), color):
self._set_bbox_props(child, facecolor=color0)
def _plt_get_face_pattern(self) -> FacePattern:
out = []
for child in self.get_children():
patch = child.get_bbox_patch()
if patch is None:
out.append(FacePattern.SOLID)
else:
out.append(FacePattern(patch.get_hatch()))
return out
def _plt_set_face_pattern(self, pattern: FacePattern):
if isinstance(pattern, FacePattern):
if pattern is FacePattern.SOLID:
ptn = [None] * len(self.get_children())
else:
ptn = [pattern.value] * len(self.get_children())
else:
ptn = [p.value if p is not FacePattern.SOLID else None for p in pattern]
for child, ptn0 in zip(self.get_children(), ptn):
self._set_bbox_props(child, hatch=ptn0)
def _plt_get_edge_color(self):
out = []
for child in self.get_children():
patch = child.get_bbox_patch()
if patch is None:
out.append(np.array([0, 0, 0, 0]))
else:
out.append(np.asarray(patch.get_edgecolor()))
return np.stack(out, axis=0)
def _plt_set_edge_color(self, color):
for child, color0 in zip(self.get_children(), color):
self._set_bbox_props(child, edgecolor=color0)
def _plt_get_edge_width(self) -> float:
out = []
for child in self.get_children():
patch = child.get_bbox_patch()
if patch is None:
out.append(0)
else:
out.append(patch.get_linewidth())
return np.array(out, dtype=np.float32)
def _plt_set_edge_width(self, width: float):
for child, width0 in zip(self.get_children(), width):
self._set_bbox_props(child, linewidth=width0)
def _plt_get_edge_style(self) -> LineStyle:
out = []
for child in self.get_children():
patch = child.get_bbox_patch()
if patch is None:
out.append(LineStyle.SOLID)
else:
out.append(LineStyle(patch.get_linestyle()))
return out
def _plt_set_edge_style(self, style: LineStyle):
if isinstance(style, LineStyle):
if style is LineStyle.SOLID:
sty = [None] * len(self.get_children())
else:
sty = [style.value] * len(self.get_children())
else:
sty = [s.value if s is not LineStyle.SOLID else None for s in style]
for child, sty0 in zip(self.get_children(), sty):
self._set_bbox_props(child, linestyle=sty0)
_VERTICAL_ALIGNMENTS = {
"bottom": Alignment.BOTTOM,
"baseline": Alignment.BOTTOM,
"center": Alignment.CENTER,
"center_baseline": Alignment.CENTER,
"top": Alignment.TOP,
}
_VERTICAL_ALIGNMENTS_INV = {v: k for k, v in _VERTICAL_ALIGNMENTS.items()}
_HORIZONTAL_ALIGNMENTS = {
"left": Alignment.LEFT,
"center": Alignment.CENTER,
"right": Alignment.RIGHT,
}
_HORIZONTAL_ALIGNMENTS_INV = {v: k for k, v in _HORIZONTAL_ALIGNMENTS.items()}
_ALIGNMENTS: dict[tuple[str, str], Alignment] = {}
_ALIGNMENTS_INV: dict[Alignment, tuple[str, str]] = {}