Skip to content

Commit bffe7f4

Browse files
authored
Merge pull request #44 from matplotlib/text
Initial implementation of text
2 parents ea5dfef + 64429f0 commit bffe7f4

File tree

9 files changed

+236
-47
lines changed

9 files changed

+236
-47
lines changed

Diff for: data_prototype/artist.py

+15
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,10 @@ def axes(self, ax):
277277
return
278278

279279
desc: Desc = Desc(("N",), coordinates="data")
280+
desc_scal: Desc = Desc((), coordinates="data")
280281
xy: dict[str, Desc] = {"x": desc, "y": desc}
282+
xy_scal: dict[str, Desc] = {"x": desc_scal, "y": desc_scal}
283+
281284
self._graph = Graph(
282285
[
283286
TransformEdge(
@@ -292,6 +295,18 @@ def axes(self, ax):
292295
desc_like(xy, coordinates="display"),
293296
transform=self._axes.transAxes,
294297
),
298+
TransformEdge(
299+
"data_scal",
300+
xy_scal,
301+
desc_like(xy_scal, coordinates="axes"),
302+
transform=self._axes.transData - self._axes.transAxes,
303+
),
304+
TransformEdge(
305+
"axes_scal",
306+
desc_like(xy_scal, coordinates="axes"),
307+
desc_like(xy_scal, coordinates="display"),
308+
transform=self._axes.transAxes,
309+
),
295310
FuncEdge.from_func(
296311
"xunits",
297312
lambda: self._axes.xaxis.units,

Diff for: data_prototype/conversion_edge.py

+36-4
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from typing import Any
88
import numpy as np
99

10-
from data_prototype.description import Desc, desc_like
10+
from data_prototype.description import Desc, desc_like, ShapeSpec
1111

1212
from matplotlib.transforms import Transform
1313

@@ -112,6 +112,17 @@ def from_default_value(
112112
) -> "DefaultEdge":
113113
return cls(name, {}, {key: output}, weight, invertable=False, value=value)
114114

115+
@classmethod
116+
def from_rc(
117+
cls, rc_name: str, key: str | None = None, coordinates: str = "display"
118+
):
119+
from matplotlib import rcParams
120+
121+
if key is None:
122+
key = rc_name.split(".")[-1]
123+
scalar = Desc((), coordinates)
124+
return cls.from_default_value(f"{rc_name}_rc", key, scalar, rcParams[rc_name])
125+
115126
def evaluate(self, input: dict[str, Any]) -> dict[str, Any]:
116127
return {k: self.value for k in self.output}
117128

@@ -165,7 +176,6 @@ def evaluate(self, input: dict[str, Any]) -> dict[str, Any]:
165176

166177
@property
167178
def inverse(self) -> "FuncEdge":
168-
169179
if self.inverse_func is None:
170180
raise RuntimeError("Trying to invert a non-invertable edge")
171181

@@ -327,6 +337,7 @@ def edges(self):
327337
import matplotlib.pyplot as plt
328338

329339
self.visualize(input)
340+
self.visualize()
330341
plt.show()
331342
raise NotImplementedError(
332343
"This may be possible, but is not a simple case already considered"
@@ -344,7 +355,7 @@ def edges(self):
344355
else:
345356
out_edges.append(SequenceEdge.from_edges("eval", edges, output_subset))
346357

347-
found_outputs = set()
358+
found_outputs = set(input)
348359
for out in out_edges:
349360
found_outputs |= set(out.output)
350361
if missing := set(output) - found_outputs:
@@ -372,7 +383,6 @@ def node_format(x):
372383
G = nx.DiGraph()
373384

374385
if input is not None:
375-
376386
for _, edges in self._subgraphs:
377387
q: list[dict[str, Desc]] = [input]
378388
explored: set[tuple[tuple[str, str], ...]] = set()
@@ -427,3 +437,25 @@ def cache_key(self):
427437
import uuid
428438

429439
return str(uuid.uuid4())
440+
441+
442+
def coord_and_default(
443+
key: str,
444+
shape: ShapeSpec = (),
445+
coordinates: str = "display",
446+
default_value: Any = None,
447+
default_rc: str | None = None,
448+
):
449+
if default_rc is not None:
450+
if default_value is not None:
451+
raise ValueError(
452+
"Only one of 'default_value' and 'default_rc' may be specified"
453+
)
454+
def_edge = DefaultEdge.from_rc(default_rc, key, coordinates)
455+
else:
456+
scalar = Desc((), coordinates)
457+
def_edge = DefaultEdge.from_default_value(
458+
f"{key}_def", key, scalar, default_value
459+
)
460+
coord_edge = CoordinateEdge.from_coords(key, {key: Desc(shape)}, coordinates)
461+
return coord_edge, def_edge

Diff for: data_prototype/image.py

+8
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,14 @@ def __init__(self, container, edges=None, norm=None, cmap=None, **kwargs):
7676
{"image": Desc(("O", "P", 4), "rgba_resampled")},
7777
{"image": Desc(("O", "P", 4), "display")},
7878
),
79+
FuncEdge.from_func(
80+
"rgb_rgba",
81+
lambda image: np.append(
82+
image, np.ones(image.shape[:-1] + (1,)), axis=-1
83+
),
84+
{"image": Desc(("M", "N", 3), "rgb")},
85+
{"image": Desc(("M", "N", 4), "rgba")},
86+
),
7987
self._interpolation_edge,
8088
]
8189

Diff for: data_prototype/line.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def __init__(self, container, edges=None, **kwargs):
1818

1919
scalar = Desc((), "display") # ... this needs thinking...
2020

21-
edges = [
21+
default_edges = [
2222
CoordinateEdge.from_coords("xycoords", {"x": "auto", "y": "auto"}, "data"),
2323
CoordinateEdge.from_coords("color", {"color": Desc(())}, "display"),
2424
CoordinateEdge.from_coords("linewidth", {"linewidth": Desc(())}, "display"),
@@ -45,7 +45,7 @@ def __init__(self, container, edges=None, **kwargs):
4545
DefaultEdge.from_default_value("mew_def", "markeredgewidth", scalar, 1),
4646
DefaultEdge.from_default_value("marker_def", "marker", scalar, "None"),
4747
]
48-
self._graph = self._graph + Graph(edges)
48+
self._graph = self._graph + Graph(default_edges)
4949
# Currently ignoring:
5050
# - cap/join style
5151
# - url

Diff for: data_prototype/text.py

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import numpy as np
2+
3+
from matplotlib.font_manager import FontProperties
4+
5+
from .artist import Artist
6+
from .description import Desc
7+
from .conversion_edge import Graph, CoordinateEdge, coord_and_default
8+
9+
10+
class Text(Artist):
11+
def __init__(self, container, edges=None, **kwargs):
12+
super().__init__(container, edges, **kwargs)
13+
14+
edges = [
15+
CoordinateEdge.from_coords(
16+
"xycoords", {"x": Desc((), "auto"), "y": Desc((), "auto")}, "data"
17+
),
18+
*coord_and_default("text", default_value=""),
19+
*coord_and_default("color", default_rc="text.color"),
20+
*coord_and_default("alpha", default_value=1),
21+
*coord_and_default("fontproperties", default_value=FontProperties()),
22+
*coord_and_default("usetex", default_rc="text.usetex"),
23+
*coord_and_default("rotation", default_value=0),
24+
*coord_and_default("antialiased", default_rc="text.antialiased"),
25+
]
26+
27+
self._graph = self._graph + Graph(edges)
28+
29+
def draw(self, renderer, graph: Graph) -> None:
30+
if not self.get_visible():
31+
return
32+
g = graph + self._graph
33+
conv = g.evaluator(
34+
self._container.describe(),
35+
{
36+
"x": Desc((), "display"),
37+
"y": Desc((), "display"),
38+
"text": Desc((), "display"),
39+
"color": Desc((), "display"),
40+
"alpha": Desc((), "display"),
41+
"fontproperties": Desc((), "display"),
42+
"usetex": Desc((), "display"),
43+
# "parse_math": Desc((), "display"),
44+
# "wrap": Desc((), "display"),
45+
# "verticalalignment": Desc((), "display"),
46+
# "horizontalalignment": Desc((), "display"),
47+
"rotation": Desc((), "display"),
48+
# "linespacing": Desc((), "display"),
49+
# "rotation_mode": Desc((), "display"),
50+
"antialiased": Desc((), "display"),
51+
},
52+
)
53+
54+
query, _ = self._container.query(g)
55+
evald = conv.evaluate(query)
56+
57+
text = evald["text"]
58+
if text == "":
59+
return
60+
61+
x = evald["x"]
62+
y = evald["y"]
63+
64+
_, canvash = renderer.get_canvas_width_height()
65+
if renderer.flipy():
66+
y = canvash - y
67+
68+
if not np.isfinite(x) or not np.isfinite(y):
69+
# TODO: log?
70+
return
71+
72+
# TODO bbox?
73+
# TODO implement wrapping/layout?
74+
# TODO implement math?
75+
# TODO implement path_effects?
76+
77+
# TODO gid?
78+
renderer.open_group("text", None)
79+
80+
gc = renderer.new_gc()
81+
gc.set_foreground(evald["color"])
82+
gc.set_alpha(evald["alpha"])
83+
# TODO url?
84+
gc.set_antialiased(evald["antialiased"])
85+
# TODO clipping?
86+
87+
if evald["usetex"]:
88+
renderer.draw_tex(
89+
gc, x, y, text, evald["fontproperties"], evald["rotation"]
90+
)
91+
else:
92+
renderer.draw_text(
93+
gc, x, y, text, evald["fontproperties"], evald["rotation"]
94+
)
95+
96+
gc.restore()
97+
renderer.close_group("text")

Diff for: examples/2Dfunc.py

+10-6
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
import matplotlib.pyplot as plt
1111
import numpy as np
1212

13-
from data_prototype.wrappers import ImageWrapper
13+
from data_prototype.artist import CompatibilityAxes
14+
from data_prototype.image import Image
1415
from data_prototype.containers import FuncContainer
1516

1617
from matplotlib.colors import Normalize
@@ -19,20 +20,23 @@
1920
fc = FuncContainer(
2021
{},
2122
xyfuncs={
22-
"xextent": ((2,), lambda x, y: [x[0], x[-1]]),
23-
"yextent": ((2,), lambda x, y: [y[0], y[-1]]),
23+
"x": ((2,), lambda x, y: [x[0], x[-1]]),
24+
"y": ((2,), lambda x, y: [y[0], y[-1]]),
2425
"image": (
2526
("N", "M"),
2627
lambda x, y: np.sin(x).reshape(1, -1) * np.cos(y).reshape(-1, 1),
2728
),
2829
},
2930
)
3031
norm = Normalize(vmin=-1, vmax=1)
31-
im = ImageWrapper(fc, norm=norm)
32+
im = Image(fc, norm=norm)
33+
34+
fig, nax = plt.subplots()
35+
ax = CompatibilityAxes(nax)
36+
nax.add_artist(ax)
3237

33-
fig, ax = plt.subplots()
3438
ax.add_artist(im)
3539
ax.set_xlim(-5, 5)
3640
ax.set_ylim(-5, 5)
37-
fig.colorbar(im)
41+
# fig.colorbar(im, ax=nax)
3842
plt.show()

Diff for: examples/animation.py

+18-9
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@
2020
from data_prototype.conversion_edge import Graph
2121
from data_prototype.description import Desc
2222

23-
from data_prototype.conversion_node import FunctionConversionNode
2423

25-
from data_prototype.wrappers import FormattedText
26-
from data_prototype.artist import CompatibilityArtist as CA
24+
from data_prototype.artist import CompatibilityAxes
2725
from data_prototype.line import Line
26+
from data_prototype.text import Text
27+
from data_prototype.conversion_edge import FuncEdge
2828

2929

3030
class SinOfTime:
@@ -63,15 +63,24 @@ def update(frame, art):
6363

6464

6565
sot_c = SinOfTime()
66-
lw = CA(Line(sot_c, linewidth=5, color="green", label="sin(time)"))
67-
fc = FormattedText(
66+
lw = Line(sot_c, linewidth=5, color="green", label="sin(time)")
67+
fc = Text(
6868
sot_c,
69-
FunctionConversionNode.from_funcs(
70-
{"text": lambda phase: f"ϕ={phase:.2f}", "x": lambda: 2 * np.pi, "y": lambda: 1}
71-
),
69+
[
70+
FuncEdge.from_func(
71+
"text",
72+
lambda phase: f"ϕ={phase:.2f}",
73+
{"phase": Desc((), "auto")},
74+
{"text": Desc((), "display")},
75+
),
76+
],
77+
x=2 * np.pi,
78+
y=1,
7279
ha="right",
7380
)
74-
fig, ax = plt.subplots()
81+
fig, nax = plt.subplots()
82+
ax = CompatibilityAxes(nax)
83+
nax.add_artist(ax)
7584
ax.add_artist(lw)
7685
ax.add_artist(fc)
7786
ax.set_xlim(0, 2 * np.pi)

0 commit comments

Comments
 (0)