Skip to content

Add functionality for graphing area under plot (integral) #6

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Mar 13, 2022
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