├── src ├── microplot │ ├── __init__.py │ ├── adafruit │ │ ├── __init__.py │ │ ├── code.py │ │ ├── fs.py │ │ ├── bittest.py │ │ ├── scatter-save.py │ │ ├── plotter.py │ │ └── adafruit_bitmapsaver.py │ ├── explorer │ │ ├── __init__.py │ │ ├── examples │ │ │ ├── __init__.py │ │ │ ├── plot_battery_data.py │ │ │ ├── send.py │ │ │ ├── demo_mono_bitmap.py │ │ │ ├── demo_color_bitmap.py │ │ │ ├── battery-volts.py │ │ │ ├── cap-voltage.py │ │ │ ├── data_log.py │ │ │ ├── rechargeable.csv │ │ │ └── volts.csv │ │ ├── plotter.py │ │ └── bitmapsaver.py │ ├── workspace.code-workspace │ └── shared │ │ ├── scatter_demo.py │ │ ├── demo.py │ │ ├── demo_multi.py │ │ ├── demo_save.py │ │ ├── plotter.py │ │ ├── color.py │ │ ├── bmp.py │ │ ├── abstract_plotter.py │ │ └── plots.py ├── host │ ├── binary.bmp │ └── receive.py └── spikes │ └── check-pixel-code.py ├── external ├── adafruit_bus_device │ ├── __init__.py │ ├── i2c_device.mpy │ └── spi_device.mpy ├── adafruit_sdcard.mpy ├── adafruit_display_text │ ├── label.mpy │ ├── __init__.mpy │ └── bitmap_label.mpy ├── adafruit_display_shapes │ ├── line.mpy │ ├── rect.mpy │ ├── circle.mpy │ ├── polygon.mpy │ ├── triangle.mpy │ ├── roundrect.mpy │ └── sparkline.mpy ├── check_so_bitmaps.py ├── so_mono_bitmap.py ├── so_mono_bitmap_annotated.py └── adafruit_bitmapsaver.py ├── demo-color24.bmp ├── docs └── img │ ├── bmp.png │ ├── demo.bmp │ ├── sine.jpg │ ├── c-mono.bmp │ ├── cap-cu.jpg │ ├── cdemo.bmp │ ├── demo1.bmp │ ├── new-sin.jpg │ ├── sine3.jpg │ ├── 3-colours.jpg │ ├── cap-demo.jpg │ ├── duff │ ├── demo.bmp │ ├── demo_multi.bmp │ ├── scatter_demo.bmp │ └── screenshot.bmp │ ├── color-plot.bmp │ ├── color-plot.jpg │ ├── rechargeable.jpg │ ├── two-colours.jpg │ └── pyportal-screen.jpg ├── plan ├── adafruit-libraries-needed.txt ├── journal.md └── microplot-plan.mm ├── LICENSE ├── README.md ├── SAMPLES.md └── .gitignore /src/microplot/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/microplot/adafruit/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/microplot/explorer/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /external/adafruit_bus_device/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/microplot/explorer/examples/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/microplot/adafruit/code.py: -------------------------------------------------------------------------------- 1 | print('Hello World') 2 | -------------------------------------------------------------------------------- /demo-color24.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romilly/microplot/HEAD/demo-color24.bmp -------------------------------------------------------------------------------- /docs/img/bmp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romilly/microplot/HEAD/docs/img/bmp.png -------------------------------------------------------------------------------- /docs/img/demo.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romilly/microplot/HEAD/docs/img/demo.bmp -------------------------------------------------------------------------------- /docs/img/sine.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romilly/microplot/HEAD/docs/img/sine.jpg -------------------------------------------------------------------------------- /plan/adafruit-libraries-needed.txt: -------------------------------------------------------------------------------- 1 | adafruit_display_text 2 | adafruit_display_shapes 3 | -------------------------------------------------------------------------------- /docs/img/c-mono.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romilly/microplot/HEAD/docs/img/c-mono.bmp -------------------------------------------------------------------------------- /docs/img/cap-cu.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romilly/microplot/HEAD/docs/img/cap-cu.jpg -------------------------------------------------------------------------------- /docs/img/cdemo.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romilly/microplot/HEAD/docs/img/cdemo.bmp -------------------------------------------------------------------------------- /docs/img/demo1.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romilly/microplot/HEAD/docs/img/demo1.bmp -------------------------------------------------------------------------------- /docs/img/new-sin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romilly/microplot/HEAD/docs/img/new-sin.jpg -------------------------------------------------------------------------------- /docs/img/sine3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romilly/microplot/HEAD/docs/img/sine3.jpg -------------------------------------------------------------------------------- /src/host/binary.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romilly/microplot/HEAD/src/host/binary.bmp -------------------------------------------------------------------------------- /docs/img/3-colours.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romilly/microplot/HEAD/docs/img/3-colours.jpg -------------------------------------------------------------------------------- /docs/img/cap-demo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romilly/microplot/HEAD/docs/img/cap-demo.jpg -------------------------------------------------------------------------------- /docs/img/duff/demo.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romilly/microplot/HEAD/docs/img/duff/demo.bmp -------------------------------------------------------------------------------- /docs/img/color-plot.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romilly/microplot/HEAD/docs/img/color-plot.bmp -------------------------------------------------------------------------------- /docs/img/color-plot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romilly/microplot/HEAD/docs/img/color-plot.jpg -------------------------------------------------------------------------------- /docs/img/rechargeable.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romilly/microplot/HEAD/docs/img/rechargeable.jpg -------------------------------------------------------------------------------- /docs/img/two-colours.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romilly/microplot/HEAD/docs/img/two-colours.jpg -------------------------------------------------------------------------------- /docs/img/duff/demo_multi.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romilly/microplot/HEAD/docs/img/duff/demo_multi.bmp -------------------------------------------------------------------------------- /docs/img/duff/scatter_demo.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romilly/microplot/HEAD/docs/img/duff/scatter_demo.bmp -------------------------------------------------------------------------------- /docs/img/duff/screenshot.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romilly/microplot/HEAD/docs/img/duff/screenshot.bmp -------------------------------------------------------------------------------- /docs/img/pyportal-screen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romilly/microplot/HEAD/docs/img/pyportal-screen.jpg -------------------------------------------------------------------------------- /external/adafruit_sdcard.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romilly/microplot/HEAD/external/adafruit_sdcard.mpy -------------------------------------------------------------------------------- /external/adafruit_display_text/label.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romilly/microplot/HEAD/external/adafruit_display_text/label.mpy -------------------------------------------------------------------------------- /external/adafruit_display_shapes/line.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romilly/microplot/HEAD/external/adafruit_display_shapes/line.mpy -------------------------------------------------------------------------------- /external/adafruit_display_shapes/rect.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romilly/microplot/HEAD/external/adafruit_display_shapes/rect.mpy -------------------------------------------------------------------------------- /external/adafruit_bus_device/i2c_device.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romilly/microplot/HEAD/external/adafruit_bus_device/i2c_device.mpy -------------------------------------------------------------------------------- /external/adafruit_bus_device/spi_device.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romilly/microplot/HEAD/external/adafruit_bus_device/spi_device.mpy -------------------------------------------------------------------------------- /external/adafruit_display_shapes/circle.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romilly/microplot/HEAD/external/adafruit_display_shapes/circle.mpy -------------------------------------------------------------------------------- /external/adafruit_display_shapes/polygon.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romilly/microplot/HEAD/external/adafruit_display_shapes/polygon.mpy -------------------------------------------------------------------------------- /external/adafruit_display_shapes/triangle.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romilly/microplot/HEAD/external/adafruit_display_shapes/triangle.mpy -------------------------------------------------------------------------------- /external/adafruit_display_text/__init__.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romilly/microplot/HEAD/external/adafruit_display_text/__init__.mpy -------------------------------------------------------------------------------- /external/adafruit_display_shapes/roundrect.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romilly/microplot/HEAD/external/adafruit_display_shapes/roundrect.mpy -------------------------------------------------------------------------------- /external/adafruit_display_shapes/sparkline.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romilly/microplot/HEAD/external/adafruit_display_shapes/sparkline.mpy -------------------------------------------------------------------------------- /external/adafruit_display_text/bitmap_label.mpy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romilly/microplot/HEAD/external/adafruit_display_text/bitmap_label.mpy -------------------------------------------------------------------------------- /src/microplot/workspace.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "../../../microplot-private" 5 | }, 6 | { 7 | "name": "microplot", 8 | "path": "../.." 9 | } 10 | ], 11 | "settings": {} 12 | } -------------------------------------------------------------------------------- /src/microplot/shared/scatter_demo.py: -------------------------------------------------------------------------------- 1 | from plotter import Plotter 2 | from plots import ScatterPlot 3 | 4 | data = [[(20, 30), (40, 50), (10, 90), (60, 60)], [(10, 25), (45, 65)], [(33, 70)]] 5 | 6 | plot = ScatterPlot(data, 'Scatter Plot', "Triangle") 7 | plotter = Plotter() 8 | plot.plot(plotter) 9 | -------------------------------------------------------------------------------- /src/microplot/shared/demo.py: -------------------------------------------------------------------------------- 1 | import math 2 | from plotter import Plotter 3 | from plots import LinePlot 4 | 5 | def run(): 6 | sines = list(math.sin(math.radians(x)) 7 | for x in range(0, 361, 5)) 8 | plot = LinePlot([sines],'MicroPlot line') 9 | plotter = Plotter() 10 | plot.plot(plotter) -------------------------------------------------------------------------------- /src/microplot/explorer/examples/plot_battery_data.py: -------------------------------------------------------------------------------- 1 | from explorer import ExplorerPlotter 2 | from plots import LinePlot 3 | from data_log import read_csv_data 4 | 5 | 6 | data = read_csv_data('rechargeable.csv', skip=2) 7 | 8 | plot = LinePlot(data,'discharging') 9 | plotter = ExplorerPlotter() 10 | plot.plot(plotter) 11 | -------------------------------------------------------------------------------- /src/microplot/explorer/examples/send.py: -------------------------------------------------------------------------------- 1 | def send(filename): 2 | with open(filename,'rb') as bf: 3 | while True: 4 | buffer = bf.read(100) 5 | if len(buffer) == 0: 6 | break 7 | text= ''.join('%02X' % ch for ch in buffer) 8 | print(text) 9 | 10 | send('demo1.bmp') -------------------------------------------------------------------------------- /src/microplot/explorer/examples/demo_mono_bitmap.py: -------------------------------------------------------------------------------- 1 | import math 2 | from plotter import Plotter 3 | from plots import LinePlot 4 | 5 | 6 | def run(): 7 | sines = list(math.sin(math.radians(x)) 8 | for x in range(0, 361, 5)) 9 | plot = LinePlot([sines],'MicroPlot line') 10 | plotter = Plotter() 11 | plot.plot(plotter) 12 | plotter.write_mono_bitmap('demo-mono.bmp') 13 | -------------------------------------------------------------------------------- /src/microplot/adafruit/fs.py: -------------------------------------------------------------------------------- 1 | import board 2 | import busio 3 | import digitalio 4 | import adafruit_sdcard 5 | import storage 6 | 7 | 8 | def mount(location='/sd'): 9 | print('Setting up SD card') 10 | spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO) 11 | cs = digitalio.DigitalInOut(board.SD_CS) 12 | sdcard = adafruit_sdcard.SDCard(spi, cs) 13 | vfs = storage.VfsFat(sdcard) 14 | storage.mount(vfs, location) -------------------------------------------------------------------------------- /src/microplot/explorer/examples/demo_color_bitmap.py: -------------------------------------------------------------------------------- 1 | import math 2 | from plotter import Plotter 3 | from plots import LinePlot 4 | from bitmapsaver import save_pixels 5 | 6 | 7 | def run(): 8 | sines = list(math.sin(math.radians(x)) 9 | for x in range(0, 361, 4)) 10 | plot = LinePlot([sines],'MicroPlot line') 11 | plotter = Plotter() 12 | plot.plot(plotter) 13 | save_pixels('color-plot.bmp', plotter) 14 | 15 | -------------------------------------------------------------------------------- /plan/journal.md: -------------------------------------------------------------------------------- 1 | # Project journal for MicroPlot 2 | 3 | ## Tuesday 02 March 2021 4 | 5 | Lots of interest, and useful work over thew last couple of days on Plotters for the Adafruit Clue and PyPortal. 6 | 7 | I need to write an end-to-end test using a saved bitmap before working on the refactoring needed to extend the range 8 | of plot types and supported boards. 9 | 10 | I'm going to adapt the adafruit 24-colour bitmap saver to work with the Explorer 11 | 12 | -------------------------------------------------------------------------------- /src/microplot/shared/demo_multi.py: -------------------------------------------------------------------------------- 1 | import math 2 | from plotter import Plotter 3 | from plots import LinePlot 4 | from bitmap_saver import save_pixels 5 | 6 | 7 | def row(i): 8 | offset = [0, 45, 90, 135, 180][i] 9 | return list(math.sin(math.radians(x + offset)) 10 | for x in range(0, 361, 5)) 11 | 12 | 13 | data = list(row(i) for i in range(5)) 14 | plot = LinePlot(data,'Muli-line plot') 15 | plotter = Plotter() 16 | plot.plot(plotter) 17 | -------------------------------------------------------------------------------- /external/check_so_bitmaps.py: -------------------------------------------------------------------------------- 1 | from so_mono_bitmap import bmp 2 | from so_mono_bitmap_annotated import bmp as bmpa 3 | 4 | smile = [[0xFF], [0x81], [0xA5], [0x81], [0xA5], [0xBD], [0x81], [0xFF]] 5 | 6 | s1 = bytearray(bmp(smile, 8)) 7 | s2 = bytearray(bmpa(reversed(smile), 8, 8)) 8 | #print(type(s1)) 9 | 10 | def compare(s1, s2): 11 | for (b1, b2) in zip(s1, s2): 12 | if b1 != b2: 13 | return False 14 | return True 15 | 16 | print(compare(s1, s2)) -------------------------------------------------------------------------------- /src/microplot/explorer/examples/battery-volts.py: -------------------------------------------------------------------------------- 1 | from machine import ADC 2 | import time 3 | 4 | def log_voltages(file_name): 5 | adc = ADC(26) 6 | with open(file_name,'w') as csv: 7 | while True: 8 | voltage = 3.3 * adc.read_u16() / 65535.0 9 | text = '%5.3f' % voltage 10 | print(text) 11 | csv.write(text) 12 | csv.write('\n') 13 | time.sleep(60) 14 | 15 | log_voltages('rechargeable.csv') 16 | -------------------------------------------------------------------------------- /src/microplot/shared/demo_save.py: -------------------------------------------------------------------------------- 1 | import math 2 | from plotter import Plotter 3 | from plots import LinePlot 4 | from bitmapsaver import save_pixels 5 | 6 | 7 | def row(i): 8 | offset = [0, 45, 90, 135, 180][i] 9 | return list(math.sin(math.radians(x + offset)) 10 | for x in range(0, 361, 5)) 11 | 12 | 13 | data = list(row(i) for i in range(5)) 14 | plot = LinePlot(data,'Muli-line plot') 15 | plotter = Plotter() 16 | plot.plot(plotter) 17 | save_pixels('demo-color24.bmp', plotter) 18 | -------------------------------------------------------------------------------- /src/microplot/shared/plotter.py: -------------------------------------------------------------------------------- 1 | from abstract_plotter import AbstractPlotter, Frame 2 | 3 | 4 | class Plotter(AbstractPlotter): 5 | def __init__(self, frame= None): 6 | AbstractPlotter.__init__(self, frame) 7 | self.circles = [] 8 | 9 | def circle(self, x, y, r, color): 10 | self.circles.append((x, y, r, color)) 11 | 12 | def triangle(self, x, y, r, color): 13 | self.triangle.append((x, y, r, color)) 14 | 15 | def default_frame(self): 16 | return Frame(320, 240, 20, 20, 60, 20) -------------------------------------------------------------------------------- /src/spikes/check-pixel-code.py: -------------------------------------------------------------------------------- 1 | class MockPlotter: 2 | def __init__(self): 3 | self.width = 10 4 | self.height = 5 5 | self._display_buffer = list(range(100)) 6 | 7 | def get_pixel(self, x, y): 8 | start = x + y*self.width 9 | data = self._display_buffer 10 | # b_low, b_high = self._display_buffer[start:start+2] 11 | # return b_low + b_high << 8 12 | return (data[start * 2] ) , (data[start * 2 + 1]) 13 | 14 | for y in range(5): 15 | print(list(MockPlotter().get_pixel(x,y) for x in range(10))) 16 | 17 | -------------------------------------------------------------------------------- /external/so_mono_bitmap.py: -------------------------------------------------------------------------------- 1 | import math, struct 2 | 3 | mult4 = lambda n: int(math.ceil(n/4))*4 4 | mult8 = lambda n: int(math.ceil(n/8))*8 5 | lh = lambda n: struct.pack(" int: 26 | r, g, b = triple 27 | return (r << 16) + (g << 8) + b 28 | 29 | @classmethod 30 | def hex_to_rgb(cls, hex_color: int) -> tuple: 31 | r = (hex_color >> 16) & 0xFF 32 | g = (hex_color >> 8) & 0xFF 33 | b = hex_color & 0xFF 34 | return (r, g, b) 35 | 36 | @classmethod 37 | def rgb565_to_bgr_tuple(cls, color: int) -> tuple: 38 | blue = (color << 3) & 0x00F8 # extract each of the RGB triple into its own byte 39 | green = (color >> 3) & 0x00FC 40 | red = (color >> 8) & 0x00F8 41 | return (blue, green, red) 42 | 43 | @classmethod 44 | def rgb565_to_rgb_tuple(cls, color: int) -> tuple: 45 | b, g, r = cls.rgb565_to_bgr_tuple(color) 46 | return (r, g, b) 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # microplot 2 | 3 | ## A simple MicroPython plotting package. 4 | 5 | The current version runs on the Raspberry Pi Pico with the Pimoroni Pico Explorer base, the Adafruit Clue and 6 | Adafruit PyPortal. 7 | 8 | It does Line plots amd Scatter plots at the moment. 9 | 10 | Some code was copied from https://www.instructables.com/Raspberry-Pi-Pico-Pico-Explorer-Workout/ 11 | - Tony Goodhew's great introduction to the Pico Explorer. 12 | 13 | The line drawing uses code from https://github.com/encukou/bresenham 14 | Copyright © 2016 Petr Viktorin 15 | 16 | ![Sample Plot](docs/img/sine3.jpg) 17 | 18 | ## Installation 19 | 20 | On all platforms, copy the Python files in `src/microplot/shared` to the device. 21 | 22 | Then copy plotter.py from `src/micorplot/explorer` if you're using the Pomoroni explorer, 23 | or from `src/microplot/adafruit` if you're using one f the Adafruit devices. 24 | 25 | ## Quick Start 26 | 27 | There are three demos you can run: 28 | 1. `demo.py` shows a single-line plot of a sine wave, 29 | 1. `demo_multi.py` shows a plot of multiple sine waves, 30 | 1. `scatter_demo.py` shows a scatter plot of some arbitrary data. 31 | 32 | ## Bitmap saving 33 | 34 | There's [code to save bitmaps](src/microplot/explorer/bitmapsaver.py) from the Pico Explorer Base. 35 | 36 | To save bitmaps on the PyPortal you'll need to use a patched version of Adafruit's bitmap saver available 37 | [here](src/microplot/adafruit/adafruit_bitmapsaver.py) and then run code like [this demo](src/microplot/adafruit/code.py) 38 | 39 | ## Samples 40 | 41 | There are more [sample displays](SAMPLES.md) along with the code used to create them 42 | 43 | ## Road Map 44 | 45 | 1. Improve the documentation 46 | 1. Add box plots 47 | 1. Add examples for other displays 48 | 1. Add a *Contributing* guide 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /external/so_mono_bitmap_annotated.py: -------------------------------------------------------------------------------- 1 | import struct 2 | 3 | WORD = "= n 16 | """ 17 | return m * ((n+m-1) // m) 18 | 19 | 20 | def word(n): 21 | """ 22 | return n as a little-endian two-byte integer 23 | """ 24 | return struct.pack(WORD, n) 25 | 26 | 27 | def dword(n): 28 | """ 29 | return n as a little-endian four-byte integer 30 | """ 31 | return struct.pack(DWORD, n) 32 | 33 | 34 | def write_mono_bmp(file_name, rows, height, width): 35 | width_in_bytes = int(mult(8, width) / 8) 36 | pad = [0]*(mult(4, width_in_bytes)-width_in_bytes) 37 | bfSize = dword(mult(4,width) * height + 0x20) 38 | bfReserved1 = b'\x00\x00' 39 | bfReserved2 = b'\x00\x00' 40 | bfOffBits = b'\x20\x00\x00\x00' 41 | bfType = b"BM" 42 | biSize = b'\x0C\x00\x00\x00' 43 | biWidth = word(width) 44 | biHeight = word(height) 45 | biPlanes = b'\x01\x00' 46 | biBitCount = b'\x01\x00' 47 | rgbBlack = b'\xff\xff\xff' 48 | rgbWhite = b'\x00\x00\x00' 49 | with open(file_name,'wb') as bmf: 50 | bmf.write( 51 | # BITMAPFILEHEADER 52 | bfType + 53 | bfSize + 54 | bfReserved1 + 55 | bfReserved2 + 56 | bfOffBits + 57 | # BITMAPINFOHEADER 58 | biSize + 59 | biWidth + 60 | biHeight + 61 | biPlanes + 62 | biBitCount + 63 | # RGB_TRIPLES 64 | rgbBlack + 65 | rgbWhite) 66 | # PIXELS 67 | for padded in pad_rows(pad, rows): 68 | bmf.write(padded) 69 | bmf.close() 70 | 71 | 72 | 73 | 74 | def pad_rows(pad, rows): 75 | return b"".join([bytes(row + pad) for row in rows]) -------------------------------------------------------------------------------- /src/microplot/explorer/bitmapsaver.py: -------------------------------------------------------------------------------- 1 | """ 2 | Adapted from "https://github.com/adafruit/Adafruit_CircuitPython_BitmapSaver.git" 3 | """ 4 | 5 | 6 | import gc 7 | import struct 8 | 9 | from abstract_plotter import AbstractPlotter 10 | 11 | 12 | def _write_bmp_header(output_file, filesize): 13 | output_file.write(bytes("BM", "ascii")) 14 | output_file.write(struct.pack(" int: 35 | return self.frame.width 36 | 37 | def height(self) -> int: 38 | return self.frame.height 39 | 40 | def text(self, x, y, text): 41 | pass 42 | 43 | def default_frame(self) -> Frame: 44 | pass 45 | 46 | def show(self): 47 | pass 48 | 49 | def get_pixel(self, x, y) -> tuple: 50 | pass 51 | 52 | def set_pen(self, color: tuple): 53 | self.pen = color 54 | 55 | def circle(self, x, y, r, color): 56 | pass 57 | 58 | def display_pixel(self, x, y): 59 | pass 60 | 61 | def write_mono_bitmap(self, file_name): 62 | with MonoBitmapWriter(file_name, self.frame.width, self.frame.height) as mbw: 63 | bytes_in_row = self.frame.width // 8 64 | row_bytes = bytearray(bytes_in_row) 65 | for i in range(self.frame.height): 66 | for j in range(bytes_in_row): 67 | row_bytes[j] = 0 68 | for k in range(8): 69 | x = k + 8 * j 70 | y = self.frame.height - (i + 1) 71 | bit = ((0,0,0) != self.get_pixel(x, y)) 72 | row_bytes[j] |= bit << (7 - k) 73 | mbw.add_row(row_bytes) 74 | 75 | """Implementation of Bresenham's line drawing algorithm 76 | 77 | See en.wikipedia.org/wiki/Bresenham's_line_algorithm 78 | Code from https://github.com/encukou/bresenham 79 | """ 80 | 81 | def bresenham(self, x0, y0, x1, y1): 82 | """Yield integer coordinates on the line from (x0, y0) to (x1, y1). 83 | Input coordinates should be integers. 84 | The result will contain both the start and the end point. 85 | 86 | """ 87 | dx = x1 - x0 88 | dy = y1 - y0 89 | 90 | xsign = 1 if dx > 0 else -1 91 | ysign = 1 if dy > 0 else -1 92 | 93 | dx = abs(dx) 94 | dy = abs(dy) 95 | 96 | if dx > dy: 97 | xx, xy, yx, yy = xsign, 0, 0, ysign 98 | else: 99 | dx, dy = dy, dx 100 | xx, xy, yx, yy = 0, ysign, xsign, 0 101 | 102 | D = 2 * dy - dx 103 | y = 0 104 | 105 | for x in range(dx + 1): 106 | yield x0 + x * xx + y * yx, y0 + x * xy + y * yy 107 | if D >= 0: 108 | y += 1 109 | D -= 2 * dx 110 | D += 2 * dy 111 | 112 | -------------------------------------------------------------------------------- /src/microplot/adafruit/plotter.py: -------------------------------------------------------------------------------- 1 | from abstract_plotter import AbstractPlotter, Frame 2 | from color import COLORS 3 | import board 4 | import displayio 5 | import terminalio 6 | from adafruit_display_text import label 7 | from adafruit_display_shapes.circle import Circle 8 | from adafruit_display_shapes.triangle import Triangle 9 | 10 | 11 | class Plotter(AbstractPlotter): 12 | def __init__(self): 13 | AbstractPlotter.__init__(self) 14 | self.display = board.DISPLAY 15 | self.width = self.display.width 16 | self.height = self.display.height 17 | self.group = displayio.Group(max_size=100) 18 | colors = COLORS.ALL + (COLORS.WHITE, COLORS.BLACK) 19 | self.bitmap = displayio.Bitmap(320, 240, len(colors)) 20 | self.palette = displayio.Palette(len(colors)) 21 | self.pallet_index = {} 22 | for (index, color) in enumerate(colors): 23 | hex_color = COLORS.to_hex_color(color) 24 | self.palette[index] = hex_color 25 | self.pallet_index[color] = index 26 | tile_grid = displayio.TileGrid(self.bitmap, pixel_shader=self.palette) 27 | self.group.append(tile_grid) 28 | self.font = terminalio.FONT 29 | self.blk() 30 | self.display.show(self.group) 31 | self.display.refresh() 32 | 33 | def display_pixel(self, x, y): 34 | self.bitmap[x, y] = self.pen 35 | 36 | def get_pixel(self, x, y): 37 | return self.bitmap[x, y] 38 | 39 | def text(self, x, y, text): 40 | text_area = label.Label(self.font, text=text, color=COLORS.to_hex_color(COLORS.WHITE)) 41 | text_area.x = x 42 | text_area.y = y + 10 43 | self.group.append(text_area) 44 | 45 | def circle(self, x, y, r, color=COLORS.RED): 46 | color_hex = COLORS.to_hex_color(color) 47 | c = Circle(x, y, r, fill=color_hex, outline=color_hex) 48 | self.group.append(c) 49 | 50 | def triangle(self, x: int, y: int, r: int, color=COLORS.RED) -> None: 51 | """ 52 | triangle function 53 | Draws a equilateral triangle with center in cordinates (x, y) and side 54 | size ``a``. Where ``a`` is equal to ``(6 x r) / √3`` 55 | 56 | (x0,y0) 57 | /\ 58 | / \ 59 | / . \ 60 | / x, y \ 61 | (x2,y2)/________\ (x1,y1) 62 | 63 | :param int x: x coordinate of the triangle center 64 | :param int y: y coordinate of the triangle center 65 | :param int r: r radius of the circle inside the triangle 66 | :param int color: color identification 67 | :return: None 68 | :rtype None 69 | """ 70 | color_hex = COLORS.to_hex_color(color) 71 | # to simplify math we take the following approximation √3≈1.732 72 | square_three = 1.732 73 | r = r // 2 74 | x0 = x - int(round(square_three * r)) 75 | y0 = y + r 76 | x1 = x 77 | y1 = y - int(round(square_three * 2 * r)) 78 | x2 = x + int(round(square_three * r)) 79 | y2 = y + r 80 | 81 | c = Triangle(x0, y0, x1, y1, x2, y2, fill=color_hex, outline=color_hex) 82 | self.group.append(c) 83 | 84 | def set_pen(self, color): 85 | self.pen = self.pallet_index[color] 86 | 87 | def default_frame(self): 88 | return Frame(320, 240, 20, 20, 60, 20) 89 | 90 | def blk(self): 91 | self.bitmap.fill(len(self.palette)) 92 | 93 | def show(self): 94 | self.display.show(self.group) 95 | -------------------------------------------------------------------------------- /src/microplot/explorer/examples/rechargeable.csv: -------------------------------------------------------------------------------- 1 | 1.235 2 | 1.235 3 | 1.232 4 | 1.230 5 | 1.227 6 | 1.227 7 | 1.222 8 | 1.223 9 | 1.219 10 | 1.219 11 | 1.216 12 | 1.215 13 | 1.215 14 | 1.212 15 | 1.213 16 | 1.211 17 | 1.210 18 | 1.204 19 | 1.207 20 | 1.206 21 | 1.204 22 | 1.202 23 | 1.202 24 | 1.201 25 | 1.200 26 | 1.196 27 | 1.202 28 | 1.197 29 | 1.197 30 | 1.199 31 | 1.196 32 | 1.197 33 | 1.196 34 | 1.194 35 | 1.193 36 | 1.193 37 | 1.192 38 | 1.194 39 | 1.193 40 | 1.190 41 | 1.193 42 | 1.191 43 | 1.189 44 | 1.190 45 | 1.190 46 | 1.189 47 | 1.188 48 | 1.189 49 | 1.189 50 | 1.189 51 | 1.188 52 | 1.187 53 | 1.187 54 | 1.186 55 | 1.186 56 | 1.185 57 | 1.184 58 | 1.185 59 | 1.185 60 | 1.184 61 | 1.183 62 | 1.183 63 | 1.183 64 | 1.184 65 | 1.183 66 | 1.181 67 | 1.181 68 | 1.183 69 | 1.181 70 | 1.182 71 | 1.181 72 | 1.180 73 | 1.180 74 | 1.180 75 | 1.179 76 | 1.180 77 | 1.180 78 | 1.178 79 | 1.177 80 | 1.177 81 | 1.177 82 | 1.176 83 | 1.177 84 | 1.175 85 | 1.174 86 | 1.173 87 | 1.173 88 | 1.175 89 | 1.173 90 | 1.175 91 | 1.175 92 | 1.171 93 | 1.173 94 | 1.171 95 | 1.172 96 | 1.173 97 | 1.172 98 | 1.171 99 | 1.170 100 | 1.171 101 | 1.172 102 | 1.169 103 | 1.169 104 | 1.169 105 | 1.168 106 | 1.168 107 | 1.169 108 | 1.168 109 | 1.167 110 | 1.168 111 | 1.168 112 | 1.167 113 | 1.168 114 | 1.168 115 | 1.167 116 | 1.166 117 | 1.166 118 | 1.164 119 | 1.167 120 | 1.164 121 | 1.164 122 | 1.165 123 | 1.167 124 | 1.164 125 | 1.163 126 | 1.164 127 | 1.164 128 | 1.164 129 | 1.164 130 | 1.163 131 | 1.164 132 | 1.164 133 | 1.163 134 | 1.161 135 | 1.162 136 | 1.162 137 | 1.163 138 | 1.160 139 | 1.161 140 | 1.160 141 | 1.161 142 | 1.158 143 | 1.160 144 | 1.160 145 | 1.162 146 | 1.160 147 | 1.159 148 | 1.160 149 | 1.157 150 | 1.160 151 | 1.159 152 | 1.160 153 | 1.159 154 | 1.158 155 | 1.158 156 | 1.157 157 | 1.156 158 | 1.157 159 | 1.156 160 | 1.157 161 | 1.156 162 | 1.156 163 | 1.156 164 | 1.156 165 | 1.156 166 | 1.156 167 | 1.154 168 | 1.156 169 | 1.154 170 | 1.154 171 | 1.155 172 | 1.152 173 | 1.156 174 | 1.154 175 | 1.152 176 | 1.154 177 | 1.152 178 | 1.155 179 | 1.152 180 | 1.152 181 | 1.152 182 | 1.152 183 | 1.153 184 | 1.152 185 | 1.152 186 | 1.154 187 | 1.152 188 | 1.151 189 | 1.150 190 | 1.150 191 | 1.151 192 | 1.151 193 | 1.148 194 | 1.149 195 | 1.151 196 | 1.150 197 | 1.150 198 | 1.148 199 | 1.149 200 | 1.148 201 | 1.149 202 | 1.148 203 | 1.148 204 | 1.147 205 | 1.148 206 | 1.148 207 | 1.148 208 | 1.146 209 | 1.148 210 | 1.146 211 | 1.148 212 | 1.146 213 | 1.145 214 | 1.144 215 | 1.144 216 | 1.145 217 | 1.144 218 | 1.144 219 | 1.144 220 | 1.143 221 | 1.143 222 | 1.143 223 | 1.143 224 | 1.143 225 | 1.144 226 | 1.142 227 | 1.143 228 | 1.142 229 | 1.141 230 | 1.142 231 | 1.141 232 | 1.141 233 | 1.143 234 | 1.139 235 | 1.139 236 | 1.139 237 | 1.139 238 | 1.138 239 | 1.139 240 | 1.138 241 | 1.137 242 | 1.137 243 | 1.135 244 | 1.136 245 | 1.137 246 | 1.135 247 | 1.134 248 | 1.135 249 | 1.135 250 | 1.135 251 | 1.135 252 | 1.135 253 | 1.135 254 | 1.135 255 | 1.131 256 | 1.135 257 | 1.131 258 | 1.130 259 | 1.129 260 | 1.131 261 | 1.131 262 | 1.130 263 | 1.130 264 | 1.131 265 | 1.130 266 | 1.130 267 | 1.129 268 | 1.128 269 | 1.128 270 | 1.128 271 | 1.127 272 | 1.128 273 | 1.126 274 | 1.127 275 | 1.127 276 | 1.124 277 | 1.125 278 | 1.125 279 | 1.125 280 | 1.125 281 | 1.126 282 | 1.124 283 | 1.125 284 | 1.123 285 | 1.125 286 | 1.123 287 | 1.122 288 | 1.122 289 | 1.123 290 | 1.123 291 | 1.122 292 | 1.122 293 | 1.120 294 | 1.120 295 | 1.122 296 | 1.120 297 | 1.119 298 | 1.119 299 | 1.118 300 | 1.119 301 | 1.116 302 | 1.118 303 | 1.116 304 | 1.117 305 | 1.116 306 | 1.117 307 | 1.114 308 | 1.115 309 | 1.115 310 | 1.114 311 | 1.113 312 | 1.112 313 | 1.112 314 | 1.112 315 | 1.111 316 | 1.110 317 | 1.110 318 | 1.110 319 | 1.108 320 | 1.109 321 | 1.109 322 | 1.109 323 | 1.107 324 | 1.106 325 | 1.106 326 | 1.106 327 | 1.103 328 | 1.104 329 | 1.098 330 | 1.105 331 | 1.103 332 | 1.102 333 | 1.102 334 | -------------------------------------------------------------------------------- /src/microplot/shared/plots.py: -------------------------------------------------------------------------------- 1 | from color import COLORS 2 | 3 | 4 | class Scale: 5 | def __init__(self, d_min, d_max, o_min, o_max): 6 | self.d_min = d_min 7 | self.d_max = d_max 8 | self.o_min = o_min 9 | self.o_max = o_max 10 | self.d_range = d_max - d_min 11 | self.o_range = o_max - o_min 12 | self.c = self.o_range / self.d_range 13 | 14 | def scale(self, val): 15 | return round(self.o_min + self.c * (val - self.d_min)) 16 | 17 | 18 | class Plot: 19 | def __init__(self, data, title: str): 20 | self.data = data 21 | self.title = title 22 | 23 | def add_y_scale(self, frame, plotter, scale_y, y_max, y_min, color): 24 | y_step = (y_max - y_min) / 10 25 | y_nums = list(y_min + y_step * index for index in range(11)) 26 | y_ticks = list(scale_y.scale(y_num) for y_num in y_nums) 27 | for (y_num, y_tick) in zip(y_nums, y_ticks): 28 | plotter.text(5, round(y_tick - 10), '%4.2f' % y_num) 29 | plotter.line(frame.lm, round(y_tick), frame.lm - 5, round(y_tick), color) 30 | coords = list(enumerate(self.data)) 31 | return coords 32 | 33 | def add_x_scale(self, frame, plotter, scale_x, x_max, x_min, color): 34 | x_step = (x_max - x_min) / 10 35 | x_nums = list(x_min + x_step * index for index in range(11)) 36 | x_ticks = list(scale_x.scale(x_num) for x_num in x_nums) 37 | for (x_num, x_tick) in zip(x_nums, x_ticks): 38 | plotter.text(round(x_tick - 5), frame.bottom()+5, '%d' % round(x_num)) 39 | plotter.line(round(x_tick), frame.bottom(), round(x_tick), frame.bottom()+5, color) 40 | coords = list(enumerate(self.data)) 41 | return coords 42 | 43 | def add_axes(self, frame, plotter, color): 44 | plotter.line(frame.lm, frame.tm + 30, frame.lm, frame.bottom(), color) 45 | plotter.line(frame.lm, frame.bottom(), frame.right(), frame.bottom(), color) 46 | 47 | 48 | class LinePlot(Plot): 49 | 50 | def plot(self, plotter): 51 | frame = plotter.frame 52 | self.add_axes(frame, plotter, COLORS.WHITE) 53 | y_max = max(max(each_set) for each_set in self.data) 54 | y_min = min(min(each_set) for each_set in self.data) 55 | scale_x = Scale(0, len(self.data[0]), frame.lm, frame.right()) 56 | plotter.text(frame.lm, frame.tm - 20, self.title) 57 | text_margin = 30 58 | scale_y = Scale(y_min, y_max, frame.bottom(), frame.tm + text_margin) 59 | self.add_y_scale(frame, plotter, scale_y, y_max, y_min, COLORS.WHITE) 60 | for (index, each_set) in enumerate(self.data): 61 | color = COLORS.color(index) 62 | coords = list(enumerate(each_set)) 63 | old_x, old_y = coords[0] 64 | for new_x, new_y in coords[1:]: 65 | x1= scale_x.scale(old_x) 66 | y1 = scale_y.scale(old_y) 67 | x2 = scale_x.scale(new_x) 68 | y2 = scale_y.scale(new_y) 69 | plotter.line(x1, y1, x2, y2, color) 70 | old_x, old_y = new_x, new_y 71 | plotter.show() 72 | 73 | 74 | class ScatterPlot(Plot): 75 | """ 76 | Draw a scatter plot 77 | """ 78 | 79 | def __init__(self, data, title: str, pointer: str): 80 | super().__init__(data, title) 81 | self._scatter_point = pointer 82 | 83 | def plot(self, plotter): 84 | frame = plotter.frame 85 | frame.bm = frame.bm+10 86 | self.add_axes(frame, plotter, COLORS.WHITE) 87 | plotter.text(frame.lm, frame.tm - 20, self.title) 88 | if len(self.data) == 0: 89 | return 90 | x_max = max(max((item[0]) for item in each_set) for each_set in self.data) 91 | x_min = min(min((item[0]) for item in each_set) for each_set in self.data) 92 | y_max = max(max((item[1]) for item in each_set) for each_set in self.data) 93 | y_min = min(min((item[1]) for item in each_set) for each_set in self.data) 94 | text_margin = 30 95 | radius = 5 96 | scale_x = Scale(x_min, x_max, frame.lm+radius, frame.right()-radius) 97 | scale_y = Scale(y_min, y_max, frame.bottom()-(radius+text_margin), frame.tm + text_margin+radius) 98 | self.add_y_scale(frame, plotter, scale_y, y_max, y_min, COLORS.WHITE) 99 | self.add_x_scale(frame, plotter, scale_x, x_max, x_min, COLORS.WHITE) 100 | 101 | for (index, each_set) in enumerate(self.data): 102 | color = COLORS.color(index) 103 | for x, y in each_set: 104 | if self._scatter_point == "Triangle": 105 | plotter.triangle(scale_x.scale(x), scale_y.scale(y), 5, color) 106 | else: 107 | plotter.circle(scale_x.scale(x), scale_y.scale(y), 5, color) 108 | plotter.show() 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /src/microplot/adafruit/adafruit_bitmapsaver.py: -------------------------------------------------------------------------------- 1 | """ 2 | Adapted from 3 | "https://github.com/adafruit/Adafruit_CircuitPython_BitmapSaver.git" 4 | # SPDX-FileCopyrightText: 2019 Dave Astels for Adafruit Industries 5 | # SPDX-License-Identifier: MIT 6 | 7 | """ 8 | 9 | """ 10 | Save a displayio.Bitmap (and associated displayio.Palette) in a BMP file. 11 | Make a screenshot (the contents of a displayio.Display) and save in a BMP file. 12 | 13 | 14 | * Author(s): Dave Astels 15 | 16 | Implementation Notes 17 | -------------------- 18 | 19 | **Hardware:** 20 | 21 | 22 | **Software and Dependencies:** 23 | 24 | * Adafruit CircuitPython firmware for the supported boards: 25 | https://github.com/adafruit/circuitpython/releases 26 | 27 | """ 28 | 29 | # imports 30 | 31 | import gc 32 | import struct 33 | import board 34 | from displayio import Bitmap, Palette, Display 35 | 36 | 37 | def _write_bmp_header(output_file, filesize): 38 | output_file.write(bytes("BM", "ascii")) 39 | output_file.write(struct.pack("> 3) & 0x00FC 71 | red = (color >> 8) & 0x00F8 72 | return (blue, green, red) 73 | 74 | 75 | # pylint:disable=too-many-locals 76 | def _write_pixels(output_file, pixel_source, palette): 77 | saving_bitmap = isinstance(pixel_source, Bitmap) 78 | width, height = _rotated_height_and_width(pixel_source) 79 | row_buffer = bytearray(_bytes_per_row(width)) 80 | # result_buffer = bytearray(2048) replaced by fix below 81 | for y in range(height, 0, -1): 82 | buffer_index = 0 83 | if saving_bitmap: 84 | for x in range(width): 85 | pixel = pixel_source[x, y - 1] 86 | color = palette[pixel] 87 | for _ in range(3): 88 | row_buffer[buffer_index] = color & 0xFF 89 | color >>= 8 90 | buffer_index += 1 91 | else: 92 | result_buffer = bytearray(2048) # fix to Issue #8 93 | data = pixel_source.fill_row(y - 1, result_buffer) 94 | for i in range(width): 95 | pixel565 = (data[i * 2] << 8) + data[i * 2 + 1] 96 | for b in _rgb565_to_bgr_tuple(pixel565): 97 | row_buffer[buffer_index] = b & 0xFF 98 | buffer_index += 1 99 | output_file.write(row_buffer) 100 | gc.collect() 101 | 102 | 103 | # pylint:enable=too-many-locals 104 | 105 | 106 | def save_pixels(file_or_filename, pixel_source=None, palette=None): 107 | """Save pixels to a 24 bit per pixel BMP file. 108 | If pixel_source if a displayio.Bitmap, save it's pixels through palette. 109 | If it's a displayio.Display, a palette isn't required. 110 | 111 | :param file_or_filename: either the file to save to, or its absolute name 112 | :param pixel_source: the Bitmap or Display to save 113 | :param palette: the Palette to use for looking up colors in the bitmap 114 | """ 115 | if not pixel_source: 116 | if "DISPLAY" in dir(board): 117 | pixel_source = board.DISPLAY 118 | else: 119 | raise ValueError("Second argument must be a Bitmap or Display") 120 | if isinstance(pixel_source, Bitmap): 121 | if not isinstance(palette, Palette): 122 | raise ValueError("Third argument must be a Palette for a Bitmap save") 123 | elif not isinstance(pixel_source, Display): 124 | raise ValueError("Second argument must be a Bitmap or Display") 125 | try: 126 | if isinstance(file_or_filename, str): 127 | output_file = open(file_or_filename, "wb") 128 | else: 129 | output_file = file_or_filename 130 | 131 | width, height = _rotated_height_and_width(pixel_source) 132 | filesize = 54 + height * _bytes_per_row(width) 133 | _write_bmp_header(output_file, filesize) 134 | _write_dib_header(output_file, width, height) 135 | _write_pixels(output_file, pixel_source, palette) 136 | except Exception as ex: 137 | raise ex 138 | else: 139 | output_file.close() 140 | -------------------------------------------------------------------------------- /external/adafruit_bitmapsaver.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2019 Dave Astels for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | """ 6 | `adafruit_bitmapsaver` 7 | ================================================================================ 8 | 9 | Save a displayio.Bitmap (and associated displayio.Palette) in a BMP file. 10 | Make a screenshot (the contents of a displayio.Display) and save in a BMP file. 11 | 12 | 13 | * Author(s): Dave Astels 14 | 15 | Implementation Notes 16 | -------------------- 17 | 18 | **Hardware:** 19 | 20 | 21 | **Software and Dependencies:** 22 | 23 | * Adafruit CircuitPython firmware for the supported boards: 24 | https://github.com/adafruit/circuitpython/releases 25 | 26 | """ 27 | 28 | # imports 29 | 30 | import gc 31 | import struct 32 | import board 33 | from displayio import Bitmap, Palette, Display 34 | 35 | __version__ = "0.0.0-auto.0" 36 | __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BitmapSaver.git" 37 | 38 | 39 | def _write_bmp_header(output_file, filesize): 40 | output_file.write(bytes("BM", "ascii")) 41 | output_file.write(struct.pack("> 3) & 0x00FC 73 | red = (color >> 8) & 0x00F8 74 | return (blue, green, red) 75 | 76 | 77 | # pylint:disable=too-many-locals 78 | def _write_pixels(output_file, pixel_source, palette): 79 | saving_bitmap = isinstance(pixel_source, Bitmap) 80 | width, height = _rotated_height_and_width(pixel_source) 81 | row_buffer = bytearray(_bytes_per_row(width)) 82 | result_buffer = bytearray(2048) 83 | for y in range(height, 0, -1): 84 | buffer_index = 0 85 | if saving_bitmap: 86 | for x in range(width): 87 | pixel = pixel_source[x, y - 1] 88 | color = palette[pixel] 89 | for _ in range(3): 90 | row_buffer[buffer_index] = color & 0xFF 91 | color >>= 8 92 | buffer_index += 1 93 | else: 94 | data = pixel_source.fill_row(y - 1, result_buffer) 95 | for i in range(width): 96 | pixel565 = (data[i * 2] << 8) + data[i * 2 + 1] 97 | for b in _rgb565_to_bgr_tuple(pixel565): 98 | row_buffer[buffer_index] = b & 0xFF 99 | buffer_index += 1 100 | output_file.write(row_buffer) 101 | gc.collect() 102 | 103 | 104 | # pylint:enable=too-many-locals 105 | 106 | 107 | def save_pixels(file_or_filename, pixel_source=None, palette=None): 108 | """Save pixels to a 24 bit per pixel BMP file. 109 | If pixel_source if a displayio.Bitmap, save it's pixels through palette. 110 | If it's a displayio.Display, a palette isn't required. 111 | 112 | :param file_or_filename: either the file to save to, or it's absolute name 113 | :param pixel_source: the Bitmap or Display to save 114 | :param palette: the Palette to use for looking up colors in the bitmap 115 | """ 116 | if not pixel_source: 117 | if "DISPLAY" in dir(board): 118 | pixel_source = board.DISPLAY 119 | else: 120 | raise ValueError("Second argument must be a Bitmap or Display") 121 | if isinstance(pixel_source, Bitmap): 122 | if not isinstance(palette, Palette): 123 | raise ValueError("Third argument must be a Palette for a Bitmap save") 124 | elif not isinstance(pixel_source, Display): 125 | raise ValueError("Second argument must be a Bitmap or Display") 126 | try: 127 | if isinstance(file_or_filename, str): 128 | output_file = open(file_or_filename, "wb") 129 | else: 130 | output_file = file_or_filename 131 | 132 | width, height = _rotated_height_and_width(pixel_source) 133 | filesize = 54 + height * _bytes_per_row(width) 134 | _write_bmp_header(output_file, filesize) 135 | _write_dib_header(output_file, width, height) 136 | _write_pixels(output_file, pixel_source, palette) 137 | except Exception as ex: 138 | raise ex 139 | else: 140 | output_file.close() 141 | -------------------------------------------------------------------------------- /plan/microplot-plan.mm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /src/microplot/explorer/examples/volts.csv: -------------------------------------------------------------------------------- 1 | 1.50 2 | 1.50 3 | 1.50 4 | 1.50 5 | 1.50 6 | 1.49 7 | 1.50 8 | 1.49 9 | 1.50 10 | 1.49 11 | 1.49 12 | 1.49 13 | 1.49 14 | 1.49 15 | 1.49 16 | 1.49 17 | 1.48 18 | 1.48 19 | 1.48 20 | 1.48 21 | 1.48 22 | 1.48 23 | 1.47 24 | 1.47 25 | 1.47 26 | 1.47 27 | 1.47 28 | 1.47 29 | 1.47 30 | 1.47 31 | 1.47 32 | 1.46 33 | 1.47 34 | 1.46 35 | 1.46 36 | 1.46 37 | 1.46 38 | 1.46 39 | 1.46 40 | 1.46 41 | 1.45 42 | 1.45 43 | 1.45 44 | 1.45 45 | 1.45 46 | 1.45 47 | 1.45 48 | 1.44 49 | 1.44 50 | 1.44 51 | 1.44 52 | 1.44 53 | 1.44 54 | 1.44 55 | 1.44 56 | 1.44 57 | 1.44 58 | 1.43 59 | 1.43 60 | 1.43 61 | 1.43 62 | 1.43 63 | 1.43 64 | 1.43 65 | 1.43 66 | 1.43 67 | 1.43 68 | 1.43 69 | 1.43 70 | 1.43 71 | 1.42 72 | 1.43 73 | 1.42 74 | 1.42 75 | 1.42 76 | 1.42 77 | 1.42 78 | 1.42 79 | 1.42 80 | 1.42 81 | 1.42 82 | 1.42 83 | 1.42 84 | 1.42 85 | 1.42 86 | 1.42 87 | 1.42 88 | 1.42 89 | 1.42 90 | 1.41 91 | 1.41 92 | 1.41 93 | 1.41 94 | 1.41 95 | 1.41 96 | 1.41 97 | 1.41 98 | 1.41 99 | 1.41 100 | 1.41 101 | 1.41 102 | 1.41 103 | 1.41 104 | 1.41 105 | 1.41 106 | 1.41 107 | 1.41 108 | 1.40 109 | 1.41 110 | 1.41 111 | 1.40 112 | 1.40 113 | 1.40 114 | 1.40 115 | 1.40 116 | 1.40 117 | 1.40 118 | 1.40 119 | 1.40 120 | 1.40 121 | 1.40 122 | 1.40 123 | 1.40 124 | 1.40 125 | 1.40 126 | 1.40 127 | 1.40 128 | 1.40 129 | 1.40 130 | 1.40 131 | 1.39 132 | 1.39 133 | 1.40 134 | 1.39 135 | 1.39 136 | 1.39 137 | 1.39 138 | 1.39 139 | 1.39 140 | 1.39 141 | 1.39 142 | 1.39 143 | 1.39 144 | 1.39 145 | 1.39 146 | 1.39 147 | 1.39 148 | 1.39 149 | 1.39 150 | 1.39 151 | 1.39 152 | 1.39 153 | 1.39 154 | 1.39 155 | 1.39 156 | 1.39 157 | 1.39 158 | 1.39 159 | 1.38 160 | 1.39 161 | 1.39 162 | 1.39 163 | 1.39 164 | 1.38 165 | 1.38 166 | 1.38 167 | 1.38 168 | 1.38 169 | 1.38 170 | 1.38 171 | 1.38 172 | 1.38 173 | 1.38 174 | 1.38 175 | 1.38 176 | 1.38 177 | 1.38 178 | 1.38 179 | 1.38 180 | 1.38 181 | 1.38 182 | 1.38 183 | 1.38 184 | 1.38 185 | 1.38 186 | 1.38 187 | 1.38 188 | 1.38 189 | 1.38 190 | 1.38 191 | 1.38 192 | 1.38 193 | 1.38 194 | 1.38 195 | 1.38 196 | 1.38 197 | 1.38 198 | 1.37 199 | 1.37 200 | 1.38 201 | 1.38 202 | 1.37 203 | 1.38 204 | 1.38 205 | 1.38 206 | 1.38 207 | 1.37 208 | 1.37 209 | 1.38 210 | 1.37 211 | 1.37 212 | 1.37 213 | 1.38 214 | 1.37 215 | 1.37 216 | 1.37 217 | 1.37 218 | 1.37 219 | 1.37 220 | 1.37 221 | 1.37 222 | 1.37 223 | 1.37 224 | 1.37 225 | 1.37 226 | 1.37 227 | 1.37 228 | 1.37 229 | 1.37 230 | 1.37 231 | 1.37 232 | 1.37 233 | 1.37 234 | 1.37 235 | 1.37 236 | 1.37 237 | 1.37 238 | 1.37 239 | 1.37 240 | 1.37 241 | 1.37 242 | 1.37 243 | 1.37 244 | 1.37 245 | 1.37 246 | 1.37 247 | 1.37 248 | 1.37 249 | 1.37 250 | 1.37 251 | 1.37 252 | 1.37 253 | 1.36 254 | 1.37 255 | 1.36 256 | 1.37 257 | 1.37 258 | 1.36 259 | 1.37 260 | 1.36 261 | 1.36 262 | 1.37 263 | 1.36 264 | 1.36 265 | 1.36 266 | 1.36 267 | 1.36 268 | 1.36 269 | 1.36 270 | 1.36 271 | 1.36 272 | 1.36 273 | 1.36 274 | 1.36 275 | 1.36 276 | 1.36 277 | 1.36 278 | 1.36 279 | 1.36 280 | 1.36 281 | 1.36 282 | 1.36 283 | 1.36 284 | 1.36 285 | 1.36 286 | 1.36 287 | 1.36 288 | 1.36 289 | 1.36 290 | 1.36 291 | 1.36 292 | 1.36 293 | 1.36 294 | 1.36 295 | 1.36 296 | 1.36 297 | 1.36 298 | 1.36 299 | 1.35 300 | 1.36 301 | 1.36 302 | 1.36 303 | 1.35 304 | 1.35 305 | 1.36 306 | 1.35 307 | 1.35 308 | 1.36 309 | 1.35 310 | 1.35 311 | 1.35 312 | 1.35 313 | 1.35 314 | 1.35 315 | 1.35 316 | 1.35 317 | 1.35 318 | 1.35 319 | 1.35 320 | 1.35 321 | 1.35 322 | 1.35 323 | 1.35 324 | 1.35 325 | 1.35 326 | 1.35 327 | 1.35 328 | 1.35 329 | 1.35 330 | 1.35 331 | 1.35 332 | 1.35 333 | 1.35 334 | 1.35 335 | 1.35 336 | 1.35 337 | 1.35 338 | 1.35 339 | 1.35 340 | 1.35 341 | 1.35 342 | 1.35 343 | 1.35 344 | 1.35 345 | 1.34 346 | 1.35 347 | 1.34 348 | 1.34 349 | 1.35 350 | 1.34 351 | 1.34 352 | 1.34 353 | 1.34 354 | 1.34 355 | 1.34 356 | 1.34 357 | 1.34 358 | 1.34 359 | 1.34 360 | 1.34 361 | 1.34 362 | 1.34 363 | 1.34 364 | 1.34 365 | 1.34 366 | 1.34 367 | 1.34 368 | 1.34 369 | 1.34 370 | 1.34 371 | 1.34 372 | 1.34 373 | 1.34 374 | 1.34 375 | 1.34 376 | 1.34 377 | 1.34 378 | 1.34 379 | 1.34 380 | 1.34 381 | 1.34 382 | 1.34 383 | 1.34 384 | 1.34 385 | 1.34 386 | 1.34 387 | 1.34 388 | 1.34 389 | 1.34 390 | 1.34 391 | 1.33 392 | 1.34 393 | 1.33 394 | 1.33 395 | 1.33 396 | 1.33 397 | 1.33 398 | 1.33 399 | 1.33 400 | 1.33 401 | 1.33 402 | 1.34 403 | 1.33 404 | 1.33 405 | 1.33 406 | 1.33 407 | 1.33 408 | 1.33 409 | 1.33 410 | 1.33 411 | 1.33 412 | 1.33 413 | 1.33 414 | 1.33 415 | 1.33 416 | 1.33 417 | 1.33 418 | 1.33 419 | 1.33 420 | 1.33 421 | 1.33 422 | 1.33 423 | 1.33 424 | 1.33 425 | 1.33 426 | 1.33 427 | 1.33 428 | 1.33 429 | 1.33 430 | 1.32 431 | 1.33 432 | 1.32 433 | 1.33 434 | 1.33 435 | 1.33 436 | 1.32 437 | 1.32 438 | 1.32 439 | 1.32 440 | 1.32 441 | 1.32 442 | 1.32 443 | 1.33 444 | 1.32 445 | 1.32 446 | 1.32 447 | 1.32 448 | 1.32 449 | 1.32 450 | 1.32 451 | 1.32 452 | 1.32 453 | 1.32 454 | 1.32 455 | 1.32 456 | 1.32 457 | 1.32 458 | 1.32 459 | 1.32 460 | 1.32 461 | 1.32 462 | 1.32 463 | 1.32 464 | 1.32 465 | 1.32 466 | 1.32 467 | 1.32 468 | 1.32 469 | 1.32 470 | 1.32 471 | 1.32 472 | 1.32 473 | 1.32 474 | 1.32 475 | 1.32 476 | 1.32 477 | 1.32 478 | 1.32 479 | 1.32 480 | 1.32 481 | 1.32 482 | 1.32 483 | 1.31 484 | 1.32 485 | 1.31 486 | 1.32 487 | 1.31 488 | 1.31 489 | 1.31 490 | 1.31 491 | 1.31 492 | 1.31 493 | 1.31 494 | 1.31 495 | 1.31 496 | 1.31 497 | 1.31 498 | 1.31 499 | 1.31 500 | 1.31 501 | 1.31 502 | 1.31 503 | 1.31 504 | 1.31 505 | 1.31 506 | 1.31 507 | 1.31 508 | 1.31 509 | 1.31 510 | 1.31 511 | 1.31 512 | 1.31 513 | 1.31 514 | 1.31 515 | 1.31 516 | 1.31 517 | 1.31 518 | 1.31 519 | 1.31 520 | 1.31 521 | 1.31 522 | 1.31 523 | 1.31 524 | 1.31 525 | 1.31 526 | 1.31 527 | 1.31 528 | 1.31 529 | 1.31 530 | 1.31 531 | 1.31 532 | 1.31 533 | 1.31 534 | 1.31 535 | 1.31 536 | 1.31 537 | 1.31 538 | 1.31 539 | 1.30 540 | 1.31 541 | 1.30 542 | 1.31 543 | 1.30 544 | 1.31 545 | 1.31 546 | 1.30 547 | 1.30 548 | 1.30 549 | 1.31 550 | 1.30 551 | 1.30 552 | 1.30 553 | 1.30 554 | 1.30 555 | 1.30 556 | 1.30 557 | 1.30 558 | 1.30 559 | 1.30 560 | 1.30 561 | 1.30 562 | 1.30 563 | 1.30 564 | 1.30 565 | 1.30 566 | 1.30 567 | 1.30 568 | 1.30 569 | 1.30 570 | 1.30 571 | 1.30 572 | 1.30 573 | 1.30 574 | 1.30 575 | 1.30 576 | 1.30 577 | 1.30 578 | 1.30 579 | 1.30 580 | 1.30 581 | 1.30 582 | 1.30 583 | 1.30 584 | 1.30 585 | 1.30 586 | 1.30 587 | 1.30 588 | 1.30 589 | 1.30 590 | 1.30 591 | 1.30 592 | 1.30 593 | 1.30 594 | 1.30 595 | 1.30 596 | 1.30 597 | 1.30 598 | 1.30 599 | 1.30 600 | 1.30 601 | 1.30 602 | 1.30 603 | 1.29 604 | 1.30 605 | 1.30 606 | 1.30 607 | 1.30 608 | 1.30 609 | 1.30 610 | 1.29 611 | 1.29 612 | 1.29 613 | 1.29 614 | 1.29 615 | 1.29 616 | 1.29 617 | 1.29 618 | 1.29 619 | 1.29 620 | 1.29 621 | 1.29 622 | 1.29 623 | 1.29 624 | 1.29 625 | 1.29 626 | 1.29 627 | 1.29 628 | 1.29 629 | 1.29 630 | 1.29 631 | 1.29 632 | 1.29 633 | 1.29 634 | 1.29 635 | 1.29 636 | 1.29 637 | 1.29 638 | 1.29 639 | 1.29 640 | 1.29 641 | 1.29 642 | 1.29 643 | 1.29 644 | 1.29 645 | 1.29 646 | 1.29 647 | 1.29 648 | 1.29 649 | 1.29 650 | 1.29 651 | 1.29 652 | 1.29 653 | 1.29 654 | 1.29 655 | 1.29 656 | 1.29 657 | 1.29 658 | 1.29 659 | 1.29 660 | 1.29 661 | 1.29 662 | 1.29 663 | 1.29 664 | 1.29 665 | 1.29 666 | 1.29 667 | 1.29 668 | 1.28 669 | 1.28 670 | 1.29 671 | 1.28 672 | 1.29 673 | 1.28 674 | 1.28 675 | 1.28 676 | 1.28 677 | 1.28 678 | 1.29 679 | 1.28 680 | 1.29 681 | 1.28 682 | 1.28 683 | 1.29 684 | 1.28 685 | 1.28 686 | 1.29 687 | 1.28 688 | 1.28 689 | 1.28 690 | 1.28 691 | 1.28 692 | 1.28 693 | 1.28 694 | 1.28 695 | 1.28 696 | 1.28 697 | 1.28 698 | 1.29 699 | 1.28 700 | 1.28 701 | 1.28 702 | 1.28 703 | 1.28 704 | 1.28 705 | 1.28 706 | 1.28 707 | 1.28 708 | 1.28 709 | 1.28 710 | 1.28 711 | 1.28 712 | 1.28 713 | 1.28 714 | 1.28 715 | 1.28 716 | 1.28 717 | 1.28 718 | 1.28 719 | 1.28 720 | 1.28 721 | 1.28 722 | 1.28 723 | 1.28 724 | 1.28 725 | 1.28 726 | 1.28 727 | 1.28 728 | 1.28 729 | 1.28 730 | 1.28 731 | 1.28 732 | 1.28 733 | 1.28 734 | 1.28 735 | 1.28 736 | 1.28 737 | 1.28 738 | 1.28 739 | --------------------------------------------------------------------------------