Skip to content

feat: histogram, scatter, bubble, heatmap and box plot charts - #106

Merged
tempus2016 merged 8 commits into
mainfrom
release/1.5.0
Nov 6, 2026
Merged

tempus2016 merged 8 commits into
mainfrom
release/1.5.0

Conversation

@tempus2016

Copy link
Copy Markdown
Owner

Release 1.5.0, milestone 1.5.0 Patterns over time. It adds five history charts in a new "Distributions" group in the style picker. They reuse the history layer and the shared frame, so period, previous/next buttons, source, grouping, axes, gridlines, compact layout and the header work as on the other history charts. Every option is in the editor's Chart section.

Histogram (style: histogram)

Shows how often each entity's readings fell in each range, such as how long the living room sat below 18 °C this week. Bins count time by default (count: time): each value counts until the next reading, and unavailable time counts nothing. count: readings counts rows instead. Bin width is automatic, or set with bin_width or bins (at most 100). x_min / x_max fix the range. show_as: percent | total, bars: overlap for several entities, and color_by: bands.

Scatter (style: scatter)

The first entity goes along the bottom and every other entity is plotted up the side against it. Readings are paired in time: at every moment any sensor changed, each one's value is the last it reported. With grouping set, each group is one dot. "Along the bottom: Time" plots readings over time as dots. Options include jitter, dot size and x_min / x_max. Hover picks the nearest dot.

Bubble (style: bubble)

A scatter where a third entity sets each bubble's area. A legend under the chart shows the smallest and largest bubble with the readings they stand for. Fewer than three entities shows a message saying what to add.

Heatmap (style: heatmap)

grid: hours (default) is 7 weekday rows by 24 hours, averaged over the last 4 weeks. grid: calendar is one cell per day for the last 12 months of statistics, like a contribution graph. Colour is a smooth scale of the entity's colour (or color_min / color_max), or its colour bands. Cells can be rounded, square or hexagons. Hours or days with no readings are drawn empty, not as zero.

Box plot (style: box)

Median, quartiles and whiskers of each entity's readings, one box per entity or one per hour, day, week or month with group_by. Each value counts for as long as it held. whiskers: tukey | range, orientation: horizontal, readings: true for jittered reading dots.

Also in this PR

  • The five charts share src/chart/xy.ts (value axes along the bottom, picking one mark, its tooltip) and src/chart/scatter-plot.ts (scatter and bubble). chartArea takes optional xLabel / yLabel formatters, merged alongside main's right-hand axis and axes.label.
  • test: period-card.test.ts failed between midnight and 1 am, because its mocked history returned a reading in the future. The mock now never returns one later than now. Checked with the clock pinned at 00:20.

Checked on ha-dev during the build, and again after merging onto main with the screenshot harness in light and dark. 550 tests pass, typecheck, lint and build are clean.

Closes #19
Closes #20
Closes #21
Closes #22
Closes #23

A new chart style, "Histogram" (style: histogram, in the editor's Distributions group), shows
how often each entity's readings fell in each range: how long the living room sat below 18 °C
this week, or the spread of a response time.

Bins count time by default (count: time). History records changes, not samples: a room at 20 °C
all day that flickers to 21 ten times in a minute has eleven rows, ten of them 21. Counting rows
says it was mostly 21; adding up how long each value held says 20, which is what the room was.
Each value counts until the next reading (the last until now or the end of the range), and time
spent unavailable counts nothing. count: readings counts rows instead, for sensors where each
reading is an event; the value carried in from before the range and the copy carried to its right
edge are not counted. Long-term statistics rows count one period each, so a missing row is missing
data rather than a long-held value. Grouped readings (group_by) count each group's length.

Bin width is automatic (a round 1, 2 or 5 × 10ⁿ for a number of bins that suits the card's
width), or set with bin_width (edges on its multiples) or bins (an exact split from the lowest to
the highest reading), at most 100. x_min / x_max fix the range; readings outside it are left out.
Bars show each entity's share of the whole (show_as: percent) or the hours / number of readings
(show_as: total). Two or more entities are drawn side by side in each bin or over each other
(bars: overlap, translucent with an outline). color_by: bands colours each bin with the colour
band its middle falls in.

It reuses the history layer and the shared frame, so periods and previous / next, statistics,
grouping, axes, gridlines, compact and the header placement all work. Hovering or touching a bin
shades it and lists each entity's share and time there. Every option is in the editor's Chart
section; each entity has a bar colour.

Value axes along the bottom, x_min / x_max and the pick-one-mark tooltip live in the new
src/chart/xy.ts, shared with the other distribution charts; chartArea takes optional label
formatters for axes that aren't time.

Closes #19
A new "Scatter" style in the Distributions group. By default the first
entity goes along the bottom and every other entity is plotted up the
side against it, one coloured series each: outside temperature against
heating power, say. Readings are paired in time: history records changes,
not samples, so at every moment any of the sensors changed, each one's
value is the last it reported. Moments when one of them was unavailable,
or had not reported yet, are left out, and so is the reading the chart
carries on to its right edge. With "Group readings by" set, each group
(an hour's average outside against that hour's heating, say) is one dot,
which is the better way to compare a fast sensor with a slow one.

"Along the bottom: Time" plots each entity's readings over time as dots,
on the usual time axis. Jitter (px) nudges each dot a steady random
amount, worked out from the reading's own time so a dot stays put as new
readings arrive, so readings that land on top of each other stay
visible. Dot size, an optional fixed range along the bottom (x_min,
x_max), the period, source, grouping, axes, gridlines, header and compact
layout are all in the editor's Chart section; each entity's dot colour is
in its own options. Hover or touch picks the nearest dot and shows its
time and both readings.

Fewer entities than the chart needs shows a message saying what to add;
a missing or unavailable entity just has no dots. The value axes, picking
and tooltip box live in src/chart/xy.ts, shared with the other charts
that have values along the bottom, and the pairing and drawing in
src/chart/scatter-plot.ts, shared with the bubble chart.

Closes #20
A new "Bubble" style in the Distributions group: the first entity goes
along the bottom, the second up the side, and the third sets the size of
each bubble (temperature against humidity, sized by CO2, say). The three
are paired in time as the scatter chart does: at every moment any of
them changed, each one's value is the last it reported, leaving out
moments when one was unavailable or had not reported yet. Grouping by
hour or day gives one bubble per group, which keeps a busy sensor from
burying the chart.

A bubble's area, not its width, grows with the size reading, from the
smallest bubble at the lowest reading in view to the largest at the
highest, so twice as far up the range covers twice as much of the chart.
Larger bubbles are drawn first so small ones stay on top, half see
through. A legend under the chart shows the smallest and largest bubble
with the readings they stand for; hover or touch shows all three readings
and the time. Smallest and largest bubble (px), jitter, an optional fixed
range along the bottom, and the usual period, source, grouping, axes and
layout options are in the editor's Chart section; the bubble colour is
the second entity's own colour option.

Fewer than three entities shows a message saying what to add; a missing
or unavailable size entity leaves no bubbles. Shares src/chart/xy.ts and
src/chart/scatter-plot.ts, byte for byte, with the scatter chart.

Closes #21
A new chart style, "Heatmap", in the Distributions group of the style picker. It shows when
something happens: each cell is coloured by a value, so a house's power by hour of the day and day
of the week shows the busy evenings at a glance, and a calendar of daily values shows a year.

Two layouts (`grid`):
- hours (the default): 7 rows of weekdays by 24 hours. Readings are grouped by hour in Home
  Assistant's time zone (the shared grouping), then every Tuesday 14:00 is averaged into one cell.
  Rows start on the user's first weekday. Without a period it covers the last 4 weeks of history.
- calendar: a cell per day, a column per week, weekdays down the side and month names along the
  bottom, like a contribution graph. Without a period it covers the last 12 months of long-term
  statistics, since history rarely reaches back a year.

Readings combine with the shared `aggregate` (mean by default; change for energy meters), and
period, previous/next buttons and source work as on every history chart. `group_by` is set by the
layout and ignored.

Colour comes from a smooth scale of the entity's colour (faint for the lowest, full for the
highest, or between `color_min` and `color_max`), or from the entity's colour bands
(`color_by: bands`). A legend under each grid shows the gradient with its ends, or the bands.
Cells can be rounded (default), square or hexagons, and keep their shape at any card width.
Hours or days with no readings are drawn in the empty track colour, not as zero.

Each entity gets its own grid, scale and legend; tapping its name runs its tap action. Hovering or
touching a cell shows its time, value and, for the hours grid, how many days were averaged.
Unavailable and missing entities draw an empty grid. The grid fades in, without motion when the
browser asks for less.

Every option is in the editor's Chart section: layout, period, source, how to combine, colour,
cell shape, colour scale ends, legend and tooltip; the per-entity options hold the colour.

Value-axis charts in this release share `src/chart/xy.ts` (picking a mark under the pointer and its
tooltip, which the heatmap uses) and optional label formatters on `chartArea`.

Closes #22
…ay, week or month

A new chart style, "Box plot" (style: box), in the editor's Distributions group. Each box shows
the median, the quartiles and the whiskers of an entity's readings over the period. Without
grouping there is one box per entity; with group_by (hour, day, week, month) there is one box per
group, and several entities sit side by side in their series colours.

History records changes, so each value counts for as long as it held: a room that sat at 20 °C
all day and flickered to 30 °C for a few minutes has a median of 20. The value carried in from
before a group counts from its start, unavailable time doesn't count, and the current group stops
at now. Long-term statistics are fetched as hourly rows (five-minute rows for hourly boxes); rows
are equal length, so each counts once, and their own min and max widen the box's lowest and
highest. Quantiles are weighted Hazen quantiles (four readings 1-4 have a median of 2.5).

Whiskers reach 1.5 times the box's height with readings beyond drawn as outlier dots (whiskers:
tukey, the default), or the lowest and highest (whiskers: range). orientation: horizontal turns
the boxes on their side with the names up the side; readings: true draws each reading as a
jittered dot over its box (at most 120 per box, always in the same place). Hovering or touching a
box shows its highest, quartiles, median, lowest, average and reading count.

It reuses the history charts' period, previous/next buttons, source, axes, gridlines, value range,
compact layout and header. All options are in the editor's Chart section; each entity has a box
colour. Grouping gives only the boundaries here, so there is no "combine with" choice.

Also adds src/chart/xy.ts (value axes and a pick-one-mark tooltip shared with the other
distribution charts) and lets chartArea take its own label text for each axis.

Closes #23
The mocked history returned a reading an hour after the period start, which is in the future just after midnight, so the line ran past now. The mock now never returns a reading later than now, like the recorder.
@tempus2016 tempus2016 added this to the 1.5.0 milestone Nov 6, 2026
@github-actions github-actions Bot added the tests The unit suite and coverage label Nov 6, 2026
@tempus2016 tempus2016 closed this Nov 6, 2026
@tempus2016
tempus2016 deleted the release/1.5.0 branch November 6, 2026 14:22
@github-project-automation github-project-automation Bot moved this from Todo to Done in Chartdeck Planner Nov 6, 2026
@tempus2016
tempus2016 restored the release/1.5.0 branch November 6, 2026 14:23
@tempus2016 tempus2016 reopened this Nov 6, 2026
@tempus2016
tempus2016 merged commit 1af60ee into main Nov 6, 2026
9 checks passed
@tempus2016
tempus2016 deleted the release/1.5.0 branch November 6, 2026 14:26
github-actions Bot pushed a commit that referenced this pull request Nov 6, 2026
## [1.5.0](v1.4.0...v1.5.0) (2026-11-06)

### Features

* histogram, scatter, bubble, heatmap and box plot charts ([#106](#106)) ([1af60ee](1af60ee)), closes [#19](#19) [#20](#20) [#21](#21) [#22](#22) [#23](#23)

### Documentation

* list the pie, rings, pictograph and waffle charts in the README ([#107](#107)) ([886b1fc](886b1fc))
github-actions Bot pushed a commit that referenced this pull request Nov 6, 2026
## [2.0.0](v1.3.0...v2.0.0) (2026-11-06)

### ⚠ BREAKING CHANGES

* on/off states now read as 1 and 0 on number charts, so a binary sensor on a line, area or column chart draws a 0/1 step instead of nothing.

### Features

* Chartdeck 2.0, every remaining roadmap chart ([#111](#111)) ([3aa1f9b](3aa1f9b)), closes [#13](#13) [#14](#14) [#24](#24) [#25](#25) [#26](#26) [#27](#27) [#28](#28) [#29](#29) [#30](#30) [#31](#31) [#32](#32) [#33](#33) [#34](#34) [#35](#35) [#36](#36) [#37](#37) [#38](#38) [#40](#40) [#41](#41) [#42](#42) [#43](#43) [#44](#44) [#45](#45) [#46](#46) [#57](#57) [#58](#58) [#59](#59) [#82](#82) [#83](#83) [#84](#84) [#110](#110)
* histogram, scatter, bubble, heatmap and box plot charts ([#106](#106)) ([1af60ee](1af60ee)), closes [#19](#19) [#20](#20) [#21](#21) [#22](#22) [#23](#23)
* pie and donut, rings, pictograph and waffle charts ([#104](#104)) ([e596014](e596014)), closes [#15](#15) [#16](#16) [#17](#17) [#18](#18)

### Documentation

* list the histogram, scatter, bubble, heatmap and box plot charts in the README ([bd6116f](bd6116f))
* list the pie, rings, pictograph and waffle charts in the README ([#107](#107)) ([886b1fc](886b1fc))
@tempus2016 tempus2016 modified the milestones: 1.5.0, 2.0.0 Nov 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tests The unit suite and coverage

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[Chart]: Box plot [Chart]: Heatmap and calendar heatmap [Chart]: Bubble chart [Chart]: Scatter chart [Chart]: Histogram

1 participant