Skip to content
Merged
66 changes: 59 additions & 7 deletions displayio_cartesian.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ def __init__(
nudge_x: int = 0,
nudge_y: int = 0,
verbose: bool = False,
fill_area: bool = False,
**kwargs,
) -> None:

Expand Down Expand Up @@ -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"):
Expand Down Expand Up @@ -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("")
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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:
Expand All @@ -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.

Expand All @@ -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:
Expand All @@ -625,17 +635,59 @@ 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
(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)
66 changes: 66 additions & 0 deletions examples/displayio_cartesion_fillarea.py
Original file line number Diff line number Diff line change
@@ -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