Heatmap TrackerDataview heatmaps

Obsidian heatmaps with Dataview and dataviewjs

When frontmatter alone is not enough, a dataviewjs block gives you the whole dataset: computed values, merged sources, a colour per day. Here is the working shape of one.

The no-code heatmap-tracker codeblock covers frontmatter. A dataviewjs block covers everything else: computed values, several sources merged, a colour per entry, values that live in inline fields or in the note body. You build an entries array and hand it to renderHeatmapTracker().

What you need first

  • The Dataview plugin, installed and enabled.
  • Dataview → Settings → Enable JavaScript Queries, which is off by default. Without it a dataviewjs block renders nothing at all.
  • Heatmap Tracker itself, which registers the global renderHeatmapTracker(container, trackerData).

The minimal working block

Steps.md
```dataviewjs
const trackerData = {
    entries: [],
    heatmapTitle: "Steps",
    heatmapSubtitle: "Pulled from my daily notes",
    separateMonths: true,
};

const FOLDER = "daily notes";
const PROPERTY = "steps";

for (let page of dv.pages(`"${FOLDER}"`).where((p) => p[PROPERTY])) {
    trackerData.entries.push({
        date: page.file.name,
        filePath: page.file.path,
        intensity: page[PROPERTY],
    });
}

trackerData.basePath = FOLDER;

renderHeatmapTracker(this.container, trackerData);
```

Three fields carry the weight. date places the square, intensity decides its colour, and filePath is what a click opens.

The shape of an entry

Field Type What it does
date string A plain YYYY-MM-DD string. Required.
intensity number Feeds the colour scale.
filePath string Absolute vault path, so a click opens the right note.
customColor string Overrides the palette for that one day.
content string Extra text shown for the day.

Getting dates right

The single most common bug in scripted heatmaps is an off-by-one day, and it is almost always a timezone issue. Pass plain YYYY-MM-DD strings. A JavaScript Date or a full ISO timestamp carries a time component, and that time can cross midnight once it is interpreted in the local zone.

safe vs unsafe dates
// Good — the filename of a daily note is already YYYY-MM-DD
date: page.file.name

// Good — a Dataview date, formatted explicitly
date: page.due.toFormat("yyyy-MM-dd")

// Risky — a Date object or an ISO timestamp with a time part
date: new Date(page.created)

Computed and combined values

Because it is JavaScript, the value does not have to exist in any note. Sum several fields, count tasks, or weigh categories differently:

Productivity.md
```dataviewjs
const trackerData = { entries: [], heatmapTitle: "Focus score" };

for (let page of dv.pages('"daily notes"')) {
    const deep = page["deep-work"] ?? 0;
    const meetings = page["meetings"] ?? 0;
    const score = deep * 2 - meetings;
    if (score <= 0) continue;

    trackerData.entries.push({
        date: page.file.name,
        filePath: page.file.path,
        intensity: score,
        customColor: score > 8 ? "#39d353" : undefined,
    });
}

renderHeatmapTracker(this.container, trackerData);
```

Counting completed tasks per day works the same way — iterate page.file.tasks and use the count as the intensity.

How a click resolves a file

  1. If the entry has filePath, that exact file opens. If it is missing, the plugin offers to create it at the same path.
  2. Otherwise, if trackerData.basePath is set, it proposes basePath/YYYY-MM-DD.md.
  3. Otherwise it falls back to your Daily Notes folder and format.

Use page.file.path rather than page.file.name whenever two notes in the vault can share a filename. Set disableFileCreation: true if you would rather empty squares did nothing.

Useful trackerData options

Option Effect
year Which year to open on. Defaults to the current one.
colorScheme paletteName from settings, or an inline customColors array.
intensityConfig scaleStart / scaleEnd, plus excludeFalsy.
layout "monthly" for a calendar-style view instead of the year grid, or "month" / "week" for a single calendar period.
monthsToShow, daysToShow, startDate/endDate Show a window instead of a full year.
insights Your own metrics in the Statistics tab.

The configuration reference documents every parameter, each with a worked example in the example vault.

Does it have to be Dataview?

renderHeatmapTracker(container, trackerData) only cares about the entries array. Any JavaScript that can build one — a Templater script, a fetch from a local file — works the same way. Dataview is simply the most convenient source of dated pages, and the heatmap-tracker codeblock does depend on it.

Get the plugin

Heatmap Tracker is a free, open-source Obsidian community plugin. Install it from Settings → Community plugins → Browse, or open the plugin page directly.

Install Heatmap Tracker Source on GitHub