diff --git a/displayio_cartesian.py b/displayio_cartesian.py index 4b6e41b..7477e56 100644 --- a/displayio_cartesian.py +++ b/displayio_cartesian.py @@ -33,8 +33,9 @@ try: import bitmaptools -except NameError: +except ImportError: pass + try: from typing import Tuple except ImportError: @@ -73,6 +74,7 @@ class Cartesian(Widget): :param int nudge_y: movement in pixels in the y direction to move the origin. Defaults to 0 + :param bool verbose: print debugging information in some internal functions. Default to False **Quickstart: Importing and using Cartesian** @@ -180,11 +182,14 @@ def __init__( subticks: bool = False, nudge_x: int = 0, nudge_y: int = 0, + verbose: bool = False, **kwargs, ) -> None: super().__init__(**kwargs) + self._verbose = verbose + self._background_color = background_color self._axes_line_color = axes_color @@ -249,8 +254,8 @@ def __init__( self._axesy_bitmap = displayio.Bitmap(self._axesy_width, self.height, 4) self._axesy_bitmap.fill(0) - self._screen_bitmap = displayio.Bitmap(self.width, self.height, 5) - self._screen_bitmap.fill(5) + self._plot_bitmap = displayio.Bitmap(self.width, self.height, 5) + self.clear_plot_lines() self._screen_palette = displayio.Palette(6) self._screen_palette.make_transparent(0) self._screen_palette[1] = self._tick_color @@ -292,7 +297,7 @@ def __init__( ) self._screen_tilegrid = displayio.TileGrid( - self._screen_bitmap, + self._plot_bitmap, pixel_shader=self._screen_palette, x=0, y=0, @@ -309,11 +314,8 @@ def __init__( self.append(self._screen_tilegrid) self.append(self._corner_tilegrid) - self._update_line = True - self._pointer = None self._circle_palette = None - self._pointer_vector_shape = None self.plot_line_point = None @staticmethod @@ -389,10 +391,12 @@ def _draw_ticks(self) -> None: if self._subticks: if i in subticks: + # calc subtick_line_height; force min lineheigt to 1. + subtick_line_height = max(1, self._tick_line_height // 2) rectangle_helper( text_dist, self._axes_line_thickness, - self._tick_line_height // 2, + subtick_line_height, 1, self._axesx_bitmap, 1, @@ -448,18 +452,138 @@ def _draw_ticks(self) -> None: ) def _draw_pointers(self, x: int, y: int) -> None: - self._pointer = vectorio.Circle(self._pointer_radius) - self._circle_palette = displayio.Palette(2) - self._circle_palette.make_transparent(0) - self._circle_palette[1] = self._pointer_color - - self._pointer_vector_shape = vectorio.VectorShape( - shape=self._pointer, - pixel_shader=self._circle_palette, - x=x, - y=y, + + self._circle_palette = displayio.Palette(1) + self._circle_palette[0] = self._pointer_color + self._pointer = vectorio.Circle( + radius=self._pointer_radius, x=x, y=y, pixel_shader=self._circle_palette ) - self.append(self._pointer_vector_shape) + + self.append(self._pointer) + + def _calc_local_xy(self, x: int, y: int) -> Tuple[int, int]: + local_x = ( + int((x - self._xrange[0]) * self._factorx * self._valuex) + self._nudge_x + ) + # details on `+ (self.height - 1)` : + # the bitmap is set to self.width & self.height + # but we are only allowed to draw to pixels 0..height-1 and 0..width-1 + local_y = ( + int((self._yrange[0] - y) * self._factory * self._valuey) + + (self.height - 1) + + self._nudge_y + ) + return (local_x, local_y) + + def _check_local_x_in_range(self, local_x): + return 0 <= local_x < self.width + + def _check_local_y_in_range(self, local_y): + return 0 <= local_y < self.height + + def _check_local_xy_in_range(self, local_x, local_y): + return self._check_local_x_in_range(local_x) and self._check_local_y_in_range( + local_y + ) + + def _check_x_in_range(self, x): + return self._xrange[0] <= x <= self._xrange[1] + + def _check_y_in_range(self, y): + return self._yrange[0] <= y <= self._yrange[1] + + def _check_xy_in_range(self, x, y): + return self._check_x_in_range(x) and self._check_y_in_range(y) + + def _add_point(self, x: int, y: int) -> None: + """_add_point function + helper function to add a point to the graph in the plane + :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("") + print( + "xy: ({: >4}, {: >4}) " + "_xrange: ({: >4}, {: >4}) " + "_yrange: ({: >4}, {: >4}) " + "".format( + x, + y, + self._xrange[0], + self._xrange[1], + self._yrange[0], + self._yrange[1], + ) + ) + print( + "local_*: ({: >4}, {: >4}) " + " width: ({: >4}, {: >4}) " + " height: ({: >4}, {: >4}) " + "".format( + local_x, + local_y, + 0, + self.width, + 0, + self.height, + ) + ) + if self._check_xy_in_range(x, y): + if self._check_local_xy_in_range(local_x, local_y): + if self.plot_line_point is None: + self.plot_line_point = [] + self.plot_line_point.append((local_x, local_y)) + else: + # for better error messages we check in detail what failed... + # this should never happen: + # we already checked the range of the input values. + # but in case our calculation is wrong we handle this case to.. + if not self._check_local_x_in_range(local_x): + raise ValueError( + "local_x out of range: " + "local_x:{: >4}; _xrange({: >4}, {: >4})" + "".format( + local_x, + 0, + self.width, + ) + ) + if not self._check_local_y_in_range(local_y): + raise ValueError( + "local_y out of range: " + "local_y:{: >4}; _yrange({: >4}, {: >4})" + "".format( + local_y, + 0, + self.height, + ) + ) + else: + # for better error messages we check in detail what failed... + if not self._check_x_in_range(x): + raise ValueError( + "x out of range: " + "x:{: >4}; xrange({: >4}, {: >4})" + "".format( + x, + self._xrange[0], + self._xrange[1], + ) + ) + if not self._check_y_in_range(y): + raise ValueError( + "y out of range: " + "y:{: >4}; yrange({: >4}, {: >4})" + "".format( + y, + self._yrange[0], + self._yrange[1], + ) + ) def update_pointer(self, x: int, y: int) -> None: """updater_pointer function @@ -469,46 +593,49 @@ def update_pointer(self, x: int, y: int) -> None: :return: None rtype: None """ - local_x = int((x - self._xrange[0]) * self._factorx) + self._nudge_x - local_y = ( - int((self._yrange[0] - y) * self._factory) + self.height + self._nudge_y - ) + self._add_point(x, y) + if not self._pointer: + self._draw_pointers( + self.plot_line_point[-1][0], + self.plot_line_point[-1][1], + ) + else: + self._pointer.x = self.plot_line_point[-1][0] + self._pointer.y = self.plot_line_point[-1][1] - if local_x >= 0 or local_y <= 100: - if self._update_line: - self._draw_pointers(local_x, local_y) - self._update_line = False - else: - self._pointer_vector_shape.x = local_x - self._pointer_vector_shape.y = local_y + def add_plot_line(self, x: int, y: int) -> None: + """add_plot_line function. - def _set_plotter_line(self) -> None: - self.plot_line_point = list() + add line to the plane. + multiple calls create a line-plot graph. - def update_line(self, x: int, y: int) -> None: - """updater_line function - helper function to update pointer in the plane :param int x: ``x`` coordinate in the local plane :param int y: ``y`` coordinate in the local plane :return: None + rtype: None """ - local_x = int((x - self._xrange[0]) * self._factorx) + self._nudge_x - local_y = ( - int((self._yrange[0] - y) * self._factory) + self.height + self._nudge_y - ) - if x < self._xrange[1] and y < self._yrange[1]: - if local_x > 0 or local_y < 100: - if self._update_line: - self._set_plotter_line() - self.plot_line_point.append((local_x, local_y)) - self._update_line = False - else: - bitmaptools.draw_line( - self._screen_bitmap, - self.plot_line_point[-1][0], - self.plot_line_point[-1][1], - local_x, - local_y, - 1, - ) + self._add_point(x, y) + if len(self.plot_line_point) > 1: + bitmaptools.draw_line( + self._plot_bitmap, + self.plot_line_point[-2][0], + self.plot_line_point[-2][1], + self.plot_line_point[-1][0], + self.plot_line_point[-1][1], + 1, + ) + + def clear_plot_lines(self, palette_index=5): + """clear_plot_lines function. + + clear all added lines + (clear line-plot graph) + + :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_cartesian_advanced_test.py b/examples/displayio_cartesian_advanced_test.py index 9d3ccbc..da89b58 100644 --- a/examples/displayio_cartesian_advanced_test.py +++ b/examples/displayio_cartesian_advanced_test.py @@ -10,7 +10,7 @@ import board import displayio import terminalio -from adafruit_displayio_layout.widgets.cartesian import Cartesian +from displayio_cartesian import Cartesian # Fonts used for the Dial tick labels tick_font = terminalio.FONT diff --git a/examples/displayio_cartesian_lineplot.py b/examples/displayio_cartesian_lineplot.py new file mode 100644 index 0000000..52b95bd --- /dev/null +++ b/examples/displayio_cartesian_lineplot.py @@ -0,0 +1,63 @@ +# 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 +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) + +# 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=(0, 10), # x range + yrange=(0, 10), # y range +) + +my_group = displayio.Group() +my_group.append(my_plane) +display.show(my_group) # add high level Group to the display + +data = [ + # (0, 0), # we do this point manually - so we have no wait... + (1, 1), + (2, 1), + (2, 2), + (3, 3), + (4, 3), + (4, 4), + (5, 5), + (6, 5), + (6, 6), + (7, 7), + (8, 7), + (8, 8), + (9, 9), + (10, 9), + (10, 10), +] + +print("examples/displayio_layout_cartesian_lineplot.py") + +# first point without a wait. +my_plane.add_plot_line(0, 0) +for x, y in data: + my_plane.add_plot_line(x, y) + time.sleep(0.5) + +while True: + pass diff --git a/examples/displayio_cartesian_simpletest.py b/examples/displayio_cartesian_simpletest.py index 3eb604a..4e815fa 100644 --- a/examples/displayio_cartesian_simpletest.py +++ b/examples/displayio_cartesian_simpletest.py @@ -10,7 +10,7 @@ import board import displayio import terminalio -from adafruit_displayio_layout.widgets.cartesian import Cartesian +from displayio_cartesian import Cartesian # Fonts used for the Dial tick labels tick_font = terminalio.FONT