Skip to content

Sparkline memory improvements #59

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 20 commits into from
Jan 9, 2023
Merged

Sparkline memory improvements #59

merged 20 commits into from
Jan 9, 2023

Conversation

matemaciek
Copy link

  • use cyclic buffer for storing values to reduce fragmentation
  • draw as a single Polygon instead of multiple Lines

In my case current implemntation was running out of memory on ~64 data points, now I can show all 160 data points that I wanted to.

Since we specify the maximum ammount of data points, we can use cyclic buffer underneath, therefore avoiding memory fragmentation.

This should also help for problems decribed in adafruit#25
Apparently contributors are not listed there
Update docs
Previous implementation used multiple Line elements, causing unnecessary memory overhead
@matemaciek
Copy link
Author

This should help for #25

Formatting fixes
Triangle is a subclass of Polygon, so we need to provide support for more than one color in palette
Decouple polygon drawing from creating bitmap, so the code can be reused for drawing on external bitmaps
Fix some of the old warnings, reenable some of overrides
And rewrite Sparkline to be a subclass of MultiSparkline
Copy link

@FoamyGuy FoamyGuy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@matemaciek Thanks for working on improving the efficiency of this!

I've spotted a few issues attempting to run the existing examples with the new version of the repo. We'll definitely want to get those straightened out either by modifying the classes as needed, or modifying the examples if an API has changed or something causing a need for that.

Some are noted at relevant sections of the code.

With the "triple" example, specifically sparkline2 within it, I've also found that the new version of the library raises a pixel out of bounds error:

Traceback (most recent call last):
  File "code.py", line 245, in <module>
  File "/lib/adafruit_display_shapes/sparkline.py", line 96, in add_value
  File "/lib/adafruit_display_shapes/multisparkline.py", line 197, in add_values
  File "/lib/adafruit_display_shapes/multisparkline.py", line 316, in update_line
  File "/lib/adafruit_display_shapes/multisparkline.py", line 237, in _draw
  File "/lib/adafruit_display_shapes/polygon.py", line 101, in draw
  File "/lib/adafruit_display_shapes/polygon.py", line 161, in _line_on
IndexError: pixel coordinates out of bounds

It seems to only be a problem with sparkline2, but I'm not certain exactly why that one is the problematic one. Likely something about the behavior around the range, or size and shape has changed a little bit causing it.

I've started debugging a bit, but don't have a conclusive answer yet. So far it's looking like it tries to put pixels either at negative y values, or at y value 100 which is 1 pixel too high, 99 is the largest available index in the bitmap in question.

From that it seems to point toward there being some logic issue in the conversion between points in the range of the sparkline graph to points in the 2D plane of the pixels in the bitmap. Or maybe a bug in the example that was previously handled more gracefully despite being a bug?

We'll need to get it figured out and fixed as well though. We want to make sure all examples in the repo. are able to run successfully.

@matemaciek
Copy link
Author

Thanks for the review! I've addressed inline comments directly. As for of-by-one errors: The existing version (in original repo) seem to have a semi-bug where in some scenarios it draws on larger bitmap than requested - but since bitmap size is auto-set basing on list of points provided, they always fit. I'll debug the existing example that is failing, as I tought I fixed that bug (-:

@matemaciek
Copy link
Author

While I'm working on fixing issues above, a screenshot of how it looks in action:
20230107_201345

@matemaciek
Copy link
Author

OK, I know where the bug is (-: When reorganising the code I broke an edge case when first point of sparkline would be out of screen. Working on a fix.

@matemaciek matemaciek requested a review from FoamyGuy January 7, 2023 23:04
Copy link

@FoamyGuy FoamyGuy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for digging in and figuring out some fixes!

I think you also fixed a another issue I noticed yesterday (with the released version ) where the dynxwidth config sometimes causes the line to be drawn below the chart. The latest version from this PR does not exhibit that issue any more.

The functionality on this is looking good to me now. I tested the 3 existing examples and all are looking good with this version.

I have one question about the static functions, and one question whether we can use a builtin draw_line instead of needing an implementation here.


super().__init__(
self._bitmap, pixel_shader=self._palette, x=x_offset, y=y_offset
)

# pylint: disable=invalid-name, too-many-locals, too-many-branches
@staticmethod
def draw(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious about a few of these methods being static. draw(), _line_on() and _safe_draw() Did this change to static factor in to memory efficiency somehow?

Is there a situation in which they are intended for use outside of in an instance of this class ultimately inside of a sparkline?

My first inclination was that they could be regular functions and could access bitmap from self rather than having it passed in separately. That way they are always acting upon only the bitmap that is part of this instance.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see resposibilities as follows:

  • Polygon class: class containing logic of drawing polygons
  • polygon object: bitmap containing single drawn polygon

Having draw() method as static alows us to reuse drawing logic to draw multiple times on the same bitmap - which allows for two things previously not possible:

  • having multiple polygons on single bitmap (lower memory usage)
  • reusing same bitmap for sequential redrawing of different polygons on the same bitmap (common case for sparklines, and not recreating bitmap over and over potentially means less fragmentation).


# pylint: disable=too-many-branches, too-many-locals
@staticmethod
def _line_on(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it may be possible to use this built-in bitmaptools function from the core rather than having the implementation in python here. https://docs.circuitpython.org/en/latest/shared-bindings/bitmaptools/index.html#bitmaptools.draw_line

Perhaps ultimately we could make a draw_polygon() function inside bitmaptools as well which could then be used generally from anywhere, including this library.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's great to see that built-in function, I didn't knew it exists! Feel free to approve/merge this MR already, I'll work on using built-in function either way - one doubt I have is handling out-of-bonds coordinates by it, I'll need to check how it behaves.

As for adding draw_polygon() to bitmaptools I'm totally up for it, sounds like a nice starting point for contribting to circuitpython core (-:

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just checked, bitmaptools.draw_line() does not like lines going out of bitmap - I think that it could be a valid scenario, so I may end up trying to change that behaviour in bitmaptools before using it here.

@FoamyGuy
Copy link

FoamyGuy commented Jan 8, 2023

While I'm working on fixing issues above, a screenshot of how it looks in action: 20230107_201345

That looks awesome! Thank you for sharing! Do post anywhere online about your project? If you interested it could totally be submitted for inclusion in the weekly python on hardware newsletter.

@matemaciek
Copy link
Author

That looks awesome! Thank you for sharing! Do post anywhere online about your project? If you interested it could totally be submitted for inclusion in the weekly python on hardware newsletter.

Thanks! I'm definitely up for including my project in the newsletter (-: I think it would be best to do it after I publish code of it, so If someone is interested, they can reuse it. My goal was to build stand-alone CO₂ sensor, so one can know when to open the window and let more air in (just by color-coding values). I ended up adding screen, then some in-memory logging, then simple webserver for better viewing of charts, all living on Pico W (I try to be cost-aware in this project). Without improvements it was running out of memory after ~30 data points, now it seem to be safely running on 160 data points while serving some data online, but I want to fit more (-:

@matemaciek matemaciek requested a review from FoamyGuy January 8, 2023 20:03
Copy link

@FoamyGuy FoamyGuy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. Thank you!

@FoamyGuy FoamyGuy merged commit cf2b173 into adafruit:main Jan 9, 2023
adafruit-adabot added a commit to adafruit/Adafruit_CircuitPython_Bundle that referenced this pull request Jan 9, 2023
Updating https://github.com/adafruit/Adafruit_CircuitPython_Display_Shapes to 2.6.0 from 2.5.6:
  > Merge pull request adafruit/Adafruit_CircuitPython_Display_Shapes#59 from matemaciek/main
  > Add .venv to .gitignore
  > Update .pylintrc for v2.15.5
  > Fix release CI files
  > Update pylint to 2.15.5
  > Updated pylint version to 2.13.0
  > Switching to composite actions

Updating https://github.com/adafruit/Adafruit_CircuitPython_Bundle/circuitpython_library_list.md to NA from NA:
  > Updated download stats for the libraries
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants