|
1 | 1 | from collections import deque
|
2 | 2 |
|
3 |
| -from . import Widget |
| 3 | +from . import ArrayWin |
4 | 4 |
|
5 |
| -HEIGHTS = "⠀", "⣀", "⣤", "⣶", "⣿" |
| 5 | +FULL = "⣿" |
| 6 | +FRACTION = "⠀", "⣀", "⣤", "⣶", "⣿" |
6 | 7 |
|
7 | 8 |
|
8 |
| -class Chart(Widget): |
9 |
| - """A Widget that provides a real-time view of some value over time. |
| 9 | +class Chart(ArrayWin): |
10 | 10 | """
|
11 |
| - def __init__(*args, values=None, maxlen=None, **kwargs): |
12 |
| - super().__init__(*args, **kwargs) |
| 11 | + A Widget that provides a real-time view of some value over time. |
13 | 12 |
|
14 |
| - if values is None: |
15 |
| - values = self.width * [0] |
16 |
| - elif len(values) < self.width: |
17 |
| - values = [0] * (self.width - len(values)) + values |
| 13 | + Notes |
| 14 | + ----- |
| 15 | + If `y_label` or `x_label` are set as any positive integer `n`, labels will be |
| 16 | + turned on and `n` labels will be printed. |
| 17 | + """ |
| 18 | + values = None |
| 19 | + maxlen = None |
| 20 | + gradient = None |
| 21 | + |
| 22 | + y_label = False |
| 23 | + y_label_width = 5 |
| 24 | + |
| 25 | + def update_geometry(self): |
| 26 | + if self.root is None: |
| 27 | + return |
| 28 | + |
| 29 | + super().update_geometry() |
18 | 30 |
|
19 |
| - if max_len is not None: |
20 |
| - values = deque(values, maxlen=maxlen) |
| 31 | + if self.values is None: |
| 32 | + self.values = self.width * [0] |
| 33 | + elif len(self.values) < self.width: |
| 34 | + self.values = [0] * (self.width - len(self.values)) + self.values |
| 35 | + |
| 36 | + if self.maxlen is not None: |
| 37 | + self.values = deque(self.values, maxlen=self.maxlen) |
21 | 38 |
|
22 | 39 | def update(self, value):
|
23 | 40 | self.values.append(value)
|
24 | 41 |
|
25 | 42 | def refresh(self):
|
26 |
| - ... |
| 43 | + n_labels = self.y_label |
| 44 | + y_label_width = self.y_label_width * bool(n_labels) + 2 |
| 45 | + h, w = self.buffer[1:, 1:].shape |
| 46 | + |
| 47 | + it = reversed(self.values) |
| 48 | + values = tuple(next(it) for _ in range(w - y_label_width)) |
| 49 | + max_v = max(values) |
| 50 | + |
| 51 | + gradient = self.gradient |
| 52 | + |
| 53 | + self[:] = " " |
| 54 | + self.colors[:] = self.color |
| 55 | + |
| 56 | + if y_label_width: |
| 57 | + self[:, y_label_width] = "|" |
| 58 | + |
| 59 | + # Print y labels |
| 60 | + for i in range(n_labels): |
| 61 | + percent_of_height = i / (n_labels - 1) |
| 62 | + value = f"{percent_of_height * max_v:.1f}" # TODO: attribute to specify places after decimal |
| 63 | + self[round((1 - percent_of_height) * h), :y_label_width - 2] = f"{value:>{y_label_width - 2}}" |
| 64 | + |
| 65 | + # Print vertical bars |
| 66 | + for i, value in enumerate(reversed(values)): |
| 67 | + percent_of_height = value / max_v |
| 68 | + whole, fraction = divmod(percent_of_height * h * 4, 4) |
| 69 | + |
| 70 | + row = round((1 - percent_of_height) * h) |
| 71 | + self[row + 1:, i + y_label_width + 1] = FULL |
| 72 | + self[row, i + y_label_width + 1] = FRACTION[round(fraction)] |
| 73 | + |
| 74 | + if gradient is not None: |
| 75 | + self.colors[row: , i + y_label_width + 1] = gradient[round(percent_of_height * (len(gradient) - 1))] |
| 76 | + |
| 77 | + super().refresh() |
0 commit comments