Coverage for src/plotly_gtk/widgets/axis_title.py: 16%
67 statements
« prev ^ index » next coverage.py v7.6.4, created at 2024-11-08 21:22 +0000
« prev ^ index » next coverage.py v7.6.4, created at 2024-11-08 21:22 +0000
1import gi
3gi.require_version("Gdk", "4.0")
4gi.require_version("Gtk", "4.0")
6from typing import TYPE_CHECKING
8from gi.repository import Gdk, Gtk, Pango # noqa: E402
10from plotly_gtk.utils import parse_font, update_dict
11from plotly_gtk.widgets.base import Base
13if TYPE_CHECKING:
14 from plotly_gtk.chart import PlotlyGtk
17Spec = None | list["Spec"] | dict[str, "Spec"]
20class AxisTitle(Base):
21 def __init__(self, plot: "PlotlyGtk", axis: Spec, axis_name: str):
22 super().__init__()
23 axis_letter = axis_name[0 : axis_name.find("axis")]
24 standoff = axis["title"]["standoff"]
25 ticklen = axis["ticklen"] if axis["ticks"] == "outside" else 0
26 context = self.get_pango_context()
27 tickfont = update_dict(plot.layout["font"], axis["tickfont"])
28 tickfont = parse_font(tickfont)
29 metrics = context.get_metrics(tickfont)
31 self.label = Gtk.Label()
32 self.label.set_markup(axis["title"]["text"])
33 self.append(self.label)
35 anchor_axis = (
36 "free"
37 if "anchor" not in axis or axis["anchor"] == "free"
38 else (axis["anchor"][0] + "axis" + axis["anchor"][1:])
39 )
40 position = (
41 axis["_position"]
42 if "anchor" not in axis or anchor_axis == "free"
43 else (
44 plot.layout[anchor_axis]["domain"][0]
45 if axis["side"] == "left" or axis["side"] == "bottom"
46 else plot.layout[anchor_axis]["domain"][-1]
47 )
48 )
50 if axis_letter == "x":
51 font_extra = (metrics.get_ascent() + metrics.get_descent()) / Pango.SCALE
53 orientation = "h"
54 x = (axis["domain"][0] + axis["domain"][-1]) / 2
55 y = position
56 xanchor = "center"
57 yanchor = "top" if axis["side"] == "bottom" else "bottom"
58 xoffset = 0
59 yoffset = (
60 standoff + ticklen + font_extra
61 if axis["side"] == "bottom"
62 else -standoff - ticklen - font_extra
63 )
64 angle = 0
65 elif axis_letter == "y":
66 font_extra = 0
67 layout = Pango.Layout(context)
68 layout.set_font_description(tickfont)
69 for tick in axis["_ticktext"]:
70 layout.set_text(tick)
71 font_extra = max(layout.get_pixel_size()[0], font_extra)
73 orientation = "v"
74 x = position
75 y = (axis["domain"][0] + axis["domain"][-1]) / 2
76 xanchor = "right" if axis["side"] == "left" else "left"
77 yanchor = "middle"
78 _width = self.get_preferred_size()[-1].width
79 _height = self.get_preferred_size()[-1].height
81 x_size_error = (_height - _width) / 2
82 xoffset = (
83 -standoff - ticklen - font_extra - x_size_error
84 if axis["side"] == "left"
85 else standoff + ticklen + font_extra + x_size_error
86 ) + axis["_shift"]
87 yoffset = 0
88 angle = 270 # angle = 270 if axis["side"] == "left" else 90
89 self.set_orientation(Gtk.Orientation.VERTICAL)
90 else:
91 return
93 self.spec = dict(
94 x=x, xanchor=xanchor, xoffset=xoffset, y=y, yanchor=yanchor, yoffset=yoffset
95 )
96 self.angle = angle
98 font = axis["title"]["font"]
99 defaults = plot.layout["font"]
100 font = update_dict(defaults, font)
101 font_description = parse_font(font, single_family=True).to_string()
103 custom_css = Gtk.CssProvider()
104 custom_css.load_from_string(
105 f"""
106 .plotly-{axis_name}-title label {
107 color: {font["color"]};
108 font-family: {font["family"]};
109 font-size: {font["size"]}px;
110 font-style: {font["style"]};
111 font-variant: {font["variant"]};
112 font-weight: {font["weight"]};
113 }
114 .vertical-90-text {
115 transform: rotate(90deg);
116 }
117 .vertical-270-text {
118 transform: rotate(270deg);
119 }
120 """
121 )
122 Gtk.StyleContext().add_provider_for_display(
123 Gdk.Display().get_default(),
124 custom_css,
125 Gtk.STYLE_PROVIDER_PRIORITY_USER,
126 )
128 self.add_css_class(f"plotly-{axis_name}-title")
129 if angle in [90, 270]:
130 self.add_css_class(f"vertical-{angle}-text")