From ea8fa01d750ca90cc04ba91be19d1308e4c6c623 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 19 Feb 2022 00:53:55 -0500 Subject: [PATCH 01/13] Preliminary work on integral graph --- displayio_cartesian.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/displayio_cartesian.py b/displayio_cartesian.py index 7477e56..6079ab4 100644 --- a/displayio_cartesian.py +++ b/displayio_cartesian.py @@ -183,6 +183,7 @@ def __init__( nudge_x: int = 0, nudge_y: int = 0, verbose: bool = False, + fill_area: bool = False, **kwargs, ) -> None: @@ -318,6 +319,8 @@ def __init__( self._circle_palette = None self.plot_line_point = None + self._fill_area = False + @staticmethod def _get_font_height(font, scale: int) -> Tuple[int, int]: if hasattr(font, "get_bounding_box"): @@ -503,6 +506,7 @@ def _add_point(self, x: int, y: int) -> None: :return: None rtype: None """ + print("X: {}, Y: {}".format(x, y)) local_x, local_y = self._calc_local_xy(x, y) if self._verbose: print("") @@ -603,6 +607,14 @@ def update_pointer(self, x: int, y: int) -> None: self._pointer.x = self.plot_line_point[-1][0] self._pointer.y = self.plot_line_point[-1][1] + @property + def fill_area(self) -> bool: + return self._fill_area + + @fill_area.setter + def fill_area_under(self, setting: bool) -> None: + self._fill_area = setting + def add_plot_line(self, x: int, y: int) -> None: """add_plot_line function. @@ -612,10 +624,10 @@ def add_plot_line(self, x: int, y: int) -> None: :param int x: ``x`` coordinate in the local plane :param int y: ``y`` coordinate in the local plane :return: None - - rtype: None """ self._add_point(x, y) + print("CURRENT POINTS:") + print(self.plot_line_point) if len(self.plot_line_point) > 1: bitmaptools.draw_line( self._plot_bitmap, From b81f70380b6b10485b2f4021b5c987da5c9bd3f5 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 19 Feb 2022 01:28:45 -0500 Subject: [PATCH 02/13] Remove debug text --- displayio_cartesian.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/displayio_cartesian.py b/displayio_cartesian.py index 6079ab4..1feaaee 100644 --- a/displayio_cartesian.py +++ b/displayio_cartesian.py @@ -506,7 +506,7 @@ def _add_point(self, x: int, y: int) -> None: :return: None rtype: None """ - print("X: {}, Y: {}".format(x, y)) + local_x, local_y = self._calc_local_xy(x, y) if self._verbose: print("") @@ -626,8 +626,6 @@ def add_plot_line(self, x: int, y: int) -> None: :return: None """ self._add_point(x, y) - print("CURRENT POINTS:") - print(self.plot_line_point) if len(self.plot_line_point) > 1: bitmaptools.draw_line( self._plot_bitmap, From 443512dc9cd196e1a32b3081b1eec2e940c949ba Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 19 Feb 2022 01:31:16 -0500 Subject: [PATCH 03/13] Fix setter name --- displayio_cartesian.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/displayio_cartesian.py b/displayio_cartesian.py index 1feaaee..692bf45 100644 --- a/displayio_cartesian.py +++ b/displayio_cartesian.py @@ -612,7 +612,7 @@ def fill_area(self) -> bool: return self._fill_area @fill_area.setter - def fill_area_under(self, setting: bool) -> None: + def fill_area(self, setting: bool) -> None: self._fill_area = setting def add_plot_line(self, x: int, y: int) -> None: From 666a4b51b858a678d34f7fc9732813a859dfa36e Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 19 Feb 2022 01:31:46 -0500 Subject: [PATCH 04/13] Minor reformatting, remove broken Sphinx markup --- displayio_cartesian.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/displayio_cartesian.py b/displayio_cartesian.py index 692bf45..57d5695 100644 --- a/displayio_cartesian.py +++ b/displayio_cartesian.py @@ -504,7 +504,6 @@ def _add_point(self, x: int, y: int) -> None: :param int x: ``x`` coordinate in the local plane :param int y: ``y`` coordinate in the local plane :return: None - rtype: None """ local_x, local_y = self._calc_local_xy(x, y) @@ -566,6 +565,7 @@ def _add_point(self, x: int, y: int) -> None: self.height, ) ) + else: # for better error messages we check in detail what failed... if not self._check_x_in_range(x): @@ -644,8 +644,6 @@ def clear_plot_lines(self, palette_index=5): :param int palette_index: color palett index. Defaults to 5 :return: None - - rtype: None """ self.plot_line_point = None self._plot_bitmap.fill(palette_index) From 69ec0d685d3c5b3cf88fd52a1a83bef97ab12de9 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 19 Feb 2022 01:32:48 -0500 Subject: [PATCH 05/13] Add ability to graph area under plot --- displayio_cartesian.py | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/displayio_cartesian.py b/displayio_cartesian.py index 57d5695..9fa3850 100644 --- a/displayio_cartesian.py +++ b/displayio_cartesian.py @@ -635,6 +635,47 @@ def add_plot_line(self, x: int, y: int) -> None: self.plot_line_point[-1][1], 1, ) + # Plot area under graph + if self._fill_area: + + delta_x = self.plot_line_point[-2][0] - self.plot_line_point[-1][0] + delta_y = self.plot_line_point[-2][1] - self.plot_line_point[-1][1] + delta_y_product = self.plot_line_point[-1][1] * self.plot_line_point[-2][1] + + if delta_x == 0: + return + + slope = delta_y/delta_x + above_x_axis = False if slope > 0 else True + + if delta_y_product < 0: + # TODO: Area needs to be split into two triangles + + c = self.plot_line_point[-1][1] + zero_point_x = (-1 * c)/slope + + self._draw_area_under(self.plot_line_point[-2], (zero_point_x, 0)) + self._draw_area_under((zero_point_x, 0), self.plot_line_point[-1]) + + else: + + point_to_check = self.plot_line_point[-1] if self.plot_line_point[-1][1] != 0 else self.plot_line_point[-2] + above_x_axis = point_to_check[1] > 0 + + self._draw_area_under(self.plot_line_point[-2], self.plot_line_point[-1]) + + def _draw_area_under(self, xy0: Tuple[float, float], xy1: Tuple[float, float]) -> None: + + _, plot_y_zero = self._calc_local_xy(0, 0) + + delta_x = self.plot_line_point[-2][0] - self.plot_line_point[-1][0] + delta_y = self.plot_line_point[-2][1] - self.plot_line_point[-1][1] + slope = delta_y/delta_x + + for pixel_x in range(xy0[0], xy1[0]+1): + if pixel_x != xy1[0]: + pixel_y = round(slope*(pixel_x-xy1[0]) + xy1[1]) + bitmaptools.draw_line(self._plot_bitmap, pixel_x, pixel_y, pixel_x, plot_y_zero, 1) def clear_plot_lines(self, palette_index=5): """clear_plot_lines function. From 965fda74baea802c0f97c22c9a99163d77e9b3c4 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 19 Feb 2022 01:41:48 -0500 Subject: [PATCH 06/13] Linted and reformatted per pre-commit --- displayio_cartesian.py | 44 ++++++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/displayio_cartesian.py b/displayio_cartesian.py index 9fa3850..e0e8e9f 100644 --- a/displayio_cartesian.py +++ b/displayio_cartesian.py @@ -319,7 +319,7 @@ def __init__( self._circle_palette = None self.plot_line_point = None - self._fill_area = False + self._fill_area = fill_area @staticmethod def _get_font_height(font, scale: int) -> Tuple[int, int]: @@ -595,7 +595,6 @@ def update_pointer(self, x: int, y: int) -> None: :param int x: ``x`` coordinate in the local plane :param int y: ``y`` coordinate in the local plane :return: None - rtype: None """ self._add_point(x, y) if not self._pointer: @@ -609,6 +608,7 @@ def update_pointer(self, x: int, y: int) -> None: @property def fill_area(self) -> bool: + """Whether the area under the graph (integral) should be shaded""" return self._fill_area @fill_area.setter @@ -640,42 +640,52 @@ def add_plot_line(self, x: int, y: int) -> None: delta_x = self.plot_line_point[-2][0] - self.plot_line_point[-1][0] delta_y = self.plot_line_point[-2][1] - self.plot_line_point[-1][1] - delta_y_product = self.plot_line_point[-1][1] * self.plot_line_point[-2][1] + delta_y_product = ( + self.plot_line_point[-1][1] * self.plot_line_point[-2][1] + ) if delta_x == 0: return - slope = delta_y/delta_x - above_x_axis = False if slope > 0 else True + slope = delta_y / delta_x if delta_y_product < 0: - # TODO: Area needs to be split into two triangles - - c = self.plot_line_point[-1][1] - zero_point_x = (-1 * c)/slope + # TODO: Area needs to be split into two triangles + + intercept = self.plot_line_point[-1][1] + zero_point_x = (-1 * intercept) / slope self._draw_area_under(self.plot_line_point[-2], (zero_point_x, 0)) self._draw_area_under((zero_point_x, 0), self.plot_line_point[-1]) else: - point_to_check = self.plot_line_point[-1] if self.plot_line_point[-1][1] != 0 else self.plot_line_point[-2] - above_x_axis = point_to_check[1] > 0 + point_to_check = ( + self.plot_line_point[-1] + if self.plot_line_point[-1][1] != 0 + else self.plot_line_point[-2] + ) - self._draw_area_under(self.plot_line_point[-2], self.plot_line_point[-1]) + self._draw_area_under( + self.plot_line_point[-2], self.plot_line_point[-1] + ) - def _draw_area_under(self, xy0: Tuple[float, float], xy1: Tuple[float, float]) -> None: + def _draw_area_under( + self, xy0: Tuple[float, float], xy1: Tuple[float, float] + ) -> None: _, plot_y_zero = self._calc_local_xy(0, 0) delta_x = self.plot_line_point[-2][0] - self.plot_line_point[-1][0] delta_y = self.plot_line_point[-2][1] - self.plot_line_point[-1][1] - slope = delta_y/delta_x + slope = delta_y / delta_x - for pixel_x in range(xy0[0], xy1[0]+1): + for pixel_x in range(xy0[0], xy1[0] + 1): if pixel_x != xy1[0]: - pixel_y = round(slope*(pixel_x-xy1[0]) + xy1[1]) - bitmaptools.draw_line(self._plot_bitmap, pixel_x, pixel_y, pixel_x, plot_y_zero, 1) + pixel_y = round(slope * (pixel_x - xy1[0]) + xy1[1]) + bitmaptools.draw_line( + self._plot_bitmap, pixel_x, pixel_y, pixel_x, plot_y_zero, 1 + ) def clear_plot_lines(self, palette_index=5): """clear_plot_lines function. From 2d0b356f4c47a96f32e4b5a868af7ff01259788a Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 19 Feb 2022 01:45:52 -0500 Subject: [PATCH 07/13] Remove TODO --- displayio_cartesian.py | 1 - 1 file changed, 1 deletion(-) diff --git a/displayio_cartesian.py b/displayio_cartesian.py index e0e8e9f..5d57a05 100644 --- a/displayio_cartesian.py +++ b/displayio_cartesian.py @@ -650,7 +650,6 @@ def add_plot_line(self, x: int, y: int) -> None: slope = delta_y / delta_x if delta_y_product < 0: - # TODO: Area needs to be split into two triangles intercept = self.plot_line_point[-1][1] zero_point_x = (-1 * intercept) / slope From 0f1c50e6fcf774ef5160c6259e668306e6de868c Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 19 Feb 2022 01:46:05 -0500 Subject: [PATCH 08/13] Remove unused variable --- displayio_cartesian.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/displayio_cartesian.py b/displayio_cartesian.py index 5d57a05..46caf0a 100644 --- a/displayio_cartesian.py +++ b/displayio_cartesian.py @@ -659,12 +659,6 @@ def add_plot_line(self, x: int, y: int) -> None: else: - point_to_check = ( - self.plot_line_point[-1] - if self.plot_line_point[-1][1] != 0 - else self.plot_line_point[-2] - ) - self._draw_area_under( self.plot_line_point[-2], self.plot_line_point[-1] ) From e21abd7c5abedd398d2c5147d9128e6077e25559 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 19 Feb 2022 01:46:16 -0500 Subject: [PATCH 09/13] Add missing type annotation --- displayio_cartesian.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/displayio_cartesian.py b/displayio_cartesian.py index 46caf0a..763ced9 100644 --- a/displayio_cartesian.py +++ b/displayio_cartesian.py @@ -680,7 +680,7 @@ def _draw_area_under( self._plot_bitmap, pixel_x, pixel_y, pixel_x, plot_y_zero, 1 ) - def clear_plot_lines(self, palette_index=5): + def clear_plot_lines(self, palette_index: int = 5) -> None: """clear_plot_lines function. clear all added lines From f55bb166714c9b6a7a0456dab055ba1d2db08032 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 19 Feb 2022 10:32:24 -0500 Subject: [PATCH 10/13] Add example file for creating normal distribution --- examples/displayio_cartesion_fillarea.py | 60 ++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 examples/displayio_cartesion_fillarea.py diff --git a/examples/displayio_cartesion_fillarea.py b/examples/displayio_cartesion_fillarea.py new file mode 100644 index 0000000..4ff194c --- /dev/null +++ b/examples/displayio_cartesion_fillarea.py @@ -0,0 +1,60 @@ +# SPDX-FileCopyrightText: 2021 Stefan Krüger +# +# SPDX-License-Identifier: MIT +############################# +""" +This is a basic demonstration of a Cartesian widget for line-ploting +""" + +import time +import board +import displayio +import random +from displayio_cartesian import Cartesian + +# create the display on the PyPortal or Clue or PyBadge(for example) +display = board.DISPLAY +# otherwise change this to setup the display +# for display chip driver and pinout you have (e.g. ILI9341) + +# Generate data - here we'll make a normal distribution +X_LOWER_BOUND = 0 +X_UPPER_BOUND = 10 +raw_data = [] +data = [] + +for _ in range(100): + data_point = int(10 * random.random(X_LOWER_BOUND, X_UPPER_BOUND + 1)) + raw_data.append(data_point) + +y_upper_bound = 0 +for value in range(len(X_UPPER_BOUND + 1)): + value_count = raw_data.count(value) + data.append(value_count) + y_upper_bound = max(y_upper_bound, value_count) + +# pybadge display: 160x128 +# Create a Cartesian widget +# https://circuitpython.readthedocs.io/projects/displayio-layout/en/latest/api.html#module-adafruit_displayio_layout.widgets.cartesian +my_plane = Cartesian( + x=15, # x position for the plane + y=2, # y plane position + width=140, # display width + height=105, # display height + xrange=(X_LOWER_BOUND, X_UPPER_BOUND), # x range + yrange=(0, y_upper_bound), # y range + fill_area=True +) + +my_group = displayio.Group() +my_group.append(my_plane) +display.show(my_group) # add high level Group to the display + +print("examples/displayio_layout_cartesian_fillarea.py") + +for x, y in data: + my_plane.add_plot_line(x, y) + time.sleep(0.5) + +while True: + pass From 838a0678f1d6db251336b9dc6010acc73a186d39 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 19 Feb 2022 10:33:17 -0500 Subject: [PATCH 11/13] Reformatted new example file --- examples/displayio_cartesion_fillarea.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/displayio_cartesion_fillarea.py b/examples/displayio_cartesion_fillarea.py index 4ff194c..976416e 100644 --- a/examples/displayio_cartesion_fillarea.py +++ b/examples/displayio_cartesion_fillarea.py @@ -43,7 +43,7 @@ height=105, # display height xrange=(X_LOWER_BOUND, X_UPPER_BOUND), # x range yrange=(0, y_upper_bound), # y range - fill_area=True + fill_area=True, ) my_group = displayio.Group() From 25c183a6c3bcc4563757925c212e2a42909b397c Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 19 Feb 2022 10:37:10 -0500 Subject: [PATCH 12/13] Bring y-axis values into full view --- examples/displayio_cartesion_fillarea.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/displayio_cartesion_fillarea.py b/examples/displayio_cartesion_fillarea.py index 976416e..da1ac4d 100644 --- a/examples/displayio_cartesion_fillarea.py +++ b/examples/displayio_cartesion_fillarea.py @@ -37,9 +37,9 @@ # Create a Cartesian widget # https://circuitpython.readthedocs.io/projects/displayio-layout/en/latest/api.html#module-adafruit_displayio_layout.widgets.cartesian my_plane = Cartesian( - x=15, # x position for the plane + x=20, # x position for the plane y=2, # y plane position - width=140, # display width + width=135, # display width height=105, # display height xrange=(X_LOWER_BOUND, X_UPPER_BOUND), # x range yrange=(0, y_upper_bound), # y range From b3c3e00c5622c9edeae00977f33e43f6f2345b76 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Sat, 19 Feb 2022 10:39:26 -0500 Subject: [PATCH 13/13] Update example file to parameter based example --- examples/displayio_cartesion_fillarea.py | 26 +++++++++++++++--------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/examples/displayio_cartesion_fillarea.py b/examples/displayio_cartesion_fillarea.py index da1ac4d..81eedf3 100644 --- a/examples/displayio_cartesion_fillarea.py +++ b/examples/displayio_cartesion_fillarea.py @@ -18,19 +18,25 @@ # for display chip driver and pinout you have (e.g. ILI9341) # Generate data - here we'll make a normal distribution -X_LOWER_BOUND = 0 -X_UPPER_BOUND = 10 raw_data = [] data = [] +MAX_DICE_SIDE = 20 +NUMBER_OF_DICE = 10 +NUMBER_OF_ROLLS = 500 -for _ in range(100): - data_point = int(10 * random.random(X_LOWER_BOUND, X_UPPER_BOUND + 1)) - raw_data.append(data_point) +for _ in range(NUMBER_OF_ROLLS): + # Simulate equivalent dice rolls + sum_random = 0 + for _ in range(NUMBER_OF_DICE): + sum_random += random.uniform(1, MAX_DICE_SIDE) + average_random = sum_random // NUMBER_OF_DICE + raw_data.append(average_random) +# Calculate the number of each roll result and pair with itself y_upper_bound = 0 -for value in range(len(X_UPPER_BOUND + 1)): - value_count = raw_data.count(value) - data.append(value_count) +for value in range(MAX_DICE_SIDE + 1): + value_count = raw_data.count(value) / 10 + data.append((value, value_count)) y_upper_bound = max(y_upper_bound, value_count) # pybadge display: 160x128 @@ -41,7 +47,7 @@ y=2, # y plane position width=135, # display width height=105, # display height - xrange=(X_LOWER_BOUND, X_UPPER_BOUND), # x range + xrange=(0, MAX_DICE_SIDE), # x range yrange=(0, y_upper_bound), # y range fill_area=True, ) @@ -54,7 +60,7 @@ for x, y in data: my_plane.add_plot_line(x, y) - time.sleep(0.5) + time.sleep(0.1) while True: pass