Skip to content

Commit

Permalink
First attempt at memory efficiency with use of fixed size circular bu…
Browse files Browse the repository at this point in the history
…ffer pimoroni#19.

A single list is now preallocated at a fixed size and not replaced with slices to reduce continual allocations and fragmentation.
Includes fix for calling update() several times before draw().
The full_refresh implementation now only clears the bitmap when it scrolls.
Removed the commented-out code.
  • Loading branch information
Kevin J Walters committed Jan 6, 2021
1 parent 3b1a1d8 commit e4d8aa9
Showing 1 changed file with 70 additions and 65 deletions.
135 changes: 70 additions & 65 deletions library/pimoroni_envirowing/screen/plotter.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
class ScreenPlotter:
def __init__(self, colours, bg_colour=None, max_value=None, min_value=None,
display=None, top_space=None, width=None, height=None):
display=None, top_space=None, width=None, height=None,
extra_data=16):
"""__init__
:param list colours: a list of colours to use for data lines
Expand All @@ -14,6 +15,12 @@ def __init__(self, colours, bg_colour=None, max_value=None, min_value=None,
:param display: a supplied display object (creates one if not supplied)
:param int top_space: the number of pixels in height to reserve for titles and labels (and not draw lines in)
:param int width: the width in pixels of the plot (uses display width if not supplied)
:param int height: the height in pixels of the plot (uses display width if not supplied)
:param int extra_data: the number of updates that can be applied before a draw (16 if not supplied)
"""
import displayio

Expand All @@ -24,6 +31,7 @@ def __init__(self, colours, bg_colour=None, max_value=None, min_value=None,
else:
self.display = display

self.num_lines = len(colours)
self.num_colours = len(colours) + 1

plot_width = display.width if width is None else width
Expand Down Expand Up @@ -67,10 +75,17 @@ def __init__(self, colours, bg_colour=None, max_value=None, min_value=None,

self.value_range = self.max_value - self.min_value

self.data_points = []
# The extra list element is a gap used for this implementation
# of a circular buffer
self.data_len = plot_width + extra_data + 1
self.data_points = [None] * self.data_len
self.data_head = 0
self.data_tail = 0
self.display_tail = 0
self.displayed_points = 0

def remap(self, Value, OldMin, OldMax, NewMin, NewMax):
return (((Value - OldMin) * (NewMax - NewMin)) / (OldMax - OldMin)) + NewMin
def remap(self, value, old_min, old_max, new_min, new_max):
return (((value - old_min) * (new_max - new_min)) / (old_max - old_min)) + new_min

def update(self, *values, draw=True):
"""update
Expand All @@ -79,90 +94,80 @@ def update(self, *values, draw=True):
:param bool draw: if set to false, will not draw the line graph, just update the data points
"""

values = list(values)

if len(values) > (self.num_colours - 1):
raise Exception("The list of values shouldn't have more entries than the list of colours")
if self.data_tail == (self.data_head - 1) % self.data_len:
raise OverflowError("data_points full")

if len(values) != self.num_lines:
raise Exception("The list of values should match the number of colours")

for i, j in enumerate(values):
if j > self.max_value:
values[i] = self.max_value
if j < self.min_value:
values[i] = self.min_value

"""
#TODO: Scroll the screen here
for i in range(1, (self.bitmap.width*self.bitmap.height)):
if (i + 1) % self.bitmap.width != 0:
self.bitmap[i] = self.bitmap[i+1]
else:
self.bitmap[i] = 0
for index,value in enumerate(values):
self.bitmap[(self.bitmap.width - 1),round(((value - self.min_value) / self.value_range) * self.bitmap.height)] = index + 1
"""
if not(len(self.data_points) > self.bitmap.width):
self.old_points = self.data_points

self.data_points.append(values)

if len(self.data_points) > (self.bitmap.width + 1):
difflen = len(self.data_points) - (self.bitmap.width + 1)
self.data_points = self.data_points[difflen:]
self.data_points[self.data_tail] = values
self.data_tail = (self.data_tail + 1) % self.data_len

if draw:
self.draw()

def draw(self, full_refresh=False):
"""draw
:param bool full_refresh: select clear bitmap algorithm when scrolling,
default is to undraw individual prevously drawn pixels
"""
new_points = (self.data_tail - self.display_tail if self.data_tail >= self.display_tail
else self.data_len - self.display_tail + self.data_tail)
if new_points == 0:
return

restore_auto_refresh = False
if self.display.auto_refresh:
self.display.auto_refresh = False
restore_auto_refresh = True

if not full_refresh:
if len(self.data_points) > self.bitmap.width:
difflen = len(self.data_points) - self.bitmap.width
self.data_points = self.data_points[difflen:]

difference = []

for i, j in zip(self.data_points, self.old_points):
subarray = []
for value in zip(i, j):
subarray.append((value[0] - value[1]))
difference.append(subarray)

for index, value in enumerate(difference):
for subindex, point in enumerate(value):
if point != 0:
# self.bitmap[index,round(((old_points[index][subindex] - self.min_value) / self.value_range) * -(self.bitmap.height -1) + (self.bitmap.height -1))] = 0
self.bitmap[index, round(self.remap(self.old_points[index][subindex], self.min_value, self.max_value, self.bitmap.height - 1, 0))] = 0
for subindex, point in enumerate(value):
# self.bitmap[index,round(((self.data_points[index][subindex] - self.min_value) / self.value_range) * -(self.bitmap.height -1) + (self.bitmap.height -1))] = subindex + 1
self.bitmap[index, round(self.remap(self.data_points[index][subindex], self.min_value, self.max_value, self.bitmap.height - 1, 0))] = subindex + 1
num_points = (self.data_tail - self.data_head if self.data_tail >= self.data_head
else self.data_len - self.data_head + self.data_tail)
heightm1 = self.bitmap.height - 1
if num_points > self.bitmap.width:
# scrolling
if not full_refresh:
for index, dpnew_index in zip(range(self.bitmap.width),
range(self.data_tail - self.bitmap.width, self.data_tail)):
# undraw old pixels if they were in a different position
for subindex, new_value in enumerate(self.data_points[dpnew_index]):
old_values = self.data_points[self.display_tail - self.displayed_points + index] if index < self.displayed_points else None
if old_values is not None and old_values[subindex] != new_value:
self.bitmap[index, round(self.remap(old_values[subindex], self.min_value, self.max_value, heightm1, 0))] = 0
# draw new pixels - this must be performed unconditionally
# to cater for overlapping lines
for subindex, new_value in enumerate(self.data_points[dpnew_index]):
self.bitmap[index, round(self.remap(new_value, self.min_value, self.max_value, heightm1, 0))] = subindex + 1
else:
try:
for subindex, point in enumerate(self.data_points[-1]):
# self.bitmap[(len(self.data_points) - 1),round(((point - self.min_value) / self.value_range) * -(self.bitmap.height -1) + (self.bitmap.height -1))] = subindex + 1
self.bitmap[(len(self.data_points) - 1), round(self.remap(point, self.min_value, self.max_value, self.bitmap.height - 1, 0))] = subindex + 1
except IndexError:
print("You shouldn't call draw() without calling update() first")
else:
try:
if len(self.data_points) > self.bitmap.width:
difflen = len(self.data_points) - self.bitmap.width
self.data_points = self.data_points[difflen:]

# clear bitmap
self.bitmap.fill(0)

for index, value in enumerate(self.data_points):
for subindex, point in enumerate(value):
# self.bitmap[(len(self.data_points) - 1), round(((point - self.min_value) / self.value_range) * -(self.bitmap.height -1) + (self.bitmap.height -1))] = subindex + 1
self.bitmap[index, round(self.remap(point, self.min_value, self.max_value, self.bitmap.height - 1, 0))] = subindex + 1
except IndexError:
print("You shouldn't call draw() without calling update() first")
data_start = self.data_tail - self.bitmap.width
for index in range(self.bitmap.width):
for subindexp1, point in enumerate(self.data_points[data_start + index],
start=1):
self.bitmap[index, round(self.remap(point, self.min_value, self.max_value, heightm1, 0))] = subindexp1
self.displayed_points = self.bitmap.width
else:
# not yet scrolling
for index in range(self.data_head + self.displayed_points, self.data_tail):
for subindexp1, point in enumerate(self.data_points[index], start=1):
self.bitmap[index, round(self.remap(point, self.min_value, self.max_value, heightm1, 0))] = subindexp1
self.displayed_points = self.data_tail

# remove data points which have scrolled off the screen
if num_points > self.bitmap.width:
self.data_head = (self.data_tail - self.bitmap.width) % self.data_len
self.display_tail = self.data_tail

if restore_auto_refresh:
self.display.auto_refresh = True

0 comments on commit e4d8aa9

Please sign in to comment.