{"id":21268309,"url":"https://github.com/unital/tempe","last_synced_at":"2025-07-11T04:32:29.870Z","repository":{"id":257917655,"uuid":"863387706","full_name":"unital/tempe","owner":"unital","description":"Beautiful Micropython Graphics","archived":false,"fork":false,"pushed_at":"2024-10-18T12:18:15.000Z","size":1871,"stargazers_count":0,"open_issues_count":3,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-10-19T13:06:24.072Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/unital.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-09-26T07:58:40.000Z","updated_at":"2024-10-18T12:18:17.000Z","dependencies_parsed_at":"2024-10-19T09:49:06.513Z","dependency_job_id":null,"html_url":"https://github.com/unital/tempe","commit_stats":null,"previous_names":["unital/tempe"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/unital%2Ftempe","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/unital%2Ftempe/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/unital%2Ftempe/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/unital%2Ftempe/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/unital","download_url":"https://codeload.github.com/unital/tempe/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225693834,"owners_count":17509227,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-21T08:01:40.953Z","updated_at":"2025-07-11T04:32:29.860Z","avatar_url":"https://github.com/unital.png","language":"Python","readme":"# Tempe\n\nBeautiful, Efficient Micropython Graphics\n\n\u003cimg src=\"./pico-tempe.png\" width=\"480\" alt=\"A Raspberry Pi Pico with a data visualization on a screen\" /\u003e\n\nTempe is a graphics library designed to support everyday display and data\nvisualization tasks on small, 16-bit+ color screens.\n\n- Pure Micropython codebase—no C libraries, cross-compiling or custom firmware\n  needed—``mip``-install and go.\n- Full 16-bit color support even on memory-constrained microcontrollers.\n- API designed to support common data visualization patterns, such as polar\n  coordinates, efficiently.\n- Transparent support for partial display updates and damage-region tracking,\n  allowing memory-efficency and fast updates for small changes.\n- Core API avoids floating-point operations.\n- Asyncio integration to allow simple support for dynamically changing\n  graphics.\n\n## Documentation\n\n[Tempe Documentation](https://unital.github.io/tempe)\n- [User Guide](https://unital.github.io/tempe/user_guide)\n  - [Tutorial](https://unital.github.io/tempe/user_guide/tutorial)\n- [API docs](https://unital.github.io/tempe/api)\n- [Change Log](https://unital.github.io/tempe/changes)\n\n## Gallery\n\n\u003cimg src=\"./pico-tempe-shapes.png\" width=\"480\" alt=\"A Raspberry Pi Pico with a variety of shapes drawn on a screen\" /\u003e \u003cimg src=\"./docs/source/user_guide/shapes.png\" width=\"160\" alt=\"A variety of shapes drawn in an image\" /\u003e\n\n\u003cimg src=\"./pico-tempe-polar.png\" width=\"480\" alt=\"A Raspberry Pi Pico with a variety of polar plots on the screen\" /\u003e \u003cimg src=\"./docs/source/user_guide/polar.png\" width=\"160\" alt=\"A variety of polar plots drawn in an image\" /\u003e\n\n\u003cimg src=\"./docs/source/user_guide/clock_example.gif\" alt=\"A clock face updating asynchronously\" /\u003e\n\n\u003cimg src=\"./docs/source/user_guide/pico-tempe-updating.gif\" alt=\"A Raspberry Pi Pico updating time and temperature asynchronously\" /\u003e\n\n\u003cimg src=\"./docs/source/user_guide/line_plot_4.png\" width=\"160\" alt=\"A line plot of temperature over time drawn in an image\" /\u003e \u003cimg src=\"./docs/source/user_guide/scatter_plot.png\" width=\"160\" alt=\"A scatter plot of environmental data with scales drawn in an image\" /\u003e \u003cimg src=\"./docs/source/user_guide/polar_plot.png\" width=\"160\" alt=\"A polar plot of air quality data over time in an image\" /\u003e\n\n## Goals\n\nModern micropython-based microcontrollers are surpisingly capable machines.\nIn particular, you can get small, inexpensive LCD or OLED screens capable of\n16-bit or more color.  These can fill a role of providing users visual feedback\nand simple UIs for edge computing devices: dashboards that display a device's\ncurrent state and history, or allow configuration and control of a device's\nsettings.  At 16-bit color depth, these should be capable of creating data\nvisualization and UIs which look as good as modern plotting and UI libraries.\n\nHowever using these displays to their full capacity is difficult: a full-screen\nframebuffer for a 320x240 display is 150K, which is a substantial fraction\nof available memory on a device like a Rasperry Pi Pico.  As a result, graphics\non memory-constrained devices often ends up compromising: either by using\na smaller color palette that can fit in a smaller buffer, or by using a smaller\narea of the screen for active display.\n\nAdditionally these device typically use SPI or I2C as the interface bus, so\neven if there is memory to support a framebuffer, the time taken to simply\ntransmit the full framebuffer data to the display can take 10s of\nmilliseconds.\n\nThere are long-known techniques for working around these issues such as working\nwith smaller, partial framebuffers and doing multi-stage transfers to the\ndisplay; or tracking changes to the graphics being displayed and only updating\nthe damaged regions.  These can avoid compromises on graphical quality and\nspeed, but they either mean that the drawing code becomes complex or the\nunderlying graphics library needs to take care of these issues.\n\nTempe aims to be such a library.  It sits on top of the standard Micropython\n:py:mod:`framebuf` drawing library, giving a memory-efficient API that is\nparticularly suited for building data visualizations, and which quietly\nhandles updating the screen in a way that is both memory efficient and fast.\n\n## Design Notes\n\nThis is a graphics library designed to produce crisp, clear graphics,\nsubject to the following constraints:\n\n- very restricted memory\n- small screen sizes\n- reduced color ranges/alpha support\n- no C code and standard Micropython as much as possible\n- fast rendering (aiming for \u003c100ms for reasonable images and \u003c1s for unreasonable images)\n- integration with asyncio for events/updates\n\nGeneral goals are to be able to support basic UIs (eg. terminal emulation, form display)\nand data visualization up to about 1000 data points, with reasonable responsiveness.\n\nThis leads to some design decisions to support generic graphical\nobjects well:\n\n- use the micropython framebuf module as the underlying drawing framework\n- use 16 bit color spaces (most likely RGB565, but could be greyscale or arbitrary palette)\n- work in integers and screen space as much as possible (16-bit space probably fine)\n- the ability to render to arbitrary regions for partial updates\n- an underlying data framework that supports terse data description (eg. avoid allocating\n  buffers containing simple ranges, broadcasting of values, etc.)\n- a representation of geometry that is flexible and re-usable with different aesthetics\n- a framework for drawing that remembers the geometry rather than regenerating from scratch\n  on each re-draw\n- minimizing function calls by drawing multiple similar objects in one go\n- a collection of primitives sufficient for most situations\n- text is the basic framebuffer text for now\n\nBecause of the possibility of weak floating-point support, full affine transforms seem\nto be out of scope, but a subset of D4 x R x R2 (dihedral, scale, translate) seems\npractical (although maybe not for text).\n\nThings that this is explicitly not doing:\n- a general vector drawing API, like SVG, HTML Canvas, Agg, etc.\n- alpha blending (other than blit with a transparent color)\n\n\n### Surfaces\n\nA Surface is the basic canvas where shapes are drawn.  The surface is responsible for\ntracking what has been drawn, what needs updating, and knows how to render a region of\nitself into a raster array.\n\nSurfaces store drawn objects in layers from 'BACKGROUND' to 'OVERLAY', with objects in\nlower layers being drawn before upper ones, so that upper layers will overlay lower ones\nin the screen.\n\nSurfaces provide methods for drawing primitive shapes.\n\n### Shapes\n\nA shape is a drawable entity that usually consists of geometry (at a minimum a set of\nlocations) and additional aesthetic information (colors, etc.) that are used to generate\ndrawing commands on the underlying raster image.  To reduce the number of function calls,\nmost shapes are actually collections of the same type of object (points, rectangles,\ncircles, line segments, etc.) that can be drawn in a simple loop that draws data from\niterating over the geometry and aesthetics.\n\nShapes can be updated in-place, and know how to inform the surface that they belong to\nthat they have been modified and what region will need to be redrawn.\n\n### Geometry\n\nGeometries provide iterable sequences of values in known formats that can then be consumed\nby shapes.  Geometries can be re-used by muliple shapes, and can generate derived\ngeometries (eg. from rectangles to the corresponding vertices) efficiently.\n\nBecause data visualizations frequently involve geometries where one or more coordinates\nare repeated, either held constant over a collection of objects (such as the width of a\nset of bars in a barplot) or shared between objects (such as the segment endpoints of a\npolyline) there are a number of different ways to present a geometry.  For example, a\nset of rectangles can most simply be presented as a sequence of (x, y, w, h) values,\nbut it can also be presented as a 4-tuple of sequences of xs, ys, widths and heights\nwhich gives the opportuntiy to represent the geometric information more compactly.\n\nThese compact representations are generated by DataView objects.\n\n### Data Views\n\nData views permit more compact representations of repeated or derived data as iterable\nobjects.  They represent a trade-off of speed vs memory: rather than storing everything\nof interest in arrays of data, data views permit values to be computed on-the-fly from\nother pieces of data in a consistent way.\n\nData views can be combined by the usual arithmetic operations, and dataviews of\ninconsistent length can be lengthend vie operations such as cyclic repetition.\n\n### Components\n\nA component is an application-level entity that encapsulates the drawing of a related\nset of shapes on a surface.  For example a line plot component would be resposible\nfor taking the raw data, using that to generate an appropriate geometry and aesthetic\ndata views, which it then uses to draw shapes onto a surface.\n\n### Rasters\n\nThe final piece of the puzzle is the actual rendering and screen display.  A Raster is\na wrapper around a Micropython FrameBuffer that represents a visible region of a display.\nWhen asked to render itself into a raster, the surface and shapes knows enough about the\ngeometry of what is being rendered that they can skip drawing objects which are ouside\nthe region of the raster.\n\nThe display device driver needs be able to take the data in a raster and copy it into the\ncorresponding region of the physical screen.\n\nThis permits devices without sufficient memory to hold a framebuffer for the entire\ndisplay to render out the display in a series of smaller chunks (eg. one quarter of the\nrows at a time), and to speed up screen updates by only drawing to the regions which need\nto be changed (and if the device supports windowed writing, to send the data more swiftly).\n\nThe application needs to coordinate with the surface to handle the generation of appropriate\nrasters given the constraints of the application.  This might include keeping a preallocated\nbuffer available for use as a raster.\n\n## Other Considerations\n\n### Coordinates\n\n\nSince we are drawing to the framebuffer, we assume all screen coordinates are signed 16-bit\nintegers.  Geometry values are supplied in screen coordinate values rather than floating point.\n\n### Colors\n\nFor the time being we're assuming that the screen can handle RGB565 color, and so colors are\nassumed to be unsigned 16-bit integer values, with utility routines to handle conversion to\nand from more familiar formats (such as RGB888 or floating point RGB tuples).  Where we need\na key color for transparency, we will use 0b00000_000001_00000 (ie. the color with only the\nleast-significant green bit set).\n\nColor maps can be extremely memory intesive, particularly if supplied as floating point rgb\nvalues.  To save on memory, color maps should be arrays of unsigned 16-bit numbers  There are modules available that supply\n\n### Text\n\nThe basic framebuffer 8x8 font is always available and may be acceptible for use as a plot\nmarker or for very small text, but professional data visualization larger and clearer fonts\nare needed for display of textual information.  For the time being \"font_to_py\"-style fonts\nseem to be an acceptible compromise between aesthetics and performance.\n\n### Styles\n\nFor UI-like components (labels, numeric displays, etc.) it makes sense to re-use aesthetic values,\na set of aesthetic values that is used in common is a style, and applications should aim to\nshare styles between similar objects where possible.\n\n\n\n","funding_links":[],"categories":["Libraries"],"sub_categories":["Display"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Funital%2Ftempe","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Funital%2Ftempe","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Funital%2Ftempe/lists"}