Coverage for src/plotly_gtk/utils/__init__.py: 41%

39 statements  

« prev     ^ index     » next       coverage.py v7.6.4, created at 2024-11-08 21:22 +0000

1"""This module provides utility classes and functions used by 

2:class:`plotly_gtk.chart.PlotlyGtk`.""" 

3 

4import collections 

5import importlib 

6import json 

7import typing 

8 

9import gi 

10import numpy as np 

11 

12from plotly_gtk.utils import plotly_types 

13 

14if typing.TYPE_CHECKING: 

15 from plotly_gtk.utils.plotly_types import GenericType, get_type 

16 

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

18from gi.repository import ( # pylint: disable=wrong-import-position, wrong-import-order 

19 Gdk, 

20 Pango, 

21 PangoCairo, 

22) 

23 

24 

25def update_dict(d: "GenericType", u: "GenericType") -> "GenericType": 

26 """Return a copy of :class:`dict` `d` recursively updated with values from 

27 :class:`dict` `u`. 

28 

29 Parameters 

30 ------ 

31 d: dict 

32 The dictionary to update 

33 u: dict 

34 The dictionary of new values 

35 

36 Returns 

37 ------- 

38 dict 

39 A copy of `d` updated with values from `u` 

40 """ 

41 d = dict(d) 

42 for k, v in u.items(): 

43 if isinstance(v, collections.abc.Mapping): 

44 d[k] = update_dict(d.get(k, {}), v) 

45 else: 

46 d[k] = v 

47 return d 

48 

49 

50def parse_color(color: str) -> tuple[float, float, float]: 

51 """Return the RGB components of a color provided as a string. 

52 

53 Parameters 

54 ---------- 

55 color: str 

56 

57 Returns 

58 ------- 

59 tuple[float,float, float] 

60 The red, green, and blue components 

61 

62 Raises 

63 ------ 

64 ValueError 

65 If Gdk cannot parse the color 

66 """ 

67 rgba = Gdk.RGBA() 

68 if rgba.parse(color): 

69 return rgba.red, rgba.green, rgba.blue 

70 raise ValueError 

71 

72 

73def parse_font( 

74 font: dict[str, str | int], single_family: bool = False 

75) -> Pango.FontDescription: 

76 """Parse a dictionary of font parameters and return a 

77 :class:`gi.repository.Pango.FontDescription`. 

78 

79 Parameters 

80 ---------- 

81 font: dict[str, str|int] 

82 Must contain the following keys: family, style, variant, weight, and size 

83 

84 single_family: bool 

85 If the returned :class:`gi.repository.Pango.FontDescription` 

86 should contain only one value for family 

87 

88 Returns 

89 ------- 

90 gi.repository.Pango.FontDescription 

91 The fields are set as provided in the input :class:`dict`. 

92 """ 

93 font = f"{font["family"]} {font["style"]} {font["variant"]} {font["weight"]} {font["size"]}px" 

94 font_desc = Pango.FontDescription.from_string(font) 

95 if single_family: 

96 font_desc = ( 

97 PangoCairo.FontMap.get_default() 

98 .load_font(PangoCairo.FontMap.get_default().create_context(), font_desc) 

99 .describe() 

100 ) 

101 return font_desc 

102 

103 

104def get_cartesian_subplots(data: list[dict]) -> list[tuple[str, str]]: 

105 """Get the list of cartesian axes pairings with data plotted on them. 

106 

107 Parameters 

108 ---------- 

109 data: list[dict] 

110 A list of traces 

111 

112 Returns 

113 ------- 

114 list[tuple[str, str]] 

115 A list of tuples of the form ("xaxis([0-9]+)?", "yaxis([0-9]+)?") 

116 """ 

117 return list( 

118 { 

119 ( 

120 trace["xaxis"][0] + "axis" + trace["xaxis"][1:], 

121 trace["yaxis"][0] + "axis" + trace["yaxis"][1:], 

122 ) 

123 for trace in data 

124 } 

125 ) 

126 

127 

128def get_base_fig() -> dict: 

129 """Avoid importing plotly by creating a base figure dictionary. 

130 

131 Returns 

132 ------- 

133 dict 

134 The dictionary returned by plotly.graph_object.Figure().to_dict() 

135 """ 

136 template = "plotly" 

137 file = ( 

138 importlib.resources.files(anchor="plotly_gtk.utils") 

139 / "templates" 

140 / f"{template}.json" 

141 ) 

142 with open(file, encoding="utf-8") as f: 

143 template_dict = json.load(f) 

144 return { 

145 "data": [], 

146 "layout": {"template": template_dict}, 

147 } 

148 

149 

150def round_sf(val: float | int, sf: int = 1) -> float: 

151 """Round to specified significant figures. 

152 

153 Parameters 

154 ---------- 

155 val: float | int 

156 The value to be rounded 

157 sf: 

158 The number of significant figures 

159 

160 Returns 

161 ------- 

162 float 

163 The rounded value 

164 """ 

165 return np.round(val, sf - 1 - int(np.floor(np.log10(np.abs(val)))))