Skip to content

Commit a8698be

Browse files
committed
y axis labels implemented; test added
1 parent 2cbb803 commit a8698be

File tree

3 files changed

+81
-13
lines changed

3 files changed

+81
-13
lines changed

examples/chart_test.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from random import random
2+
3+
from nurses import ScreenManager, colors
4+
5+
6+
with ScreenManager() as sm:
7+
blue_to_purple = colors.gradient(20, (0, 255, 255), (103, 15, 215), "blue_to_purple")
8+
chart = sm.root.new_widget(create_with="Chart", maxlen=200, gradient=blue_to_purple, size_hint=(.5, .5), y_label=5)
9+
10+
def update():
11+
chart.update(random() * 50)
12+
sm.root.refresh()
13+
14+
sm.schedule(update)
15+
sm.run()

nurses/widgets/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
from .textbox import Textbox
1515
from .text_pad import TextPad
1616

17+
from .chart import Chart
18+
1719
# Layouts
1820
from .grid import Grid
1921
from .splits import HSplit, VSplit

nurses/widgets/chart.py

Lines changed: 64 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,77 @@
11
from collections import deque
22

3-
from . import Widget
3+
from . import ArrayWin
44

5-
HEIGHTS = "⠀", "⣀", "⣤", "⣶", "⣿"
5+
FULL = "⣿"
6+
FRACTION = "⠀", "⣀", "⣤", "⣶", "⣿"
67

78

8-
class Chart(Widget):
9-
"""A Widget that provides a real-time view of some value over time.
9+
class Chart(ArrayWin):
1010
"""
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.
1312
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()
1830

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)
2138

2239
def update(self, value):
2340
self.values.append(value)
2441

2542
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

Comments
 (0)