plotext-plus 1.0.1__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.
@@ -0,0 +1,848 @@
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
+ import math
7
+
8
+ # 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
+ class monitor_class(build_class):
11
+
12
+ def __init__(self):
13
+ self.default = default_monitor_class() # default values
14
+ self.labels_init()
15
+ self.axes_init()
16
+ self.color_init()
17
+ self.data_init()
18
+ self.matrix = matrix_class()
19
+
20
+ def copy(self): # to deep copy
21
+ return deepcopy(self)
22
+
23
+ def set_size(self, size): # called externally by the figure containing it, to pass the size
24
+ self.size = size
25
+
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
27
+ self.date = date
28
+
29
+ ##############################################
30
+ ######### Internal Variables ###########
31
+ ##############################################
32
+
33
+ def labels_init(self):
34
+ self.title = None
35
+ self.xlabel = [None, None]
36
+ self.ylabel = [None, None]
37
+
38
+ def axes_init(self):
39
+ self.xscale = [self.default.xscale[0]] * 2 # the scale on x axis
40
+ self.yscale = [self.default.xscale[0]] * 2
41
+
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
45
+ self.xdirection = self.default.xdirection
46
+
47
+ self.yticks = self.default.yticks
48
+ self.ylabels = self.default.yticks[:]
49
+ self.yfrequency = self.default.yfrequency # left and right yaxes ticks frequency
50
+ self.ydirection = self.default.ydirection
51
+
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
54
+
55
+ self.grid = self.default.grid # whatever to show the horizontal and vertical grid lines
56
+
57
+ def color_init(self):
58
+ self.set_theme('default')
59
+
60
+ 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
63
+
64
+ self.fast_plot = False
65
+ self.lines_init()
66
+ self.text_init()
67
+ self.draw_init()
68
+
69
+ def lines_init(self):
70
+ self.vcoord = [[], []] # those are user defined extra grid lines, vertical or horizontal, for each axis
71
+ self.hcoord = [[], []]
72
+ self.vcolors = [[], []] # their color
73
+ self.hcolors = [[], []]
74
+
75
+ def text_init(self):
76
+ self.text = []
77
+ self.tx = []
78
+ self.ty = []
79
+ self.txside = []
80
+ self.tyside = []
81
+ self.torien = []
82
+ self.talign = []
83
+ self.tfull = []
84
+ self.tback = []
85
+ self.tstyle = []
86
+
87
+ 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
94
+ self.y_date = [False, False]
95
+ self.signals = 0 # number of signals to plot
96
+
97
+ self.lines = [] # whatever to draw lines between points
98
+
99
+ self.marker = [] # list of markers used for each plot
100
+ self.color = [] # list of marker colors used for each plot
101
+ self.past_colors = []
102
+ self.style = []
103
+
104
+ self.fillx = [] # fill data vertically (till x axis)
105
+ self.filly = [] # fill data horizontally (till y axis)
106
+
107
+ self.label = [] # subplot list of labels
108
+
109
+ ##############################################
110
+ ####### External Set Functions #########
111
+ ##############################################
112
+
113
+ def set_title(self, title = None):
114
+ self.title = self.set_label(title)
115
+
116
+ def set_xlabel(self, label = None, xside = None):
117
+ pos = self.xside_to_pos(xside)
118
+ self.xlabel[pos] = self.set_label(label)
119
+
120
+ def set_ylabel(self, label = None, yside = None):
121
+ pos = self.yside_to_pos(yside)
122
+ self.ylabel[pos] = self.set_label(label)
123
+
124
+ def set_xlim(self, left = None, right = None, xside = None):
125
+ left = self.date.string_to_time(left) if isinstance(left, str) else left
126
+ right = self.date.string_to_time(right) if isinstance(right, str) else right
127
+ left = None if left is None else float(left)
128
+ right = None if right is None else float(right)
129
+ xlim = [left, right]
130
+ xlim = xlim if None in xlim else [min(xlim), max(xlim)]
131
+ pos = self.xside_to_pos(xside)
132
+ self.xlim[pos] = xlim
133
+
134
+ def set_ylim(self, lower = None, upper = None, yside = None):
135
+ lower = self.date.string_to_time(lower) if isinstance(lower, str) else lower
136
+ upper = self.date.string_to_time(upper) if isinstance(upper, str) else upper
137
+ lower = None if lower is None else float(lower)
138
+ upper = None if upper is None else float(upper)
139
+ ylim = [lower, upper]
140
+ ylim = ylim if None in ylim else [min(ylim), max(ylim)]
141
+ pos = self.yside_to_pos(yside)
142
+ self.ylim[pos] = ylim
143
+
144
+ def set_xscale(self, scale = None, xside = None):
145
+ default_case = (scale is None or scale not in self.default.xscale)
146
+ scale = self.default.xscale[0] if default_case else scale
147
+ pos = self.xside_to_pos(xside)
148
+ self.xscale[pos] = scale
149
+
150
+ def set_yscale(self, scale = None, yside = None):
151
+ default_case = (scale is None or scale not in self.default.yscale)
152
+ scale = self.default.yscale[0] if default_case else scale
153
+ pos = self.yside_to_pos(yside)
154
+ self.yscale[pos] = scale
155
+
156
+ def set_xticks(self, ticks = None, labels = None, xside = None):
157
+ pos = self.xside_to_pos(xside)
158
+ ticks = self.default.xticks[pos] if ticks is None else list(ticks)
159
+ string_ticks = any([isinstance(el, str) for el in ticks])
160
+ labels = ticks if string_ticks and labels is None else labels
161
+ ticks = self.date.strings_to_time(ticks) if string_ticks else ticks
162
+ labels = ut.get_labels(ticks) if labels is None else list(labels)
163
+ labels = list(map(str, labels))
164
+ ticks, labels = ut.brush(ticks, labels)
165
+ self.xticks[pos] = ticks
166
+ self.xlabels[pos] = labels
167
+ self.xfrequency[pos] = self.xfrequency[pos] if ticks is None else len(ticks)
168
+
169
+ def set_yticks(self, ticks = None, labels = None, yside = None):
170
+ pos = self.yside_to_pos(yside)
171
+ ticks = self.default.yticks[pos] if ticks is None else list(ticks)
172
+ string_ticks = any([isinstance(el, str) for el in ticks])
173
+ labels = ticks if string_ticks and labels is None else labels
174
+ ticks = self.date.strings_to_time(ticks) if string_ticks else ticks
175
+ labels = ut.get_labels(ticks) if labels is None else list(labels)
176
+ labels = list(map(str, labels))
177
+ ticks, labels = ut.brush(ticks, labels)
178
+ self.yticks[pos] = ticks
179
+ self.ylabels[pos] = labels
180
+ self.yfrequency[pos] = self.yfrequency[pos] if ticks is None else len(ticks)
181
+
182
+ def set_xfrequency(self, frequency = None, xside = None):
183
+ pos = self.xside_to_pos(xside)
184
+ frequency = self.default.xfrequency[pos] if frequency is None else int(frequency)
185
+ self.xfrequency[pos] = frequency
186
+
187
+ def set_yfrequency(self, frequency = None, yside = None):
188
+ pos = self.yside_to_pos(yside)
189
+ frequency = self.default.yfrequency[pos] if frequency is None else int(frequency)
190
+ self.yfrequency[pos] = frequency
191
+
192
+ def set_xreverse(self, reverse = None, xside = None):
193
+ pos = self.xside_to_pos(xside)
194
+ direction = self.default.xdirection[pos] if reverse is None else 2 * int(not reverse) - 1
195
+ self.xdirection[pos] = direction
196
+
197
+ def set_yreverse(self, reverse = None, yside = None):
198
+ pos = self.yside_to_pos(yside)
199
+ direction = self.default.ydirection[pos] if reverse is None else 2 * int(not reverse) - 1
200
+ self.ydirection[pos] = direction
201
+
202
+ def set_xaxes(self, lower = None, upper = None):
203
+ self.xaxes[0] = self.default.xaxes[0] if lower is None else bool(lower)
204
+ self.xaxes[1] = self.default.xaxes[1] if upper is None else bool(upper)
205
+
206
+ def set_yaxes(self, left = None, right = None):
207
+ self.yaxes[0] = self.default.yaxes[0] if left is None else bool(left)
208
+ self.yaxes[1] = self.default.yaxes[1] if right is None else bool(right)
209
+
210
+ def set_frame(self, frame = None):
211
+ self.set_xaxes(frame, frame)
212
+ self.set_yaxes(frame, frame)
213
+
214
+ def set_grid(self, horizontal = None, vertical = None):
215
+ horizontal = self.default.grid[0] if horizontal is None else bool(horizontal)
216
+ vertical = self.default.grid[1] if vertical is None else bool(vertical)
217
+ self.grid = [horizontal, vertical]
218
+
219
+ def set_color(self, color = None):
220
+ color = color if ut.is_color(color) else None
221
+ return self.default.canvas_color if color is None else color
222
+
223
+ def set_canvas_color(self, color = None):
224
+ self.canvas_color = self.set_color(color)
225
+
226
+ def set_axes_color(self, color = None):
227
+ self.axes_color = self.set_color(color)
228
+
229
+ def set_ticks_color(self, color = None):
230
+ self.ticks_color = self.set_color(color)
231
+
232
+ def set_ticks_style(self, style = None):
233
+ style = style if ut.is_style(style) else None
234
+ style = self.default.ticks_style if style is None else ut.clean_styles(style)
235
+ self.ticks_style = style
236
+
237
+ def set_theme(self, theme = None):
238
+ theme = 'default' if theme is None or theme not in ut.themes else theme
239
+ self._set_theme(*ut.themes[theme])
240
+
241
+ def clear_color(self):
242
+ self.set_theme('clear')
243
+
244
+ ##############################################
245
+ ####### Set Functions Utilities ########
246
+ ##############################################
247
+
248
+ def set_label(self, label = None):
249
+ label = None if label is None else str(label).strip()
250
+ spaces = ut.only_spaces(label)
251
+ label = None if spaces else label
252
+ return label
253
+
254
+ def correct_xside(self, xside = None): # from axis side to position
255
+ 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()
257
+ return xside
258
+
259
+ def correct_yside(self, yside = None):
260
+ 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()
262
+ return yside
263
+
264
+ def xside_to_pos(self, xside = None): # from axis side to position
265
+ xside = self.correct_xside(xside)
266
+ pos = self.default.xside.index(xside)
267
+ return pos
268
+
269
+ def yside_to_pos(self, yside = None):
270
+ yside = self.correct_yside(yside)
271
+ pos = self.default.yside.index(yside)
272
+ return pos
273
+
274
+ def _set_theme(self, canvas_color, axes_color, ticks_color, ticks_style, color_sequence):
275
+ self.canvas_color = canvas_color
276
+ self.axes_color = axes_color
277
+ self.ticks_color = ticks_color
278
+ self.ticks_style = ticks_style
279
+ self.color_sequence = color_sequence
280
+
281
+ ##############################################
282
+ ########## Draw() Function #############
283
+ ##############################################
284
+
285
+ def draw(self, *args, **kwargs): # from draw() comes directly the functions scatter() and plot()
286
+ self.add_xside(kwargs.get("xside"))
287
+ self.add_yside(kwargs.get("yside"))
288
+ self.add_data(*args)
289
+ self.add_lines(kwargs.get("lines"))
290
+ self.add_markers(kwargs.get("marker"))
291
+ self.add_colors(kwargs.get("color"))
292
+ self.add_styles(kwargs.get("style"))
293
+ self.add_fillx(kwargs.get("fillx"))
294
+ self.add_filly(kwargs.get("filly"))
295
+ self.add_label(kwargs.get("label"))
296
+
297
+ ##############################################
298
+ ####### Draw() Called Functions ########
299
+ ##############################################
300
+
301
+ def add_xside(self, xside = None):
302
+ xside = self.correct_xside(xside)
303
+ self.xside.append(xside)
304
+
305
+ def add_yside(self, yside = None):
306
+ yside = self.correct_yside(yside)
307
+ self.yside.append(yside)
308
+
309
+ def add_data(self, *args):
310
+ x, y = ut.set_data(*args)
311
+ x, x_date = self.to_time(x)
312
+ y, y_date = self.to_time(y)
313
+ self.x_date[self.xside_to_pos(self.xside[-1])] = x_date
314
+ self.y_date[self.yside_to_pos(self.yside[-1])] = y_date
315
+ self.x.append(x)
316
+ self.y.append(y)
317
+ self.signals += 1
318
+
319
+ def add_lines(self, lines):
320
+ lines = self.default.lines if lines is None else bool(lines)
321
+ self.lines.append(lines)
322
+
323
+ def add_markers(self, marker = None):
324
+ 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))
326
+ length = len(self.x[-1])
327
+ marker = ut.to_list(marker, length)
328
+ self.marker.append(marker)
329
+
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)
333
+ length = len(self.x[-1])
334
+ self.past_colors = self.past_colors + [color] if color not in self.past_colors else self.past_colors
335
+ color = ut.to_list(color, length)
336
+ self.color.append(color)
337
+
338
+ def add_styles(self, style = None):
339
+ 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))
341
+ length = len(self.x[-1])
342
+ style = ut.to_list(style, length)
343
+ self.style.append(style)
344
+
345
+ def add_fillx(self, fillx = None):
346
+ fillx = self.check_fill(fillx)
347
+ self.fillx.append(fillx)
348
+
349
+ def add_filly(self, filly = None):
350
+ filly = self.check_fill(filly)
351
+ self.filly.append(filly)
352
+
353
+ def add_label(self, label = None):
354
+ 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
356
+ self.label.append(label)
357
+ #figure.subplot.label_show.append(default.label_show)
358
+
359
+ ##############################################
360
+ ###### Draw() Functions Utilities #######
361
+ ##############################################
362
+
363
+ def to_time(self, data):
364
+ dates = any([isinstance(el, str) for el in data])
365
+ data = self.date.strings_to_time(data) if dates else data
366
+ return data, dates
367
+
368
+ def check_marker(self, marker = None):
369
+ marker = None if marker is None else str(marker)
370
+ marker = self.default.marker if marker is None else marker
371
+ marker = ut.marker_codes[marker] if marker in ut.marker_codes else marker
372
+ marker = marker if marker in ut.hd_symbols else marker[0]
373
+ return marker
374
+
375
+ def check_color(self, color = None):
376
+ color = color if ut.is_color(color) else None
377
+ color = self.next_color() if color is None else color
378
+ return color
379
+
380
+ def next_color(self):
381
+ color = ut.difference(self.color_sequence, self.past_colors)
382
+ color = color[0] if len(color) > 0 else self.color_sequence[0]
383
+ return color
384
+
385
+ def check_style(self, style = None):
386
+ style = None if style is None else str(style)
387
+ style = style if ut.is_style(style) else ut.no_color
388
+ return style
389
+
390
+ def check_fill(self, fill = None):
391
+ 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
393
+ fill = 0 if fill is True else fill
394
+ return fill
395
+
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):
401
+ x, y = ut.set_data(*args)
402
+ marker = self.default.bar_marker if marker is None else marker
403
+ fill = self.default.bar_fill if fill is None else fill
404
+ width = self.default.bar_width if width is None else width
405
+ width = 1 if width > 1 else 0 if width < 0 else width
406
+ orientation = self.check_orientation(orientation, 1)
407
+ minimum = 0 if minimum is None else minimum
408
+ offset = 0 if offset is None else offset
409
+ reset_ticks = True if reset_ticks is None else reset_ticks
410
+
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
414
+ xlabels = x if x_string else map(str, x)
415
+ x = xticks if x_string else x
416
+ x = [el + offset for el in x]
417
+ 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
423
+
424
+ for b in range(len(x)):
425
+ xb = xbar[b]; yb = ybar[b]
426
+ plot_label = label if b == firstbar else None
427
+ 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')
429
+ plot_marker = " " if nobar else marker
430
+ 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)
443
+ 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
445
+ color = [color] * ly if color is None else color
446
+ labels = [labels] * ly if labels is None else labels
447
+ 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
+
450
+ 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
468
+ color = [color] * ly if color is None else color
469
+ labels = [labels] * ly if labels is None else labels
470
+ Y = ut.transpose([ut.cumsum(el) for el in ut.transpose(Y)])
471
+ 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):
485
+ bins = self.default.hist_bins if bins is None else bins
486
+ norm = False if norm is None else norm
487
+ 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):
501
+ 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 = []
505
+ 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"]
508
+ for i in range(ln):
509
+ d = dates[i]
510
+ o, c, h, l = Open[i], Close[i], High[i], Low[i]
511
+ color = colors[0] if c > o else colors[1]
512
+ m, M = min(o, c), max(o, c)
513
+ 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):
524
+ x, y = ut.set_data(*args)
525
+ fill = self.default.bar_fill if fill is None else fill
526
+ width = self.default.bar_width if width is None else width
527
+ width = 1 if width > 1 else 0 if width < 0 else width
528
+ orientation = self.check_orientation(orientation, 1)
529
+ minimum = 0 if minimum is None else minimum
530
+ offset = 0 if offset is None else offset
531
+ reset_ticks = True if reset_ticks is None else reset_ticks
532
+ colors = ['green', 'red'] if colors is None else colors
533
+ quintuples = False if quintuples is None else quintuples
534
+
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
538
+ xlabels = x if x_string else map(str, x)
539
+ x = xticks if x_string else x
540
+ 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
542
+ if quintuples:
543
+ # todo: check y is aligned.
544
+ _, _, _, _, _, c, xbar = ut.box(x, y, width, minimum)
545
+ q1, q2, q3, max_, min_ = [], [], [], [], []
546
+ for d in y:
547
+ max_.append(d[0])
548
+ q3.append(d[1])
549
+ q2.append(d[2])
550
+ q1.append(d[3])
551
+ min_.append(d[4])
552
+ else:
553
+ 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):
557
+ lab = label if i == 0 else None
558
+ color = colors[0]
559
+ 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):
586
+ 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):
591
+ 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)
593
+ 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):
598
+ x, y = data, [1.1] * len(data)
599
+ 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)
602
+ self.set_ylim(0, 1)
603
+ self.set_yfrequency(0)
604
+ else:
605
+ self.draw(y, x, yside = side, marker = marker, color = color, filly = True)
606
+ self.set_xlim(0, 1)
607
+ self.set_xfrequency(0)
608
+
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
611
+ pos = self.xside_to_pos(xside)
612
+ self.vcoord[pos].append(coordinate)
613
+ color = self.ticks_color if color is None else color
614
+ self.vcolors[pos].append(self.check_color(color))
615
+
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
618
+ pos = self.xside_to_pos(yside)
619
+ self.hcoord[pos].append(coordinate)
620
+ color = self.ticks_color if color is None else color
621
+ self.hcolors[pos].append(self.check_color(color))
622
+
623
+ def draw_text(self, text, x, y, xside = None, yside = None, color = None, background = None, style = None, orientation = None, alignment = None):
624
+ orientation = self.check_orientation(orientation)
625
+ text = text if orientation is self.default.orientation[0] else text[::-1]
626
+ self.text.append(str(text))
627
+ x = self.date.string_to_time(x) if isinstance(x, str) else x
628
+ y = self.date.string_to_time(y) if isinstance(y, str) else y
629
+ self.tx.append(x)
630
+ self.ty.append(y)
631
+ self.txside.append(self.correct_xside(xside))
632
+ self.tyside.append(self.correct_yside(yside))
633
+ 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
635
+ self.tfull.append(color)
636
+ self.tback.append(background)
637
+ self.tstyle.append(self.check_style(style))
638
+ alignment = self.check_alignment(alignment)
639
+ self.torien.append(orientation)
640
+ self.talign.append(alignment)
641
+
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
645
+ xpos = self.xside_to_pos(xside)
646
+ ypos = self.yside_to_pos(yside)
647
+ lines = True if lines is None else lines
648
+ 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);
652
+ 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):
667
+ x = 0 if x is None else x
668
+ y = 0 if y is None else y
669
+ radius = 1 if radius is None else abs(int(radius))
670
+ sides = 3 if sides is None else max(3, int(abs(sides)))
671
+ xpos = self.xside_to_pos(xside)
672
+ ypos = self.yside_to_pos(yside)
673
+ lines = True if lines is None else lines
674
+ fill = False if fill is None else fill
675
+
676
+ 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]
680
+ # 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
+ points = [get_point(i) for i in range(sides + 1)]
682
+ if reset_lim:
683
+ self.xlim[xpos] = [x - 1.5 * radius, x + 1.5 * radius]
684
+ 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):
696
+ color = self.default.cmatrix_color if color is None else self.check_color(color)
697
+ style = self.default.cmatrix_style if style is None else self.check_style(style)
698
+
699
+ L = len(actual)
700
+ n_labels = sorted(ut.no_duplicates(actual))
701
+ 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):
714
+ count = cmatrix[r][c]
715
+ 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)
718
+
719
+ self.set_yreverse(True)
720
+ self.set_xticks(n_labels, labels)
721
+ 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):
729
+ color = self.default.cmatrix_color if color is None else self.check_color(color)
730
+ style = self.default.cmatrix_style if style is None else self.check_style(style)
731
+
732
+ 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')
737
+ self.set_xfrequency(0)
738
+ self.set_yfrequency(0)
739
+
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]
750
+ style = ut.no_color if style is None else self.check_style(style)
751
+ cols, rows = ut.matrix_size(matrix)
752
+ 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)
754
+ marker = ut.repeat(marker, cols)
755
+ if not fast:
756
+ for r in range(rows):
757
+ xyc = [(c, r, matrix[rows - 1 - r][c]) for c in range(cols)]
758
+ x, y, color = ut.transpose(xyc, 3)
759
+ self.draw(x, y, marker = marker, color = color, style = style)
760
+ self.set_canvas_color("black")
761
+ self.set_xlabel('column')
762
+ self.set_ylabel('row')
763
+ xf, yf = min(self.xfrequency[0], cols), min(self.yfrequency[0], rows)
764
+ xt = ut.linspace(0, cols - 1, xf)
765
+ xl = ut.get_labels([el + 1 for el in xt])
766
+ yt = ut.linspace(0, rows - 1, yf)
767
+ yl = ut.get_labels([rows - el for el in yt])
768
+ self.set_xticks(xt, xl)
769
+ self.set_yticks(yt, yl)
770
+ else: # if fast
771
+ for r in range(rows):
772
+ for c in range(cols):
773
+ ansi = ut.colors_to_ansi(matrix[r][c], style, "black")
774
+ matrix[r][c] = ansi + marker[c] + ut.ansi_end
775
+ self.matrix.canvas = '\n'.join([''.join(row) for row in matrix])
776
+ self.fast_plot = True
777
+
778
+ def draw_heatmap(self, dataframe, color = None, style=None):
779
+ color = self.default.cmatrix_color if color is None else self.check_color(color)
780
+ style = self.default.cmatrix_style if style is None else self.check_style(style)
781
+
782
+ xlabels = dataframe.columns.tolist()
783
+ ylabels = dataframe.index.tolist()
784
+
785
+ cmatrix = dataframe.values.tolist()
786
+ cm = ut.join(cmatrix)
787
+ m, M, t = min(cm), max(cm), sum(cm)
788
+
789
+ lm = 253
790
+ lM = 80
791
+ to_255 = lambda l: round(lm + (lM - lm) * (l - m) / (M - m)) # l=m -> lm; l=M->lM
792
+ to_color = lambda l: tuple([to_255(l)] * 3)
793
+
794
+ for r in range(len(dataframe.index.tolist())):
795
+ for c in range(len(dataframe.columns.tolist())):
796
+ count = cmatrix[r][c]
797
+ col = to_color(count)
798
+ self.draw_rectangle([c - 0.5, c + 0.5], [r - 0.5, r + 0.5], marker= 'sd', color=col, fill=True)
799
+
800
+ y_labels = list(set(range(len(dataframe.columns))))
801
+ x_labels = list(set(range(len(dataframe.columns))))
802
+
803
+ self.set_yreverse(True)
804
+ self.set_xticks(x_labels, xlabels)
805
+ self.set_yticks(y_labels, ylabels)
806
+ self.set_ticks_color(color);
807
+ self.set_ticks_style(style);
808
+ self.set_axes_color('default');
809
+ self.set_canvas_color('default');
810
+ self.set_title('Heatmap')
811
+ print(dataframe)
812
+
813
+ def draw_image(self, path, marker = None, style = None, fast = False, grayscale = False):
814
+ from PIL import Image
815
+ path = ut.correct_path(path)
816
+ if not ut.is_file(path):
817
+ return
818
+ image = Image.open(path)
819
+ self._draw_image(image, marker = marker, style = style, grayscale = grayscale, fast = fast)
820
+
821
+ ##############################################
822
+ ####### Plotting Tools Utilities #######
823
+ ##############################################
824
+
825
+ def check_orientation(self, orientation = None, default_index = 0):
826
+ default = self.default.orientation
827
+ default_first_letter = [el[0] for el in default]
828
+ orientation = default[default_first_letter.index(orientation)] if orientation in default_first_letter else orientation
829
+ orientation = default[default_index] if orientation not in default else orientation
830
+ return orientation
831
+
832
+ def check_alignment(self, alignment = None):
833
+ default = self.default.alignment[0:-1]
834
+ default_first_letter = [el[0] for el in default]
835
+ alignment = default[default_first_letter.index(alignment)] if alignment in default_first_letter else alignment
836
+ alignment = default[1] if alignment not in default else alignment
837
+ return alignment
838
+
839
+ def _draw_image(self, image, marker = None, style = None, fast = False, grayscale = False):
840
+ from PIL import ImageOps
841
+ image = ImageOps.grayscale(image) if grayscale else image
842
+ image = image.convert('RGB')
843
+ size = ut.update_size(image.size, self.size)
844
+ image = image.resize(size, resample = True)
845
+ matrix = ut.image_to_matrix(image)
846
+ self.set_xfrequency(0); self.set_yfrequency(0);
847
+ self.draw_matrix(matrix, marker = marker, style = style, fast = fast)
848
+ self.set_xlabel(); self.set_ylabel()