463 lines
13 KiB
Python
463 lines
13 KiB
Python
# SPDX-FileCopyrightText: Copyright (c) 2021, 2023 Jose David M.
|
|
#
|
|
# SPDX-License-Identifier: MIT
|
|
"""
|
|
|
|
`scales`
|
|
================================================================================
|
|
|
|
Allows display data in a graduated level
|
|
|
|
|
|
* Author(s): Jose David M.
|
|
|
|
Implementation Notes
|
|
--------------------
|
|
|
|
Scales version in CircuitPython
|
|
|
|
"""
|
|
|
|
################################
|
|
# A scales library for CircuitPython, using `displayio`` and `vectorio``
|
|
#
|
|
# Features:
|
|
# - Vertical and Horizontal direction
|
|
# - Animation to use with different sensor
|
|
|
|
try:
|
|
from typing import Union, Tuple, Optional
|
|
except ImportError:
|
|
pass
|
|
|
|
import displayio
|
|
import terminalio
|
|
from adafruit_display_text.bitmap_label import Label
|
|
from vectorio import Polygon, Rectangle
|
|
import ulab.numpy as np
|
|
|
|
try:
|
|
from typing import Tuple
|
|
except ImportError:
|
|
pass
|
|
|
|
__version__ = "0.0.0-auto.0"
|
|
__repo__ = "https://github.com/jposada202020/CircuitPython_scales.git"
|
|
|
|
# pylint: disable=too-many-arguments, too-few-public-methods,too-many-instance-attributes
|
|
# pylint: disable=invalid-unary-operand-type
|
|
class Axes(displayio.Group):
|
|
"""
|
|
:param int x: pixel position. Defaults to :const:`0`
|
|
:param int y: pixel position. Defaults to :const:`0`
|
|
|
|
:param int,int limits: tuple of value range for the scale. Defaults to (0, 100)
|
|
:param list ticks: list to ticks to display. If this is not enter a equally spaced scale
|
|
will be created between the given limits.
|
|
|
|
:param str direction: direction of the scale either :attr:`horizontal` or :attr:`vertical`
|
|
defaults to :attr:`horizontal`
|
|
|
|
:param int stroke: width in pixels of the scale axes. Defaults to :const:`3`
|
|
|
|
:param int length: scale length in pixels. Defaults to :const:`100`
|
|
|
|
:param int color: 24-bit hex value axes line color, Defaults to Purple :const:`0x990099`
|
|
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
x: int = 0,
|
|
y: int = 0,
|
|
limits: Tuple[int, int] = (0, 100),
|
|
ticks: Optional[Union[np.array, list]] = None,
|
|
direction: str = "horizontal",
|
|
stroke: int = 3,
|
|
length: int = 100,
|
|
color: int = 0x990099,
|
|
):
|
|
|
|
super().__init__()
|
|
|
|
if direction == "horizontal":
|
|
self.direction = True
|
|
else:
|
|
self.direction = False
|
|
|
|
self.x = x
|
|
self.y = y
|
|
self.limits = limits
|
|
|
|
self._valuemin = limits[0]
|
|
self._valuemax = limits[1]
|
|
self._newvalmin = length
|
|
self._newvalmax = 0
|
|
|
|
self.stroke = stroke
|
|
self.length = length
|
|
|
|
if ticks:
|
|
self.ticks = np.array(ticks)
|
|
else:
|
|
self.ticks = np.array(list(range(self._valuemin, self._valuemax, 10)))
|
|
|
|
self.ticksynorm = np.array(
|
|
transform(
|
|
self._valuemin,
|
|
self._valuemax,
|
|
self._newvalmax,
|
|
self._newvalmin,
|
|
self.ticks,
|
|
),
|
|
dtype=np.int16,
|
|
)
|
|
|
|
self._palette = displayio.Palette(2)
|
|
self._palette.make_transparent(0)
|
|
self._palette[1] = color
|
|
|
|
self._tick_length = None
|
|
self._tick_stroke = None
|
|
|
|
self.text_ticks = []
|
|
|
|
def _draw_line(self) -> None:
|
|
"""Private function to draw the Axe.
|
|
:return: None
|
|
"""
|
|
if self.direction:
|
|
self.append(rectangle_draw(0, 0, self.stroke, self.length, self._palette))
|
|
else:
|
|
self.append(
|
|
rectangle_draw(0, -self.length, self.length, self.stroke, self._palette)
|
|
)
|
|
|
|
def _draw_ticks(self, tick_length: int = 10, tick_stroke: int = 4) -> None:
|
|
"""Private function to draw the ticks
|
|
:param int tick_length: tick length in pixels
|
|
:param int tick_stroke: tick thickness in pixels
|
|
:return: None
|
|
"""
|
|
|
|
self._tick_length = tick_length
|
|
self._tick_stroke = tick_stroke
|
|
|
|
if self.direction:
|
|
for val in self.ticksynorm[:-1]:
|
|
self.append(
|
|
rectangle_draw(
|
|
int(val) - 1,
|
|
-self._tick_length,
|
|
self._tick_length,
|
|
3,
|
|
self._palette,
|
|
)
|
|
)
|
|
else:
|
|
for val in self.ticksynorm[:-1]:
|
|
self.append(
|
|
rectangle_draw(0, -int(val), 3, self._tick_length, self._palette)
|
|
)
|
|
|
|
def _draw_text(self) -> None:
|
|
"""Private function to draw the text, uses values found in ``_conversion``
|
|
:return: None
|
|
"""
|
|
index = 0
|
|
separation = 20
|
|
font_width = 12
|
|
if self.direction:
|
|
print("aca")
|
|
for tick_text in self.ticks[:-1]:
|
|
dist_x = self.ticksynorm[index] - font_width // 2
|
|
dist_y = separation // 2
|
|
text = str(int(tick_text))
|
|
tick_label = Label(terminalio.FONT, text=text, x=dist_x, y=dist_y)
|
|
self.append(tick_label)
|
|
index = index + 1
|
|
else:
|
|
for tick_text in self.ticks[:-1]:
|
|
dist_x = -separation
|
|
dist_y = -self.ticksynorm[index]
|
|
text = str(int(tick_text))
|
|
tick_label = Label(terminalio.FONT, text=text, x=dist_x, y=dist_y)
|
|
self.append(tick_label)
|
|
index = index + 1
|
|
|
|
|
|
class Scale(Axes):
|
|
"""
|
|
:param int x: pixel position. Defaults to :const:`0`
|
|
:param int y: pixel position. Defaults to :const:`0`
|
|
|
|
:param str direction: direction of the scale either :attr:`horizontal` or :attr:`vertical`
|
|
defaults to :attr:`horizontal`
|
|
|
|
:param int stroke: width in pixels of the axes line. Defaults to :const:`3` pixels
|
|
|
|
:param int length: scale length in pixels. Defaults to :const:`100` pixels
|
|
|
|
:param int color: 24-bit hex value axes line color, Defaults to Purple :const:`0x990099`
|
|
|
|
:param int width: scale width in pixels. Defaults to :const:`50` pixels
|
|
|
|
:param limits: tuple of value range for the scale. Defaults to :const:`(0, 100)`
|
|
:param list ticks: list to ticks to display. If this is not enter a equally spaced scale
|
|
will be created between the given limits.
|
|
|
|
:param int back_color: 24-bit hex value axes line color.
|
|
Defaults to Light Blue :const:`0x9FFFFF`
|
|
|
|
:param int tick_length: Scale tick length in pixels. Defaults to :const:`10`
|
|
:param int tick_stroke: Scale tick width in pixels. Defaults to :const:`4`
|
|
|
|
:param int pointer_length: length in pixels for the point. Defaults to :const:`20` pixels
|
|
:param int pointer_stroke: pointer thickness in pixels. Defaults to :const:`6` pixels
|
|
|
|
|
|
**Quickstart: Importing and using Scales**
|
|
|
|
Here is one way of importing the `Scale` class, so you can use it as
|
|
the name ``my_scale``:
|
|
|
|
.. code-block::
|
|
|
|
from scale import Scale
|
|
|
|
Now you can create a vertical Scale at pixel position x=50, y=180 and a range
|
|
of 0 to 80 using:
|
|
|
|
.. code-block::
|
|
|
|
my_scale = Scale(x=50, y=180, direction="vertical", limits=(0, 80))
|
|
|
|
Once you setup your display, you can now add ``my_scale`` to your display using:
|
|
|
|
.. code-block::
|
|
|
|
display.show(my_scale)
|
|
|
|
If you want to have multiple display elements, you can create a group and then
|
|
append the scale and the other elements to the group. Then, you can add the full
|
|
group to the display as in this example:
|
|
|
|
.. code-block:: python
|
|
|
|
my_scale= Scale(x=20, y=30)
|
|
my_group = displayio.Group() # make a group
|
|
my_group.append(my_scale) # Add my_slider to the group
|
|
|
|
#
|
|
# Append other display elements to the group
|
|
#
|
|
|
|
display.show(my_group) # add the group to the display
|
|
|
|
|
|
**Summary: Slider Features and input variables**
|
|
|
|
The `Scale` class has some options for controlling its position, visible appearance,
|
|
and value through a collection of input variables:
|
|
|
|
- **position**: :attr:`x``, :attr:`y`
|
|
|
|
- **size**: :attr:`length` and :attr:`width`
|
|
|
|
- **color**: :attr:`color`, :attr:`back_color`
|
|
|
|
- **linewidths**: :attr:`stroke` and :attr:`tick_stroke`
|
|
|
|
- **range**: :attr:`limits`
|
|
|
|
|
|
.. figure:: scales.png
|
|
:scale: 100 %
|
|
:align: center
|
|
:alt: Diagram of scales
|
|
|
|
Diagram showing a simple scale.
|
|
|
|
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
x: int = 0,
|
|
y: int = 0,
|
|
direction: str = "horizontal",
|
|
stroke: int = 3,
|
|
length: int = 100,
|
|
color: int = 0x990099,
|
|
width: int = 50,
|
|
limits: Tuple[int, int] = (0, 100),
|
|
back_color: int = 0x9FFFFF,
|
|
ticks: Optional[Union[np.array, list]] = None,
|
|
tick_length: int = 5,
|
|
tick_stroke: int = 4,
|
|
pointer_length: int = 20,
|
|
pointer_stroke: int = 6,
|
|
):
|
|
|
|
super().__init__(
|
|
x=x,
|
|
y=y,
|
|
direction=direction,
|
|
stroke=stroke,
|
|
length=length,
|
|
limits=limits,
|
|
ticks=ticks,
|
|
color=color,
|
|
)
|
|
|
|
self._width = width
|
|
self._back_color = back_color
|
|
self._draw_background()
|
|
self._draw_line()
|
|
self._draw_ticks()
|
|
self.value = 0
|
|
|
|
self._tick_length = tick_length
|
|
self._tick_stroke = tick_stroke
|
|
self._pointer_length = pointer_length
|
|
self._pointer_stroke = pointer_stroke
|
|
|
|
# Pointer Definitions
|
|
self._x0 = 0
|
|
self._y0 = 0
|
|
|
|
if self.direction:
|
|
self.center = width // 2
|
|
self._x1 = pointer_stroke
|
|
self._y1 = pointer_length
|
|
self._posx = 0
|
|
self._posy = -self.center - self._pointer_length // 2
|
|
else:
|
|
self.center = width // 2
|
|
self._x1 = pointer_length
|
|
self._y1 = pointer_stroke
|
|
self._posx = self.center - self._pointer_length // 2
|
|
self._posy = -10
|
|
self.pointer = None
|
|
|
|
self._draw_text()
|
|
self._draw_pointer()
|
|
|
|
def _draw_background(self):
|
|
"""Private function to draw the background for the scale
|
|
:return: None
|
|
"""
|
|
back_palette = displayio.Palette(2)
|
|
back_palette.make_transparent(0)
|
|
back_palette[1] = self._back_color
|
|
|
|
if self.direction:
|
|
self.append(
|
|
rectangle_draw(0, -self._width, self._width, self.length, back_palette)
|
|
)
|
|
else:
|
|
self.append(
|
|
rectangle_draw(0, -self.length, self.length, self._width, back_palette)
|
|
)
|
|
|
|
def _draw_pointer(
|
|
self,
|
|
color: int = 0xFF0000,
|
|
):
|
|
"""Private function to initial draw the pointer.
|
|
|
|
:param int color: 24-bit hex value axes line color. Defaults to red :const:`0xFF0000`
|
|
:param int val_ini: initial value to draw the pointer
|
|
|
|
|
|
:return: None
|
|
|
|
"""
|
|
|
|
pointer_palette = displayio.Palette(2)
|
|
pointer_palette.make_transparent(0)
|
|
pointer_palette[1] = color
|
|
|
|
points = [
|
|
(self._x0, self._y0),
|
|
(self._x1, self._y0),
|
|
(self._x1, self._y1),
|
|
(self._x0, self._y1),
|
|
]
|
|
self.pointer = Polygon(
|
|
pixel_shader=pointer_palette,
|
|
points=points,
|
|
x=self._posx,
|
|
y=self._posy,
|
|
color_index=1,
|
|
)
|
|
|
|
self.append(self.pointer)
|
|
|
|
def animate_pointer(self, new_value):
|
|
"""Public function to animate the pointer
|
|
|
|
:param new_value: value to draw the pointer
|
|
:return: None
|
|
|
|
"""
|
|
value = int(
|
|
transform(
|
|
self._valuemin,
|
|
self._valuemax,
|
|
self._newvalmax,
|
|
self._newvalmin,
|
|
new_value,
|
|
)
|
|
)
|
|
if self.direction:
|
|
self.pointer.x = value - self._pointer_stroke // 2
|
|
else:
|
|
self.pointer.y = -value - self._pointer_stroke // 2
|
|
|
|
|
|
# pylint: disable=invalid-name
|
|
def rectangle_draw(x0: int, y0: int, height: int, width: int, palette):
|
|
"""rectangle_draw function
|
|
|
|
Draws a rectangle using or `vectorio.Rectangle`
|
|
|
|
:param int x0: rectangle lower corner x position
|
|
:param int y0: rectangle lower corner y position
|
|
|
|
:param int width: rectangle upper corner x position
|
|
:param int height: rectangle upper corner y position
|
|
|
|
:param `~displayio.Palette` palette: palette object to be used to draw the rectangle
|
|
|
|
"""
|
|
|
|
return Rectangle(
|
|
pixel_shader=palette, width=width, height=height, x=x0, y=y0, color_index=1
|
|
)
|
|
|
|
|
|
def transform(
|
|
oldrangemin: Union[float, int],
|
|
oldrangemax: Union[float, int],
|
|
newrangemin: Union[float, int],
|
|
newrangemax: Union[float, int],
|
|
value: Union[float, int],
|
|
) -> Union[float, int]:
|
|
"""
|
|
This function converts the original value into a new defined value in the new range
|
|
|
|
:param int|float oldrangemin: minimum of the original range
|
|
:param int|float oldrangemax: maximum of the original range
|
|
:param int|float newrangemin: minimum of the new range
|
|
:param int|float newrangemax: maximum of the new range
|
|
:param int|float value: value to be converted
|
|
|
|
:return int|float: converted value
|
|
|
|
"""
|
|
|
|
return (
|
|
((value - oldrangemin) * (newrangemax - newrangemin))
|
|
/ (oldrangemax - oldrangemin)
|
|
) + newrangemin
|