2525Various common shapes for use with displayio - Sparkline!
2626
2727
28- * Author(s): Kevin Matocha, Maciej Sokołowski
28+ * Author(s): Kevin Matocha
2929
3030Implementation Notes
3131--------------------
4444except ImportError :
4545 pass
4646import displayio
47- from adafruit_display_shapes .line import Line
47+ from adafruit_display_shapes .polygon import Polygon
4848
4949
5050class _CyclicBuffer ():
@@ -62,23 +62,13 @@ def push(self, value: float) -> None:
6262 def pop (self ) -> float :
6363 if self .len () == 0 :
6464 raise RuntimeError ("Trying to pop from empty buffer" )
65- result = self .first ()
65+ result = self ._buffer [ self . _start ]
6666 self ._start += 1
6767 if self ._start == len (self ._buffer ):
6868 self ._start -= len (self ._buffer )
6969 self ._end -= len (self ._buffer )
7070 return result
7171
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-
8272 def len (self ) -> int :
8373 return self ._end - self ._start
8474
@@ -147,18 +137,17 @@ def __init__(
147137 self .y_top = y_max
148138 # y_top: The actual minimum value of the vertical scale, will be
149139 # updated if autorange
150- self ._redraw = True # _redraw: redraw primitives
151- self ._last = [] # _last: last point of sparkline
140+ self ._points = [] # _points: all points of sparkline
152141
153- super ().__init__ (x = x , y = y ) # self is a group of lines
142+ super ().__init__ (x = x , y = y ) # self is a group of single Polygon
143+ # (TODO: it has one element, maybe group is no longer needed?)
154144
155145 def clear_values (self ) -> None :
156146 """Clears _buffer and removes all lines in the group"""
157147
158148 for _ in range (len (self )): # remove all items from the current group
159149 self .pop ()
160150 self ._buffer .clear ()
161- self ._redraw = True
162151
163152 def add_value (self , value : float , update : bool = True ) -> None :
164153 """Add a value to the sparkline.
@@ -181,16 +170,13 @@ def add_value(self, value: float, update: bool = True) -> None:
181170 self .y_bottom = min (self ._buffer .values ())
182171 if self .y_max is None and first == self .y_top :
183172 self .y_top = max (self ._buffer .values ())
184- self ._redraw = True
185173 self ._buffer .push (value )
186174
187175 if self .y_min is None :
188- self ._redraw = self ._redraw or value < self .y_bottom
189176 self .y_bottom = (
190177 value if not self .y_bottom else min (value , self .y_bottom )
191178 )
192179 if self .y_max is None :
193- self ._redraw = self ._redraw or value > self .y_top
194180 self .y_top = value if not self .y_top else max (value , self .y_top )
195181
196182 if update :
@@ -218,25 +204,23 @@ def _xintercept(
218204 ) / slope # calculate the x-intercept at position y=horizontalY
219205 return int (xint )
220206
221- def _plotline (
207+ def _add_point (
222208 self ,
223- x_1 : int ,
224- last_value : float ,
225- x_2 : int ,
209+ x : int ,
226210 value : float ,
227211 ) -> None :
228212
229213 # Guard for y_top and y_bottom being the same
230214 if self .y_top == self .y_bottom :
231- y_2 = int (0.5 * self .height )
232- y_1 = int (0.5 * self .height )
215+ y = int (0.5 * self .height )
233216 else :
234- y_2 = int (self .height * (self .y_top - value ) / (self .y_top - self .y_bottom ))
235- y_1 = int (
236- self .height * (self .y_top - last_value ) / (self .y_top - self .y_bottom )
237- )
238- self .append (Line (x_1 , y_1 , x_2 , y_2 , self .color )) # plot the line
239- self ._last = [x_2 , value ]
217+ y = int (self .height * (self .y_top - value ) / (self .y_top - self .y_bottom ))
218+ self ._points .append ((x , y ))
219+
220+ def _draw (self ) -> None :
221+ while (len (self )):
222+ self .pop ()
223+ self .append (Polygon (self ._points , outline = self .color , close = False )) # plot the polyline
240224
241225 # pylint: disable= too-many-branches, too-many-nested-blocks, too-many-locals, too-many-statements
242226
@@ -246,42 +230,27 @@ def update(self) -> None:
246230 # bail out early if we only have a single point
247231 n_points = self ._buffer .len ()
248232 if n_points < 2 :
249- self ._last = [0 , self ._buffer .first ()]
250233 return
251234
252235 if self .dyn_xpitch :
253236 # this is a float, only make int when plotting the line
254237 xpitch = (self .width - 1 ) / (n_points - 1 )
255- self ._redraw = True
256238 else :
257239 xpitch = self ._xpitch
258240
259- # only add new segment if redrawing is not necessary
260- if not self ._redraw :
261- # end of last line (last point, read as "x(-1)")
262- x_m1 = self ._last [0 ]
263- y_m1 = self ._last [1 ]
264- # end of new line (new point, read as "x(0)")
265- x_0 = int (x_m1 + xpitch )
266- y_0 = self ._buffer .last ()
267- self ._plotline (x_m1 , y_m1 , x_0 , y_0 )
268- return
269-
270- self ._redraw = False # reset, since we now redraw everything
271- for _ in range (len (self )): # remove all items from the current group
272- self .pop ()
241+ self ._points = [] # remove all points
273242
274243 for count , value in enumerate (self ._buffer .values ()):
275244 if count == 0 :
276- pass # don't draw anything for a first point
245+ self . _add_point ( 0 , value )
277246 else :
278- x_2 = int (xpitch * count )
279- x_1 = int (xpitch * (count - 1 ))
247+ x = int (xpitch * count )
248+ last_x = int (xpitch * (count - 1 ))
280249
281250 if (self .y_bottom <= last_value <= self .y_top ) and (
282251 self .y_bottom <= value <= self .y_top
283252 ): # both points are in range, plot the line
284- self ._plotline ( x_1 , last_value , x_2 , value )
253+ self ._add_point ( x , value )
285254
286255 else : # at least one point is out of range, clip one or both ends the line
287256 if ((last_value > self .y_top ) and (value > self .y_top )) or (
@@ -291,45 +260,34 @@ def update(self) -> None:
291260 pass
292261 else :
293262 xint_bottom = self ._xintercept (
294- x_1 , last_value , x_2 , value , self .y_bottom
263+ last_x , last_value , x , value , self .y_bottom
295264 ) # get possible new x intercept points
296265 xint_top = self ._xintercept (
297- x_1 , last_value , x_2 , value , self .y_top
266+ last_x , last_value , x , value , self .y_top
298267 ) # on the top and bottom of range
299268 if (xint_bottom is None ) or (
300269 xint_top is None
301270 ): # out of range doublecheck
302271 pass
303272 else :
304273 # Initialize the adjusted values as the baseline
305- adj_x_1 = x_1
306- adj_last_value = last_value
307- adj_x_2 = x_2
274+ adj_x = x
308275 adj_value = value
309276
310277 if value > last_value : # slope is positive
311- if xint_bottom >= x_1 : # bottom is clipped
312- adj_x_1 = xint_bottom
313- adj_last_value = self .y_bottom # y_1
314- if xint_top <= x_2 : # top is clipped
315- adj_x_2 = xint_top
316- adj_value = self .y_top # y_2
278+ if xint_top <= x : # top is clipped
279+ adj_x = xint_top
280+ adj_value = self .y_top # y
317281 else : # slope is negative
318- if xint_top >= x_1 : # top is clipped
319- adj_x_1 = xint_top
320- adj_last_value = self .y_top # y_1
321- if xint_bottom <= x_2 : # bottom is clipped
322- adj_x_2 = xint_bottom
323- adj_value = self .y_bottom # y_2
324-
325- self ._plotline (
326- adj_x_1 ,
327- adj_last_value ,
328- adj_x_2 ,
329- adj_value ,
330- )
282+ if xint_bottom <= x : # bottom is clipped
283+ adj_x = xint_bottom
284+ adj_value = self .y_bottom # y
285+
286+ self ._add_point (adj_x , adj_value )
331287
332288 last_value = value # store value for the next iteration
289+
290+ self ._draw ()
333291
334292 def values (self ) -> List [float ]:
335293 """Returns the values displayed on the sparkline."""
0 commit comments