2525Various common shapes for use with displayio - Sparkline!
2626
2727
28- * Author(s): Kevin Matocha
28+ * Author(s): Kevin Matocha, Maciej Sokołowski
2929
3030Implementation Notes
3131--------------------
4747from adafruit_display_shapes .line import Line
4848
4949
50+ class _CyclicBuffer ():
51+ def __init__ (self , size : int ) -> None :
52+ self ._buffer = [None ] * size
53+ self ._start = 0 # between 0 and size-1
54+ self ._end = 0 # between 0 and 2*size-1
55+
56+ def push (self , value : float ) -> None :
57+ if self .len () == len (self ._buffer ):
58+ raise RuntimeError ("Trying to push to full buffer" )
59+ self ._buffer [self ._end % len (self ._buffer )] = value
60+ self ._end += 1
61+
62+ def pop (self ) -> float :
63+ if self .len () == 0 :
64+ raise RuntimeError ("Trying to pop from empty buffer" )
65+ result = self .first ()
66+ self ._start += 1
67+ if self ._start == len (self ._buffer ):
68+ self ._start -= len (self ._buffer )
69+ self ._end -= len (self ._buffer )
70+ return result
71+
72+ def first (self ) -> float :
73+ if self .len () == 0 :
74+ return None
75+ return self ._buffer [self ._start ]
76+
77+ def last (self ) -> float :
78+ if self .len () == 0 :
79+ return None
80+ return self ._buffer [(self ._end - 1 ) % len (self ._buffer )]
81+
82+ def len (self ) -> int :
83+ return self ._end - self ._start
84+
85+ def clear (self ) -> None :
86+ self ._start = 0
87+ self ._end = 0
88+
89+ def values (self ) -> List [float ]:
90+ if self .len () == 0 :
91+ return []
92+ start = self ._start
93+ end = self ._end % len (self ._buffer )
94+ if start < end :
95+ return self ._buffer [start :end ]
96+ else :
97+ return self ._buffer [start :] + self ._buffer [:end ]
98+
99+
50100class Sparkline (displayio .Group ):
51101 # pylint: disable=too-many-arguments
52102 """A sparkline graph.
@@ -85,7 +135,7 @@ def __init__(
85135 self .height = height # in pixels
86136 self .color = color #
87137 self ._max_items = max_items # maximum number of items in the list
88- self ._spark_list = [] # list containing the values
138+ self ._buffer = _CyclicBuffer ( self . _max_items )
89139 self .dyn_xpitch = dyn_xpitch
90140 if not dyn_xpitch :
91141 self ._xpitch = (width - 1 ) / (self ._max_items - 1 )
@@ -103,11 +153,11 @@ def __init__(
103153 super ().__init__ (x = x , y = y ) # self is a group of lines
104154
105155 def clear_values (self ) -> None :
106- """Removes all values from the _spark_list list and removes all lines in the group"""
156+ """Clears _buffer and removes all lines in the group"""
107157
108158 for _ in range (len (self )): # remove all items from the current group
109159 self .pop ()
110- self ._spark_list = [] # empty the list
160+ self ._buffer . clear ()
111161 self ._redraw = True
112162
113163 def add_value (self , value : float , update : bool = True ) -> None :
@@ -123,16 +173,16 @@ def add_value(self, value: float, update: bool = True) -> None:
123173
124174 if value is not None :
125175 if (
126- len ( self ._spark_list ) >= self ._max_items
176+ self ._buffer . len ( ) >= self ._max_items
127177 ): # if list is full, remove the first item
128- first = self ._spark_list .pop (0 )
178+ first = self ._buffer .pop ()
129179 # check if boundaries have to be updated
130180 if self .y_min is None and first == self .y_bottom :
131- self .y_bottom = min (self ._spark_list )
181+ self .y_bottom = min (self ._buffer . values () )
132182 if self .y_max is None and first == self .y_top :
133- self .y_top = max (self ._spark_list )
183+ self .y_top = max (self ._buffer . values () )
134184 self ._redraw = True
135- self ._spark_list . append (value )
185+ self ._buffer . push (value )
136186
137187 if self .y_min is None :
138188 self ._redraw = self ._redraw or value < self .y_bottom
@@ -194,9 +244,9 @@ def update(self) -> None:
194244 """Update the drawing of the sparkline."""
195245
196246 # bail out early if we only have a single point
197- n_points = len ( self ._spark_list )
247+ n_points = self ._buffer . len ( )
198248 if n_points < 2 :
199- self ._last = [0 , self ._spark_list [ 0 ] ]
249+ self ._last = [0 , self ._buffer . first () ]
200250 return
201251
202252 if self .dyn_xpitch :
@@ -213,15 +263,15 @@ def update(self) -> None:
213263 y_m1 = self ._last [1 ]
214264 # end of new line (new point, read as "x(0)")
215265 x_0 = int (x_m1 + xpitch )
216- y_0 = self ._spark_list [ - 1 ]
266+ y_0 = self ._buffer . last ()
217267 self ._plotline (x_m1 , y_m1 , x_0 , y_0 )
218268 return
219269
220270 self ._redraw = False # reset, since we now redraw everything
221271 for _ in range (len (self )): # remove all items from the current group
222272 self .pop ()
223273
224- for count , value in enumerate (self ._spark_list ):
274+ for count , value in enumerate (self ._buffer . values () ):
225275 if count == 0 :
226276 pass # don't draw anything for a first point
227277 else :
@@ -284,4 +334,4 @@ def update(self) -> None:
284334 def values (self ) -> List [float ]:
285335 """Returns the values displayed on the sparkline."""
286336
287- return self ._spark_list
337+ return self ._buffer . values ()
0 commit comments