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
« 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`."""
4import collections
5import importlib
6import json
7import typing
9import gi
10import numpy as np
12from plotly_gtk.utils import plotly_types
14if typing.TYPE_CHECKING:
15 from plotly_gtk.utils.plotly_types import GenericType, get_type
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)
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`.
29 Parameters
30 ------
31 d: dict
32 The dictionary to update
33 u: dict
34 The dictionary of new values
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
50def parse_color(color: str) -> tuple[float, float, float]:
51 """Return the RGB components of a color provided as a string.
53 Parameters
54 ----------
55 color: str
57 Returns
58 -------
59 tuple[float,float, float]
60 The red, green, and blue components
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
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`.
79 Parameters
80 ----------
81 font: dict[str, str|int]
82 Must contain the following keys: family, style, variant, weight, and size
84 single_family: bool
85 If the returned :class:`gi.repository.Pango.FontDescription`
86 should contain only one value for family
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
104def get_cartesian_subplots(data: list[dict]) -> list[tuple[str, str]]:
105 """Get the list of cartesian axes pairings with data plotted on them.
107 Parameters
108 ----------
109 data: list[dict]
110 A list of traces
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 )
128def get_base_fig() -> dict:
129 """Avoid importing plotly by creating a base figure dictionary.
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 }
150def round_sf(val: float | int, sf: int = 1) -> float:
151 """Round to specified significant figures.
153 Parameters
154 ----------
155 val: float | int
156 The value to be rounded
157 sf:
158 The number of significant figures
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)))))