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/__init__.py +20 -15
- plotext_plus/__main__.py +1 -0
- plotext_plus/_api.py +632 -371
- plotext_plus/_build.py +765 -149
- plotext_plus/_core.py +609 -164
- plotext_plus/_date.py +50 -32
- plotext_plus/_default.py +35 -28
- plotext_plus/_dict.py +807 -103
- plotext_plus/_doc.py +867 -296
- plotext_plus/_doc_utils.py +279 -245
- plotext_plus/_figure.py +1295 -303
- plotext_plus/_global.py +238 -140
- plotext_plus/_matrix.py +217 -63
- plotext_plus/_monitor.py +1036 -489
- plotext_plus/_output.py +29 -23
- plotext_plus/_shtab.py +2 -0
- plotext_plus/_themes.py +363 -247
- plotext_plus/_utility.py +581 -313
- plotext_plus/api.py +418 -332
- plotext_plus/charts.py +47 -24
- plotext_plus/core.py +570 -177
- plotext_plus/mcp_cli.py +15 -13
- plotext_plus/mcp_server.py +842 -166
- plotext_plus/plotext_cli.py +414 -275
- plotext_plus/plotting.py +86 -70
- plotext_plus/themes.py +10 -13
- plotext_plus/utilities.py +33 -33
- plotext_plus/utils.py +240 -140
- {plotext_plus-1.0.8.dist-info → plotext_plus-1.0.10.dist-info}/METADATA +7 -2
- plotext_plus-1.0.10.dist-info/RECORD +33 -0
- plotext_plus-1.0.8.dist-info/RECORD +0 -33
- {plotext_plus-1.0.8.dist-info → plotext_plus-1.0.10.dist-info}/WHEEL +0 -0
- {plotext_plus-1.0.8.dist-info → plotext_plus-1.0.10.dist-info}/entry_points.txt +0 -0
- {plotext_plus-1.0.8.dist-info → plotext_plus-1.0.10.dist-info}/licenses/LICENSE +0 -0
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
|
-
|
|
11
|
-
|
|
11
|
+
|
|
12
|
+
class MonitorClass(BuildClass):
|
|
13
|
+
|
|
12
14
|
def __init__(self):
|
|
13
|
-
self.default =
|
|
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 =
|
|
20
|
+
self.matrix = MatrixClass()
|
|
19
21
|
|
|
20
|
-
def copy(self):
|
|
22
|
+
def copy(self): # to deep copy
|
|
21
23
|
return deepcopy(self)
|
|
22
24
|
|
|
23
|
-
def set_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(
|
|
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
|
|
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
|
|
43
|
-
self.xlabels = self.default.xticks[:]
|
|
44
|
-
self.xfrequency =
|
|
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 =
|
|
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
|
|
53
|
-
self.yaxes = self.default.yaxes
|
|
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 =
|
|
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(
|
|
70
|
+
self.set_theme("default")
|
|
59
71
|
|
|
60
72
|
def data_init(self):
|
|
61
|
-
self.xlim = [
|
|
62
|
-
|
|
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 = [
|
|
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 = [[], []]
|
|
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 =
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
self.
|
|
92
|
-
|
|
93
|
-
|
|
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
|
|
120
|
+
self.signals = 0 # number of signals to plot
|
|
96
121
|
|
|
97
|
-
self.lines = []
|
|
122
|
+
self.lines = [] # whatever to draw lines between points
|
|
98
123
|
|
|
99
|
-
self.marker = []
|
|
100
|
-
self.color = []
|
|
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 = []
|
|
105
|
-
self.filly = []
|
|
129
|
+
self.fillx = [] # fill data vertically (till x axis)
|
|
130
|
+
self.filly = [] # fill data horizontally (till y axis)
|
|
106
131
|
|
|
107
|
-
self.label = []
|
|
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
|
|
138
|
+
def set_title(self, title=None):
|
|
114
139
|
self.title = self.set_label(title)
|
|
115
140
|
|
|
116
|
-
def set_xlabel(self, label
|
|
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
|
|
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
|
|
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
|
|
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
|
|
145
|
-
default_case =
|
|
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
|
|
151
|
-
default_case =
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
207
|
+
def set_xfrequency(self, frequency=None, xside=None):
|
|
183
208
|
pos = self.xside_to_pos(xside)
|
|
184
|
-
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
|
|
213
|
+
|
|
214
|
+
def set_yfrequency(self, frequency=None, yside=None):
|
|
188
215
|
pos = self.yside_to_pos(yside)
|
|
189
|
-
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
|
|
221
|
+
def set_xreverse(self, reverse=None, xside=None):
|
|
193
222
|
pos = self.xside_to_pos(xside)
|
|
194
|
-
direction =
|
|
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
|
|
230
|
+
def set_yreverse(self, reverse=None, yside=None):
|
|
198
231
|
pos = self.yside_to_pos(yside)
|
|
199
|
-
direction =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
238
|
-
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(
|
|
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
|
|
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
|
|
291
|
+
def correct_xside(self, xside=None): # from axis side to position
|
|
255
292
|
xaxis = self.default.xside
|
|
256
|
-
xside =
|
|
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
|
|
304
|
+
def correct_yside(self, yside=None):
|
|
260
305
|
yaxis = self.default.yside
|
|
261
|
-
yside =
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
379
|
+
|
|
380
|
+
def add_markers(self, marker=None):
|
|
324
381
|
single_marker = isinstance(marker, str) or marker is None
|
|
325
|
-
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
|
|
331
|
-
list_color = isinstance(color, list)
|
|
332
|
-
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 =
|
|
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
|
|
407
|
+
def add_styles(self, style=None):
|
|
339
408
|
single_style = isinstance(style, str) or style is None
|
|
340
|
-
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
|
|
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
|
|
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
|
|
426
|
+
def add_label(self, label=None):
|
|
354
427
|
spaces = ut.only_spaces(label)
|
|
355
|
-
label =
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
465
|
+
def check_fill(self, fill=None):
|
|
391
466
|
fill = self.default.fill if fill is None else fill
|
|
392
|
-
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(
|
|
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(
|
|
412
|
-
|
|
413
|
-
xticks = range(1,
|
|
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]
|
|
419
|
-
(
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
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]
|
|
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] ==
|
|
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(
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
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
|
|
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 =
|
|
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(
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
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
|
-
|
|
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(
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
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(
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
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 = [
|
|
503
|
-
colors = [
|
|
504
|
-
|
|
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 =
|
|
507
|
-
|
|
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,
|
|
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,
|
|
689
|
+
m, max_val = min(o, c), max(o, c)
|
|
513
690
|
lab = label if i == 0 else None
|
|
514
|
-
if orientation in [
|
|
515
|
-
self.draw(
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
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 = [
|
|
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(
|
|
536
|
-
|
|
537
|
-
xticks = range(1,
|
|
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
|
-
(
|
|
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 = [
|
|
555
|
-
|
|
556
|
-
for i in range(
|
|
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,
|
|
561
|
-
|
|
562
|
-
if orientation in [
|
|
563
|
-
self.draw(
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
self.draw(
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
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
|
-
|
|
588
|
-
xerr = [0] *
|
|
589
|
-
yerr = [0] *
|
|
590
|
-
for i in range(
|
|
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(
|
|
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(
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
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 [
|
|
601
|
-
self.draw(x, y, xside
|
|
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
|
|
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
|
|
610
|
-
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
|
|
617
|
-
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(
|
|
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 =
|
|
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(
|
|
643
|
-
|
|
644
|
-
|
|
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
|
-
|
|
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)
|
|
650
|
-
|
|
651
|
-
|
|
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,
|
|
654
|
-
self.ylim[xpos] = [ym - 0.5 * dy,
|
|
655
|
-
x, y = [xm, xm,
|
|
656
|
-
self.draw(
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
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
|
-
|
|
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 =
|
|
678
|
-
|
|
679
|
-
|
|
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(
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
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
|
-
|
|
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
|
-
|
|
703
|
-
|
|
704
|
-
get_sum
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
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(
|
|
717
|
-
|
|
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)
|
|
723
|
-
self.
|
|
724
|
-
self.
|
|
725
|
-
self.
|
|
726
|
-
self.
|
|
727
|
-
|
|
728
|
-
|
|
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(
|
|
736
|
-
self.set_canvas_color(
|
|
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
|
|
741
|
-
|
|
742
|
-
##############################################
|
|
743
|
-
############## 2D Plots ################
|
|
744
|
-
##############################################
|
|
745
|
-
|
|
746
|
-
def draw_matrix(self, matrix, marker
|
|
747
|
-
matrix = [
|
|
748
|
-
marker = [marker] if
|
|
749
|
-
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 =
|
|
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
|
|
1206
|
+
self.draw(x, y, marker=marker, color=color, style=style)
|
|
760
1207
|
self.set_canvas_color("black")
|
|
761
|
-
self.set_xlabel(
|
|
762
|
-
self.set_ylabel(
|
|
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:
|
|
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 =
|
|
1222
|
+
self.matrix.canvas = "\n".join(["".join(row) for row in matrix])
|
|
776
1223
|
self.fast_plot = True
|
|
777
1224
|
|
|
778
|
-
def draw_pie(
|
|
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 = [
|
|
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(
|
|
825
|
-
self.set_canvas_color(
|
|
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(
|
|
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) / (
|
|
866
|
-
|
|
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
|
|
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
|
|
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(
|
|
913
|
-
for i, (start_angle, end_angle) in enumerate(
|
|
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 + (
|
|
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
|
|
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(
|
|
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) / (
|
|
959
|
-
|
|
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
|
|
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) / (
|
|
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 =
|
|
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
|
|
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(
|
|
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(
|
|
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=
|
|
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
|
|
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 =
|
|
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(
|
|
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 =
|
|
1125
|
-
|
|
1126
|
-
|
|
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
|
|
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=
|
|
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
|
|
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,
|
|
1711
|
+
m, max_val, _t = min(cm), max(cm), sum(cm)
|
|
1191
1712
|
|
|
1192
1713
|
lm = 253
|
|
1193
|
-
|
|
1194
|
-
to_255
|
|
1195
|
-
|
|
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(
|
|
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(
|
|
1212
|
-
self.set_canvas_color(
|
|
1213
|
-
self.set_title(
|
|
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
|
|
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(
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
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
|
|
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 =
|
|
1232
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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(
|
|
1790
|
+
image = image.convert("RGB")
|
|
1246
1791
|
size = ut.update_size(image.size, self.size)
|
|
1247
|
-
image = image.resize(size, resample
|
|
1792
|
+
image = image.resize(size, resample=True)
|
|
1248
1793
|
matrix = ut.image_to_matrix(image)
|
|
1249
|
-
self.set_xfrequency(0)
|
|
1250
|
-
self.
|
|
1251
|
-
self.
|
|
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()
|