A product that measures something eventually has to show it. A chart is the fastest way for a user to understand what a device saw, and it is also one of the most deceptively expensive features in a companion app scope. The demo looks trivial: plot the numbers. What gets expensive is everything around the plot. What happens when the device streams faster than the screen can draw? What does the chart show for the six hours the device sat in a drawer with no connection? And which timestamp do you trust when the device clock has drifted eleven minutes?
These questions are not cosmetic. They decide firmware memory budgets, the BLE service layout, the backend schema, and the app's battery reputation. Deciding them after the hardware is frozen is how a six-week app turns into a five-month app.
Sampling rate and render rate are different numbers
The single most common mistake is treating the device's sampling rate as the chart's update rate. A sensor may sample at 200 Hz because the physics demand it; a phone screen refreshes at 60 or 120 Hz, and a human cannot perceive a chart redrawing faster than a few times per second. If you push every sample straight into a chart view, you get a UI thread that never catches up, a battery that drains in an hour, and a plot that is visually identical to one updating ten times slower.
The workable pattern is three separate rates:
- Sample rate — set by what you are measuring. Motion and vibration need hundreds of hertz; temperature and humidity are fine at one reading every few seconds to a few minutes.
- Transport rate — how often batched samples leave the device. Batching 20 to 50 samples per packet is dramatically cheaper on radio time than sending each one.
- Render rate — how often the chart redraws. Five to ten frames per second reads as "live" to a user and costs a fraction of the CPU.
Aggregation between transport and render must preserve peaks. Averaging a vibration signal down to ten points per second erases the spike that mattered; min/max envelope aggregation — keeping the highest and lowest value in each bucket and drawing them as a band — stays honest at a fraction of the point count.
Downsampling and decimation for long time ranges
A chart showing the last thirty seconds is easy. A chart showing the last ninety days is a different engineering problem, because the honest dataset behind it can hold millions of points and no mobile chart library will draw that smoothly.
Two approaches solve it, and most products end up using both:
- Pre-aggregated rollups. The backend stores raw data plus hourly and daily summaries — min, max, mean, sample count. Zoomed-out views query rollups; zoomed-in views query raw. This is exactly the access pattern a purpose-built time-series database is designed for, and it is why a plain relational table starts choking once a fleet has been reporting for a few months.
- Visual decimation. Algorithms such as largest-triangle-three-buckets pick the points that preserve the visible shape of the curve, reducing 100,000 points to about 1,000 with a plot most people cannot distinguish from the original.
Set a hard rule early: no chart view ever receives more points than the screen has horizontal pixels, roughly 400 to 1,200 on a phone. Everything above that is wasted work.
Getting the data off the device
On the device side, the enabling structure is a ring buffer in RAM or external flash that keeps recording whether or not a phone is nearby. Size it from the disconnection window you promise: a device sampling once per second with a 12-byte record needs roughly 1 MB to hold a full day. Once the buffer wraps, the oldest data is gone, so state the retention window in the spec rather than discovering it in a support ticket.
For transport, BLE notifications almost always beat app-side polling. With notifications the device pushes a packet when it has one and the radio sleeps in between; with polling the app wakes the link on a timer whether there is data or not. Getting throughput out of BLE means negotiating a larger MTU and a connection interval that matches your batch cadence, which is firmware work in the GATT layer, not something the app can fix afterward. Historical backfill — the dump of everything recorded while disconnected — should be a separate characteristic with its own chunked, resumable protocol, not the live stream running very fast.
Wi-Fi and cellular products face the same question one layer up, where the tradeoff between a persistent socket and periodic requests is covered in the comparison of WebSockets and polling for live product data.
Battery: the cost nobody budgets
A live chart is the most power-hungry screen in most companion apps, because it holds the radio link, the screen, and continuous GPU work open at once. Stop the render loop the instant the view is not visible — many chart libraries keep animating in the background unless you explicitly pause them. Drop the render rate when nobody is interacting. Prefer a GPU-accelerated chart library over one redrawing paths on the CPU each frame; on cross-platform stacks this is the biggest performance difference between chart packages. And do not hold a stream open purely to keep a chart warm — a short burst sync is far cheaper, the same logic behind syncing wearable data without draining the battery.
Time: the bug you will ship at least once
Most low-cost devices have no real-time clock, or one that drifts several seconds per day. The device stamps readings with milliseconds since boot; the app must convert those to wall-clock time. If you convert at the moment of sync using the phone's current time, then travel across a time zone, then sync again, your chart grows a step discontinuity and users report "missing hours."
What works: store every reading in UTC, record the device-to-phone clock offset at each sync alongside the data, keep the raw device tick count so timestamps can be re-derived if the offset was wrong, and render in the phone's current local zone.
Gaps are data, not blanks
When a device was off, out of range, or out of battery, the chart must say so. Interpolating a straight line across a twelve-hour hole tells the user something false. Draw gaps as a visible break with a dimmed band, and if the device can distinguish "powered off" from "buffer overflowed" from "never connected," surface that in the tooltip. This matters most in products where the app has to behave sensibly with no connection at all, and it feeds directly into how you interpret engagement metrics from the app — a user who never sees data is not a disengaged user, they are a user with a hardware problem.
Zoom, pan, and export
Pinch-to-zoom needs a loading strategy or every gesture fires a query: debounce the fetch, cache one zoom level on either side of the visible window, and cap the total range so nobody requests three years of raw samples. Export deserves more attention than it gets — in lab, agricultural, and clinical products, CSV export is frequently the feature that closes the sale. Document the column layout, put units and time zone in the header, and version the format so later firmware does not break someone's spreadsheet.
Getting it specified before the hardware freezes
Charting decisions reach backward into hardware: buffer size, flash part, sample rate, radio duty cycle, and whether the device needs a real-time clock at all. Projects House designs the device, the firmware, and the app together for exactly this reason, so the data model that makes the chart honest exists before the board is laid out. If you are scoping a measuring product and want the data path sized properly the first time, send the details through our contact form.