Coverage for procpath/plotting.py: 99%
128 statements
« prev ^ index » next coverage.py v6.5.0, created at 2025-04-05 18:56 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2025-04-05 18:56 +0000
1import bisect
2import collections
3import itertools
4import math
5import tempfile
6from datetime import datetime
7from functools import partial
8from typing import Callable, Iterable, List, Mapping, Optional, Tuple
10import pygal.config
11import pygal.formatters
12import pygal.serie
13import pygal.style
14import pygal.util
17__all__ = 'Point', 'decimate', 'moving_average', 'plot'
19Point = Tuple[float, float]
22def get_line_distance(p0: Point, p1: Point, p2: Point) -> float:
23 """
24 Return the distance from p0 to the line formed by p1 and p2.
26 Points are represented by 2-tuples.
27 """
29 if p1 == p2: procpath.test.cmd.TestPlotCommand.test_plot_rdp_epsilonprocpath.test.unit.TestPlotting.test_decimateprocpath.test.unit.TestPlotting.test_get_line_distance
30 return math.hypot(p1[0] - p0[0], p1[1] - p0[1]) procpath.test.unit.TestPlotting.test_decimateprocpath.test.unit.TestPlotting.test_get_line_distance
32 slope_nom = p2[1] - p1[1] procpath.test.cmd.TestPlotCommand.test_plot_rdp_epsilonprocpath.test.unit.TestPlotting.test_decimateprocpath.test.unit.TestPlotting.test_get_line_distance
33 slope_denom = p2[0] - p1[0] procpath.test.cmd.TestPlotCommand.test_plot_rdp_epsilonprocpath.test.unit.TestPlotting.test_decimateprocpath.test.unit.TestPlotting.test_get_line_distance
35 return ( procpath.test.cmd.TestPlotCommand.test_plot_rdp_epsilonprocpath.test.unit.TestPlotting.test_decimateprocpath.test.unit.TestPlotting.test_get_line_distance
36 abs(slope_nom * p0[0] - slope_denom * p0[1] + p2[0] * p1[1] - p2[1] * p1[0])
37 / math.hypot(slope_denom, slope_nom)
38 )
41def decimate(points: List[Point], epsilon: float) -> List[Point]:
42 """
43 Decimate given poly-line using Ramer-Douglas-Peucker algorithm.
45 It reduces the points to a simplified version that loses detail,
46 but retains its peaks.
47 """
49 if len(points) < 3: procpath.test.cmd.TestPlotCommand.test_plot_rdp_epsilonprocpath.test.unit.TestPlotting.test_decimate
50 return points procpath.test.unit.TestPlotting.test_decimate
52 dist_iter = map(partial(get_line_distance, p1=points[0], p2=points[-1]), points[1:-1]) procpath.test.cmd.TestPlotCommand.test_plot_rdp_epsilonprocpath.test.unit.TestPlotting.test_decimate
53 max_index, max_value = max(enumerate(dist_iter, start=1), key=lambda v: v[1]) procpath.test.cmd.TestPlotCommand.test_plot_rdp_epsilonprocpath.test.unit.TestPlotting.test_decimate
55 if max_value > epsilon: procpath.test.cmd.TestPlotCommand.test_plot_rdp_epsilonprocpath.test.unit.TestPlotting.test_decimate
56 return ( procpath.test.unit.TestPlotting.test_decimate
57 decimate(points[:max_index + 1], epsilon)[:-1]
58 + decimate(points[max_index:], epsilon)
59 )
60 else:
61 return [points[0], points[-1]] procpath.test.cmd.TestPlotCommand.test_plot_rdp_epsilonprocpath.test.unit.TestPlotting.test_decimate
64def moving_average(points: Iterable[Point], n: int) -> Iterable[Point]:
65 """
66 Calculate moving average time series of given time series.
68 The average is taken from an equal number of points on either side
69 of a central value. This ensures that variations in the average are
70 aligned with the variations in the data rather than being shifted
71 in time. ``n // 2`` values are skipped from either side of the
72 series. It is equivalent of::
74 df = pandas.DataFrame(points, columns=('x', 'y'))
75 df.y = df.y.rolling(window=n, center=True).mean()
76 df.dropna()
78 """
80 assert n > 0, 'n must be a positive number' procpath.test.cmd.TestPlotCommand.test_plot_moving_average_windowprocpath.test.unit.TestPlotting.test_moving_average
82 x_series, y_series = itertools.tee(points, 2) procpath.test.cmd.TestPlotCommand.test_plot_moving_average_windowprocpath.test.unit.TestPlotting.test_moving_average
83 x_series = itertools.islice(x_series, n // 2, None) procpath.test.cmd.TestPlotCommand.test_plot_moving_average_windowprocpath.test.unit.TestPlotting.test_moving_average
85 d = collections.deque(y_point[1] for y_point in itertools.islice(y_series, n - 1)) procpath.test.cmd.TestPlotCommand.test_plot_moving_average_windowprocpath.test.unit.TestPlotting.test_moving_average
86 d.appendleft(0) procpath.test.cmd.TestPlotCommand.test_plot_moving_average_windowprocpath.test.unit.TestPlotting.test_moving_average
87 s = sum(d) procpath.test.cmd.TestPlotCommand.test_plot_moving_average_windowprocpath.test.unit.TestPlotting.test_moving_average
88 for x_point, y_point in zip(x_series, y_series): procpath.test.cmd.TestPlotCommand.test_plot_moving_average_windowprocpath.test.unit.TestPlotting.test_moving_average
89 s += y_point[1] - d.popleft() procpath.test.cmd.TestPlotCommand.test_plot_moving_average_windowprocpath.test.unit.TestPlotting.test_moving_average
90 d.append(y_point[1]) procpath.test.cmd.TestPlotCommand.test_plot_moving_average_windowprocpath.test.unit.TestPlotting.test_moving_average
91 yield x_point[0], s / n procpath.test.cmd.TestPlotCommand.test_plot_moving_average_windowprocpath.test.unit.TestPlotting.test_moving_average
94class CompactXLabelDateTimeLine(pygal.DateTimeLine): # type: ignore[module-attr]
96 def _compute_x_labels(self):
97 """Override to make compact X labels -- no repetition of dates."""
99 super()._compute_x_labels() procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_logarithmic_two_axesprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_absolute_x_label_formattingprocpath.test.unit.TestPlotting.test_plot_compact_x_labelsprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_no_dotsprocpath.test.unit.TestPlotting.test_plot_partially_defined_seriesprocpath.test.unit.TestPlotting.test_plot_relative_x_label_formatting
101 if self.relative_time: procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_logarithmic_two_axesprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_absolute_x_label_formattingprocpath.test.unit.TestPlotting.test_plot_compact_x_labelsprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_no_dotsprocpath.test.unit.TestPlotting.test_plot_partially_defined_seriesprocpath.test.unit.TestPlotting.test_plot_relative_x_label_formatting
102 return procpath.test.unit.TestPlotting.test_plot_relative_x_label_formatting
104 last_date_str = None procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_logarithmic_two_axesprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_absolute_x_label_formattingprocpath.test.unit.TestPlotting.test_plot_compact_x_labelsprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_no_dotsprocpath.test.unit.TestPlotting.test_plot_partially_defined_series
105 for i, (ts_str, ts) in enumerate(self._x_labels): procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_logarithmic_two_axesprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_absolute_x_label_formattingprocpath.test.unit.TestPlotting.test_plot_compact_x_labelsprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_no_dotsprocpath.test.unit.TestPlotting.test_plot_partially_defined_series
106 if last_date_str == ts_str[:10]: procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_logarithmic_two_axesprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_absolute_x_label_formattingprocpath.test.unit.TestPlotting.test_plot_compact_x_labelsprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_no_dotsprocpath.test.unit.TestPlotting.test_plot_partially_defined_series
107 self._x_labels[i] = ts_str[11:], ts procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_logarithmic_two_axesprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_absolute_x_label_formattingprocpath.test.unit.TestPlotting.test_plot_compact_x_labelsprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_no_dotsprocpath.test.unit.TestPlotting.test_plot_partially_defined_series
109 last_date_str = ts_str[:10] procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_logarithmic_two_axesprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_absolute_x_label_formattingprocpath.test.unit.TestPlotting.test_plot_compact_x_labelsprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_no_dotsprocpath.test.unit.TestPlotting.test_plot_partially_defined_series
112class DateTimeDotLine(CompactXLabelDateTimeLine):
113 """
114 An override of Pygal's date-time line chart that adds a few dots.
116 It displays dots on each serie's line close to its intersection
117 with X axis' labels. Dots show a tooltip on hover with the exact
118 value and series name.
119 """
121 _dot_class = 'dot reactive tooltip-trigger'
123 def __init__(self, config=None, **kwargs):
124 super().__init__(config, **kwargs) procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_logarithmic_two_axesprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_partially_defined_series
126 # Disable default Pygal dots.
127 self.config.show_dots = False procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_logarithmic_two_axesprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_partially_defined_series
129 def _get_x_label_view_points(
130 self, serie: pygal.serie.Serie, rescale: bool
131 ) -> Iterable[Tuple[int, Point]]:
132 if rescale and self.secondary_series: procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_partially_defined_series
133 points = self._rescale(serie.points) procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.unit.TestPlotting.test_plot_partially_defined_series
134 else:
135 points = serie.points procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_partially_defined_series
137 # Note that Pygal BaseGraph.prepare_values aligns with None
138 # values all series on the respective axis to have the same
139 # number of values
140 safe_index_points = [(i, p) for i, p in enumerate(points) if p[0] is not None] procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_partially_defined_series
141 # It is assumed that the X values are chronologically ordered
142 safe_x_range = [t[1][0] for t in safe_index_points] procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_partially_defined_series
144 # Note that dictionaries retain insertion order, so it can
145 # be used here for deduplication
146 label_points = {} procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_partially_defined_series
147 for _, ts in self._x_labels: procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_partially_defined_series
148 safe_index = bisect.bisect_left(safe_x_range, ts) procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_partially_defined_series
149 if safe_index < len(safe_index_points): procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_partially_defined_series
150 point_index, point = safe_index_points[safe_index] procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_partially_defined_series
151 label_points[point_index] = point procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_partially_defined_series
153 for i, (x, y) in zip(label_points.keys(), map(self.view, label_points.values())): procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_partially_defined_series
154 if None in (x, y): 154 ↛ 155line 154 didn't jump to line 155, because the condition on line 154 was never trueprocpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_partially_defined_series
155 continue
156 elif self.logarithmic and (points[i][1] is None or points[i][1] <= 0): procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_partially_defined_series
157 continue procpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plot_log_negative
159 yield i, (x, y) procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_partially_defined_series
161 def _draw_x_label_dots(
162 self, serie: pygal.serie.Serie, view_points: Iterable[Tuple[int, Point]]
163 ):
164 serie_node = self.svg.serie(serie) procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_partially_defined_series
165 for i, (x, y) in view_points: procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_partially_defined_series
166 metadata = serie.metadata.get(i) procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_partially_defined_series
167 dots = pygal.util.decorate( procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_partially_defined_series
168 self.svg,
169 self.svg.node(serie_node['overlay'], class_='dots'),
170 metadata
171 )
173 val = self._format(serie, i) procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_partially_defined_series
174 circle = self.svg.transposable_node( procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_partially_defined_series
175 dots, 'circle', cx=x, cy=y, r=serie.dots_size, class_=self._dot_class
176 )
177 pygal.util.alter(circle, metadata) procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_partially_defined_series
178 self._tooltip_data(dots, val, x, y, xlabel=self._get_x_label(i)) procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_partially_defined_series
179 self._static_value( procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_partially_defined_series
180 serie_node,
181 val,
182 x + self.style.value_font_size,
183 y + self.style.value_font_size,
184 metadata,
185 )
187 def line(self, serie, rescale=False):
188 """Override to plot dots at around X label intersections."""
190 super().line(serie, rescale) procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_partially_defined_series
192 view_points = self._get_x_label_view_points(serie, rescale) procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_partially_defined_series
193 self._draw_x_label_dots(serie, view_points) procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_partially_defined_series
196class PlottingConfig(pygal.config.Config):
197 """The Pygal way to add custom attribute on a Graph instance."""
199 relative_time = pygal.config.Key(False, bool, 'Value', 'Display X axis as timedeltas')
202def format_x_value_absolute(v: Optional[datetime]) -> str:
203 s = v.isoformat() if v is not None else '' procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_logarithmic_two_axesprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_format_x_value_absoluteprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_absolute_x_label_formattingprocpath.test.unit.TestPlotting.test_plot_compact_x_labelsprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_no_dotsprocpath.test.unit.TestPlotting.test_plot_partially_defined_series
204 s = s.rstrip('0') if '.' in s else s procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_logarithmic_two_axesprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_format_x_value_absoluteprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_absolute_x_label_formattingprocpath.test.unit.TestPlotting.test_plot_compact_x_labelsprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_no_dotsprocpath.test.unit.TestPlotting.test_plot_partially_defined_series
205 return s procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_logarithmic_two_axesprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_format_x_value_absoluteprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_absolute_x_label_formattingprocpath.test.unit.TestPlotting.test_plot_compact_x_labelsprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_no_dotsprocpath.test.unit.TestPlotting.test_plot_partially_defined_series
208def format_x_value_relative(start: datetime, v: Optional[datetime]) -> str:
209 delta = str(v - start) if v is not None else '' procpath.test.unit.TestPlotting.test_format_x_value_relativeprocpath.test.unit.TestPlotting.test_plot_relative_x_label_formatting
210 delta = delta.rstrip('0') if '.' in delta else delta procpath.test.unit.TestPlotting.test_format_x_value_relativeprocpath.test.unit.TestPlotting.test_plot_relative_x_label_formatting
211 return delta procpath.test.unit.TestPlotting.test_format_x_value_relativeprocpath.test.unit.TestPlotting.test_plot_relative_x_label_formatting
214def get_x_value_formatter(
215 pid_series_list: List[Mapping[int, List[Point]]], config: pygal.config.Config
216) -> Callable[[Optional[datetime]], str]:
217 if not config.relative_time: procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_logarithmic_two_axesprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_absolute_x_label_formattingprocpath.test.unit.TestPlotting.test_plot_compact_x_labelsprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_no_dotsprocpath.test.unit.TestPlotting.test_plot_partially_defined_seriesprocpath.test.unit.TestPlotting.test_plot_relative_x_label_formatting
218 return format_x_value_absolute procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_logarithmic_two_axesprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_absolute_x_label_formattingprocpath.test.unit.TestPlotting.test_plot_compact_x_labelsprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_no_dotsprocpath.test.unit.TestPlotting.test_plot_partially_defined_series
220 # Points are expected to be ordered in each serie so it sufficient
221 # to take the first and the last
222 x_min = min( procpath.test.unit.TestPlotting.test_plot_relative_x_label_formatting
223 min(points[0][0] for points in pid_series.values())
224 for pid_series in pid_series_list
225 )
226 x_max = max( procpath.test.unit.TestPlotting.test_plot_relative_x_label_formatting
227 max(points[-1][0] for points in pid_series.values())
228 for pid_series in pid_series_list
229 )
230 # Use PyGal's X scale for the first timedelta tick to be exactly 0
231 x_scale: List[float] = pygal.util.compute_scale( procpath.test.unit.TestPlotting.test_plot_relative_x_label_formatting
232 x_min,
233 x_max,
234 config.logarithmic,
235 config.order_min,
236 config.min_scale,
237 config.max_scale,
238 )
239 start = datetime.utcfromtimestamp(x_scale[0]) procpath.test.unit.TestPlotting.test_plot_relative_x_label_formatting
240 return partial(format_x_value_relative, start) procpath.test.unit.TestPlotting.test_plot_relative_x_label_formatting
243def plot(
244 pid_series_list: List[Mapping[int, List[Point]]],
245 queries: list,
246 plot_file: str,
247 *,
248 title: Optional[str] = None,
249 style: Optional[str] = None,
250 formatter: Optional[str] = None,
251 share_y_axis: bool = False,
252 logarithmic: bool = False,
253 no_dots: bool = False,
254 relative_time: bool = False,
255):
256 assert pid_series_list and len(pid_series_list) == len(queries), 'Series must match queries' procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_logarithmic_two_axesprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_absolute_x_label_formattingprocpath.test.unit.TestPlotting.test_plot_compact_x_labelsprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_no_dotsprocpath.test.unit.TestPlotting.test_plot_partially_defined_seriesprocpath.test.unit.TestPlotting.test_plot_relative_x_label_formatting
257 assert share_y_axis or len(pid_series_list) <= 2, 'Only one Y axis allowed with share_y_axis' procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_logarithmic_two_axesprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_absolute_x_label_formattingprocpath.test.unit.TestPlotting.test_plot_compact_x_labelsprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_no_dotsprocpath.test.unit.TestPlotting.test_plot_partially_defined_seriesprocpath.test.unit.TestPlotting.test_plot_relative_x_label_formatting
259 if not title: procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_logarithmic_two_axesprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_absolute_x_label_formattingprocpath.test.unit.TestPlotting.test_plot_compact_x_labelsprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_no_dotsprocpath.test.unit.TestPlotting.test_plot_partially_defined_seriesprocpath.test.unit.TestPlotting.test_plot_relative_x_label_formatting
260 if share_y_axis: procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_logarithmic_two_axesprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axis
261 title = '; '.join(f'{i}. {q.title}' for i, q in enumerate(queries, start=1)) procpath.test.cmd.TestPlotCommand.test_plot_share_y_axis
262 elif len(queries) == 1: procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_logarithmic_two_axes
263 title = queries[0].title procpath.test.cmd.TestPlotCommand.test_plot
264 else:
265 title = f'{queries[0].title} vs {queries[1].title}' procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plot_logarithmic_two_axes
267 with tempfile.NamedTemporaryFile('w') as f: procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_logarithmic_two_axesprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_absolute_x_label_formattingprocpath.test.unit.TestPlotting.test_plot_compact_x_labelsprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_no_dotsprocpath.test.unit.TestPlotting.test_plot_partially_defined_seriesprocpath.test.unit.TestPlotting.test_plot_relative_x_label_formatting
268 f.write(_ui_js) procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_logarithmic_two_axesprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_absolute_x_label_formattingprocpath.test.unit.TestPlotting.test_plot_compact_x_labelsprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_no_dotsprocpath.test.unit.TestPlotting.test_plot_partially_defined_seriesprocpath.test.unit.TestPlotting.test_plot_relative_x_label_formatting
269 f.flush() procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_logarithmic_two_axesprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_absolute_x_label_formattingprocpath.test.unit.TestPlotting.test_plot_compact_x_labelsprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_no_dotsprocpath.test.unit.TestPlotting.test_plot_partially_defined_seriesprocpath.test.unit.TestPlotting.test_plot_relative_x_label_formatting
271 line_cls = CompactXLabelDateTimeLine if no_dots else DateTimeDotLine procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_logarithmic_two_axesprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_absolute_x_label_formattingprocpath.test.unit.TestPlotting.test_plot_compact_x_labelsprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_no_dotsprocpath.test.unit.TestPlotting.test_plot_partially_defined_seriesprocpath.test.unit.TestPlotting.test_plot_relative_x_label_formatting
272 datetimeline = line_cls( procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_logarithmic_two_axesprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_absolute_x_label_formattingprocpath.test.unit.TestPlotting.test_plot_compact_x_labelsprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_no_dotsprocpath.test.unit.TestPlotting.test_plot_partially_defined_seriesprocpath.test.unit.TestPlotting.test_plot_relative_x_label_formatting
273 width=912,
274 height=684,
275 show_dots=False,
276 logarithmic=logarithmic,
277 x_label_rotation=35,
278 title=title,
279 value_formatter=getattr(pygal.formatters, formatter or 'human_readable'),
280 style=getattr(pygal.style, style or 'DefaultStyle'),
281 no_prefix=True,
282 js=[f'file://{f.name}'], # embed "svg/ui.py" converted to JavaScript
283 config=PlottingConfig,
284 relative_time=relative_time,
285 )
286 datetimeline.config.x_value_formatter = get_x_value_formatter( procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_logarithmic_two_axesprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_absolute_x_label_formattingprocpath.test.unit.TestPlotting.test_plot_compact_x_labelsprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_no_dotsprocpath.test.unit.TestPlotting.test_plot_partially_defined_seriesprocpath.test.unit.TestPlotting.test_plot_relative_x_label_formatting
287 pid_series_list, datetimeline.config
288 )
289 datetimeline.config.css.append(f'inline:{_ui_css}') procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_logarithmic_two_axesprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_absolute_x_label_formattingprocpath.test.unit.TestPlotting.test_plot_compact_x_labelsprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_no_dotsprocpath.test.unit.TestPlotting.test_plot_partially_defined_seriesprocpath.test.unit.TestPlotting.test_plot_relative_x_label_formatting
290 datetimeline.config.style.tooltip_font_size = 11 procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_logarithmic_two_axesprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_absolute_x_label_formattingprocpath.test.unit.TestPlotting.test_plot_compact_x_labelsprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_no_dotsprocpath.test.unit.TestPlotting.test_plot_partially_defined_seriesprocpath.test.unit.TestPlotting.test_plot_relative_x_label_formatting
292 for i, (query, pid_series) in enumerate(zip(queries, pid_series_list)): procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_logarithmic_two_axesprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_absolute_x_label_formattingprocpath.test.unit.TestPlotting.test_plot_compact_x_labelsprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_no_dotsprocpath.test.unit.TestPlotting.test_plot_partially_defined_seriesprocpath.test.unit.TestPlotting.test_plot_relative_x_label_formatting
293 for pid, points in pid_series.items(): procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_logarithmic_two_axesprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_absolute_x_label_formattingprocpath.test.unit.TestPlotting.test_plot_compact_x_labelsprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_no_dotsprocpath.test.unit.TestPlotting.test_plot_partially_defined_seriesprocpath.test.unit.TestPlotting.test_plot_relative_x_label_formatting
294 datetimeline.add( procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_logarithmic_two_axesprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_absolute_x_label_formattingprocpath.test.unit.TestPlotting.test_plot_compact_x_labelsprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_no_dotsprocpath.test.unit.TestPlotting.test_plot_partially_defined_seriesprocpath.test.unit.TestPlotting.test_plot_relative_x_label_formatting
295 '{name} {pid}'.format(pid=pid, name=query.name or f'№{i + 1}'),
296 points,
297 secondary=not share_y_axis and bool(i),
298 )
300 datetimeline.render_to_file(plot_file) procpath.test.cmd.TestPlayCommand.test_play_record_plotprocpath.test.cmd.TestPlotCommand.test_plotprocpath.test.cmd.TestPlotCommand.test_plot_logarithmic_two_axesprocpath.test.cmd.TestPlotCommand.test_plot_share_y_axisprocpath.test.unit.TestPlotting.test_plotprocpath.test.unit.TestPlotting.test_plot_absolute_x_label_formattingprocpath.test.unit.TestPlotting.test_plot_compact_x_labelsprocpath.test.unit.TestPlotting.test_plot_log_negativeprocpath.test.unit.TestPlotting.test_plot_no_dotsprocpath.test.unit.TestPlotting.test_plot_partially_defined_seriesprocpath.test.unit.TestPlotting.test_plot_relative_x_label_formatting
303_ui_css = '''
304.tooltip text.legend {font-size: 1em}
305.tooltip text.value {font-size: 1.2em}
306'''
308_ui_js = (
309 r'var e,t;function i(e){return e.set_properties=function(e,t){var i,s,n=t;for(var o in n)'
310 r'n.hasOwnProperty(o)&&(i=!((s=t[o])instanceof Map||s instanceof WeakMap)&&s instanceof O'
311 r'bject&&"get"in s&&s.get instanceof Function?s:{value:s,enumerable:!1,configurable:!0,wr'
312 r'itable:!0},Object.defineProperty(e.prototype,o,i))},e}function*s(e){var t;t=0;for(var i'
313 r',s=0,n=e,o=n.length;s<o;s+=1)i=n[s],yield[t,i],t+=1}function n(e,t=null){var i;return t'
314 r'=t||window.document,i=[...t.querySelectorAll(e)],function(){for(var e=[],s=i,n=0,o=s.le'
315 r'ngth;n<o;n+=1){var r=s[n];r!==t&&e.push(r)}return e}.call(this)}function o(e,t){return '
316 r'function(){for(var i=[],s=e.parentElement.children,n=0,o=s.length;n<o;n+=1){var r=s[n];'
317 r'r===e||t&&!r.matches(t)||i.push(r)}return i}.call(this)}function r(e){var i,s;return i='
318 r't.exec,s=i.call(t,e.getAttribute("transform"))||[],function(){for(var e=[],t=[...s].sli'
319 r'ce(1),i=0,n=t.length;i<n;i+=1){var o=t[i];e.push(Number.parseInt(o))}return e}.call(thi'
320 r's)}i(e={}),t=new RegExp("translate\\((\\d+)[ ,]+(\\d+)\\)");class l{constructor(e,t){va'
321 r'r i;this._chartNode=e,t.no_prefix?this._config=t:(i=e.id.replace("chart-",""),this._con'
322 r'fig=t[i]),this._tooltipElement=n(".tooltip",e)[0],this._setConverters(),this._setToolti'
323 r'pTriggerListeners(),this._setTooltipListeners(),this._setGraphListeners(),this._setNode'
324 r'Listeners()}_setConverters(){var e,t,i,s;(s=n("svg",this._chartNode)).length?(i=s[0].pa'
325 r'rentElement,t=s[0].viewBox.baseVal,e=i.getBBox(),this._xconvert=i=>(i-t.x)/t.width*e.wi'
326 r'dth,this._yconvert=i=>(i-t.y)/t.height*e.height):this._xconvert=this._yconvert=e=>e}_on'
327 r'GraphMouseMove(e){!this._tooltipTimeoutHandle&&e.target.matches(".background")&&this.hi'
328 r'de(1e3)}_setGraphListeners(){n(".graph",this._chartNode)[0].addEventListener("mousemove'
329 r'",(e=>this._onGraphMouseMove(e)))}_onNodeMouseLeave(){this._tooltipTimeoutHandle&&(wind'
330 r'ow.clearTimeout(this._tooltipTimeoutHandle),this._tooltipTimeoutHandle=null),this.hide('
331 r')}_setNodeListeners(){this._chartNode.addEventListener("mouseleave",(()=>this._onNodeMo'
332 r'useLeave()))}_setTooltipTriggerListener(e){e.addEventListener("mouseenter",(()=>this.sh'
333 r'ow(e)))}_setTooltipTriggerListeners(){for(var e,t=0,i=n(".tooltip-trigger",this._chartN'
334 r'ode),s=i.length;t<s;t+=1)e=i[t],this._setTooltipTriggerListener(e)}_onTooltipMouseEnter'
335 r'(){this._tooltipElement&&this._tooltipElement.classList.remove("active")}_onTooltipMous'
336 r'eLeave(){this._tooltipElement&&this._tooltipElement.classList.remove("active")}_setTool'
337 r'tipListeners(){this._tooltipElement.addEventListener("mouseenter",(()=>this._onTooltipM'
338 r'ouseEnter())),this._tooltipElement.addEventListener("mouseleave",(()=>this._onTooltipMo'
339 r'useLeave()))}_getSerieIndex(e){var t,i,s;for(i=null,t=e,s=[];t&&(s=[...s,t],!t.classLis'
340 r't.contains("series"));)t=t.parentElement;if(t)for(var n,o=0,r=t.classList,l=r.length;o<'
341 r'l;o+=1)if((n=r[o]).startsWith("serie-")){i=Number.parseInt(n.replace("serie-",""));brea'
342 r'k}return i}_createTextGroup(e,t){var i,s,o,r,l,a,h,d,c;(h=n("g.text",this._tooltipEleme'
343 r'nt)[0]).innerHTML="",o=0,d={};for(var _,p=0,u=e,v=u.length;p<v;p+=1)_=u[p],[r,l]=_,r&&('
344 r'(a=window.document.createElementNS(this.svg_ns,"text")).textContent=r,a.setAttribute("x'
345 r'",this.padding),a.setAttribute("dy",o),a.classList.add(l.startsWith("value")?"value":l)'
346 r',l.startsWith("value")&&this._config.tooltip_fancy_mode&&a.classList.add(`color-${t}`),'
347 r'"xlink"===l?((i=window.document.createElementNS(this.svg_ns,"a")).setAttributeNS(this.x'
348 r'link_ns,"href",r),i.textContent="",i.appendChild(a),a.textContent="Link >",h.appendChil'
349 r'd(i)):h.appendChild(a),o+=a.getBBox().height+this.padding/2,s=this.padding,a.style.domi'
350 r'nantBaseline?a.style.dominantBaseline="text-before-edge":s+=.8*a.getBBox().height,a.set'
351 r'Attribute("y",s),d[l]=a);return c=h.getBBox().width+2*this.padding,d.value&&d.value.set'
352 r'Attribute("dx",(c-d.value.getBBox().width)/2-this.padding),d.x_label&&d.x_label.setAttr'
353 r'ibute("dx",c-d.x_label.getBBox().width-2*this.padding),d.xlink&&d.xlink.setAttribute("d'
354 r'x",c-d.xlink.getBBox().width-2*this.padding),h}_constrainTooltip(e,t,i,s){var n,o;retur'
355 r'n[n,o]=r(this._tooltipElement.parentElement),e+i+n>this._config.width&&(e=this._config.'
356 r'width-i-n),t+s+o>this._config.height&&(t=this._config.height-s-o),e+n<0&&(e=-n),t+o<0&&'
357 r'(t=-o),[e,t]}_getTooltipCoordinates(e,t,i){var s,n,r,l;return n=o(e,".x")[0],l=o(e,".y"'
358 r')[0],s=Number.parseInt(n.textContent),n.classList.contains("centered")?s-=t/2:n.classLi'
359 r'st.contains("left")?s-=t:n.classList.contains("auto")&&(s=this._xconvert(e.getBBox().x+'
360 r'e.getBBox().width/2)-t/2),r=Number.parseInt(l.textContent),l.classList.contains("center'
361 r'ed")?r-=i/2:l.classList.contains("top")?r-=i:l.classList.contains("auto")&&(r=this._yco'
362 r'nvert(e.getBBox().y+e.getBBox().height/2)-i/2),this._constrainTooltip(s,r,t,i)}_getTool'
363 r'tipKeyMap(e,t){var i,n,r,l,a,h,d,c;n=[[(r=o(e,".label")).length?r[0].textContent:"","la'
364 r'bel"]];for(var _,p=0,u=[...s(o(e,".value")[0].textContent.split("\n"))],v=u.length;p<v;'
365 r'p+=1)_=u[p],[i,l]=_,n=[...n,[l,`value-${i}`]];return d=(c=o(e,".xlink")).length?c[0].te'
366 r'xtContent:"",a=(h=o(e,".x_label")).length?h[0].textContent:"",this._config.tooltip_fanc'
367 r'y_mode&&(n=[[t,"legend"],[a,"x_label"],...n,[d,"xlink"]]),n}show(e){var t,i,s,o,l,a,h,d'
368 r',c,_,p;window.clearTimeout(this._tooltipTimeoutHandle),this._tooltipTimeoutHandle=null,'
369 r'this._tooltipElement.style.opacity=1,this._tooltipElement.style.display="",l=null,null!'
370 r'==(h=this._getSerieIndex(e))&&(l=this._config.legends[h]),o=this._getTooltipKeyMap(e,l)'
371 r',c=(d=this._createTextGroup(o,h)).getBBox().width+2*this.padding,s=d.getBBox().height+2'
372 r'*this.padding,(a=n("rect",this._tooltipElement)[0]).setAttribute("width",c),a.setAttrib'
373 r'ute("height",s),[_,p]=this._getTooltipCoordinates(e,c,s),[t,i]=r(this._tooltipElement),'
374 r't===_&&i===p||this._tooltipElement.setAttribute("transform",`translate(${_} ${p})`)}_hi'
375 r'deDelayed(){this._tooltipElement.style.display="none",this._tooltipElement.style.opacit'
376 r'y=0,this._tooltipElement.classList.remove("active"),this._tooltipTimeoutHandle=null}hid'
377 r'e(e=0){this._tooltipTimeoutHandle=window.setTimeout((()=>this._hideDelayed()),e)}}e.set'
378 r'_properties(l,{_chartNode:null,_config:null,_tooltipElement:null,_tooltipTimeoutHandle:'
379 r'null,_xconvert:null,_yconvert:null,padding:5,svg_ns:"http://www.w3.org/2000/svg",xlink_'
380 r'ns:"http://www.w3.org/1999/xlink"});class a{constructor(e){this._node=e,this._setActive'
381 r'SeriesListeners()}_onActiveSerieMouseEnter(e){for(var t=0,i=(s=n(`.serie-${e} .reactive'
382 r'`,this._node)).length;t<i;t+=1)s[t].classList.add("active");var s;for(t=0,i=(s=n(`.seri'
383 r'e-${e} .showable`,this._node)).length;t<i;t+=1)s[t].classList.add("shown")}_onActiveSer'
384 r'ieMouseLeave(e){for(var t=0,i=(s=n(`.serie-${e} .reactive`,this._node)).length;t<i;t+=1'
385 r')s[t].classList.remove("active");var s;for(t=0,i=(s=n(`.serie-${e} .showable`,this._nod'
386 r'e)).length;t<i;t+=1)s[t].classList.remove("shown")}_isSerieVisible(e){return!n(`#activa'
387 r'te-serie-${e} rect`,this._node)[0].style.fill}_setSerieVisible(e,t){n(`#activate-serie-'
388 r'${e} rect`,this._node)[0].style.fill=t?"":"transparent";for(var i=0,s=(o=n(`.serie-${e}'
389 r' .reactive`,this._node)).length;i<s;i+=1)o[i].style.display=t?"":"none";var o;for(i=0,s'
390 r'=(o=n(`.text-overlay .serie-${e}`,this._node)).length;i<s;i+=1)o[i].style.display=t?"":'
391 r'"none"}_onActiveSerieClick(e,t){var i,s;if(s=!this._isSerieVisible(e),this._setSerieVis'
392 r'ible(e,s),2===t.detail){i=!0;for(var n=0,o=this._serie_count;n<o;n+=1)n!==e&&(i=i&&!thi'
393 r's._isSerieVisible(n));this._setSerieVisible(e,!0);for(n=0,o=this._serie_count;n<o;n+=1)'
394 r'n!==e&&this._setSerieVisible(n,i)}}_setActiveSerieListeners(e,t){e.addEventListener("mo'
395 r'useenter",(()=>this._onActiveSerieMouseEnter(t))),e.addEventListener("mouseleave",(()=>'
396 r'this._onActiveSerieMouseLeave(t))),e.addEventListener("click",(e=>this._onActiveSerieCl'
397 r'ick(t,e)))}_setActiveSeriesListeners(){for(var e,t,i=0,s=n(".activate-serie",this._node'
398 r'),o=s.length;i<o;i+=1)t=s[i],e=Number.parseInt(t.id.replace("activate-serie-","")),this'
399 r'._setActiveSerieListeners(t,e);this._serie_count=e+1}}function h(){if(!("pygal"in windo'
400 r'w)||!("config"in window.pygal))throw new Error("No config defined");for(var e,t=0,i=n("'
401 r'.pygal-chart"),s=i.length;t<s;t+=1)e=i[t],new a(e),new l(e,window.pygal.config)}e.set_p'
402 r'roperties(a,{_node:null,_serie_count:0}),"loading"!==window.document.readyState?h():win'
403 r'dow.document.addEventListener("DOMContentLoaded",h);'
404)