Calendar heatmap
Telemetry as a grid of days: one cell per calendar day, colored by that day’s value. Switch between week, month and year, and scroll back through earlier periods. The widget drives its own time window and always requests a one-day aggregation interval, so the platform reduces each day server-side and the cell shows exactly that aggregate. Days with no telemetry read as no data rather than as zero, and days still to come are drawn as not yet. Clicking a day hands its bounds to a widget action.
Calendar heatmap
Shows one telemetry key as a grid of calendar days, each cell colored by that day’s value. Switch between week, month and year, and step back through earlier periods — seasonality, the weekly rhythm and the days a device went quiet are all visible at once, which a line chart over the same span cannot show.
Features
- Three periods in one widget: week as a row of days, month as a wall calendar, year as a contribution-style grid
- Always opens on the current period; arrows step back up to 40 periods
- Drives its own time window and always requests a one-day aggregation interval, so the platform reduces each day server-side
- Days with no telemetry read as no data, not as zero; days still to come read as not yet
- Clicking a day hands its bounds to a widget action for drill-down
- Optional ramp taken from the data key’s own color
- Configurable fonts, colors, date formats, legend and tooltip
- No external libraries — the grid is SVG built by the controller, so it works on installations with no internet access
- Cells scale to the card; a month or week view reads best from about 6×5 grid units, the year view from about 12×3
Data keys
One datasource, one timeseries key. The widget does not expect any particular key
name — point it at whatever you measure (energy, pulseCounter, temperature,
waterVolume, rainfall, runtimeHours) and every value is bucketed into the calendar
day it arrived in.
| Datasources | 1 |
| Data keys | 1, timeseries |
| Key name | any |
| Value type | numeric; strings that parse as numbers are accepted, anything else counts as no reading |
| Units and decimals | taken from the data key, falling back to the widget-level values |
Aggregation type is read from the widget’s own time window and decides what a cell means:
| Aggregation | A cell shows | Footer reads |
|---|---|---|
SUM | the day’s total | Sum per day |
AVG | the day’s mean | Average per day |
MIN / MAX | the day’s extreme | Daily minimum / maximum |
COUNT | how many raw readings arrived | Readings per day |
NONE | raw readings reduced in the browser using How to combine a day’s readings | that setting’s label |
The shipped configuration uses SUM at a one-day interval. Set the aggregation type
rather than the widget setting: with aggregation on, the platform has already reduced each
day, and a widget setting that disagreed would only mislabel the number.
Cell states
The widget uses four visual states, only one of which is a value:
| State | How it is drawn | Meaning |
|---|---|---|
| Value | filled from the color ramp, 2–9 discrete levels | the day’s aggregate |
| No data | flat fill in the no readings color, outside the ramp | the day passed and nothing arrived |
| Not yet | dashed outline, never filled | the day has not happened yet |
| Today | accent outline over its normal fill | the current day, still accumulating, so usually the palest cell |
Missing days are excluded from the color scale entirely, so a device that stopped reporting leaves a visible gap rather than a stretch of the lowest color.
Configuration
Settings are grouped by what they affect: Period, Day value, Cell colors, Grid and labels, Label formats, Legend, Tooltip, Period selector and Interaction. Every field carries its own hint in the settings form.
Colors and text in short: the ramp is two ends plus a step count of 2–9 levels, with
separate colors for no-data and not-yet days, and it can instead be taken from the data
key’s own color. Fonts and colors are set per role — labels, day numbers, legend, tooltip
and the period selector — with the accent defaulting to var(--tb-primary-500, #1b6b2f) so
an untouched widget follows the platform theme. Day numbers and printed values take a light
or dark ink automatically, whichever reads on the cell underneath. Font sizes and label
formats are ceilings rather than promises: text shrinks below the configured size, or steps
down to a shorter weekday form, instead of overlapping.
The color scale fits the range observed in the shown period and the legend states that range. It is not anchored at zero, because anchoring flattens a week of similar days into a single color — which also means colors are not comparable between two periods. Use a fixed minimum and maximum when that matters.
Title, background and padding come from the widget’s own Appearance settings and are not duplicated here.
Time window
The widget does not read the dashboard time window. It computes the period itself and pushes the matching window down, always asking for a one-day aggregation interval. The shipped configuration therefore sets Use dashboard timewindow off and hides the platform time window picker — leave it that way, two pickers competing for the same subscription is the one setup that will not work.
Days are bucketed in the browser’s local time zone, midnight to midnight, and the window pushed to the platform is aligned to those same midnights so the platform’s daily intervals line up with the cells. Viewing the same dashboard from another time zone shifts values between adjacent cells.
Actions
One action source, Day click, with these parameters:
| Parameter | Meaning |
|---|---|
dayStartTs | local midnight at the start of the clicked day |
dayEndTs | local midnight at the start of the next day |
value | the day’s value, or null if there were no readings |
readings | how many points landed in that day |
period | week, month or year — which view was clicked |
The usual wiring is a dashboard state that drills into the clicked day, with dayStartTs
and dayEndTs feeding the target state’s time window. Days in the future are not
clickable.
Share Your Widget with the Community
Built a custom widget? Export it as a JSON from ThingsBoard and publish it to the IoT Hub through a simple 4-step wizard (Upload, Listing, Readme, Review & Submit). Share it with thousands of ThingsBoard developers worldwide and get featured in the catalog.