Coverage for procpath/plotting.py: 99%

128 statements  

« 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 

9 

10import pygal.config 

11import pygal.formatters 

12import pygal.serie 

13import pygal.style 

14import pygal.util 

15 

16 

17__all__ = 'Point', 'decimate', 'moving_average', 'plot' 

18 

19Point = Tuple[float, float] 

20 

21 

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. 

25 

26 Points are represented by 2-tuples. 

27 """ 

28 

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

31 

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

34 

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 ) 

39 

40 

41def decimate(points: List[Point], epsilon: float) -> List[Point]: 

42 """ 

43 Decimate given poly-line using Ramer-Douglas-Peucker algorithm. 

44 

45 It reduces the points to a simplified version that loses detail, 

46 but retains its peaks. 

47 """ 

48 

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

51 

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

54 

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

62 

63 

64def moving_average(points: Iterable[Point], n: int) -> Iterable[Point]: 

65 """ 

66 Calculate moving average time series of given time series. 

67 

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:: 

73 

74 df = pandas.DataFrame(points, columns=('x', 'y')) 

75 df.y = df.y.rolling(window=n, center=True).mean() 

76 df.dropna() 

77 

78 """ 

79 

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

81 

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

84 

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

92 

93 

94class CompactXLabelDateTimeLine(pygal.DateTimeLine): # type: ignore[module-attr] 

95 

96 def _compute_x_labels(self): 

97 """Override to make compact X labels -- no repetition of dates.""" 

98 

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

100 

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

103 

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

108 

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

110 

111 

112class DateTimeDotLine(CompactXLabelDateTimeLine): 

113 """ 

114 An override of Pygal's date-time line chart that adds a few dots. 

115 

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

120 

121 _dot_class = 'dot reactive tooltip-trigger' 

122 

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

125 

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

128 

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

136 

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

143 

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

152 

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

158 

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

160 

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 ) 

172 

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 ) 

186 

187 def line(self, serie, rescale=False): 

188 """Override to plot dots at around X label intersections.""" 

189 

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

191 

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

194 

195 

196class PlottingConfig(pygal.config.Config): 

197 """The Pygal way to add custom attribute on a Graph instance.""" 

198 

199 relative_time = pygal.config.Key(False, bool, 'Value', 'Display X axis as timedeltas') 

200 

201 

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

206 

207 

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

212 

213 

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

219 

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

241 

242 

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

258 

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

266 

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

270 

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

291 

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 ) 

299 

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

301 

302 

303_ui_css = ''' 

304.tooltip text.legend {font-size: 1em} 

305.tooltip text.value {font-size: 1.2em} 

306''' 

307 

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)