plotext-plus 1.0.8__py3-none-any.whl → 1.0.10__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
plotext_plus/_monitor.py CHANGED
@@ -1,34 +1,40 @@
1
- from plotext_plus._default import default_monitor_class
2
- from plotext_plus._matrix import matrix_class
3
- from plotext_plus._build import build_class
4
- import plotext_plus._utility as ut
5
- from copy import deepcopy
6
1
  import math
2
+ from copy import deepcopy
3
+
4
+ import plotext_plus._utility as ut
5
+ from plotext_plus._build import BuildClass
6
+ from plotext_plus._default import DefaultMonitorClass
7
+ from plotext_plus._matrix import MatrixClass
7
8
 
8
9
  # This file defines the monitor class, i.e. the plot, where actual data is plotted; the plot is build separately in the build class for clarity; here only the main tools and drawing methods are written
9
10
 
10
- class monitor_class(build_class):
11
-
11
+
12
+ class MonitorClass(BuildClass):
13
+
12
14
  def __init__(self):
13
- self.default = default_monitor_class() # default values
15
+ self.default = DefaultMonitorClass() # default values
14
16
  self.labels_init()
15
17
  self.axes_init()
16
18
  self.color_init()
17
19
  self.data_init()
18
- self.matrix = matrix_class()
20
+ self.matrix = MatrixClass()
19
21
 
20
- def copy(self): # to deep copy
22
+ def copy(self): # to deep copy
21
23
  return deepcopy(self)
22
24
 
23
- def set_size(self, size): # called externally by the figure containing it, to pass the size
25
+ def set_size(
26
+ self, size
27
+ ): # called externally by the figure containing it, to pass the size
24
28
  self.size = size
25
29
 
26
- def set_date(self, date): # called externally by the figure containing it, to pass the date tools so that they share the same settings
30
+ def set_date(
31
+ self, date
32
+ ): # called externally by the figure containing it, to pass the date tools so that they share the same settings
27
33
  self.date = date
28
34
 
29
- ##############################################
30
- ######### Internal Variables ###########
31
- ##############################################
35
+ ##############################################
36
+ ######### Internal Variables ###########
37
+ ##############################################
32
38
 
33
39
  def labels_init(self):
34
40
  self.title = None
@@ -36,30 +42,42 @@ class monitor_class(build_class):
36
42
  self.ylabel = [None, None]
37
43
 
38
44
  def axes_init(self):
39
- self.xscale = [self.default.xscale[0]] * 2 # the scale on x axis
45
+ self.xscale = [self.default.xscale[0]] * 2 # the scale on x axis
40
46
  self.yscale = [self.default.xscale[0]] * 2
41
47
 
42
- self.xticks = self.default.xticks # xticks coordinates for both axes
43
- self.xlabels = self.default.xticks[:] # xlabels for both axes
44
- self.xfrequency = self.default.xfrequency # lower and upper xaxes ticks frequency
48
+ self.xticks = self.default.xticks # xticks coordinates for both axes
49
+ self.xlabels = self.default.xticks[:] # xlabels for both axes
50
+ self.xfrequency = (
51
+ self.default.xfrequency
52
+ ) # lower and upper xaxes ticks frequency
45
53
  self.xdirection = self.default.xdirection
46
-
54
+
47
55
  self.yticks = self.default.yticks
48
56
  self.ylabels = self.default.yticks[:]
49
- self.yfrequency = self.default.yfrequency # left and right yaxes ticks frequency
57
+ self.yfrequency = (
58
+ self.default.yfrequency
59
+ ) # left and right yaxes ticks frequency
50
60
  self.ydirection = self.default.ydirection
51
61
 
52
- self.xaxes = self.default.xaxes # whatever to show the lower and upper x axis
53
- self.yaxes = self.default.yaxes # whatever to show the left and right y axis
62
+ self.xaxes = self.default.xaxes # whatever to show the lower and upper x axis
63
+ self.yaxes = self.default.yaxes # whatever to show the left and right y axis
54
64
 
55
- self.grid = self.default.grid # whatever to show the horizontal and vertical grid lines
65
+ self.grid = (
66
+ self.default.grid
67
+ ) # whatever to show the horizontal and vertical grid lines
56
68
 
57
69
  def color_init(self):
58
- self.set_theme('default')
70
+ self.set_theme("default")
59
71
 
60
72
  def data_init(self):
61
- self.xlim = [[None, None], [None, None]] # the x axis plot limits for lower and upper xside
62
- self.ylim = [[None, None], [None, None]] # the y axis plot limits for left and right yside
73
+ self.xlim = [
74
+ [None, None],
75
+ [None, None],
76
+ ] # the x axis plot limits for lower and upper xside
77
+ self.ylim = [
78
+ [None, None],
79
+ [None, None],
80
+ ] # the y axis plot limits for left and right yside
63
81
 
64
82
  self.fast_plot = False
65
83
  self.lines_init()
@@ -67,9 +85,12 @@ class monitor_class(build_class):
67
85
  self.draw_init()
68
86
 
69
87
  def lines_init(self):
70
- self.vcoord = [[], []] # those are user defined extra grid lines, vertical or horizontal, for each axis
88
+ self.vcoord = [
89
+ [],
90
+ [],
91
+ ] # those are user defined extra grid lines, vertical or horizontal, for each axis
71
92
  self.hcoord = [[], []]
72
- self.vcolors = [[], []] # their color
93
+ self.vcolors = [[], []] # their color
73
94
  self.hcolors = [[], []]
74
95
 
75
96
  def text_init(self):
@@ -85,43 +106,47 @@ class monitor_class(build_class):
85
106
  self.tstyle = []
86
107
 
87
108
  def draw_init(self): # Variables Set with Draw internal Arguments
88
- self.xside = [] # which side the x axis should go, for each plot (lower or upper)
89
- self.yside = [] # which side the y axis should go, for each plot (left or right)
90
-
91
- self.x = [] # list of x coordinates
92
- self.y = [] # list of y coordinates
93
- self.x_date = [False, False] # True if x axis is for date time plots
109
+ self.xside = (
110
+ []
111
+ ) # which side the x axis should go, for each plot (lower or upper)
112
+ self.yside = (
113
+ []
114
+ ) # which side the y axis should go, for each plot (left or right)
115
+
116
+ self.x = [] # list of x coordinates
117
+ self.y = [] # list of y coordinates
118
+ self.x_date = [False, False] # True if x axis is for date time plots
94
119
  self.y_date = [False, False]
95
- self.signals = 0 # number of signals to plot
120
+ self.signals = 0 # number of signals to plot
96
121
 
97
- self.lines = [] # whatever to draw lines between points
122
+ self.lines = [] # whatever to draw lines between points
98
123
 
99
- self.marker = [] # list of markers used for each plot
100
- self.color = [] # list of marker colors used for each plot
124
+ self.marker = [] # list of markers used for each plot
125
+ self.color = [] # list of marker colors used for each plot
101
126
  self.past_colors = []
102
127
  self.style = []
103
128
 
104
- self.fillx = [] # fill data vertically (till x axis)
105
- self.filly = [] # fill data horizontally (till y axis)
129
+ self.fillx = [] # fill data vertically (till x axis)
130
+ self.filly = [] # fill data horizontally (till y axis)
106
131
 
107
- self.label = [] # subplot list of labels
132
+ self.label = [] # subplot list of labels
108
133
 
109
- ##############################################
110
- ####### External Set Functions #########
111
- ##############################################
134
+ ##############################################
135
+ ####### External Set Functions #########
136
+ ##############################################
112
137
 
113
- def set_title(self, title = None):
138
+ def set_title(self, title=None):
114
139
  self.title = self.set_label(title)
115
140
 
116
- def set_xlabel(self, label = None, xside = None):
141
+ def set_xlabel(self, label=None, xside=None):
117
142
  pos = self.xside_to_pos(xside)
118
143
  self.xlabel[pos] = self.set_label(label)
119
-
120
- def set_ylabel(self, label = None, yside = None):
144
+
145
+ def set_ylabel(self, label=None, yside=None):
121
146
  pos = self.yside_to_pos(yside)
122
147
  self.ylabel[pos] = self.set_label(label)
123
-
124
- def set_xlim(self, left = None, right = None, xside = None):
148
+
149
+ def set_xlim(self, left=None, right=None, xside=None):
125
150
  left = self.date.string_to_time(left) if isinstance(left, str) else left
126
151
  right = self.date.string_to_time(right) if isinstance(right, str) else right
127
152
  left = None if left is None else float(left)
@@ -131,7 +156,7 @@ class monitor_class(build_class):
131
156
  pos = self.xside_to_pos(xside)
132
157
  self.xlim[pos] = xlim
133
158
 
134
- def set_ylim(self, lower = None, upper = None, yside = None):
159
+ def set_ylim(self, lower=None, upper=None, yside=None):
135
160
  lower = self.date.string_to_time(lower) if isinstance(lower, str) else lower
136
161
  upper = self.date.string_to_time(upper) if isinstance(upper, str) else upper
137
162
  lower = None if lower is None else float(lower)
@@ -141,22 +166,22 @@ class monitor_class(build_class):
141
166
  pos = self.yside_to_pos(yside)
142
167
  self.ylim[pos] = ylim
143
168
 
144
- def set_xscale(self, scale = None, xside = None):
145
- default_case = (scale is None or scale not in self.default.xscale)
169
+ def set_xscale(self, scale=None, xside=None):
170
+ default_case = scale is None or scale not in self.default.xscale
146
171
  scale = self.default.xscale[0] if default_case else scale
147
172
  pos = self.xside_to_pos(xside)
148
173
  self.xscale[pos] = scale
149
174
 
150
- def set_yscale(self, scale = None, yside = None):
151
- default_case = (scale is None or scale not in self.default.yscale)
175
+ def set_yscale(self, scale=None, yside=None):
176
+ default_case = scale is None or scale not in self.default.yscale
152
177
  scale = self.default.yscale[0] if default_case else scale
153
178
  pos = self.yside_to_pos(yside)
154
179
  self.yscale[pos] = scale
155
180
 
156
- def set_xticks(self, ticks = None, labels = None, xside = None):
181
+ def set_xticks(self, ticks=None, labels=None, xside=None):
157
182
  pos = self.xside_to_pos(xside)
158
183
  ticks = self.default.xticks[pos] if ticks is None else list(ticks)
159
- string_ticks = any([isinstance(el, str) for el in ticks])
184
+ string_ticks = any(isinstance(el, str) for el in ticks)
160
185
  labels = ticks if string_ticks and labels is None else labels
161
186
  ticks = self.date.strings_to_time(ticks) if string_ticks else ticks
162
187
  labels = ut.get_labels(ticks) if labels is None else list(labels)
@@ -166,10 +191,10 @@ class monitor_class(build_class):
166
191
  self.xlabels[pos] = labels
167
192
  self.xfrequency[pos] = self.xfrequency[pos] if ticks is None else len(ticks)
168
193
 
169
- def set_yticks(self, ticks = None, labels = None, yside = None):
194
+ def set_yticks(self, ticks=None, labels=None, yside=None):
170
195
  pos = self.yside_to_pos(yside)
171
196
  ticks = self.default.yticks[pos] if ticks is None else list(ticks)
172
- string_ticks = any([isinstance(el, str) for el in ticks])
197
+ string_ticks = any(isinstance(el, str) for el in ticks)
173
198
  labels = ticks if string_ticks and labels is None else labels
174
199
  ticks = self.date.strings_to_time(ticks) if string_ticks else ticks
175
200
  labels = ut.get_labels(ticks) if labels is None else list(labels)
@@ -179,110 +204,142 @@ class monitor_class(build_class):
179
204
  self.ylabels[pos] = labels
180
205
  self.yfrequency[pos] = self.yfrequency[pos] if ticks is None else len(ticks)
181
206
 
182
- def set_xfrequency(self, frequency = None, xside = None):
207
+ def set_xfrequency(self, frequency=None, xside=None):
183
208
  pos = self.xside_to_pos(xside)
184
- frequency = self.default.xfrequency[pos] if frequency is None else int(frequency)
209
+ frequency = (
210
+ self.default.xfrequency[pos] if frequency is None else int(frequency)
211
+ )
185
212
  self.xfrequency[pos] = frequency
186
-
187
- def set_yfrequency(self, frequency = None, yside = None):
213
+
214
+ def set_yfrequency(self, frequency=None, yside=None):
188
215
  pos = self.yside_to_pos(yside)
189
- frequency = self.default.yfrequency[pos] if frequency is None else int(frequency)
216
+ frequency = (
217
+ self.default.yfrequency[pos] if frequency is None else int(frequency)
218
+ )
190
219
  self.yfrequency[pos] = frequency
191
220
 
192
- def set_xreverse(self, reverse = None, xside = None):
221
+ def set_xreverse(self, reverse=None, xside=None):
193
222
  pos = self.xside_to_pos(xside)
194
- direction = self.default.xdirection[pos] if reverse is None else 2 * int(not reverse) - 1
223
+ direction = (
224
+ self.default.xdirection[pos]
225
+ if reverse is None
226
+ else 2 * int(not reverse) - 1
227
+ )
195
228
  self.xdirection[pos] = direction
196
229
 
197
- def set_yreverse(self, reverse = None, yside = None):
230
+ def set_yreverse(self, reverse=None, yside=None):
198
231
  pos = self.yside_to_pos(yside)
199
- direction = self.default.ydirection[pos] if reverse is None else 2 * int(not reverse) - 1
232
+ direction = (
233
+ self.default.ydirection[pos]
234
+ if reverse is None
235
+ else 2 * int(not reverse) - 1
236
+ )
200
237
  self.ydirection[pos] = direction
201
-
202
- def set_xaxes(self, lower = None, upper = None):
238
+
239
+ def set_xaxes(self, lower=None, upper=None):
203
240
  self.xaxes[0] = self.default.xaxes[0] if lower is None else bool(lower)
204
241
  self.xaxes[1] = self.default.xaxes[1] if upper is None else bool(upper)
205
-
206
- def set_yaxes(self, left = None, right = None):
242
+
243
+ def set_yaxes(self, left=None, right=None):
207
244
  self.yaxes[0] = self.default.yaxes[0] if left is None else bool(left)
208
245
  self.yaxes[1] = self.default.yaxes[1] if right is None else bool(right)
209
246
 
210
- def set_frame(self, frame = None):
247
+ def set_frame(self, frame=None):
211
248
  self.set_xaxes(frame, frame)
212
249
  self.set_yaxes(frame, frame)
213
250
 
214
- def set_grid(self, horizontal = None, vertical = None):
251
+ def set_grid(self, horizontal=None, vertical=None):
215
252
  horizontal = self.default.grid[0] if horizontal is None else bool(horizontal)
216
253
  vertical = self.default.grid[1] if vertical is None else bool(vertical)
217
254
  self.grid = [horizontal, vertical]
218
255
 
219
- def set_color(self, color = None):
256
+ def set_color(self, color=None):
220
257
  color = color if ut.is_color(color) else None
221
258
  return self.default.canvas_color if color is None else color
222
259
 
223
- def set_canvas_color(self, color = None):
260
+ def set_canvas_color(self, color=None):
224
261
  self.canvas_color = self.set_color(color)
225
-
226
- def set_axes_color(self, color = None):
262
+
263
+ def set_axes_color(self, color=None):
227
264
  self.axes_color = self.set_color(color)
228
-
229
- def set_ticks_color(self, color = None):
265
+
266
+ def set_ticks_color(self, color=None):
230
267
  self.ticks_color = self.set_color(color)
231
268
 
232
- def set_ticks_style(self, style = None):
269
+ def set_ticks_style(self, style=None):
233
270
  style = style if ut.is_style(style) else None
234
271
  style = self.default.ticks_style if style is None else ut.clean_styles(style)
235
272
  self.ticks_style = style
236
273
 
237
- def set_theme(self, theme = None):
238
- theme = 'default' if theme is None or theme not in ut.themes else theme
274
+ def set_theme(self, theme=None):
275
+ theme = "default" if theme is None or theme not in ut.themes else theme
239
276
  self._set_theme(*ut.themes[theme])
240
277
 
241
278
  def clear_color(self):
242
- self.set_theme('clear')
279
+ self.set_theme("clear")
243
280
 
244
- ##############################################
245
- ####### Set Functions Utilities ########
246
- ##############################################
281
+ ##############################################
282
+ ####### Set Functions Utilities ########
283
+ ##############################################
247
284
 
248
- def set_label(self, label = None):
285
+ def set_label(self, label=None):
249
286
  label = None if label is None else str(label).strip()
250
287
  spaces = ut.only_spaces(label)
251
- label = None if spaces else label
288
+ label = None if spaces else label
252
289
  return label
253
290
 
254
- def correct_xside(self, xside = None): # from axis side to position
291
+ def correct_xside(self, xside=None): # from axis side to position
255
292
  xaxis = self.default.xside
256
- xside = xaxis[xside - 1] if isinstance(xside, int) and 1 <= xside <= 2 else xaxis[0] if xside is None or xside.strip() not in xaxis else xside.strip()
293
+ xside = (
294
+ xaxis[xside - 1]
295
+ if isinstance(xside, int) and 1 <= xside <= 2
296
+ else (
297
+ xaxis[0]
298
+ if xside is None or xside.strip() not in xaxis
299
+ else xside.strip()
300
+ )
301
+ )
257
302
  return xside
258
303
 
259
- def correct_yside(self, yside = None):
304
+ def correct_yside(self, yside=None):
260
305
  yaxis = self.default.yside
261
- yside = yaxis[yside - 1] if isinstance(yside, int) and 1 <= yside <= 2 else yaxis[0] if yside is None or yside.strip() not in yaxis else yside.strip()
306
+ yside = (
307
+ yaxis[yside - 1]
308
+ if isinstance(yside, int) and 1 <= yside <= 2
309
+ else (
310
+ yaxis[0]
311
+ if yside is None or yside.strip() not in yaxis
312
+ else yside.strip()
313
+ )
314
+ )
262
315
  return yside
263
316
 
264
- def xside_to_pos(self, xside = None): # from axis side to position
317
+ def xside_to_pos(self, xside=None): # from axis side to position
265
318
  xside = self.correct_xside(xside)
266
319
  pos = self.default.xside.index(xside)
267
320
  return pos
268
321
 
269
- def yside_to_pos(self, yside = None):
322
+ def yside_to_pos(self, yside=None):
270
323
  yside = self.correct_yside(yside)
271
324
  pos = self.default.yside.index(yside)
272
325
  return pos
273
326
 
274
- def _set_theme(self, canvas_color, axes_color, ticks_color, ticks_style, color_sequence):
327
+ def _set_theme(
328
+ self, canvas_color, axes_color, ticks_color, ticks_style, color_sequence
329
+ ):
275
330
  self.canvas_color = canvas_color
276
331
  self.axes_color = axes_color
277
332
  self.ticks_color = ticks_color
278
333
  self.ticks_style = ticks_style
279
334
  self.color_sequence = color_sequence
280
-
281
- ##############################################
282
- ########## Draw() Function #############
283
- ##############################################
284
335
 
285
- def draw(self, *args, **kwargs): # from draw() comes directly the functions scatter() and plot()
336
+ ##############################################
337
+ ########## Draw() Function #############
338
+ ##############################################
339
+
340
+ def draw(
341
+ self, *args, **kwargs
342
+ ): # from draw() comes directly the functions scatter() and plot()
286
343
  self.add_xside(kwargs.get("xside"))
287
344
  self.add_yside(kwargs.get("yside"))
288
345
  self.add_data(*args)
@@ -293,16 +350,16 @@ class monitor_class(build_class):
293
350
  self.add_fillx(kwargs.get("fillx"))
294
351
  self.add_filly(kwargs.get("filly"))
295
352
  self.add_label(kwargs.get("label"))
296
-
297
- ##############################################
298
- ####### Draw() Called Functions ########
299
- ##############################################
300
353
 
301
- def add_xside(self, xside = None):
354
+ ##############################################
355
+ ####### Draw() Called Functions ########
356
+ ##############################################
357
+
358
+ def add_xside(self, xside=None):
302
359
  xside = self.correct_xside(xside)
303
360
  self.xside.append(xside)
304
361
 
305
- def add_yside(self, yside = None):
362
+ def add_yside(self, yside=None):
306
363
  yside = self.correct_yside(yside)
307
364
  self.yside.append(yside)
308
365
 
@@ -317,62 +374,80 @@ class monitor_class(build_class):
317
374
  self.signals += 1
318
375
 
319
376
  def add_lines(self, lines):
320
- lines = self.default.lines if lines is None else bool(lines)
377
+ lines = self.default.lines if lines is None else bool(lines)
321
378
  self.lines.append(lines)
322
-
323
- def add_markers(self, marker = None):
379
+
380
+ def add_markers(self, marker=None):
324
381
  single_marker = isinstance(marker, str) or marker is None
325
- marker = self.check_marker(marker) if single_marker else list(map(self.check_marker, marker))
382
+ marker = (
383
+ self.check_marker(marker)
384
+ if single_marker
385
+ else list(map(self.check_marker, marker))
386
+ )
326
387
  length = len(self.x[-1])
327
388
  marker = ut.to_list(marker, length)
328
389
  self.marker.append(marker)
329
390
 
330
- def add_colors(self, color = None):
331
- list_color = isinstance(color, list)
332
- color = list(map(self.check_color, color)) if list_color else self.check_color(color)
391
+ def add_colors(self, color=None):
392
+ list_color = isinstance(color, list)
393
+ color = (
394
+ list(map(self.check_color, color))
395
+ if list_color
396
+ else self.check_color(color)
397
+ )
333
398
  length = len(self.x[-1])
334
- self.past_colors = self.past_colors + [color] if color not in self.past_colors else self.past_colors
399
+ self.past_colors = (
400
+ self.past_colors + [color]
401
+ if color not in self.past_colors
402
+ else self.past_colors
403
+ )
335
404
  color = ut.to_list(color, length)
336
405
  self.color.append(color)
337
406
 
338
- def add_styles(self, style = None):
407
+ def add_styles(self, style=None):
339
408
  single_style = isinstance(style, str) or style is None
340
- style = self.check_style(style) if single_style else list(map(self.check_style, style))
409
+ style = (
410
+ self.check_style(style)
411
+ if single_style
412
+ else list(map(self.check_style, style))
413
+ )
341
414
  length = len(self.x[-1])
342
415
  style = ut.to_list(style, length)
343
416
  self.style.append(style)
344
417
 
345
- def add_fillx(self, fillx = None):
418
+ def add_fillx(self, fillx=None):
346
419
  fillx = self.check_fill(fillx)
347
420
  self.fillx.append(fillx)
348
421
 
349
- def add_filly(self, filly = None):
422
+ def add_filly(self, filly=None):
350
423
  filly = self.check_fill(filly)
351
424
  self.filly.append(filly)
352
425
 
353
- def add_label(self, label = None):
426
+ def add_label(self, label=None):
354
427
  spaces = ut.only_spaces(label)
355
- label = self.default.label if label is None or spaces else str(label).strip() # strip to remove spaces before and after
428
+ label = (
429
+ self.default.label if label is None or spaces else str(label).strip()
430
+ ) # strip to remove spaces before and after
356
431
  self.label.append(label)
357
- #figure.subplot.label_show.append(default.label_show)
358
-
359
- ##############################################
360
- ###### Draw() Functions Utilities #######
361
- ##############################################
432
+ # figure.subplot.label_show.append(default.label_show)
433
+
434
+ ##############################################
435
+ ###### Draw() Functions Utilities #######
436
+ ##############################################
362
437
 
363
438
  def to_time(self, data):
364
- dates = any([isinstance(el, str) for el in data])
439
+ dates = any(isinstance(el, str) for el in data)
365
440
  data = self.date.strings_to_time(data) if dates else data
366
441
  return data, dates
367
-
368
- def check_marker(self, marker = None):
442
+
443
+ def check_marker(self, marker=None):
369
444
  marker = None if marker is None else str(marker)
370
445
  marker = self.default.marker if marker is None else marker
371
- marker = ut.marker_codes[marker] if marker in ut.marker_codes else marker
446
+ marker = ut.marker_codes.get(marker, marker)
372
447
  marker = marker if marker in ut.hd_symbols else marker[0]
373
448
  return marker
374
449
 
375
- def check_color(self, color = None):
450
+ def check_color(self, color=None):
376
451
  color = color if ut.is_color(color) else None
377
452
  color = self.next_color() if color is None else color
378
453
  return color
@@ -382,145 +457,311 @@ class monitor_class(build_class):
382
457
  color = color[0] if len(color) > 0 else self.color_sequence[0]
383
458
  return color
384
459
 
385
- def check_style(self, style = None):
460
+ def check_style(self, style=None):
386
461
  style = None if style is None else str(style)
387
462
  style = style if ut.is_style(style) else ut.no_color
388
463
  return style
389
464
 
390
- def check_fill(self, fill = None):
465
+ def check_fill(self, fill=None):
391
466
  fill = self.default.fill if fill is None else fill
392
- fill = False if isinstance(fill, str) and fill != self.default.fill_internal else fill
467
+ fill = (
468
+ False
469
+ if isinstance(fill, str) and fill != self.default.fill_internal
470
+ else fill
471
+ )
393
472
  fill = 0 if fill is True else fill
394
473
  return fill
395
474
 
396
- ##############################################
397
- ###### Other Plotting Functions ########
398
- ##############################################
399
-
400
- def draw_bar(self, *args, marker = None, color = None, fill = None, width = None, orientation = None, minimum = None, offset = None, reset_ticks = None, xside = None, yside = None, label = None):
475
+ ##############################################
476
+ ###### Other Plotting Functions ########
477
+ ##############################################
478
+
479
+ def draw_bar(
480
+ self,
481
+ *args,
482
+ marker=None,
483
+ color=None,
484
+ fill=None,
485
+ width=None,
486
+ orientation=None,
487
+ minimum=None,
488
+ offset=None,
489
+ reset_ticks=None,
490
+ xside=None,
491
+ yside=None,
492
+ label=None,
493
+ ):
401
494
  x, y = ut.set_data(*args)
402
495
  marker = self.default.bar_marker if marker is None else marker
403
496
  fill = self.default.bar_fill if fill is None else fill
404
497
  width = self.default.bar_width if width is None else width
405
498
  width = 1 if width > 1 else 0 if width < 0 else width
406
499
  orientation = self.check_orientation(orientation, 1)
407
- minimum = 0 if minimum is None else minimum
500
+ minimum = 0 if minimum is None else minimum
408
501
  offset = 0 if offset is None else offset
409
502
  reset_ticks = True if reset_ticks is None else reset_ticks
410
503
 
411
- x_string = any([type(el) == str for el in x]) # if x are strings
412
- l = len(x)
413
- xticks = range(1, l + 1) if x_string else x
504
+ x_string = any(isinstance(el, str) for el in x) # if x are strings
505
+ x_length = len(x)
506
+ xticks = range(1, x_length + 1) if x_string else x
414
507
  xlabels = x if x_string else map(str, x)
415
508
  x = xticks if x_string else x
416
509
  x = [el + offset for el in x]
417
510
  xbar, ybar = ut.bars(x, y, width, minimum)
418
- xbar, ybar = [xbar, ybar] if orientation[0] == 'v' else [ybar, xbar]
419
- (self.set_xticks(xticks, xlabels, xside) if orientation[0] == 'v' else self.set_yticks(xticks, xlabels, yside)) if reset_ticks else None
420
-
421
-
422
- firstbar = min([b for b in range(len(x)) if ybar[b][1] != 0], default = 0) # finds the position of the first non zero bar
511
+ xbar, ybar = [xbar, ybar] if orientation[0] == "v" else [ybar, xbar]
512
+ (
513
+ (
514
+ self.set_xticks(xticks, xlabels, xside)
515
+ if orientation[0] == "v"
516
+ else self.set_yticks(xticks, xlabels, yside)
517
+ )
518
+ if reset_ticks
519
+ else None
520
+ )
521
+
522
+ firstbar = min(
523
+ [b for b in range(len(x)) if ybar[b][1] != 0], default=0
524
+ ) # finds the position of the first non zero bar
423
525
 
424
526
  for b in range(len(x)):
425
- xb = xbar[b]; yb = ybar[b]
527
+ xb = xbar[b]
528
+ yb = ybar[b]
426
529
  plot_label = label if b == firstbar else None
427
530
  plot_color = color if b == 0 else self.color[-1]
428
- nobar = (yb[1] == 0 and orientation[0] == 'v') or (xb[1] == 0 and orientation[0] == 'h')
531
+ nobar = (yb[1] == 0 and orientation[0] == "v") or (
532
+ xb[1] == 0 and orientation[0] == "h"
533
+ )
429
534
  plot_marker = " " if nobar else marker
430
535
  plot_color = color if b == 0 else self.color[-1][-1]
431
- self.draw_rectangle(xb, yb,
432
- xside = xside,
433
- yside = yside,
434
- lines = True,
435
- marker = plot_marker,
436
- color = plot_color,
437
- fill = fill,
438
- label = plot_label)
439
-
440
- def draw_multiple_bar(self, *args, marker = None, color = None, fill = None, width = None, orientation = None, minimum = None, offset = None, reset_ticks = None, xside = None, yside = None, labels = None):
441
- x, Y = ut.set_multiple_bar_data(*args)
442
- ly = len(Y)
536
+ self.draw_rectangle(
537
+ xb,
538
+ yb,
539
+ xside=xside,
540
+ yside=yside,
541
+ lines=True,
542
+ marker=plot_marker,
543
+ color=plot_color,
544
+ fill=fill,
545
+ label=plot_label,
546
+ )
547
+
548
+ def draw_multiple_bar(
549
+ self,
550
+ *args,
551
+ marker=None,
552
+ color=None,
553
+ fill=None,
554
+ width=None,
555
+ orientation=None,
556
+ minimum=None,
557
+ offset=None,
558
+ reset_ticks=None,
559
+ xside=None,
560
+ yside=None,
561
+ labels=None,
562
+ ):
563
+ x, y_values = ut.set_multiple_bar_data(*args)
564
+ ly = len(y_values)
443
565
  width = self.default.bar_width if width is None else width
444
- marker = [marker] * ly if marker is None or type(marker) != list else marker
566
+ marker = [marker] * ly if marker is None or not isinstance(marker, list) else marker
445
567
  color = [color] * ly if color is None else color
446
568
  labels = [labels] * ly if labels is None else labels
447
569
  width = width / ly if ly != 0 else 0
448
- offset = ut.linspace(-1 / 2 + 1 / (2 * ly), 1 / 2 - 1 / (2 * ly), ly) if ly != 0 else []
449
-
570
+ offset = (
571
+ ut.linspace(-1 / 2 + 1 / (2 * ly), 1 / 2 - 1 / (2 * ly), ly)
572
+ if ly != 0
573
+ else []
574
+ )
575
+
450
576
  for i in range(ly):
451
- self.draw_bar(x, Y[i],
452
- marker = marker[i],
453
- color = color[i],
454
- fill = fill,
455
- width = width,
456
- orientation = orientation,
457
- minimum = minimum,
458
- offset = offset[i],
459
- xside = xside,
460
- yside = yside,
461
- label = labels[i],
462
- reset_ticks = reset_ticks)
463
-
464
- def draw_stacked_bar(self, *args, marker = None, color = None, fill = None, width = None, orientation = None, minimum = None, offset = None, reset_ticks = None, xside = None, yside = None, labels = None):
465
- x, Y = ut.set_multiple_bar_data(*args)
466
- ly = len(Y)
467
- marker = [marker] * ly if marker is None or type(marker) != list else marker
577
+ self.draw_bar(
578
+ x,
579
+ y_values[i],
580
+ marker=marker[i],
581
+ color=color[i],
582
+ fill=fill,
583
+ width=width,
584
+ orientation=orientation,
585
+ minimum=minimum,
586
+ offset=offset[i],
587
+ xside=xside,
588
+ yside=yside,
589
+ label=labels[i],
590
+ reset_ticks=reset_ticks,
591
+ )
592
+
593
+ def draw_stacked_bar(
594
+ self,
595
+ *args,
596
+ marker=None,
597
+ color=None,
598
+ fill=None,
599
+ width=None,
600
+ orientation=None,
601
+ minimum=None,
602
+ offset=None,
603
+ reset_ticks=None,
604
+ xside=None,
605
+ yside=None,
606
+ labels=None,
607
+ ):
608
+ x, y_values = ut.set_multiple_bar_data(*args)
609
+ ly = len(y_values)
610
+ marker = [marker] * ly if marker is None or not isinstance(marker, list) else marker
468
611
  color = [color] * ly if color is None else color
469
612
  labels = [labels] * ly if labels is None else labels
470
- Y = ut.transpose([ut.cumsum(el) for el in ut.transpose(Y)])
613
+ y_values = ut.transpose([ut.cumsum(el) for el in ut.transpose(y_values)])
471
614
  for i in range(ly - 1, -1, -1):
472
- self.draw_bar(x, Y[i],
473
- xside = xside,
474
- yside = yside,
475
- marker = marker[i],
476
- color = color[i],
477
- fill = fill,
478
- width = width,
479
- orientation = orientation,
480
- label = labels[i],
481
- minimum = minimum,
482
- reset_ticks = reset_ticks)
483
-
484
- def draw_hist(self, data, bins = None, marker = None, color = None, fill = None, norm = None, width = None, orientation = None, minimum = None, xside = None, yside = None, label = None):
615
+ self.draw_bar(
616
+ x,
617
+ y_values[i],
618
+ xside=xside,
619
+ yside=yside,
620
+ marker=marker[i],
621
+ color=color[i],
622
+ fill=fill,
623
+ width=width,
624
+ orientation=orientation,
625
+ label=labels[i],
626
+ minimum=minimum,
627
+ reset_ticks=reset_ticks,
628
+ )
629
+
630
+ def draw_hist(
631
+ self,
632
+ data,
633
+ bins=None,
634
+ marker=None,
635
+ color=None,
636
+ fill=None,
637
+ norm=None,
638
+ width=None,
639
+ orientation=None,
640
+ minimum=None,
641
+ xside=None,
642
+ yside=None,
643
+ label=None,
644
+ ):
485
645
  bins = self.default.hist_bins if bins is None else bins
486
646
  norm = False if norm is None else norm
487
647
  x, y = ut.hist_data(data, bins, norm)
488
- self.draw_bar(x, y,
489
- xside = xside,
490
- yside = yside,
491
- marker = marker,
492
- color = color,
493
- fill = fill,
494
- width = width,
495
- orientation = orientation,
496
- label = label,
497
- minimum = None,
498
- reset_ticks = False)
499
-
500
- def draw_candlestick(self, dates, data, colors = None, orientation = None, xside = None, yside = None, label = None):
648
+ self.draw_bar(
649
+ x,
650
+ y,
651
+ xside=xside,
652
+ yside=yside,
653
+ marker=marker,
654
+ color=color,
655
+ fill=fill,
656
+ width=width,
657
+ orientation=orientation,
658
+ label=label,
659
+ minimum=None,
660
+ reset_ticks=False,
661
+ )
662
+
663
+ def draw_candlestick(
664
+ self,
665
+ dates,
666
+ data,
667
+ colors=None,
668
+ orientation=None,
669
+ xside=None,
670
+ yside=None,
671
+ label=None,
672
+ ):
501
673
  orientation = self.check_orientation(orientation, 1)
502
- markers = ['sd', '', ''] #if markers is None else markers
503
- colors = ['green', 'red'] if colors is None else colors
504
- x = []; y = []; color = []
674
+ markers = ["sd", "", ""] # if markers is None else markers
675
+ colors = ["green", "red"] if colors is None else colors
676
+ color = []
505
677
  ln = len(dates)
506
- data = {"Open": [], "Close": [], "High": [], "Low": []} if len(data) == 0 else data
507
- Open = data["Open"]; Close = data["Close"]; High = data["High"]; Low = data["Low"]
678
+ data = (
679
+ {"Open": [], "Close": [], "High": [], "Low": []} if len(data) == 0 else data
680
+ )
681
+ open_vals = data["Open"]
682
+ close_vals = data["Close"]
683
+ high_vals = data["High"]
684
+ low_vals = data["Low"]
508
685
  for i in range(ln):
509
686
  d = dates[i]
510
- o, c, h, l = Open[i], Close[i], High[i], Low[i]
687
+ o, c, h, low_val = open_vals[i], close_vals[i], high_vals[i], low_vals[i]
511
688
  color = colors[0] if c > o else colors[1]
512
- m, M = min(o, c), max(o, c)
689
+ m, max_val = min(o, c), max(o, c)
513
690
  lab = label if i == 0 else None
514
- if orientation in ['v', 'vertical']:
515
- self.draw([d, d], [M, h], xside = xside, yside = yside, color = color, marker = markers[1], lines = True)
516
- self.draw([d, d], [l, m], xside = xside, yside = yside, color = color, marker = markers[1], lines = True)
517
- self.draw([d, d], [m, M], xside = xside, yside = yside, color = color, marker = markers[0], lines = True, label = lab)
518
- elif orientation in ['h', 'horizontal']:
519
- self.draw([M, h], [d, d], xside = xside, yside = yside, color = color, marker = markers[2], lines = True)
520
- self.draw([l, m], [d, d], xside = xside, yside = yside, color = color, marker = markers[2], lines = True)
521
- self.draw([m, M], [d, d], xside = xside, yside = yside, color = color, marker = markers[0], lines = True, label = lab)
522
-
523
- def draw_box(self, *args, xside = None, yside = None, orientation = None, colors = None, label = None, fill = None, width = None, minimum = None, offset = None, reset_ticks = None, quintuples = None):
691
+ if orientation in ["v", "vertical"]:
692
+ self.draw(
693
+ [d, d],
694
+ [max_val, h],
695
+ xside=xside,
696
+ yside=yside,
697
+ color=color,
698
+ marker=markers[1],
699
+ lines=True,
700
+ )
701
+ self.draw(
702
+ [d, d],
703
+ [low_val, m],
704
+ xside=xside,
705
+ yside=yside,
706
+ color=color,
707
+ marker=markers[1],
708
+ lines=True,
709
+ )
710
+ self.draw(
711
+ [d, d],
712
+ [m, max_val],
713
+ xside=xside,
714
+ yside=yside,
715
+ color=color,
716
+ marker=markers[0],
717
+ lines=True,
718
+ label=lab,
719
+ )
720
+ elif orientation in ["h", "horizontal"]:
721
+ self.draw(
722
+ [max_val, h],
723
+ [d, d],
724
+ xside=xside,
725
+ yside=yside,
726
+ color=color,
727
+ marker=markers[2],
728
+ lines=True,
729
+ )
730
+ self.draw(
731
+ [low_val, m],
732
+ [d, d],
733
+ xside=xside,
734
+ yside=yside,
735
+ color=color,
736
+ marker=markers[2],
737
+ lines=True,
738
+ )
739
+ self.draw(
740
+ [m, max_val],
741
+ [d, d],
742
+ xside=xside,
743
+ yside=yside,
744
+ color=color,
745
+ marker=markers[0],
746
+ lines=True,
747
+ label=lab,
748
+ )
749
+
750
+ def draw_box(
751
+ self,
752
+ *args,
753
+ xside=None,
754
+ yside=None,
755
+ orientation=None,
756
+ colors=None,
757
+ label=None,
758
+ fill=None,
759
+ width=None,
760
+ minimum=None,
761
+ offset=None,
762
+ reset_ticks=None,
763
+ quintuples=None,
764
+ ):
524
765
  x, y = ut.set_data(*args)
525
766
  fill = self.default.bar_fill if fill is None else fill
526
767
  width = self.default.bar_width if width is None else width
@@ -529,16 +770,24 @@ class monitor_class(build_class):
529
770
  minimum = 0 if minimum is None else minimum
530
771
  offset = 0 if offset is None else offset
531
772
  reset_ticks = True if reset_ticks is None else reset_ticks
532
- colors = ['green', 'red'] if colors is None else colors
773
+ colors = ["green", "red"] if colors is None else colors
533
774
  quintuples = False if quintuples is None else quintuples
534
775
 
535
- x_string = any([type(el) == str for el in x]) # if x are strings
536
- l = len(x)
537
- xticks = range(1, l + 1) if x_string else x
776
+ x_string = any(isinstance(el, str) for el in x) # if x are strings
777
+ x_length = len(x)
778
+ xticks = range(1, x_length + 1) if x_string else x
538
779
  xlabels = x if x_string else map(str, x)
539
780
  x = xticks if x_string else x
540
781
  x = [el + offset for el in x]
541
- (self.set_xticks(xticks, xlabels, xside) if orientation[0] == 'v' else self.set_yticks(xticks, xlabels, yside)) if reset_ticks else None
782
+ (
783
+ (
784
+ self.set_xticks(xticks, xlabels, xside)
785
+ if orientation[0] == "v"
786
+ else self.set_yticks(xticks, xlabels, yside)
787
+ )
788
+ if reset_ticks
789
+ else None
790
+ )
542
791
  if quintuples:
543
792
  # todo: check y is aligned.
544
793
  _, _, _, _, _, c, xbar = ut.box(x, y, width, minimum)
@@ -551,87 +800,212 @@ class monitor_class(build_class):
551
800
  min_.append(d[4])
552
801
  else:
553
802
  q1, q2, q3, max_, min_, c, xbar = ut.box(x, y, width, minimum)
554
- markers = ['sd', '', ''] #if markers is None else markers
555
-
556
- for i in range(l):
803
+ markers = ["sd", "", ""] # if markers is None else markers
804
+
805
+ for i in range(x_length):
557
806
  lab = label if i == 0 else None
558
807
  color = colors[0]
559
808
  mcolor = colors[1]
560
- d, l, h, m, E, M = c[i], min_[i], max_[i], q1[i], q2[i], q3[i]
561
- Ew = (M - m) / 30
562
- if orientation in ['v', 'vertical']:
563
- self.draw([d, d], [M, h], xside = xside, yside = yside, color = color, marker = markers[1], lines = True)
564
- self.draw([d, d], [l, m], xside = xside, yside = yside, color = color, marker = markers[1], lines = True)
565
- self.draw_rectangle(xbar[i], [m, M], xside = xside, yside = yside,
566
- lines = True, color = color, fill = fill, marker = markers[0], label = lab)
567
- self.draw_rectangle(xbar[i], [E, E], xside = xside, yside = yside,
568
- lines = True, color = mcolor, fill = fill, marker = markers[2])
569
- #self.draw([d, d], [m, M], xside = xside, yside = yside, color = color, marker = markers[0], lines = True, label = lab)
570
- #self.draw(xbar[i], [E, E], xside = xside, yside = yside, color = mcolor, marker = markers[0], lines = False)
571
- elif orientation in ['h', 'horizontal']:
572
- self.draw([M, h], [d, d], xside = xside, yside = yside, color = color, marker = markers[2], lines = True)
573
- self.draw([l, m], [d, d], xside = xside, yside = yside, color = color, marker = markers[2], lines = True)
574
- self.draw_rectangle([m, M], xbar[i], xside = xside, yside = yside,
575
- lines = True, color = color, fill = fill, marker = markers[0], label = lab)
576
- self.draw_rectangle([E, E], xbar[i], xside = xside, yside = yside,
577
- lines = True, color = mcolor, fill = fill, marker = markers[1])
578
- #self.draw([m, M], [d, d], xside = xside, yside = yside, color = color, marker = markers[0], lines = True, label = lab)
579
- #self.draw([E, E], [d, d], xside = xside, yside = yside, color = 'red', marker = markers[0], lines = True)
580
-
581
- ##############################################
582
- ########### Plotting Tools #############
583
- ##############################################
584
-
585
- def draw_error(self, *args, xerr = None, yerr = None, color = None, xside = None, yside = None, label = None):
809
+ d, low_val, h, m, median, max_val = c[i], min_[i], max_[i], q1[i], q2[i], q3[i]
810
+ (max_val - m) / 30
811
+ if orientation in ["v", "vertical"]:
812
+ self.draw(
813
+ [d, d],
814
+ [max_val, h],
815
+ xside=xside,
816
+ yside=yside,
817
+ color=color,
818
+ marker=markers[1],
819
+ lines=True,
820
+ )
821
+ self.draw(
822
+ [d, d],
823
+ [low_val, m],
824
+ xside=xside,
825
+ yside=yside,
826
+ color=color,
827
+ marker=markers[1],
828
+ lines=True,
829
+ )
830
+ self.draw_rectangle(
831
+ xbar[i],
832
+ [m, max_val],
833
+ xside=xside,
834
+ yside=yside,
835
+ lines=True,
836
+ color=color,
837
+ fill=fill,
838
+ marker=markers[0],
839
+ label=lab,
840
+ )
841
+ self.draw_rectangle(
842
+ xbar[i],
843
+ [median, median],
844
+ xside=xside,
845
+ yside=yside,
846
+ lines=True,
847
+ color=mcolor,
848
+ fill=fill,
849
+ marker=markers[2],
850
+ )
851
+ # self.draw([d, d], [m, M], xside = xside, yside = yside, color = color, marker = markers[0], lines = True, label = lab)
852
+ # self.draw(xbar[i], [E, E], xside = xside, yside = yside, color = mcolor, marker = markers[0], lines = False)
853
+ elif orientation in ["h", "horizontal"]:
854
+ self.draw(
855
+ [max_val, h],
856
+ [d, d],
857
+ xside=xside,
858
+ yside=yside,
859
+ color=color,
860
+ marker=markers[2],
861
+ lines=True,
862
+ )
863
+ self.draw(
864
+ [low_val, m],
865
+ [d, d],
866
+ xside=xside,
867
+ yside=yside,
868
+ color=color,
869
+ marker=markers[2],
870
+ lines=True,
871
+ )
872
+ self.draw_rectangle(
873
+ [m, max_val],
874
+ xbar[i],
875
+ xside=xside,
876
+ yside=yside,
877
+ lines=True,
878
+ color=color,
879
+ fill=fill,
880
+ marker=markers[0],
881
+ label=lab,
882
+ )
883
+ self.draw_rectangle(
884
+ [median, median],
885
+ xbar[i],
886
+ xside=xside,
887
+ yside=yside,
888
+ lines=True,
889
+ color=mcolor,
890
+ fill=fill,
891
+ marker=markers[1],
892
+ )
893
+ # self.draw([m, M], [d, d], xside = xside, yside = yside, color = color, marker = markers[0], lines = True, label = lab)
894
+ # self.draw([E, E], [d, d], xside = xside, yside = yside, color = 'red', marker = markers[0], lines = True)
895
+
896
+ ##############################################
897
+ ########### Plotting Tools #############
898
+ ##############################################
899
+
900
+ def draw_error(
901
+ self,
902
+ *args,
903
+ xerr=None,
904
+ yerr=None,
905
+ color=None,
906
+ xside=None,
907
+ yside=None,
908
+ label=None,
909
+ ):
586
910
  x, y = ut.set_data(*args)
587
- l = len(x)
588
- xerr = [0] * l if xerr is None else xerr
589
- yerr = [0] * l if yerr is None else yerr
590
- for i in range(l):
911
+ x_length = len(x)
912
+ xerr = [0] * x_length if xerr is None else xerr
913
+ yerr = [0] * x_length if yerr is None else yerr
914
+ for i in range(x_length):
591
915
  col = self.color[-1][-1] if i > 0 else color
592
- self.draw([x[i], x[i]], [y[i] - yerr[i] / 2, y[i] + yerr[i] / 2], xside = xside, yside = yside, marker = "│", color = col, lines = True)
916
+ self.draw(
917
+ [x[i], x[i]],
918
+ [y[i] - yerr[i] / 2, y[i] + yerr[i] / 2],
919
+ xside=xside,
920
+ yside=yside,
921
+ marker="│",
922
+ color=col,
923
+ lines=True,
924
+ )
593
925
  col = self.color[-1][-1] if i == 0 else col
594
- self.draw([x[i] - xerr[i] / 2, x[i] + xerr[i] / 2], [y[i], y[i]], xside = xside, yside = yside, marker = "─", color = col, lines = True)
595
- self.draw([x[i]], [y[i]], xside = xside, yside = yside, marker = "┼", color = col, lines = True)
596
-
597
- def draw_event_plot(self, data, marker = None, color = None, orientation = None, side = None):
926
+ self.draw(
927
+ [x[i] - xerr[i] / 2, x[i] + xerr[i] / 2],
928
+ [y[i], y[i]],
929
+ xside=xside,
930
+ yside=yside,
931
+ marker="─",
932
+ color=col,
933
+ lines=True,
934
+ )
935
+ self.draw(
936
+ [x[i]],
937
+ [y[i]],
938
+ xside=xside,
939
+ yside=yside,
940
+ marker="┼",
941
+ color=col,
942
+ lines=True,
943
+ )
944
+
945
+ def draw_event_plot(
946
+ self, data, marker=None, color=None, orientation=None, side=None
947
+ ):
598
948
  x, y = data, [1.1] * len(data)
599
949
  orientation = self.check_orientation(orientation, 1)
600
- if orientation in ['v', 'vertical']:
601
- self.draw(x, y, xside = side, marker = marker, color = color, fillx = True)
950
+ if orientation in ["v", "vertical"]:
951
+ self.draw(x, y, xside=side, marker=marker, color=color, fillx=True)
602
952
  self.set_ylim(0, 1)
603
953
  self.set_yfrequency(0)
604
954
  else:
605
- self.draw(y, x, yside = side, marker = marker, color = color, filly = True)
955
+ self.draw(y, x, yside=side, marker=marker, color=color, filly=True)
606
956
  self.set_xlim(0, 1)
607
957
  self.set_xfrequency(0)
608
958
 
609
- def draw_vertical_line(self, coordinate, color = None, xside = None):
610
- coordinate = self.date.string_to_time(coordinate) if isinstance(coordinate, str) else coordinate
959
+ def draw_vertical_line(self, coordinate, color=None, xside=None):
960
+ coordinate = (
961
+ self.date.string_to_time(coordinate)
962
+ if isinstance(coordinate, str)
963
+ else coordinate
964
+ )
611
965
  pos = self.xside_to_pos(xside)
612
966
  self.vcoord[pos].append(coordinate)
613
967
  color = self.ticks_color if color is None else color
614
968
  self.vcolors[pos].append(self.check_color(color))
615
969
 
616
- def draw_horizontal_line(self, coordinate, color = None, yside = None):
617
- coordinate = self.date.string_to_time(coordinate) if isinstance(coordinate, str) else coordinate
970
+ def draw_horizontal_line(self, coordinate, color=None, yside=None):
971
+ coordinate = (
972
+ self.date.string_to_time(coordinate)
973
+ if isinstance(coordinate, str)
974
+ else coordinate
975
+ )
618
976
  pos = self.xside_to_pos(yside)
619
977
  self.hcoord[pos].append(coordinate)
620
978
  color = self.ticks_color if color is None else color
621
979
  self.hcolors[pos].append(self.check_color(color))
622
980
 
623
- def draw_text(self, text, x, y, xside = None, yside = None, color = None, background = None, style = None, orientation = None, alignment = None):
981
+ def draw_text(
982
+ self,
983
+ text,
984
+ x,
985
+ y,
986
+ xside=None,
987
+ yside=None,
988
+ color=None,
989
+ background=None,
990
+ style=None,
991
+ orientation=None,
992
+ alignment=None,
993
+ ):
624
994
  orientation = self.check_orientation(orientation)
625
995
  text = text if orientation is self.default.orientation[0] else text[::-1]
626
996
  self.text.append(str(text))
627
997
  x = self.date.string_to_time(x) if isinstance(x, str) else x
628
998
  y = self.date.string_to_time(y) if isinstance(y, str) else y
629
999
  self.tx.append(x)
630
- self.ty.append(y)
1000
+ self.ty.append(y)
631
1001
  self.txside.append(self.correct_xside(xside))
632
1002
  self.tyside.append(self.correct_yside(yside))
633
1003
  color = self.next_color() if color is None or not ut.is_color(color) else color
634
- background = self.canvas_color if background is None or not ut.is_color(background) else background
1004
+ background = (
1005
+ self.canvas_color
1006
+ if background is None or not ut.is_color(background)
1007
+ else background
1008
+ )
635
1009
  self.tfull.append(color)
636
1010
  self.tback.append(background)
637
1011
  self.tstyle.append(self.check_style(style))
@@ -639,127 +1013,200 @@ class monitor_class(build_class):
639
1013
  self.torien.append(orientation)
640
1014
  self.talign.append(alignment)
641
1015
 
642
- def draw_rectangle(self, x = None, y = None, marker = None, color = None, lines = None, fill = None, reset_lim = False, xside = None, yside = None, label = None):
643
- x = [0, 1] if x is None or len(x) < 2 else x
644
- y = [0, 1] if y is None or len(y) < 2 else y
1016
+ def draw_rectangle(
1017
+ self,
1018
+ x=None,
1019
+ y=None,
1020
+ marker=None,
1021
+ color=None,
1022
+ lines=None,
1023
+ fill=None,
1024
+ reset_lim=False,
1025
+ xside=None,
1026
+ yside=None,
1027
+ label=None,
1028
+ ):
1029
+ x = [0, 1] if x is None or len(x) < 2 else x
1030
+ y = [0, 1] if y is None or len(y) < 2 else y
645
1031
  xpos = self.xside_to_pos(xside)
646
- ypos = self.yside_to_pos(yside)
1032
+ self.yside_to_pos(yside)
647
1033
  lines = True if lines is None else lines
648
1034
  fill = False if fill is None else fill
649
- xm = min(x); xM = max(x);
650
- ym = min(y); yM = max(y);
651
- dx = abs(xM - xm); dy = abs(yM - ym);
1035
+ xm = min(x)
1036
+ x_max = max(x)
1037
+ ym = min(y)
1038
+ y_max = max(y)
1039
+ dx = abs(x_max - xm)
1040
+ dy = abs(y_max - ym)
652
1041
  if reset_lim:
653
- self.xlim[xpos] = [xm - 0.5 * dx, xM + 0.5 * dx]
654
- self.ylim[xpos] = [ym - 0.5 * dy, yM + 0.5 * dy]
655
- x, y = [xm, xm, xM, xM, xm], [ym, yM, yM, ym, ym]
656
- self.draw(x, y,
657
- xside = xside,
658
- yside = yside,
659
- lines = True if fill else lines,
660
- marker = marker,
661
- color = color,
662
- fillx = "internal" if fill else False,
663
- filly = False,
664
- label = label)
665
-
666
- def draw_polygon(self, x = None, y = None, radius = None, sides = None, marker = None, color = None, lines = None, fill = None, reset_lim = False, xside = None, yside = None, label = None):
1042
+ self.xlim[xpos] = [xm - 0.5 * dx, x_max + 0.5 * dx]
1043
+ self.ylim[xpos] = [ym - 0.5 * dy, y_max + 0.5 * dy]
1044
+ x, y = [xm, xm, x_max, x_max, xm], [ym, y_max, y_max, ym, ym]
1045
+ self.draw(
1046
+ x,
1047
+ y,
1048
+ xside=xside,
1049
+ yside=yside,
1050
+ lines=True if fill else lines,
1051
+ marker=marker,
1052
+ color=color,
1053
+ fillx="internal" if fill else False,
1054
+ filly=False,
1055
+ label=label,
1056
+ )
1057
+
1058
+ def draw_polygon(
1059
+ self,
1060
+ x=None,
1061
+ y=None,
1062
+ radius=None,
1063
+ sides=None,
1064
+ marker=None,
1065
+ color=None,
1066
+ lines=None,
1067
+ fill=None,
1068
+ reset_lim=False,
1069
+ xside=None,
1070
+ yside=None,
1071
+ label=None,
1072
+ ):
667
1073
  x = 0 if x is None else x
668
1074
  y = 0 if y is None else y
669
1075
  radius = 1 if radius is None else abs(int(radius))
670
1076
  sides = 3 if sides is None else max(3, int(abs(sides)))
671
1077
  xpos = self.xside_to_pos(xside)
672
- ypos = self.yside_to_pos(yside)
1078
+ self.yside_to_pos(yside)
673
1079
  lines = True if lines is None else lines
674
1080
  fill = False if fill is None else fill
675
-
1081
+
676
1082
  alpha = 2 * math.pi / sides
677
- init = alpha / 2 + math.pi / 2 if sides % 2 == 0 else alpha / 4 * ((-1) ** (sides // 2))# * math.pi #- ((-1) ** (sides)) * alpha / 4
678
- #init = 0 * init
679
- get_point = lambda i: [x + math.cos(alpha * i + init) * radius, y + math.sin(alpha * i + init) * radius]
1083
+ init = (
1084
+ alpha / 2 + math.pi / 2
1085
+ if sides % 2 == 0
1086
+ else alpha / 4 * ((-1) ** (sides // 2))
1087
+ ) # * math.pi #- ((-1) ** (sides)) * alpha / 4
1088
+ # init = 0 * init
1089
+ def get_point(i):
1090
+ return [
1091
+ x + math.cos(alpha * i + init) * radius,
1092
+ y + math.sin(alpha * i + init) * radius,
1093
+ ]
680
1094
  # the rounding is needed so that results like 9.9999 are rounded to 10 and display as same coordinate in the plot, otherwise the floor function will turn 9.999 into 9
681
1095
  points = [get_point(i) for i in range(sides + 1)]
682
1096
  if reset_lim:
683
1097
  self.xlim[xpos] = [x - 1.5 * radius, x + 1.5 * radius]
684
1098
  self.ylim[xpos] = [y - 1.5 * radius, y + 1.5 * radius]
685
- self.draw(*ut.transpose(points),
686
- xside = xside,
687
- yside = yside,
688
- lines = True if fill else lines,
689
- marker = marker,
690
- color = color,
691
- fillx = "internal" if fill else False,
692
- filly = False,
693
- label = label)
694
-
695
- def draw_confusion_matrix(self, actual, predicted, color = None, style = None, labels = None):
1099
+ self.draw(
1100
+ *ut.transpose(points),
1101
+ xside=xside,
1102
+ yside=yside,
1103
+ lines=True if fill else lines,
1104
+ marker=marker,
1105
+ color=color,
1106
+ fillx="internal" if fill else False,
1107
+ filly=False,
1108
+ label=label,
1109
+ )
1110
+
1111
+ def draw_confusion_matrix(
1112
+ self, actual, predicted, color=None, style=None, labels=None
1113
+ ):
696
1114
  color = self.default.cmatrix_color if color is None else self.check_color(color)
697
1115
  style = self.default.cmatrix_style if style is None else self.check_style(style)
698
-
699
- L = len(actual)
1116
+
1117
+ length = len(actual)
700
1118
  n_labels = sorted(ut.no_duplicates(actual))
701
1119
  labels = n_labels if labels is None else list(labels)
702
- l = len(n_labels)
703
-
704
- get_sum = lambda a, p: sum([actual[i] == a and predicted[i] == p for i in range(L)])
705
- cmatrix = [[get_sum(n_labels[r], n_labels[c]) for c in range(l)] for r in range(l)]
706
- cm = ut.join(cmatrix); m, M, t = min(cm), max(cm), sum(cm)
707
-
708
- lm = 253; lM = 80
709
- to_255 = lambda l: round(lm + (lM - lm) * (l - m) / (M - m)) # l=m -> lm; l=M->lM
710
- to_color = lambda l: tuple([to_255(l)] * 3)
711
- to_text = lambda n: str(round(n, 2)) + ' - ' + str(round(100 * n / t, 2)) + '%'
712
- for r in range(l):
713
- for c in range(l):
1120
+ label_count = len(n_labels)
1121
+
1122
+ def get_sum(a, p):
1123
+ return sum(
1124
+ [actual[i] == a and predicted[i] == p for i in range(length)]
1125
+ )
1126
+ cmatrix = [
1127
+ [get_sum(n_labels[r], n_labels[c]) for c in range(label_count)] for r in range(label_count)
1128
+ ]
1129
+ cm = ut.join(cmatrix)
1130
+ m, max_val, t = min(cm), max(cm), sum(cm)
1131
+
1132
+ lm = 253
1133
+ light_max = 80
1134
+ def to_255(light_val):
1135
+ return round(
1136
+ lm + (light_max - lm) * (light_val - m) / (max_val - m)
1137
+ ) # light_val=m -> lm; light_val=max_val->light_max
1138
+ def to_color(light_val):
1139
+ return tuple([to_255(light_val)] * 3)
1140
+ def to_text(n):
1141
+ return str(round(n, 2)) + " - " + str(round(100 * n / t, 2)) + "%"
1142
+ for r in range(label_count):
1143
+ for c in range(label_count):
714
1144
  count = cmatrix[r][c]
715
1145
  col = to_color(count)
716
- self.draw_rectangle([c - 0.5, c + 0.5], [r - 0.5, r + 0.5], color = col, fill = True)
717
- self.draw_text(to_text(count), c, r, color = color, background = col, style = style)
1146
+ self.draw_rectangle(
1147
+ [c - 0.5, c + 0.5], [r - 0.5, r + 0.5], color=col, fill=True
1148
+ )
1149
+ self.draw_text(
1150
+ to_text(count), c, r, color=color, background=col, style=style
1151
+ )
718
1152
 
719
1153
  self.set_yreverse(True)
720
1154
  self.set_xticks(n_labels, labels)
721
1155
  self.set_yticks(n_labels, labels)
722
- self.set_ticks_color(color); self.set_ticks_style(style);
723
- self.set_axes_color('default'); self.set_canvas_color('default');
724
- self.set_title('Confusion Matrix')
725
- self.set_xlabel('Predicted')
726
- self.set_ylabel('Actual')
727
-
728
- def draw_indicator(self, value, label = None, color = None, style = None):
1156
+ self.set_ticks_color(color)
1157
+ self.set_ticks_style(style)
1158
+ self.set_axes_color("default")
1159
+ self.set_canvas_color("default")
1160
+ self.set_title("Confusion Matrix")
1161
+ self.set_xlabel("Predicted")
1162
+ self.set_ylabel("Actual")
1163
+
1164
+ def draw_indicator(self, value, label=None, color=None, style=None):
729
1165
  color = self.default.cmatrix_color if color is None else self.check_color(color)
730
1166
  style = self.default.cmatrix_style if style is None else self.check_style(style)
731
1167
 
732
1168
  self.set_title(label)
733
- self.set_ticks_color(color);
734
- self.set_ticks_style(style);
735
- self.set_axes_color('default')
736
- self.set_canvas_color('default')
1169
+ self.set_ticks_color(color)
1170
+ self.set_ticks_style(style)
1171
+ self.set_axes_color("default")
1172
+ self.set_canvas_color("default")
737
1173
  self.set_xfrequency(0)
738
1174
  self.set_yfrequency(0)
739
1175
 
740
- self.draw_text(str(value), 0, 0, color = color, style = style, alignment = 'center')
741
-
742
- ##############################################
743
- ############## 2D Plots ################
744
- ##############################################
745
-
746
- def draw_matrix(self, matrix, marker = None, style = None, fast = False):
747
- matrix = [l.copy() for l in matrix]
748
- marker = [marker] if type(marker) != list else marker
749
- marker = [self.check_marker("sd") if el in ut.join([None, ut.hd_symbols]) else self.check_marker(el) for el in marker]
1176
+ self.draw_text(str(value), 0, 0, color=color, style=style, alignment="center")
1177
+
1178
+ ##############################################
1179
+ ############## 2D Plots ################
1180
+ ##############################################
1181
+
1182
+ def draw_matrix(self, matrix, marker=None, style=None, fast=False):
1183
+ matrix = [row.copy() for row in matrix]
1184
+ marker = [marker] if not isinstance(marker, list) else marker
1185
+ marker = [
1186
+ (
1187
+ self.check_marker("sd")
1188
+ if el in ut.join([None, ut.hd_symbols])
1189
+ else self.check_marker(el)
1190
+ )
1191
+ for el in marker
1192
+ ]
750
1193
  style = ut.no_color if style is None else self.check_style(style)
751
1194
  cols, rows = ut.matrix_size(matrix)
752
1195
  rows = 0 if cols == 0 else rows
753
- matrix = matrix if rows * cols != 0 and ut.is_rgb_color(matrix[0][0]) else ut.turn_gray(matrix)
1196
+ matrix = (
1197
+ matrix
1198
+ if rows * cols != 0 and ut.is_rgb_color(matrix[0][0])
1199
+ else ut.turn_gray(matrix)
1200
+ )
754
1201
  marker = ut.repeat(marker, cols)
755
1202
  if not fast:
756
1203
  for r in range(rows):
757
1204
  xyc = [(c, r, matrix[rows - 1 - r][c]) for c in range(cols)]
758
1205
  x, y, color = ut.transpose(xyc, 3)
759
- self.draw(x, y, marker = marker, color = color, style = style)
1206
+ self.draw(x, y, marker=marker, color=color, style=style)
760
1207
  self.set_canvas_color("black")
761
- self.set_xlabel('column')
762
- self.set_ylabel('row')
1208
+ self.set_xlabel("column")
1209
+ self.set_ylabel("row")
763
1210
  xf, yf = min(self.xfrequency[0], cols), min(self.yfrequency[0], rows)
764
1211
  xt = ut.linspace(0, cols - 1, xf)
765
1212
  xl = ut.get_labels([el + 1 for el in xt])
@@ -767,63 +1214,75 @@ class monitor_class(build_class):
767
1214
  yl = ut.get_labels([rows - el for el in yt])
768
1215
  self.set_xticks(xt, xl)
769
1216
  self.set_yticks(yt, yl)
770
- else: # if fast
1217
+ else: # if fast
771
1218
  for r in range(rows):
772
1219
  for c in range(cols):
773
1220
  ansi = ut.colors_to_ansi(matrix[r][c], style, "black")
774
1221
  matrix[r][c] = ansi + marker[c] + ut.ansi_end
775
- self.matrix.canvas = '\n'.join([''.join(row) for row in matrix])
1222
+ self.matrix.canvas = "\n".join(["".join(row) for row in matrix])
776
1223
  self.fast_plot = True
777
1224
 
778
- def draw_pie(self, labels, values, colors = None, radius = None, show_values = True, show_percentages = True, title = None, show_values_on_slices = False, donut = False, remaining_color = None):
1225
+ def draw_pie(
1226
+ self,
1227
+ labels,
1228
+ values,
1229
+ colors=None,
1230
+ radius=None,
1231
+ show_values=True,
1232
+ show_percentages=True,
1233
+ title=None,
1234
+ show_values_on_slices=False,
1235
+ donut=False,
1236
+ remaining_color=None,
1237
+ ):
779
1238
  """
780
1239
  Draw a pie chart using filled colored segments and a legend.
781
-
1240
+
782
1241
  Args:
783
1242
  donut (bool): If True, creates a doughnut chart with hollow center (inner radius = 1/3 outer radius)
784
1243
  remaining_color (str): If specified, colors the remaining slice with this color instead of leaving it as spaces
785
1244
  """
786
1245
  import math
787
-
1246
+
788
1247
  # Input validation
789
1248
  if len(labels) != len(values):
790
1249
  raise ValueError("Labels and values must have the same length")
791
-
1250
+
792
1251
  # Calculate total and percentages
793
1252
  total = sum(values)
794
1253
  if total == 0:
795
1254
  raise ValueError("Total of values cannot be zero")
796
-
1255
+
797
1256
  percentages = [(value / total) * 100 for value in values]
798
-
1257
+
799
1258
  # Default colors if not provided
800
1259
  if colors is None:
801
- color_cycle = ['red', 'blue', 'green', 'orange', 'magenta', 'cyan', 'white']
1260
+ color_cycle = ["red", "blue", "green", "orange", "magenta", "cyan", "white"]
802
1261
  colors = [color_cycle[i % len(color_cycle)] for i in range(len(labels))]
803
-
1262
+
804
1263
  # Default radius - calculate based on available plot space
805
1264
  if radius is None:
806
- # Get the actual plot area dimensions
1265
+ # Get the actual plot area dimensions
807
1266
  plot_width, plot_height = self.size
808
-
1267
+
809
1268
  # Set radius to half of the smaller dimension minus 4 for border margin
810
1269
  radius = (min(plot_width, plot_height) - 4) / 2.0
811
1270
  radius = max(radius, 3) # Ensure minimum radius of 3
812
-
1271
+
813
1272
  # Center the pie chart
814
1273
  center_x = 0
815
1274
  center_y = 0
816
-
1275
+
817
1276
  # Terminal characters have an aspect ratio of approximately 1.5:1 (height:width)
818
1277
  # To make circles appear circular, we need to adjust the x-axis scaling
819
1278
  aspect_ratio = 1.5
820
-
1279
+
821
1280
  # Remove axes - pie charts don't have them
822
1281
  self.set_xfrequency(0)
823
1282
  self.set_yfrequency(0)
824
- self.set_axes_color('default')
825
- self.set_canvas_color('default')
826
-
1283
+ self.set_axes_color("default")
1284
+ self.set_canvas_color("default")
1285
+
827
1286
  # Collect all points for each segment, then draw each segment in one call
828
1287
  # Use efficient scanning - just slightly beyond the actual pie radius
829
1288
  # For doughnuts, use denser scanning to ensure solid ring
@@ -833,18 +1292,20 @@ class monitor_class(build_class):
833
1292
  else:
834
1293
  scan_radius_x = int(radius * aspect_ratio * 1.2 + 2)
835
1294
  scan_radius_y = int(radius * 1.2 + 2)
836
-
1295
+
837
1296
  # Pre-calculate cumulative angles for segment boundaries
838
1297
  segment_boundaries = []
839
1298
  current_cumulative = 0
840
1299
  for value in values:
841
1300
  slice_angle = (value / total) * 2 * math.pi
842
- segment_boundaries.append((current_cumulative, current_cumulative + slice_angle))
1301
+ segment_boundaries.append(
1302
+ (current_cumulative, current_cumulative + slice_angle)
1303
+ )
843
1304
  current_cumulative += slice_angle
844
-
1305
+
845
1306
  # Collect all points for each segment using sets to avoid duplicates
846
1307
  segment_points = [set() for _ in range(len(labels))] # One set per segment
847
-
1308
+
848
1309
  # Use FLOOD FILL approach - systematically fill every position in concentric circles
849
1310
  # This ensures no gaps by filling from center outward
850
1311
  for y_offset in range(-scan_radius_y, scan_radius_y + 1):
@@ -853,42 +1314,49 @@ class monitor_class(build_class):
853
1314
  # Since terminal chars are ~1.5x taller than wide, compress x coordinate
854
1315
  adjusted_x = x_offset / aspect_ratio
855
1316
  distance = math.sqrt(adjusted_x * adjusted_x + y_offset * y_offset)
856
-
1317
+
857
1318
  # For doughnut inner boundary, use elliptical check to create circular appearance
858
1319
  # The inner boundary should be elliptical in terminal coordinates to appear circular
859
1320
  inner_radius = radius / 3.0 if donut else 0
860
-
1321
+
861
1322
  # Check if point is outside the inner ellipse (for circular appearance)
862
1323
  if donut:
863
1324
  # Create elliptical inner boundary: x^2/a^2 + y^2/b^2 > r^2
864
1325
  # where a = inner_radius * aspect_ratio, b = inner_radius
865
- ellipse_x_term = (x_offset * x_offset) / (inner_radius * aspect_ratio * inner_radius * aspect_ratio)
866
- ellipse_y_term = (y_offset * y_offset) / (inner_radius * inner_radius)
1326
+ ellipse_x_term = (x_offset * x_offset) / (
1327
+ inner_radius * aspect_ratio * inner_radius * aspect_ratio
1328
+ )
1329
+ ellipse_y_term = (y_offset * y_offset) / (
1330
+ inner_radius * inner_radius
1331
+ )
867
1332
  ellipse_value = ellipse_x_term + ellipse_y_term
868
1333
  outside_inner = ellipse_value > 1.0
869
-
1334
+
870
1335
  else:
871
1336
  outside_inner = True
872
-
1337
+
873
1338
  # Use exact radius to stay within plot boundaries
874
1339
  threshold = radius
875
-
1340
+
876
1341
  if distance <= threshold and outside_inner:
877
1342
  # Calculate angle for this position using adjusted coordinates
878
1343
  angle = math.atan2(y_offset, adjusted_x)
879
1344
  if angle < 0:
880
1345
  angle += 2 * math.pi
881
-
1346
+
882
1347
  # Find which segment this position belongs to using robust angle detection
883
1348
  segment_idx = 0
884
1349
  found_segment = False
885
1350
  epsilon = 0.02 # Even larger epsilon for maximum boundary coverage
886
-
1351
+
887
1352
  for i, (start_angle, end_angle) in enumerate(segment_boundaries):
888
1353
  # Handle wraparound case for segments that cross 0 degrees
889
1354
  if end_angle > 2 * math.pi:
890
1355
  wrap_end = end_angle - 2 * math.pi
891
- if angle >= start_angle - epsilon or angle <= wrap_end + epsilon:
1356
+ if (
1357
+ angle >= start_angle - epsilon
1358
+ or angle <= wrap_end + epsilon
1359
+ ):
892
1360
  segment_idx = i
893
1361
  found_segment = True
894
1362
  break
@@ -896,7 +1364,11 @@ class monitor_class(build_class):
896
1364
  # Use very generous boundary detection
897
1365
  # For the last segment, use <= to include the boundary
898
1366
  if i == len(segment_boundaries) - 1:
899
- if start_angle - epsilon <= angle <= end_angle + epsilon:
1367
+ if (
1368
+ start_angle - epsilon
1369
+ <= angle
1370
+ <= end_angle + epsilon
1371
+ ):
900
1372
  segment_idx = i
901
1373
  found_segment = True
902
1374
  break
@@ -905,75 +1377,90 @@ class monitor_class(build_class):
905
1377
  segment_idx = i
906
1378
  found_segment = True
907
1379
  break
908
-
1380
+
909
1381
  # If no segment found (due to floating point precision), assign based on closest angle
910
1382
  if not found_segment:
911
1383
  # Find the segment with the smallest angle distance
912
- min_distance = float('inf')
913
- for i, (start_angle, end_angle) in enumerate(segment_boundaries):
1384
+ min_distance = float("inf")
1385
+ for i, (start_angle, end_angle) in enumerate(
1386
+ segment_boundaries
1387
+ ):
914
1388
  mid_angle = (start_angle + end_angle) / 2
915
1389
  # Handle wraparound for mid angle calculation
916
1390
  if end_angle > 2 * math.pi:
917
- mid_angle = start_angle + ((end_angle - start_angle) / 2)
1391
+ mid_angle = start_angle + (
1392
+ (end_angle - start_angle) / 2
1393
+ )
918
1394
  if mid_angle > 2 * math.pi:
919
1395
  mid_angle -= 2 * math.pi
920
-
1396
+
921
1397
  # Calculate angular distance (accounting for circular nature)
922
1398
  angle_diff = abs(angle - mid_angle)
923
1399
  if angle_diff > math.pi:
924
1400
  angle_diff = 2 * math.pi - angle_diff
925
-
1401
+
926
1402
  if angle_diff < min_distance:
927
1403
  min_distance = angle_diff
928
1404
  segment_idx = i
929
-
1405
+
930
1406
  # Add this exact character position to the appropriate segment
931
- char_x = center_x + x_offset
1407
+ char_x = center_x + x_offset
932
1408
  char_y = center_y + y_offset
933
1409
  segment_points[segment_idx].add((char_x, char_y))
934
-
1410
+
935
1411
  # SECOND PASS: Fill any potential gaps by adding adjacent positions
936
1412
  # This ensures complete coverage by adding neighboring positions to existing points
937
1413
  additional_points = [set() for _ in range(len(labels))]
938
1414
  for segment_idx, points in enumerate(segment_points):
939
- for (x, y) in points:
1415
+ for x, y in points:
940
1416
  # Add neighboring positions to ensure no gaps
941
1417
  for dx in [-1, 0, 1]:
942
1418
  for dy in [-1, 0, 1]:
943
1419
  neighbor_x = x + dx
944
1420
  neighbor_y = y + dy
945
-
1421
+
946
1422
  # Check if this neighbor is within the circular area using same logic as main pass
947
1423
  x_offset = neighbor_x - center_x
948
1424
  y_offset = neighbor_y - center_y
949
- adjusted_x = x_offset / aspect_ratio
950
- neighbor_distance = math.sqrt(adjusted_x * adjusted_x + y_offset * y_offset)
951
-
1425
+ adjusted_x = x_offset / aspect_ratio
1426
+ neighbor_distance = math.sqrt(
1427
+ adjusted_x * adjusted_x + y_offset * y_offset
1428
+ )
1429
+
952
1430
  # Use same boundary checks as main algorithm
953
1431
  inner_radius = radius / 3.0 if donut else 0
954
-
1432
+
955
1433
  # Check if point is outside the inner ellipse (for circular appearance)
956
1434
  if donut:
957
1435
  # Use same elliptical inner boundary as main pass
958
- ellipse_x_term = (x_offset * x_offset) / (inner_radius * aspect_ratio * inner_radius * aspect_ratio)
959
- ellipse_y_term = (y_offset * y_offset) / (inner_radius * inner_radius)
1436
+ ellipse_x_term = (x_offset * x_offset) / (
1437
+ inner_radius
1438
+ * aspect_ratio
1439
+ * inner_radius
1440
+ * aspect_ratio
1441
+ )
1442
+ ellipse_y_term = (y_offset * y_offset) / (
1443
+ inner_radius * inner_radius
1444
+ )
960
1445
  outside_inner = ellipse_x_term + ellipse_y_term > 1.0
961
1446
  else:
962
1447
  outside_inner = True
963
-
1448
+
964
1449
  # Use exact radius to stay within plot boundaries (same as main pass)
965
1450
  threshold = radius
966
-
1451
+
967
1452
  if neighbor_distance <= threshold and outside_inner:
968
1453
  additional_points[segment_idx].add((neighbor_x, neighbor_y))
969
-
1454
+
970
1455
  # Merge additional points with main points
971
1456
  for segment_idx in range(len(labels)):
972
1457
  segment_points[segment_idx].update(additional_points[segment_idx])
973
-
1458
+
974
1459
  # Draw each segment using a different approach - draw filled shapes row by row
975
1460
  # This ensures complete filling without gaps
976
- for segment_idx, (points, color) in enumerate(zip(segment_points, colors)):
1461
+ for _segment_idx, (points, color) in enumerate(
1462
+ zip(segment_points, colors, strict=False)
1463
+ ):
977
1464
  if points: # Only draw if segment has points
978
1465
  # Handle remaining_color for single-value pie charts
979
1466
  if color == "default":
@@ -983,16 +1470,16 @@ class monitor_class(build_class):
983
1470
  else:
984
1471
  # Skip drawing - leave as spaces (current behavior)
985
1472
  continue
986
-
1473
+
987
1474
  points_list = list(points)
988
-
1475
+
989
1476
  # Group points by y-coordinate to draw horizontal filled lines
990
1477
  y_groups = {}
991
1478
  for x, y in points_list:
992
1479
  if y not in y_groups:
993
1480
  y_groups[y] = []
994
1481
  y_groups[y].append(x)
995
-
1482
+
996
1483
  # For doughnut charts, use smart filling that avoids the hollow center
997
1484
  # For regular pie charts, use full horizontal line filling
998
1485
  if donut:
@@ -1000,48 +1487,56 @@ class monitor_class(build_class):
1000
1487
  for y_coord, x_coords in y_groups.items():
1001
1488
  if x_coords:
1002
1489
  x_coords.sort() # Sort x coordinates
1003
-
1490
+
1004
1491
  # Find continuous segments, avoiding the center gap
1005
1492
  fill_x_coords = []
1006
1493
  x_step = 0.5
1007
-
1494
+
1008
1495
  # Determine if this y_coord passes through the hollow center
1009
1496
  y_offset = y_coord - center_y
1010
1497
  center_x_range = []
1011
-
1498
+
1012
1499
  # Calculate the x-range that should be hollow at this y-coordinate
1013
1500
  if abs(y_offset) < inner_radius:
1014
1501
  # This y-line passes through the hollow center
1015
1502
  # Calculate x-bounds of the elliptical hollow area
1016
- ellipse_y_term = (y_offset * y_offset) / (inner_radius * inner_radius)
1503
+ ellipse_y_term = (y_offset * y_offset) / (
1504
+ inner_radius * inner_radius
1505
+ )
1017
1506
  if ellipse_y_term < 1.0:
1018
1507
  ellipse_x_term_needed = 1.0 - ellipse_y_term
1019
- max_x_offset = math.sqrt(ellipse_x_term_needed) * inner_radius * aspect_ratio
1508
+ max_x_offset = (
1509
+ math.sqrt(ellipse_x_term_needed)
1510
+ * inner_radius
1511
+ * aspect_ratio
1512
+ )
1020
1513
  center_x_min = center_x - max_x_offset
1021
1514
  center_x_max = center_x + max_x_offset
1022
1515
  center_x_range = [center_x_min, center_x_max]
1023
-
1516
+
1024
1517
  # Fill between consecutive x-coordinates, but avoid the center region
1025
1518
  i = 0
1026
1519
  while i < len(x_coords):
1027
1520
  segment_start = x_coords[i]
1028
-
1521
+
1029
1522
  # Find the end of this continuous segment
1030
1523
  j = i
1031
1524
  while j < len(x_coords) - 1:
1032
1525
  gap = x_coords[j + 1] - x_coords[j]
1033
1526
  # If there's a large gap, this segment ends
1034
- if gap > 2.0: # Allow small gaps but break on large ones
1527
+ if (
1528
+ gap > 2.0
1529
+ ): # Allow small gaps but break on large ones
1035
1530
  break
1036
1531
  j += 1
1037
-
1532
+
1038
1533
  segment_end = x_coords[j]
1039
-
1534
+
1040
1535
  # Fill this segment, but avoid the center region
1041
1536
  if center_x_range:
1042
1537
  # Split segment around the hollow center
1043
1538
  center_min, center_max = center_x_range
1044
-
1539
+
1045
1540
  # Fill left part (before center)
1046
1541
  if segment_start < center_min:
1047
1542
  left_end = min(segment_end, center_min)
@@ -1049,7 +1544,7 @@ class monitor_class(build_class):
1049
1544
  while current_x <= left_end:
1050
1545
  fill_x_coords.append(current_x)
1051
1546
  current_x += x_step
1052
-
1547
+
1053
1548
  # Fill right part (after center)
1054
1549
  if segment_end > center_max:
1055
1550
  right_start = max(segment_start, center_max)
@@ -1063,20 +1558,25 @@ class monitor_class(build_class):
1063
1558
  while current_x <= segment_end:
1064
1559
  fill_x_coords.append(current_x)
1065
1560
  current_x += x_step
1066
-
1561
+
1067
1562
  i = j + 1
1068
-
1563
+
1069
1564
  # Draw the filled segments
1070
1565
  if fill_x_coords:
1071
1566
  fill_y_coords = [y_coord] * len(fill_x_coords)
1072
- self.draw(fill_x_coords, fill_y_coords, marker='sd', color=color)
1567
+ self.draw(
1568
+ fill_x_coords,
1569
+ fill_y_coords,
1570
+ marker="sd",
1571
+ color=color,
1572
+ )
1073
1573
  else:
1074
1574
  # For regular pie charts, use full horizontal line filling
1075
1575
  for y_coord, x_coords in y_groups.items():
1076
1576
  if x_coords:
1077
1577
  x_coords.sort() # Sort x coordinates
1078
1578
  x_min, x_max = min(x_coords), max(x_coords)
1079
-
1579
+
1080
1580
  # Create a continuous range of x coordinates to fill the gap
1081
1581
  if x_max > x_min:
1082
1582
  # Draw filled horizontal line from x_min to x_max
@@ -1087,25 +1587,34 @@ class monitor_class(build_class):
1087
1587
  fill_x_coords.append(current_x)
1088
1588
  current_x += x_step
1089
1589
  fill_y_coords = [y_coord] * len(fill_x_coords)
1090
- self.draw(fill_x_coords, fill_y_coords, marker='sd', color=color)
1590
+ self.draw(
1591
+ fill_x_coords,
1592
+ fill_y_coords,
1593
+ marker="sd",
1594
+ color=color,
1595
+ )
1091
1596
  else:
1092
1597
  # Single point
1093
- self.draw([x_min], [y_coord], marker='sd', color=color)
1094
-
1598
+ self.draw([x_min], [y_coord], marker="sd", color=color)
1599
+
1095
1600
  # Reset cumulative_angle for label drawing
1096
1601
  cumulative_angle = 0
1097
- for i, (label, value, percentage, color) in enumerate(zip(labels, values, percentages, colors)):
1602
+ for _i, (_label, value, percentage, _color) in enumerate(
1603
+ zip(labels, values, percentages, colors, strict=False)
1604
+ ):
1098
1605
  slice_angle = (value / total) * 2 * math.pi
1099
-
1606
+
1100
1607
  # Add value labels on the pie slice (only if show_values_on_slices is True)
1101
1608
  if show_values_on_slices and (show_values or show_percentages):
1102
1609
  # Calculate middle angle of the slice for label placement
1103
1610
  middle_angle = cumulative_angle + slice_angle / 2
1104
1611
  # Position label at 70% of radius for better visibility
1105
1612
  label_radius = radius * 0.7
1106
- label_x = center_x + (label_radius * math.cos(middle_angle)) * aspect_ratio
1613
+ label_x = (
1614
+ center_x + (label_radius * math.cos(middle_angle)) * aspect_ratio
1615
+ )
1107
1616
  label_y = center_y + label_radius * math.sin(middle_angle)
1108
-
1617
+
1109
1618
  # Build label text for the slice
1110
1619
  slice_label = ""
1111
1620
  if show_values and show_percentages:
@@ -1114,29 +1623,41 @@ class monitor_class(build_class):
1114
1623
  slice_label = str(value)
1115
1624
  elif show_percentages:
1116
1625
  slice_label = f"{percentage:.1f}%"
1117
-
1626
+
1118
1627
  # Draw the label on the slice
1119
- self.draw_text(slice_label, label_x, label_y, color='white', alignment='center')
1120
-
1628
+ self.draw_text(
1629
+ slice_label, label_x, label_y, color="white", alignment="center"
1630
+ )
1631
+
1121
1632
  cumulative_angle += slice_angle
1122
-
1633
+
1123
1634
  # Extend the plot area to accommodate legend (calculate before filtering)
1124
- max_text_length = max(len(f"{label}: {value} ({percentage:.1f}%)")
1125
- for label, value, percentage in zip(labels, values, percentages)
1126
- if label.lower() != "remaining") if any(label.lower() != "remaining" for label in labels) else 20
1127
-
1635
+ max_text_length = (
1636
+ max(
1637
+ len(f"{label}: {value} ({percentage:.1f}%)")
1638
+ for label, value, percentage in zip(
1639
+ labels, values, percentages, strict=False
1640
+ )
1641
+ if label.lower() != "remaining"
1642
+ )
1643
+ if any(label.lower() != "remaining" for label in labels)
1644
+ else 20
1645
+ )
1646
+
1128
1647
  # Set plot limits to include legend area (adjust x for aspect ratio)
1129
1648
  x_radius = radius * aspect_ratio
1130
1649
  self.set_xlim(-x_radius - 1, x_radius + max_text_length + 2)
1131
1650
  self.set_ylim(-radius - 1, radius + 1)
1132
-
1651
+
1133
1652
  # Create legend positioned in the bottom right corner of the chart
1134
1653
  legend_start_x = x_radius + 1.5
1135
1654
  legend_start_y = -radius + len(labels) * 1.0 - 0.5
1136
-
1655
+
1137
1656
  # Filter out "Remaining" labels and default colors for single-value pie charts
1138
1657
  legend_items = []
1139
- for i, (label, value, percentage, color) in enumerate(zip(labels, values, percentages, colors)):
1658
+ for _i, (label, value, percentage, color) in enumerate(
1659
+ zip(labels, values, percentages, colors, strict=False)
1660
+ ):
1140
1661
  # Handle remaining_color logic for legend
1141
1662
  if color == "default":
1142
1663
  if remaining_color is not None:
@@ -1146,17 +1667,17 @@ class monitor_class(build_class):
1146
1667
  else:
1147
1668
  # Always show non-default colors
1148
1669
  legend_items.append((label, value, percentage, color))
1149
-
1670
+
1150
1671
  # Adjust legend positioning for filtered items
1151
1672
  legend_start_y = -radius + len(legend_items) * 1.0 - 0.5
1152
-
1673
+
1153
1674
  for i, (label, value, percentage, color) in enumerate(legend_items):
1154
1675
  legend_x = legend_start_x
1155
1676
  legend_y = legend_start_y - i * 1.2 # Space between legend items
1156
-
1677
+
1157
1678
  # Draw colored square for legend matching pie chart blocks
1158
- self.draw([legend_x], [legend_y], marker='sd', color=color)
1159
-
1679
+ self.draw([legend_x], [legend_y], marker="sd", color=color)
1680
+
1160
1681
  # Build legend text with colored block prefix
1161
1682
  block_char = "█" # Solid block character
1162
1683
  legend_text = f"{block_char} {label}"
@@ -1166,19 +1687,19 @@ class monitor_class(build_class):
1166
1687
  legend_text += f": {value}"
1167
1688
  elif show_percentages:
1168
1689
  legend_text += f": {percentage:.1f}%"
1169
-
1690
+
1170
1691
  # Use draw_text for the legend with the same color as the segment
1171
1692
  self.draw_text(legend_text, legend_x, legend_y, color=color)
1172
-
1693
+
1173
1694
  # Set title if provided
1174
1695
  if title:
1175
1696
  self.set_title(title)
1176
-
1697
+
1177
1698
  # Remove axis labels since pie charts don't need them
1178
- self.set_xlabel('')
1179
- self.set_ylabel('')
1699
+ self.set_xlabel("")
1700
+ self.set_ylabel("")
1180
1701
 
1181
- def draw_heatmap(self, dataframe, color = None, style=None):
1702
+ def draw_heatmap(self, dataframe, color=None, style=None):
1182
1703
  color = self.default.cmatrix_color if color is None else self.check_color(color)
1183
1704
  style = self.default.cmatrix_style if style is None else self.check_style(style)
1184
1705
 
@@ -1187,18 +1708,28 @@ class monitor_class(build_class):
1187
1708
 
1188
1709
  cmatrix = dataframe.values.tolist()
1189
1710
  cm = ut.join(cmatrix)
1190
- m, M, t = min(cm), max(cm), sum(cm)
1711
+ m, max_val, _t = min(cm), max(cm), sum(cm)
1191
1712
 
1192
1713
  lm = 253
1193
- lM = 80
1194
- to_255 = lambda l: round(lm + (lM - lm) * (l - m) / (M - m)) # l=m -> lm; l=M->lM
1195
- to_color = lambda l: tuple([to_255(l)] * 3)
1714
+ light_max = 80
1715
+ def to_255(light_val):
1716
+ return round(
1717
+ lm + (light_max - lm) * (light_val - m) / (max_val - m)
1718
+ ) # light_val=m -> lm; light_val=max_val->light_max
1719
+ def to_color(light_val):
1720
+ return tuple([to_255(light_val)] * 3)
1196
1721
 
1197
1722
  for r in range(len(dataframe.index.tolist())):
1198
1723
  for c in range(len(dataframe.columns.tolist())):
1199
1724
  count = cmatrix[r][c]
1200
1725
  col = to_color(count)
1201
- self.draw_rectangle([c - 0.5, c + 0.5], [r - 0.5, r + 0.5], marker= 'sd', color=col, fill=True)
1726
+ self.draw_rectangle(
1727
+ [c - 0.5, c + 0.5],
1728
+ [r - 0.5, r + 0.5],
1729
+ marker="sd",
1730
+ color=col,
1731
+ fill=True,
1732
+ )
1202
1733
 
1203
1734
  y_labels = list(set(range(len(dataframe.columns))))
1204
1735
  x_labels = list(set(range(len(dataframe.columns))))
@@ -1206,46 +1737,62 @@ class monitor_class(build_class):
1206
1737
  self.set_yreverse(True)
1207
1738
  self.set_xticks(x_labels, xlabels)
1208
1739
  self.set_yticks(y_labels, ylabels)
1209
- self.set_ticks_color(color);
1210
- self.set_ticks_style(style);
1211
- self.set_axes_color('default');
1212
- self.set_canvas_color('default');
1213
- self.set_title('Heatmap')
1740
+ self.set_ticks_color(color)
1741
+ self.set_ticks_style(style)
1742
+ self.set_axes_color("default")
1743
+ self.set_canvas_color("default")
1744
+ self.set_title("Heatmap")
1214
1745
  print(dataframe)
1215
1746
 
1216
- def draw_image(self, path, marker = None, style = None, fast = False, grayscale = False):
1217
- from PIL import Image
1747
+ def draw_image(self, path, marker=None, style=None, fast=False, grayscale=False):
1748
+ from PIL import Image
1749
+
1218
1750
  path = ut.correct_path(path)
1219
1751
  if not ut.is_file(path):
1220
1752
  return
1221
1753
  image = Image.open(path)
1222
- self._draw_image(image, marker = marker, style = style, grayscale = grayscale, fast = fast)
1223
-
1224
- ##############################################
1225
- ####### Plotting Tools Utilities #######
1226
- ##############################################
1754
+ self._draw_image(
1755
+ image, marker=marker, style=style, grayscale=grayscale, fast=fast
1756
+ )
1757
+
1758
+ ##############################################
1759
+ ####### Plotting Tools Utilities #######
1760
+ ##############################################
1227
1761
 
1228
- def check_orientation(self, orientation = None, default_index = 0):
1762
+ def check_orientation(self, orientation=None, default_index=0):
1229
1763
  default = self.default.orientation
1230
1764
  default_first_letter = [el[0] for el in default]
1231
- orientation = default[default_first_letter.index(orientation)] if orientation in default_first_letter else orientation
1232
- orientation = default[default_index] if orientation not in default else orientation
1765
+ orientation = (
1766
+ default[default_first_letter.index(orientation)]
1767
+ if orientation in default_first_letter
1768
+ else orientation
1769
+ )
1770
+ orientation = (
1771
+ default[default_index] if orientation not in default else orientation
1772
+ )
1233
1773
  return orientation
1234
1774
 
1235
- def check_alignment(self, alignment = None):
1775
+ def check_alignment(self, alignment=None):
1236
1776
  default = self.default.alignment[0:-1]
1237
1777
  default_first_letter = [el[0] for el in default]
1238
- alignment = default[default_first_letter.index(alignment)] if alignment in default_first_letter else alignment
1778
+ alignment = (
1779
+ default[default_first_letter.index(alignment)]
1780
+ if alignment in default_first_letter
1781
+ else alignment
1782
+ )
1239
1783
  alignment = default[1] if alignment not in default else alignment
1240
1784
  return alignment
1241
1785
 
1242
- def _draw_image(self, image, marker = None, style = None, fast = False, grayscale = False):
1786
+ def _draw_image(self, image, marker=None, style=None, fast=False, grayscale=False):
1243
1787
  from PIL import ImageOps
1788
+
1244
1789
  image = ImageOps.grayscale(image) if grayscale else image
1245
- image = image.convert('RGB')
1790
+ image = image.convert("RGB")
1246
1791
  size = ut.update_size(image.size, self.size)
1247
- image = image.resize(size, resample = True)
1792
+ image = image.resize(size, resample=True)
1248
1793
  matrix = ut.image_to_matrix(image)
1249
- self.set_xfrequency(0); self.set_yfrequency(0);
1250
- self.draw_matrix(matrix, marker = marker, style = style, fast = fast)
1251
- self.set_xlabel(); self.set_ylabel()
1794
+ self.set_xfrequency(0)
1795
+ self.set_yfrequency(0)
1796
+ self.draw_matrix(matrix, marker=marker, style=style, fast=fast)
1797
+ self.set_xlabel()
1798
+ self.set_ylabel()