diff --git a/displayio_cartesian.py b/displayio_cartesian.py index 7477e56..763ced9 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 = fill_area + @staticmethod def _get_font_height(font, scale: int) -> Tuple[int, int]: if hasattr(font, "get_bounding_box"): @@ -501,8 +504,8 @@ 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) if self._verbose: print("") @@ -562,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): @@ -591,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: @@ -603,6 +606,15 @@ 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: + """Whether the area under the graph (integral) should be shaded""" + return self._fill_area + + @fill_area.setter + def fill_area(self, setting: bool) -> None: + self._fill_area = setting + def add_plot_line(self, x: int, y: int) -> None: """add_plot_line function. @@ -612,8 +624,6 @@ 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) if len(self.plot_line_point) > 1: @@ -625,8 +635,52 @@ 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 + + if delta_y_product < 0: + + 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: + + 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): + def clear_plot_lines(self, palette_index: int = 5) -> None: """clear_plot_lines function. clear all added lines @@ -634,8 +688,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) diff --git a/examples/displayio_cartesion_fillarea.py b/examples/displayio_cartesion_fillarea.py new file mode 100644 index 0000000..81eedf3 --- /dev/null +++ b/examples/displayio_cartesion_fillarea.py @@ -0,0 +1,66 @@ +# 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 +raw_data = [] +data = [] +MAX_DICE_SIDE = 20 +NUMBER_OF_DICE = 10 +NUMBER_OF_ROLLS = 500 + +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(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 +# 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=20, # x position for the plane + y=2, # y plane position + width=135, # display width + height=105, # display height + xrange=(0, MAX_DICE_SIDE), # 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.1) + +while True: + pass