1from plotext._default import default_monitor_class
2from plotext._matrix import matrix_class
3from plotext._build import build_class
4import plotext._utility as ut
5from copy import deepcopy
6import 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
10class 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()