/ scales.py
scales.py
1 # SPDX-FileCopyrightText: Copyright (c) 2021, 2023 Jose David M. 2 # 3 # SPDX-License-Identifier: MIT 4 """ 5 6 `scales` 7 ================================================================================ 8 9 Allows display data in a graduated level 10 11 12 * Author(s): Jose David M. 13 14 Implementation Notes 15 -------------------- 16 17 Scales version in CircuitPython 18 19 """ 20 21 ################################ 22 # A scales library for CircuitPython, using `displayio`` and `vectorio`` 23 # 24 # Features: 25 # - Vertical and Horizontal direction 26 # - Animation to use with different sensor 27 28 try: 29 from typing import Union, Tuple, Optional 30 except ImportError: 31 pass 32 33 import displayio 34 import terminalio 35 from adafruit_display_text.bitmap_label import Label 36 from vectorio import Polygon, Rectangle 37 import ulab.numpy as np 38 39 try: 40 from typing import Tuple 41 except ImportError: 42 pass 43 44 __version__ = "0.0.0-auto.0" 45 __repo__ = "https://github.com/jposada202020/CircuitPython_scales.git" 46 47 # pylint: disable=too-many-arguments, too-few-public-methods,too-many-instance-attributes 48 # pylint: disable=invalid-unary-operand-type 49 class Axes(displayio.Group): 50 """ 51 :param int x: pixel position. Defaults to :const:`0` 52 :param int y: pixel position. Defaults to :const:`0` 53 54 :param int,int limits: tuple of value range for the scale. Defaults to (0, 100) 55 :param list ticks: list to ticks to display. If this is not enter a equally spaced scale 56 will be created between the given limits. 57 58 :param str direction: direction of the scale either :attr:`horizontal` or :attr:`vertical` 59 defaults to :attr:`horizontal` 60 61 :param int stroke: width in pixels of the scale axes. Defaults to :const:`3` 62 63 :param int length: scale length in pixels. Defaults to :const:`100` 64 65 :param int color: 24-bit hex value axes line color, Defaults to Purple :const:`0x990099` 66 67 """ 68 69 def __init__( 70 self, 71 x: int = 0, 72 y: int = 0, 73 limits: Tuple[int, int] = (0, 100), 74 ticks: Optional[Union[np.array, list]] = None, 75 direction: str = "horizontal", 76 stroke: int = 3, 77 length: int = 100, 78 color: int = 0x990099, 79 ): 80 81 super().__init__() 82 83 if direction == "horizontal": 84 self.direction = True 85 else: 86 self.direction = False 87 88 self.x = x 89 self.y = y 90 self.limits = limits 91 92 self._valuemin = limits[0] 93 self._valuemax = limits[1] 94 self._newvalmin = length 95 self._newvalmax = 0 96 97 self.stroke = stroke 98 self.length = length 99 100 if ticks: 101 self.ticks = np.array(ticks) 102 else: 103 self.ticks = np.array(list(range(self._valuemin, self._valuemax, 10))) 104 105 self.ticksynorm = np.array( 106 transform( 107 self._valuemin, 108 self._valuemax, 109 self._newvalmax, 110 self._newvalmin, 111 self.ticks, 112 ), 113 dtype=np.int16, 114 ) 115 116 self._palette = displayio.Palette(2) 117 self._palette.make_transparent(0) 118 self._palette[1] = color 119 120 self._tick_length = None 121 self._tick_stroke = None 122 123 self.text_ticks = [] 124 125 def _draw_line(self) -> None: 126 """Private function to draw the Axe. 127 :return: None 128 """ 129 if self.direction: 130 self.append(rectangle_draw(0, 0, self.stroke, self.length, self._palette)) 131 else: 132 self.append( 133 rectangle_draw(0, -self.length, self.length, self.stroke, self._palette) 134 ) 135 136 def _draw_ticks(self, tick_length: int = 10, tick_stroke: int = 4) -> None: 137 """Private function to draw the ticks 138 :param int tick_length: tick length in pixels 139 :param int tick_stroke: tick thickness in pixels 140 :return: None 141 """ 142 143 self._tick_length = tick_length 144 self._tick_stroke = tick_stroke 145 146 if self.direction: 147 for val in self.ticksynorm[:-1]: 148 self.append( 149 rectangle_draw( 150 int(val) - 1, 151 -self._tick_length, 152 self._tick_length, 153 3, 154 self._palette, 155 ) 156 ) 157 else: 158 for val in self.ticksynorm[:-1]: 159 self.append( 160 rectangle_draw(0, -int(val), 3, self._tick_length, self._palette) 161 ) 162 163 def _draw_text(self) -> None: 164 """Private function to draw the text, uses values found in ``_conversion`` 165 :return: None 166 """ 167 index = 0 168 separation = 20 169 font_width = 12 170 if self.direction: 171 print("aca") 172 for tick_text in self.ticks[:-1]: 173 dist_x = self.ticksynorm[index] - font_width // 2 174 dist_y = separation // 2 175 text = str(int(tick_text)) 176 tick_label = Label(terminalio.FONT, text=text, x=dist_x, y=dist_y) 177 self.append(tick_label) 178 index = index + 1 179 else: 180 for tick_text in self.ticks[:-1]: 181 dist_x = -separation 182 dist_y = -self.ticksynorm[index] 183 text = str(int(tick_text)) 184 tick_label = Label(terminalio.FONT, text=text, x=dist_x, y=dist_y) 185 self.append(tick_label) 186 index = index + 1 187 188 189 class Scale(Axes): 190 """ 191 :param int x: pixel position. Defaults to :const:`0` 192 :param int y: pixel position. Defaults to :const:`0` 193 194 :param str direction: direction of the scale either :attr:`horizontal` or :attr:`vertical` 195 defaults to :attr:`horizontal` 196 197 :param int stroke: width in pixels of the axes line. Defaults to :const:`3` pixels 198 199 :param int length: scale length in pixels. Defaults to :const:`100` pixels 200 201 :param int color: 24-bit hex value axes line color, Defaults to Purple :const:`0x990099` 202 203 :param int width: scale width in pixels. Defaults to :const:`50` pixels 204 205 :param limits: tuple of value range for the scale. Defaults to :const:`(0, 100)` 206 :param list ticks: list to ticks to display. If this is not enter a equally spaced scale 207 will be created between the given limits. 208 209 :param int back_color: 24-bit hex value axes line color. 210 Defaults to Light Blue :const:`0x9FFFFF` 211 212 :param int tick_length: Scale tick length in pixels. Defaults to :const:`10` 213 :param int tick_stroke: Scale tick width in pixels. Defaults to :const:`4` 214 215 :param int pointer_length: length in pixels for the point. Defaults to :const:`20` pixels 216 :param int pointer_stroke: pointer thickness in pixels. Defaults to :const:`6` pixels 217 218 219 **Quickstart: Importing and using Scales** 220 221 Here is one way of importing the `Scale` class, so you can use it as 222 the name ``my_scale``: 223 224 .. code-block:: 225 226 from scale import Scale 227 228 Now you can create a vertical Scale at pixel position x=50, y=180 and a range 229 of 0 to 80 using: 230 231 .. code-block:: 232 233 my_scale = Scale(x=50, y=180, direction="vertical", limits=(0, 80)) 234 235 Once you setup your display, you can now add ``my_scale`` to your display using: 236 237 .. code-block:: 238 239 display.show(my_scale) 240 241 If you want to have multiple display elements, you can create a group and then 242 append the scale and the other elements to the group. Then, you can add the full 243 group to the display as in this example: 244 245 .. code-block:: python 246 247 my_scale= Scale(x=20, y=30) 248 my_group = displayio.Group() # make a group 249 my_group.append(my_scale) # Add my_slider to the group 250 251 # 252 # Append other display elements to the group 253 # 254 255 display.show(my_group) # add the group to the display 256 257 258 **Summary: Slider Features and input variables** 259 260 The `Scale` class has some options for controlling its position, visible appearance, 261 and value through a collection of input variables: 262 263 - **position**: :attr:`x``, :attr:`y` 264 265 - **size**: :attr:`length` and :attr:`width` 266 267 - **color**: :attr:`color`, :attr:`back_color` 268 269 - **linewidths**: :attr:`stroke` and :attr:`tick_stroke` 270 271 - **range**: :attr:`limits` 272 273 274 .. figure:: scales.png 275 :scale: 100 % 276 :align: center 277 :alt: Diagram of scales 278 279 Diagram showing a simple scale. 280 281 282 """ 283 284 def __init__( 285 self, 286 x: int = 0, 287 y: int = 0, 288 direction: str = "horizontal", 289 stroke: int = 3, 290 length: int = 100, 291 color: int = 0x990099, 292 width: int = 50, 293 limits: Tuple[int, int] = (0, 100), 294 back_color: int = 0x9FFFFF, 295 ticks: Optional[Union[np.array, list]] = None, 296 tick_length: int = 5, 297 tick_stroke: int = 4, 298 pointer_length: int = 20, 299 pointer_stroke: int = 6, 300 ): 301 302 super().__init__( 303 x=x, 304 y=y, 305 direction=direction, 306 stroke=stroke, 307 length=length, 308 limits=limits, 309 ticks=ticks, 310 color=color, 311 ) 312 313 self._width = width 314 self._back_color = back_color 315 self._draw_background() 316 self._draw_line() 317 self._draw_ticks() 318 self.value = 0 319 320 self._tick_length = tick_length 321 self._tick_stroke = tick_stroke 322 self._pointer_length = pointer_length 323 self._pointer_stroke = pointer_stroke 324 325 # Pointer Definitions 326 self._x0 = 0 327 self._y0 = 0 328 329 if self.direction: 330 self.center = width // 2 331 self._x1 = pointer_stroke 332 self._y1 = pointer_length 333 self._posx = 0 334 self._posy = -self.center - self._pointer_length // 2 335 else: 336 self.center = width // 2 337 self._x1 = pointer_length 338 self._y1 = pointer_stroke 339 self._posx = self.center - self._pointer_length // 2 340 self._posy = -10 341 self.pointer = None 342 343 self._draw_text() 344 self._draw_pointer() 345 346 def _draw_background(self): 347 """Private function to draw the background for the scale 348 :return: None 349 """ 350 back_palette = displayio.Palette(2) 351 back_palette.make_transparent(0) 352 back_palette[1] = self._back_color 353 354 if self.direction: 355 self.append( 356 rectangle_draw(0, -self._width, self._width, self.length, back_palette) 357 ) 358 else: 359 self.append( 360 rectangle_draw(0, -self.length, self.length, self._width, back_palette) 361 ) 362 363 def _draw_pointer( 364 self, 365 color: int = 0xFF0000, 366 ): 367 """Private function to initial draw the pointer. 368 369 :param int color: 24-bit hex value axes line color. Defaults to red :const:`0xFF0000` 370 :param int val_ini: initial value to draw the pointer 371 372 373 :return: None 374 375 """ 376 377 pointer_palette = displayio.Palette(2) 378 pointer_palette.make_transparent(0) 379 pointer_palette[1] = color 380 381 points = [ 382 (self._x0, self._y0), 383 (self._x1, self._y0), 384 (self._x1, self._y1), 385 (self._x0, self._y1), 386 ] 387 self.pointer = Polygon( 388 pixel_shader=pointer_palette, 389 points=points, 390 x=self._posx, 391 y=self._posy, 392 color_index=1, 393 ) 394 395 self.append(self.pointer) 396 397 def animate_pointer(self, new_value): 398 """Public function to animate the pointer 399 400 :param new_value: value to draw the pointer 401 :return: None 402 403 """ 404 value = int( 405 transform( 406 self._valuemin, 407 self._valuemax, 408 self._newvalmax, 409 self._newvalmin, 410 new_value, 411 ) 412 ) 413 if self.direction: 414 self.pointer.x = value - self._pointer_stroke // 2 415 else: 416 self.pointer.y = -value - self._pointer_stroke // 2 417 418 419 # pylint: disable=invalid-name 420 def rectangle_draw(x0: int, y0: int, height: int, width: int, palette): 421 """rectangle_draw function 422 423 Draws a rectangle using or `vectorio.Rectangle` 424 425 :param int x0: rectangle lower corner x position 426 :param int y0: rectangle lower corner y position 427 428 :param int width: rectangle upper corner x position 429 :param int height: rectangle upper corner y position 430 431 :param `~displayio.Palette` palette: palette object to be used to draw the rectangle 432 433 """ 434 435 return Rectangle( 436 pixel_shader=palette, width=width, height=height, x=x0, y=y0, color_index=1 437 ) 438 439 440 def transform( 441 oldrangemin: Union[float, int], 442 oldrangemax: Union[float, int], 443 newrangemin: Union[float, int], 444 newrangemax: Union[float, int], 445 value: Union[float, int], 446 ) -> Union[float, int]: 447 """ 448 This function converts the original value into a new defined value in the new range 449 450 :param int|float oldrangemin: minimum of the original range 451 :param int|float oldrangemax: maximum of the original range 452 :param int|float newrangemin: minimum of the new range 453 :param int|float newrangemax: maximum of the new range 454 :param int|float value: value to be converted 455 456 :return int|float: converted value 457 458 """ 459 460 return ( 461 ((value - oldrangemin) * (newrangemax - newrangemin)) 462 / (oldrangemax - oldrangemin) 463 ) + newrangemin