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

1import gi 

2 

3gi.require_version("Gdk", "4.0") 

4gi.require_version("Gtk", "4.0") 

5 

6from typing import TYPE_CHECKING 

7 

8from gi.repository import Gdk, Gtk, Pango # noqa: E402 

9 

10from plotly_gtk.utils import parse_font, update_dict 

11from plotly_gtk.widgets.base import Base 

12 

13if TYPE_CHECKING: 

14 from plotly_gtk.chart import PlotlyGtk 

15 

16 

17Spec = None | list["Spec"] | dict[str, "Spec"] 

18 

19 

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) 

30 

31 self.label = Gtk.Label() 

32 self.label.set_markup(axis["title"]["text"]) 

33 self.append(self.label) 

34 

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 ) 

49 

50 if axis_letter == "x": 

51 font_extra = (metrics.get_ascent() + metrics.get_descent()) / Pango.SCALE 

52 

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) 

72 

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 

80 

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 

92 

93 self.spec = dict( 

94 x=x, xanchor=xanchor, xoffset=xoffset, y=y, yanchor=yanchor, yoffset=yoffset 

95 ) 

96 self.angle = angle 

97 

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() 

102 

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 ) 

127 

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")