Skip to content

Commit 188341f

Browse files
authored
Merge pull request #1 from adafruit/master
merge from adafruit
2 parents 6b64c9f + 64eb9da commit 188341f

File tree

9 files changed

+836
-8
lines changed

9 files changed

+836
-8
lines changed

README.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Introduction
66
:alt: Documentation Status
77

88
.. image:: https://img.shields.io/discord/327254708534116352.svg
9-
:target: https://discord.gg/nBQh6qu
9+
:target: https://adafru.it/discord
1010
:alt: Discord
1111

1212
.. image:: https://github.com/adafruit/Adafruit_CircuitPython_Display_Shapes/workflows/Build%20CI/badge.svg
@@ -53,7 +53,7 @@ To install in a virtual environment in your current project:
5353
Usage Example
5454
=============
5555

56-
..code-block:: python
56+
.. code-block:: python
5757
5858
import board
5959
import displayio

adafruit_display_shapes/circle.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545

4646

4747
class Circle(RoundRect):
48+
# pylint: disable=too-few-public-methods, invalid-name
4849
"""A circle.
4950
5051
:param x0: The x-position of the center.
@@ -54,10 +55,18 @@ class Circle(RoundRect):
5455
``None`` for transparent.
5556
:param outline: The outline of the rounded-corner rectangle. Can be a hex value for a color or
5657
``None`` for no outline.
58+
:param stroke: Used for the outline. Will not change the radius.
5759
5860
"""
5961

60-
def __init__(self, x0, y0, r, *, fill=None, outline=None):
62+
def __init__(self, x0, y0, r, *, fill=None, outline=None, stroke=1):
6163
super().__init__(
62-
x0 - r, y0 - r, 2 * r + 1, 2 * r + 1, r, fill=fill, outline=outline
64+
x0 - r,
65+
y0 - r,
66+
2 * r + 1,
67+
2 * r + 1,
68+
r,
69+
fill=fill,
70+
outline=outline,
71+
stroke=stroke,
6372
)

adafruit_display_shapes/line.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545

4646

4747
class Line(Polygon):
48-
# pylint: disable=too-many-arguments,invalid-name
48+
# pylint: disable=too-many-arguments,invalid-name, too-few-public-methods
4949
"""A line.
5050
5151
:param x0: The x-position of the first vertex.

adafruit_display_shapes/polygon.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,12 +100,12 @@ def _line(self, x0, y0, x1, y1, color):
100100
if x0 == x1:
101101
if y0 > y1:
102102
y0, y1 = y1, y0
103-
for _h in range(y0, y1):
103+
for _h in range(y0, y1 + 1):
104104
self._bitmap[x0, _h] = color
105105
elif y0 == y1:
106106
if x0 > x1:
107107
x0, x1 = x1, x0
108-
for _w in range(x0, x1):
108+
for _w in range(x0, x1 + 1):
109109
self._bitmap[_w, y0] = color
110110
else:
111111
steep = abs(y1 - y0) > abs(x1 - x0)
@@ -127,7 +127,7 @@ def _line(self, x0, y0, x1, y1, color):
127127
else:
128128
ystep = -1
129129

130-
for x in range(x0, x1):
130+
for x in range(x0, x1 + 1):
131131
if steep:
132132
self._bitmap[y0, x] = color
133133
else:

adafruit_display_shapes/sparkline.py

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
# class of sparklines in CircuitPython
2+
# created by Kevin Matocha - Copyright 2020 (C)
3+
4+
# See the bottom for a code example using the `sparkline` Class.
5+
6+
# # File: display_shapes_sparkline.py
7+
# A sparkline is a scrolling line graph, where any values added to sparkline using `
8+
# add_value` are plotted.
9+
#
10+
# The `sparkline` class creates an element suitable for adding to the display using
11+
# `display.show(mySparkline)`
12+
# or adding to a `displayio.Group` to be displayed.
13+
#
14+
# When creating the sparkline, identify the number of `max_items` that will be
15+
# included in the graph. When additional elements are added to the sparkline and
16+
# the number of items has exceeded max_items, any excess values are removed from
17+
# the left of the graph, and new values are added to the right.
18+
"""
19+
`sparkline`
20+
================================================================================
21+
22+
Various common shapes for use with displayio - Sparkline!
23+
24+
25+
* Author(s): Kevin Matocha
26+
27+
Implementation Notes
28+
--------------------
29+
30+
**Software and Dependencies:**
31+
32+
* Adafruit CircuitPython firmware for the supported boards:
33+
https://github.com/adafruit/circuitpython/releases
34+
35+
"""
36+
37+
import displayio
38+
from adafruit_display_shapes.line import Line
39+
40+
41+
class Sparkline(displayio.Group):
42+
# pylint: disable=too-many-arguments
43+
"""A sparkline graph.
44+
45+
:param width: Width of the sparkline graph in pixels
46+
:param height: Height of the sparkline graph in pixels
47+
:param max_items: Maximum number of values housed in the sparkline
48+
:param y_min: Lower range for the y-axis. Set to None for autorange.
49+
:param y_max: Upper range for the y-axis. Set to None for autorange.
50+
:param x: X-position on the screen, in pixels
51+
:param y: Y-position on the screen, in pixels
52+
:param color: Line color, the default value is 0xFFFFFF (WHITE)
53+
"""
54+
55+
def __init__(
56+
self,
57+
width,
58+
height,
59+
max_items,
60+
y_min=None, # None = autoscaling
61+
y_max=None, # None = autoscaling
62+
x=0,
63+
y=0,
64+
color=0xFFFFFF, # line color, default is WHITE
65+
):
66+
67+
# define class instance variables
68+
self.width = width # in pixels
69+
self.height = height # in pixels
70+
self.color = color #
71+
self._max_items = max_items # maximum number of items in the list
72+
self._spark_list = [] # list containing the values
73+
self.y_min = y_min # minimum of y-axis (None: autoscale)
74+
self.y_max = y_max # maximum of y-axis (None: autoscale)
75+
self.y_bottom = y_min
76+
# y_bottom: The actual minimum value of the vertical scale, will be
77+
# updated if autorange
78+
self.y_top = y_max
79+
# y_top: The actual minimum value of the vertical scale, will be
80+
# updated if autorange
81+
self._x = x
82+
self._y = y
83+
84+
super().__init__(
85+
max_size=self._max_items - 1, x=x, y=y
86+
) # self is a group of lines
87+
88+
def add_value(self, value):
89+
"""Add a value to the sparkline.
90+
:param value: The value to be added to the sparkline
91+
"""
92+
93+
if value is not None:
94+
if (
95+
len(self._spark_list) >= self._max_items
96+
): # if list is full, remove the first item
97+
self._spark_list.pop(0)
98+
self._spark_list.append(value)
99+
self.update()
100+
101+
# pylint: disable=no-else-return
102+
@staticmethod
103+
def _xintercept(
104+
x_1, y_1, x_2, y_2, horizontal_y
105+
): # finds intercept of the line and a horizontal line at horizontalY
106+
slope = (y_2 - y_1) / (x_2 - x_1)
107+
b = y_1 - slope * x_1
108+
109+
if slope == 0 and y_1 != horizontal_y: # does not intercept horizontalY
110+
return None
111+
else:
112+
xint = (
113+
horizontal_y - b
114+
) / slope # calculate the x-intercept at position y=horizontalY
115+
return int(xint)
116+
117+
def _plotline(self, x_1, last_value, x_2, value, y_bottom, y_top):
118+
119+
y_2 = int(self.height * (y_top - value) / (y_top - y_bottom))
120+
y_1 = int(self.height * (y_top - last_value) / (y_top - y_bottom))
121+
self.append(Line(x_1, y_1, x_2, y_2, self.color)) # plot the line
122+
123+
# pylint: disable= too-many-branches, too-many-nested-blocks
124+
125+
def update(self):
126+
"""Update the drawing of the sparkline."""
127+
128+
# get the y range
129+
if self.y_min is None:
130+
self.y_bottom = min(self._spark_list)
131+
else:
132+
self.y_bottom = self.y_min
133+
134+
if self.y_max is None:
135+
self.y_top = max(self._spark_list)
136+
else:
137+
self.y_top = self.y_max
138+
139+
if len(self._spark_list) > 2:
140+
xpitch = (self.width - 1) / (
141+
len(self._spark_list) - 1
142+
) # this is a float, only make int when plotting the line
143+
144+
for _ in range(len(self)): # remove all items from the current group
145+
self.pop()
146+
147+
for count, value in enumerate(self._spark_list):
148+
if count == 0:
149+
pass # don't draw anything for a first point
150+
else:
151+
x_2 = int(xpitch * count)
152+
x_1 = int(xpitch * (count - 1))
153+
154+
if (self.y_bottom <= last_value <= self.y_top) and (
155+
self.y_bottom <= value <= self.y_top
156+
): # both points are in range, plot the line
157+
self._plotline(
158+
x_1, last_value, x_2, value, self.y_bottom, self.y_top
159+
)
160+
161+
else: # at least one point is out of range, clip one or both ends the line
162+
if ((last_value > self.y_top) and (value > self.y_top)) or (
163+
(last_value < self.y_bottom) and (value < self.y_bottom)
164+
):
165+
# both points are on the same side out of range: don't draw anything
166+
pass
167+
else:
168+
xint_bottom = self._xintercept(
169+
x_1, last_value, x_2, value, self.y_bottom
170+
) # get possible new x intercept points
171+
xint_top = self._xintercept(
172+
x_1, last_value, x_2, value, self.y_top
173+
) # on the top and bottom of range
174+
175+
if (xint_bottom is None) or (
176+
xint_top is None
177+
): # out of range doublecheck
178+
pass
179+
else:
180+
# Initialize the adjusted values as the baseline
181+
adj_x_1 = x_1
182+
adj_last_value = last_value
183+
adj_x_2 = x_2
184+
adj_value = value
185+
186+
if value > last_value: # slope is positive
187+
if xint_bottom >= x_1: # bottom is clipped
188+
adj_x_1 = xint_bottom
189+
adj_last_value = self.y_bottom # y_1
190+
if xint_top <= x_2: # top is clipped
191+
adj_x_2 = xint_top
192+
adj_value = self.y_top # y_2
193+
else: # slope is negative
194+
if xint_top >= x_1: # top is clipped
195+
adj_x_1 = xint_top
196+
adj_last_value = self.y_top # y_1
197+
if xint_bottom <= x_2: # bottom is clipped
198+
adj_x_2 = xint_bottom
199+
adj_value = self.y_bottom # y_2
200+
201+
self._plotline(
202+
adj_x_1,
203+
adj_last_value,
204+
adj_x_2,
205+
adj_value,
206+
self.y_bottom,
207+
self.y_top,
208+
)
209+
210+
last_value = value # store value for the next iteration
211+
212+
def values(self):
213+
"""Returns the values displayed on the sparkline."""
214+
215+
return self._spark_list

docs/api.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,12 @@
1515

1616
.. automodule:: adafruit_display_shapes.triangle
1717
:members:
18+
19+
.. automodule:: adafruit_display_shapes.line
20+
:members:
21+
22+
.. automodule:: adafruit_display_shapes.polygon
23+
:members:
24+
25+
.. automodule:: adafruit_display_shapes.sparkline
26+
:members:

0 commit comments

Comments
 (0)