Module 06 — Calendar Heatmap: Daily Economic Activity
What We’re Building
The calendar heatmap maps values onto a calendar grid — each cell is one day, colour encodes the value. Seasonal patterns, weekly rhythms, and one-off events are immediately readable.
This module also establishes the correct data flow pattern that applies from here forward: the controller calls the service and passes data to the component.
By the end of this module you will have:
- A single-year calendar heatmap
- A multi-year calendar heatmap using the same component
visualMapfor the colour scale
Here’s what we’ll be building:

6.1 — The Service
|
|
pluck retrieves only the two columns needed — no full ActiveRecord objects
for 2,557 rows. The result is an array of [date_string, value] pairs — the
exact format ECharts calendar series expects.
6.2 — The Calendar Coordinate System
ECharts’ calendar coordinate system is unlike any other. Instead of X/Y axes, data is plotted onto a calendar grid. Two options work together:
calendar — defines the grid:
|
|
The series — references the calendar:
|
|
range accepts a year string ("2022"), a month ("2022-03"), or a date
range (["2022-01-01", "2022-06-30"]).
dayLabel.firstDay: 1 starts weeks on Monday — correct for Australian calendars.
6.3 — The Component
A single year is a special case of a multi-year range. One component handles both by building arrays of calendar grids and series:
|
|
When year_range: [2022, 2022] — one grid, one series, height 220px.
When year_range: [2019, 2021] — three grids, three series, height 540px.
The logic is identical — only the number of iterations changes.
calendarIndex assigns each series to the correct grid. Without it all
series plot on the first grid.
Shared visualMap — this is the key advantage over rendering multiple
single-year components in a loop. All years share one colour scale, so the
COVID dip in 2020 is visually comparable to 2019 and 2021.
dynamic_height — the component calculates its own height from the year
range, overriding the base height prop.
6.4 — Tooltip Formatter
Add to custom_chart_formatters.js:
|
|
Note: the “item:xxx” prefix for the custom formatter. Since the tooltip is for a single item, we differentiate it from our tabular (axis) based tooltips'
The
+ "T00:00:00"is important —new Date("2022-01-15")parses as UTC midnight, which displays as January 14 in Australian timezones. Appending the time forces local time parsing.
6.5 — Controller and Views
|
|
|
|
<%# app/views/charts/single_year_calendar.html.erb %>
<h1 class="text-2xl font-bold mb-2">Daily Activity — 2022</h1>
<p class="text-neutral-500 text-sm mb-6">
Illustrative daily index — generated data, not real ABS statistics.
</p>
<%= render Components::Charts::ActivityCalendar.new(
data: @data,
year_range: [2022, 2022]
) %><%# app/views/charts/multi_year_calendar.html.erb %>
<h1 class="text-2xl font-bold mb-2">Daily Activity — COVID Years</h1>
<p class="text-neutral-500 text-sm mb-6">
The COVID shock of 2020 is simulated as a sustained period of low activity
from March through December, followed by gradual recovery through 2021.
All three years share the same colour scale for direct comparison.
</p>
<%= render Components::Charts::ActivityCalendar.new(
data: @data,
year_range: [2019, 2021]
) %>And add the cards to the index:
|
|
6.6 — Module Summary
New files:
| File | Purpose |
|---|---|
app/services/stats/daily_activity.rb |
Query and shape for calendar series |
app/services/stats/daily_activity_monthly.rb |
groupdate monthly bucketing |
app/views/components/charts/activity_calendar.rb |
Single and multi-year heatmap |
Patterns introduced:
- ECharts calendar coordinate system —
calendar+coordinateSystem: "calendar" - Multi-year calendar — arrays of
calendarandserieswithcalendarIndex - Single component as general case — single year is
[2022, 2022] - Shared
visualMap— one colour scale across all years for direct comparison - Dynamic height — component calculates its own height from the year range
- UTC date fix in JavaScript tooltip formatters
calendar option reference:
| Key | Purpose |
|---|---|
range |
"2022" (year), "2022-03" (month), date range array |
cellSize |
[width, height] — use "auto" for responsive width |
dayLabel.firstDay |
0 = Sunday, 1 = Monday |
calendarIndex |
Which grid a series plots on — multi-year only |