Reference/heatmap.md
heatmap: the year
Properties
code block
heatmapkeys
13 on the block, 3 on each layer
checked on
Dashy 1.5.2
A year by days: one cell per day, coloured by a number from frontmatter.
From the demo vault
```heatmap
source: Diary
field: sleep_score
color: purple
bands: [90, 80, 70]
title: Sleep, last twelve months
```
renders as
A minimal block
The shortest config worth pasting. Change the folder and the property names to yours.
```heatmap
source: 01-Areas/Personal/Diary
field: sleep_score
color: purple
bands: [90, 80, 60]
```
Keys on the block
| Key | What it does | Type | Default |
|---|---|---|---|
sourcealso folder, from |
folder; includes nested ones | text | none |
tag |
tag, with or without the hash | text | none |
where |
a condition like year = 2026, rating >= 4, tags contains books, or several that must all hold: joined with and (year = 2026 and rating >= 4) or written as a list ([year = 2026, "rating >= 4"]). The field name may be a dotted path into a nested property, like health.sleep > 70. or is not supported; quote a value holding the word and or or. One unreadable condition drops the whole filter with a warning, and the grid is drawn unfiltered |
text or list | none |
fieldalso property, prop |
required unless layers is set, and not allowed together with it: exactly one of the two. A numeric frontmatter property, or a checkbox: ticked days are painted, unticked stay empty; dotted for a nested one like health.sleep. Also takes a list, like [mood_am, mood_pm], to collapse several properties from the same note into one day with per_day. A field missing from the selection, or holding text rather than a number, errors and says which; count text elsewhere with a stats card's where: "field contains ..." and agg: count. A duration string like 5h 58min or 7:30 counts as minutes, and tooltips, the caption's average and the legend then read 5h 58m; H:MM is always a duration, never a time of day. A field mixing durations and plain numbers counts both as minutes, shows plain numbers and warns |
text or list | none |
per_day |
how several values landing on one day combine: sum, avg or max. Several values happen either from two or more notes on the same day, or from a field list on one note, or both at once. An unrecognised value warns and falls back to sum. With layers, it applies to each layer's own field(s) separately |
text | sum |
date_field |
a date frontmatter property to read instead of the note name: 2026-03-02 or 2026-03-02T10:30, or a dotted path into a nested property like meta.date |
text | none |
skip_field |
a property marking a day special, like vacation: true or sick: flu; a day is special once any note landing on it sets the property to anything other than false, a blank string, 0 or absent. Its cell gets a hatched overlay, on top of its painted colour when it has one. Works with layers too |
text | none |
coloralso colour |
blue green cyan purple pink orange red gray, or #rrggbb. Ignored, with a warning, when layers is set: each layer carries its own colour instead |
text | blue |
layers |
several activities on one grid, each in its own colour, instead of one field: [{field, color, label}]. Not used together with the block's own field. The first layer painted on a day colours that cell and supplies its value and link; the tooltip lists every layer with a value that day, in list order. The caption then counts days where any layer painted and drops the average, since averaging different fields together says nothing useful |
list | none |
bands |
thresholds from the top down: [90, 80, 60] or [{min, alpha, label}]; anything below the lowest falls into the bottom band. Without bands, each grid (a calendar year, or the one range window) shades itself by its own minimum and maximum: a checkbox field or a grid where every painted value is the same still paints one flat colour instead. A threshold may be a duration, [8h, 7h] or {min: 7h}, read as minutes; a plain number against a field of durations means minutes, and the legend reads as durations |
list | none |
link |
clicking a cell opens that day's note | true or false | true |
range |
one grid over a window ending today, instead of a grid per calendar year: week, month, year (1 January of the current year to today) or a rolling count of days like 365d. Columns still align to the week the same way; a day outside the window is neither drawn nor counted. An unrecognised value warns and falls back to a grid per year |
text or number | none |
title |
a custom heading instead of the automatic one | text | none |
Keys on each layer
Each entry under items (here layers) is one layer.
| Key | What it does | Type | Default |
|---|---|---|---|
field requiredalso property, prop |
this layer's own numeric frontmatter property, or a checkbox; dotted for a nested one, or a list to collapse several properties into this layer's day. Same shapes as the block's own field, durations included |
text or list | none |
coloralso colour |
blue green cyan purple pink orange red gray, or #rrggbb. Without one, the next free colour from the palette, skipping colours other layers already claimed | text | none |
labelalso title, name |
name shown in the legend and the cell tooltip. Defaults to the field name(s) | text | none |
How it behaves
- A note's date is its name, as long as it starts with
YYYY-MM-DD(2026-01-05 Mondaycounts,2026-01-051does not), unlessdate_fieldnames a property instead. That is how the block knows which cell it belongs to. - Two or more notes landing on the same day paint one cell, and so does a
fieldlist on one note:per_day(default sum) says how the day's values combine, and a ticked or numeric contributor always outweighs a false one on the same day. - Years are taken from the data, newest first, but never one later than today: a note dated in the future draws nothing extra and counts nowhere in the grid. If every dated note turns out to be in the future, the current year is still drawn, empty.
layersreplacesfieldfor tracking several activities on one grid, each its own colour:layers: [{field: gym, color: blue}, {field: run, color: green, label: Running}]. When two layers land on the same day, the first one in the list colours the cell; the tooltip still lists every layer that has a value that day.skip_fieldmarks special days, vacation or sick for example: they still show a hatched cell, keeping any painted colour underneath, and their count in the caption is unchanged (a special day with no value is still not present).rangedraws one grid over a window ending today instead of a grid per year:range: 365dis a rolling year that crosses 1 January in a single grid rather than splitting into two. Everything else works the same over that one grid: bands, layers, skip_field and its legend row, the caption's count and average, tooltips and links.
Guides that use it
- A habit tracker in Obsidian without Dataview
- Two activities on one heatmap in Obsidian
- A habit streak in Obsidian that skips weekends
Dashy is in Obsidian's community plugins. Search for Dashy, install, enable, then run Dashy: Insert block and pick heatmap.