Skip to content

Commit da84037

Browse files
authored
Merge pull request #62 from s-light/widget_cartesian__update_line__bugfix
Widget cartesian update line bugfix
2 parents 5f85122 + 8d8c94d commit da84037

File tree

2 files changed

+235
-41
lines changed

2 files changed

+235
-41
lines changed

adafruit_displayio_layout/widgets/cartesian.py

Lines changed: 172 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ class Cartesian(Widget):
7474
:param int nudge_y: movement in pixels in the y direction to move the origin.
7575
Defaults to 0
7676
77+
:param bool verbose: print debugging information in some internal functions. Default to False
7778
7879
**Quickstart: Importing and using Cartesian**
7980
@@ -181,11 +182,14 @@ def __init__(
181182
subticks: bool = False,
182183
nudge_x: int = 0,
183184
nudge_y: int = 0,
185+
verbose: bool = False,
184186
**kwargs,
185187
) -> None:
186188

187189
super().__init__(**kwargs)
188190

191+
self._verbose = verbose
192+
189193
self._background_color = background_color
190194

191195
self._axes_line_color = axes_color
@@ -250,8 +254,8 @@ def __init__(
250254
self._axesy_bitmap = displayio.Bitmap(self._axesy_width, self.height, 4)
251255
self._axesy_bitmap.fill(0)
252256

253-
self._screen_bitmap = displayio.Bitmap(self.width, self.height, 5)
254-
self._screen_bitmap.fill(5)
257+
self._plot_bitmap = displayio.Bitmap(self.width, self.height, 5)
258+
self.clear_plot_lines()
255259
self._screen_palette = displayio.Palette(6)
256260
self._screen_palette.make_transparent(0)
257261
self._screen_palette[1] = self._tick_color
@@ -293,7 +297,7 @@ def __init__(
293297
)
294298

295299
self._screen_tilegrid = displayio.TileGrid(
296-
self._screen_bitmap,
300+
self._plot_bitmap,
297301
pixel_shader=self._screen_palette,
298302
x=0,
299303
y=0,
@@ -310,8 +314,6 @@ def __init__(
310314
self.append(self._screen_tilegrid)
311315
self.append(self._corner_tilegrid)
312316

313-
self._update_line = True
314-
315317
self._pointer = None
316318
self._circle_palette = None
317319
self.plot_line_point = None
@@ -389,10 +391,12 @@ def _draw_ticks(self) -> None:
389391

390392
if self._subticks:
391393
if i in subticks:
394+
# calc subtick_line_height; force min lineheigt to 1.
395+
subtick_line_height = max(1, self._tick_line_height // 2)
392396
rectangle_helper(
393397
text_dist,
394398
self._axes_line_thickness,
395-
self._tick_line_height // 2,
399+
subtick_line_height,
396400
1,
397401
self._axesx_bitmap,
398402
1,
@@ -457,6 +461,130 @@ def _draw_pointers(self, x: int, y: int) -> None:
457461

458462
self.append(self._pointer)
459463

464+
def _calc_local_xy(self, x: int, y: int) -> (int, int):
465+
local_x = (
466+
int((x - self._xrange[0]) * self._factorx * self._valuex) + self._nudge_x
467+
)
468+
# details on `+ (self.height - 1)` :
469+
# the bitmap is set to self.width & self.height
470+
# but we are only allowed to draw to pixels 0..height-1 and 0..width-1
471+
local_y = (
472+
int((self._yrange[0] - y) * self._factory * self._valuey)
473+
+ (self.height - 1)
474+
+ self._nudge_y
475+
)
476+
return (local_x, local_y)
477+
478+
def _check_local_x_in_range(self, local_x):
479+
return 0 <= local_x < self.width
480+
481+
def _check_local_y_in_range(self, local_y):
482+
return 0 <= local_y < self.height
483+
484+
def _check_local_xy_in_range(self, local_x, local_y):
485+
return self._check_local_x_in_range(local_x) and self._check_local_y_in_range(
486+
local_y
487+
)
488+
489+
def _check_x_in_range(self, x):
490+
return self._xrange[0] <= x <= self._xrange[1]
491+
492+
def _check_y_in_range(self, y):
493+
return self._yrange[0] <= y <= self._yrange[1]
494+
495+
def _check_xy_in_range(self, x, y):
496+
return self._check_x_in_range(x) and self._check_y_in_range(y)
497+
498+
def _add_point(self, x: int, y: int) -> None:
499+
"""_add_point function
500+
helper function to add a point to the graph in the plane
501+
:param int x: ``x`` coordinate in the local plane
502+
:param int y: ``y`` coordinate in the local plane
503+
:return: None
504+
rtype: None
505+
"""
506+
local_x, local_y = self._calc_local_xy(x, y)
507+
if self._verbose:
508+
print("")
509+
print(
510+
"xy: ({: >4}, {: >4}) "
511+
"_xrange: ({: >4}, {: >4}) "
512+
"_yrange: ({: >4}, {: >4}) "
513+
"".format(
514+
x,
515+
y,
516+
self._xrange[0],
517+
self._xrange[1],
518+
self._yrange[0],
519+
self._yrange[1],
520+
)
521+
)
522+
print(
523+
"local_*: ({: >4}, {: >4}) "
524+
" width: ({: >4}, {: >4}) "
525+
" height: ({: >4}, {: >4}) "
526+
"".format(
527+
local_x,
528+
local_y,
529+
0,
530+
self.width,
531+
0,
532+
self.height,
533+
)
534+
)
535+
if self._check_xy_in_range(x, y):
536+
if self._check_local_xy_in_range(local_x, local_y):
537+
if self.plot_line_point is None:
538+
self.plot_line_point = []
539+
self.plot_line_point.append((local_x, local_y))
540+
else:
541+
# for better error messages we check in detail what failed...
542+
# this should never happen:
543+
# we already checked the range of the input values.
544+
# but in case our calculation is wrong we handle this case to..
545+
if not self._check_local_x_in_range(local_x):
546+
raise ValueError(
547+
"local_x out of range: "
548+
"local_x:{: >4}; _xrange({: >4}, {: >4})"
549+
"".format(
550+
local_x,
551+
0,
552+
self.width,
553+
)
554+
)
555+
if not self._check_local_y_in_range(local_y):
556+
raise ValueError(
557+
"local_y out of range: "
558+
"local_y:{: >4}; _yrange({: >4}, {: >4})"
559+
"".format(
560+
local_y,
561+
0,
562+
self.height,
563+
)
564+
)
565+
else:
566+
# for better error messages we check in detail what failed...
567+
if not self._check_x_in_range(x):
568+
raise ValueError(
569+
"x out of range: "
570+
"x:{: >4}; xrange({: >4}, {: >4})"
571+
"".format(
572+
x,
573+
self._xrange[0],
574+
self._xrange[1],
575+
)
576+
)
577+
if not self._check_y_in_range(y):
578+
raise ValueError(
579+
"y out of range: "
580+
"y:{: >4}; yrange({: >4}, {: >4})"
581+
"".format(
582+
y,
583+
self._yrange[0],
584+
self._yrange[1],
585+
)
586+
)
587+
460588
def update_pointer(self, x: int, y: int) -> None:
461589
"""updater_pointer function
462590
helper function to update pointer in the plane
@@ -465,46 +593,49 @@ def update_pointer(self, x: int, y: int) -> None:
465593
:return: None
466594
rtype: None
467595
"""
468-
local_x = int((x - self._xrange[0]) * self._factorx) + self._nudge_x
469-
local_y = (
470-
int((self._yrange[0] - y) * self._factory) + self.height + self._nudge_y
471-
)
596+
self._add_point(x, y)
597+
if not self._pointer:
598+
self._draw_pointers(
599+
self.plot_line_point[-1][0],
600+
self.plot_line_point[-1][1],
601+
)
602+
else:
603+
self._pointer.x = self.plot_line_point[-1][0]
604+
self._pointer.y = self.plot_line_point[-1][1]
472605

473-
if local_x >= 0 or local_y <= 100:
474-
if self._update_line:
475-
self._draw_pointers(local_x, local_y)
476-
self._update_line = False
477-
else:
478-
self._pointer.x = local_x
479-
self._pointer.y = local_y
606+
def add_plot_line(self, x: int, y: int) -> None:
607+
"""add_plot_line function.
480608
481-
def _set_plotter_line(self) -> None:
482-
self.plot_line_point = []
609+
add line to the plane.
610+
multiple calls create a line-plot graph.
483611
484-
def update_line(self, x: int, y: int) -> None:
485-
"""updater_line function
486-
helper function to update pointer in the plane
487612
:param int x: ``x`` coordinate in the local plane
488613
:param int y: ``y`` coordinate in the local plane
489614
:return: None
615+
490616
rtype: None
491617
"""
492-
local_x = int((x - self._xrange[0]) * self._factorx) + self._nudge_x
493-
local_y = (
494-
int((self._yrange[0] - y) * self._factory) + self.height + self._nudge_y
495-
)
496-
if x < self._xrange[1] and y < self._yrange[1]:
497-
if local_x > 0 or local_y < 100:
498-
if self._update_line:
499-
self._set_plotter_line()
500-
self.plot_line_point.append((local_x, local_y))
501-
self._update_line = False
502-
else:
503-
bitmaptools.draw_line(
504-
self._screen_bitmap,
505-
self.plot_line_point[-1][0],
506-
self.plot_line_point[-1][1],
507-
local_x,
508-
local_y,
509-
1,
510-
)
618+
self._add_point(x, y)
619+
if len(self.plot_line_point) > 1:
620+
bitmaptools.draw_line(
621+
self._plot_bitmap,
622+
self.plot_line_point[-2][0],
623+
self.plot_line_point[-2][1],
624+
self.plot_line_point[-1][0],
625+
self.plot_line_point[-1][1],
626+
1,
627+
)
628+
629+
def clear_plot_lines(self, palette_index=5):
630+
"""clear_plot_lines function.
631+
632+
clear all added lines
633+
(clear line-plot graph)
634+
635+
:param int palette_index: color palett index. Defaults to 5
636+
:return: None
637+
638+
rtype: None
639+
"""
640+
self.plot_line_point = None
641+
self._plot_bitmap.fill(palette_index)
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# SPDX-FileCopyrightText: 2021 Stefan Krüger
2+
#
3+
# SPDX-License-Identifier: MIT
4+
#############################
5+
"""
6+
This is a basic demonstration of a Cartesian widget for line-ploting
7+
"""
8+
9+
import time
10+
import board
11+
import displayio
12+
from adafruit_displayio_layout.widgets.cartesian import Cartesian
13+
14+
# create the display on the PyPortal or Clue or PyBadge(for example)
15+
display = board.DISPLAY
16+
# otherwise change this to setup the display
17+
# for display chip driver and pinout you have (e.g. ILI9341)
18+
19+
# pybadge display: 160x128
20+
# Create a Cartesian widget
21+
# https://circuitpython.readthedocs.io/projects/displayio-layout/en/latest/api.html#module-adafruit_displayio_layout.widgets.cartesian
22+
my_plane = Cartesian(
23+
x=15, # x position for the plane
24+
y=2, # y plane position
25+
width=140, # display width
26+
height=105, # display height
27+
xrange=(0, 10), # x range
28+
yrange=(0, 10), # y range
29+
)
30+
31+
my_group = displayio.Group()
32+
my_group.append(my_plane)
33+
display.show(my_group) # add high level Group to the display
34+
35+
data = [
36+
# (0, 0), # we do this point manually - so we have no wait...
37+
(1, 1),
38+
(2, 1),
39+
(2, 2),
40+
(3, 3),
41+
(4, 3),
42+
(4, 4),
43+
(5, 5),
44+
(6, 5),
45+
(6, 6),
46+
(7, 7),
47+
(8, 7),
48+
(8, 8),
49+
(9, 9),
50+
(10, 9),
51+
(10, 10),
52+
]
53+
54+
print("examples/displayio_layout_cartesian_lineplot.py")
55+
56+
# first point without a wait.
57+
my_plane.add_plot_line(0, 0)
58+
for x, y in data:
59+
my_plane.add_plot_line(x, y)
60+
time.sleep(0.5)
61+
62+
while True:
63+
pass

0 commit comments

Comments
 (0)