Module 07 — Gauge Charts: National Accounts
What We’re Building
Gauge charts show a single value in context — where does it sit within a meaningful range, and is it in a good or bad zone? They work best for indicators with clear thresholds and a natural min/max.
This module builds three gauges from the National Accounts dataset — each using a different visual style — connected to a single time slider. Dragging the slider scrubs through 25 years of quarterly data, animating all three gauges simultaneously.
By the end of this module you will have:
- Three gauge components with different visual styles
- A Stimulus controller that animates gauges via a time slider
- A service returning all three indicators as parallel time series
- ECharts gauge configuration — colour bands, tick marks, labels
Here’s what we’ll be building:

7.1 — The Service
The service returns all three indicators as parallel arrays indexed by quarter. The controller passes this to the component; the component serialises it for the Stimulus controller to scrub through.
|
|
The service aligns the three series on the intersection of their periods — if one indicator is missing a quarter the others won’t be either, but the intersection guarantees parallel arrays with no nil gaps.
7.2 — The Controller
|
|
|
|
7.3 — Gauge Configuration
ECharts gauge charts have a distinctive configuration. The key options:
|
|
Colour bands are defined as [threshold, colour] pairs where threshold is a
fraction of the full range (0.0–1.0). The arc fills left to right — the needle
sits on the arc and the band behind it shows the zone.
startAngle: 210, endAngle: -30 — ECharts angles are measured clockwise from
3 o’clock. 210° puts the start at the bottom-left; -30° (or 330°) puts the end
at the bottom-right. This gives the classic gauge shape.
detail — the numeric readout below the needle. valueAnimation: true makes
it count up/down smoothly when the value changes.
7.4 — Chart 1: GDP Gauge (Needle Style)
GDP in chain volume measures, billions of dollars. The range covers the full dataset — approximately $350B (2000) to $700B (2024).
Colour bands:
- Red: below $450B — pre-GFC levels, now considered low
- Amber: $450B–$580B — moderate
- Green: above $580B — strong/recent
|
|
7.5 — Chart 2: Saving Ratio Gauge (Arc Style)
Household saving ratio — what percentage of income households are saving. The COVID spike to ~20% and subsequent collapse below zero is one of the most dramatic movements in the dataset.
Arc style uses a wider axisLine and no pointer — the arc fill itself indicates
the value. Achieved by setting pointer: { show: false } and increasing axisLine
width.
Colour bands:
- Red: below 2% — households are spending more than they earn (dissaving)
- Amber: 2%–8% — moderate saving
- Green: above 8% — strong saving (pandemic behaviour, post-GFC caution)
|
|
progress: { show: true, width: 28 } fills the arc up to the current value —
the “progress bar” style. With pointer: { show: false } the filled arc is the
only indicator of the current value.
7.6 — Chart 3: Terms of Trade Gauge (Speedometer Style)
Terms of trade — Australia’s export prices relative to import prices, indexed to 100. Values above 100 mean exports are relatively more valuable (good for Australia); below 100 means imports are relatively expensive.
Speedometer style uses finer tick marks and a thinner arc — more like a car’s rev counter than a fuel gauge.
Colour bands:
- Red: below 80 — unfavourable terms
- Amber: 80–105 — near-neutral
- Green: above 105 — favourable (mining boom territory)
|
|
pointer: { itemStyle: { color: "auto" } } colours the needle to match the
colour band it’s currently pointing at — a nice visual touch.
splitNumber: 10 increases the number of major tick divisions — the “speedometer”
effect comes from having more granular markings.
7.7 — The Stimulus Controller
The slider controller holds all time series data as a Stimulus value. On input
it reads the current slider position, extracts the values for that quarter, and
calls setOption on each gauge chart instance.
|
|
echarts.getInstanceByDom(el) retrieves the ECharts instance attached to a DOM
element — this is how the slider controller accesses gauge instances initialised
by chart_controller.js without needing a direct reference.
setOption with notMerge: false merges the new value into the existing options
— only the data changes, all configuration stays intact. ECharts animates the
needle movement automatically.
7.8 — The View
<%# app/views/charts/national_accounts_gauges.html.erb %>
<div class="max-w-5xl mx-auto px-4 py-8"
data-controller="gauge-slider"
data-gauge-slider-data-value="<%= @data.to_json %>">
<h1 class="text-2xl font-bold mb-1">Australian National Accounts</h1>
<p class="text-neutral-500 text-sm mb-8">
Drag the slider to move through time. All three gauges update simultaneously.
Source: ABS National Accounts, CC BY 4.0.
</p>
<%# Three gauges side by side %>
<div class="grid grid-cols-3 gap-4 mb-6">
<div>
<p class="text-center text-sm font-medium text-neutral-600 mb-1">
GDP ($B, chain volume)
</p>
<div data-gauge-slider-target="gdp">
<%= render Components::Charts::GdpGauge.new(
value: @data[:gdp].last,
height: "280px"
) %>
</div>
</div>
<div>
<p class="text-center text-sm font-medium text-neutral-600 mb-1">
Household Saving Ratio (%)
</p>
<div data-gauge-slider-target="saving">
<%= render Components::Charts::SavingGauge.new(
value: @data[:saving].last,
height: "280px"
) %>
</div>
</div>
<div>
<p class="text-center text-sm font-medium text-neutral-600 mb-1">
Terms of Trade (index)
</p>
<div data-gauge-slider-target="trade">
<%= render Components::Charts::TradeGauge.new(
value: @data[:trade].last,
height: "280px"
) %>
</div>
</div>
</div>
<%# Slider and period label %>
<div class="px-4">
<input type="range"
min="0"
max="<%= @data[:periods].length - 1 %>"
value="<%= @data[:periods].length - 1 %>"
class="w-full accent-neutral-800"
data-gauge-slider-target="slider"
data-action="input->gauge-slider#update">
<div class="flex justify-between text-xs text-neutral-400 mt-1">
<span><%= @data[:periods].first %></span>
<span class="font-medium text-neutral-700"
data-gauge-slider-target="period">
<%= @data[:periods].last %>
</span>
<span><%= @data[:periods].last %></span>
</div>
</div>
</div>The Stimulus controller wraps the entire section — data-controller="gauge-slider".
The serialised data sits on the same element as a Stimulus value — @data.to_json
passes all periods and values to JavaScript in one shot.
Each gauge is wrapped in a data-gauge-slider-target div. The slider controller
uses echarts.getInstanceByDom to find the ECharts instance inside each wrapper.
7.9 — How getInstanceByDom Works
chart_controller.js initialises an ECharts instance on the mount div. ECharts
registers that instance internally, keyed to the DOM element. getInstanceByDom
retrieves it by element reference.
The gauge slider targets are the wrapper divs — not the mount divs — so we need to find the mount div inside:
|
|
This is the correct version — getInstanceByDom needs the exact element that
echarts.init was called on, which is the mount div.
7.10 — Gallery
<%= render "charts/gallery_card",
title: "National Accounts Gauges",
description: "Three gauge styles — needle, arc, speedometer — animated by a "\
"time slider across 25 years of quarterly data.",
path: charts_national_accounts_gauges_path %>7.11 — Module Summary
New files:
| File | Purpose |
|---|---|
app/services/stats/national_accounts_gauges.rb |
Three aligned time series |
app/views/components/charts/gdp_gauge.rb |
Needle gauge — GDP |
app/views/components/charts/saving_gauge.rb |
Arc/progress gauge — saving ratio |
app/views/components/charts/trade_gauge.rb |
Speedometer gauge — terms of trade |
app/javascript/controllers/gauge_slider_controller.js |
Time slider animation |
Patterns introduced:
- ECharts gauge chart configuration —
min,max,startAngle,endAngle - Colour bands —
axisLine.lineStyle.colorthreshold array - Three gauge styles — needle, arc/progress (
progress: { show: true }), speedometer pointer: { itemStyle: { color: "auto" } }— needle matches current band colourvalueAnimation: true— smooth numeric readout transitionsecharts.getInstanceByDom— accessing an existing ECharts instance from another controller- Stimulus across multiple chart instances — one controller, multiple gauges
setOptionwithnotMerge: false— partial update without full re-render
Gauge configuration quick reference:
| Option | Purpose |
|---|---|
startAngle / endAngle |
Arc extent — 210/-30 gives classic gauge shape |
axisLine.lineStyle.color |
Colour bands as [threshold, colour] pairs (0–1) |
progress: { show: true } |
Fill arc to current value (arc style) |
pointer: { show: false } |
Hide needle (arc style only) |
pointer.itemStyle.color: "auto" |
Needle matches current band |
detail.valueAnimation |
Animate the numeric readout |
splitNumber |
Number of major divisions (higher = speedometer effect) |