Module 05 — Scatter Charts: Labour Force Analysis
What We’re Building
This module introduces scatter charts and a pattern that matters beyond chart mechanics: charts as supporting evidence for prose. Real dashboards and data pages rarely show a chart in isolation — they surround it with context, explanation, and narrative.
We build a single page that tells a story about the Australian labour market using three charts interspersed with text. The page is driven by one controller action, one service, and three Phlex chart components.
By the end of this module you will have:
- A service that shapes Labour Force data for scatter charts
- Three chart components consuming the same service differently
- A data story page that mixes prose and charts
- Consistent colour correspondence across all three charts using a shared palette
visualMapfor encoding a third dimension as colourmarkLinefor reference lines
5.1 — The Data Story
Australia’s labour market varies significantly by state. The ACT consistently leads on participation rate — a high-income, public-sector dominated economy keeps more people in the workforce. Tasmania and South Australia have historically higher unemployment. The COVID-19 pandemic of 2020 disrupted all states but unevenly — the speed of recovery differed markedly.
Three questions worth charting:
-
How do participation rate and unemployment rate relate across states? A scatter chart — one point per state per year — reveals the structural differences and the COVID disruption.
-
How has employment volume changed over time? A line chart per state shows the absolute numbers and the different scales of state economies.
-
How have participation rates trended? A multi-series line chart shows which states are improving, stagnating, or declining in workforce engagement.
One service. Three charts. One page.
5.2 — The Service
This module reuses Stats::LabourForce from Module 04 — unchanged. All three
charts consume the same service call:
|
|
This is the service layer principle in practice. Stats::LabourForce transforms
raw monthly readings into annual averages per state. It has no knowledge of how
that data will be used — whether as a bar chart, a scatter chart, or a line chart.
The same hash structure feeds all three visualisations on this page.
The service already returns everything we need:
|
|
No new service file. No duplication. Three charts, one service call per component.
5.3 — Chart 1: Participation vs Unemployment Scatter
Each point is one state in one year. X axis is participation rate, Y axis is unemployment rate. Each state is a separate series so it gets its own colour and can be toggled in the legend.
The trajectory of each state traces a path through the chart over 12 years — in a healthy economy, points move right (higher participation) and down (lower unemployment). The COVID year appears as an outlier point for every state.
visualMap — encoding year as colour
Adding visualMap encodes the year dimension as colour intensity within each
series — earlier years appear lighter, recent years darker. This makes the time
progression visible without adding a separate series per year:
|
|
With visualMap, each data point becomes [participation, rate, year] — a
three-element array where the third element drives the colour lightness:
|
|
dimension: 2 tells visualMap to use the third element (index 2) of each data
point. colorLightness: [0.35, 0.85] maps the min year to a darker shade and the
max year to a lighter shade — or reverse the array to invert the mapping.
show: false hides the visual map control widget. Since the legend already
identifies states by colour, a second colour scale widget would be confusing.
|
|
Note: Axis minimum for scatter charts: ECharts defaults to starting value axes at zero. For scatter charts this almost always produces a chart where all points are bunched into one corner. Set min: “dataMin” on both axes to fit the scale to your data. Use an explicit value like min: 55 if you want a fixed lower bound rather than a dynamic one.
Custom tooltip for scatter
The default "item" formatter shows the raw array — not readable. Add a custom
formatter to custom_chart_formatters.js:
|
|
Note: This is our first example of using the
custom_chart_formatters.jsthat we introduced back inLesson 02.
5.4 — Chart 2: Employment Volume Over Time
A line chart showing absolute employment numbers per state. This answers a different question — not the ratio of workers to population, but the raw scale of each state’s economy.
|
|
5.5 — Chart 3: Participation Rate Trends
The third chart shows participation rate over time. A markLine draws the national
average — this is more semantically correct than adding a fake series, and ECharts
renders it as a distinct annotation rather than a legend entry.
markLine — statistical annotations
markLine adds reference lines to any series. We add it to the first series only
to avoid duplicating the line eight times:
|
|
|
|
Note:
markLinewithtype: "average"draws a horizontal average line across the entire series. Here we want the national average across states, not the average of one state’s values — so we compute it separately and pass it as a custom data point. ThemarkLineis attached to the first series (i == 0) to avoid rendering it eight times.
5.6 — Controller and View
One action loads the data once. The view renders three charts interspersed with prose.
|
|
|
|
<%# app/views/charts/labour_market_analysis.html.erb %>
<div class="max-w-4xl mx-auto px-4 py-8">
<h1 class="text-3xl font-bold mb-2">Australian Labour Market by State</h1>
<p class="text-neutral-500 text-sm mb-8">
Annual averages, seasonally adjusted, 2012–2024.
Source: <a href="https://www.abs.gov.au" class="underline">Australian Bureau of Statistics</a>,
Labour Force (ABS catalogue 6202.0), CC BY 4.0.
</p>
<%# ── Chart 1 ────────────────────────────────────────────────── %>
<h2 class="text-xl font-semibold mb-2">Participation vs Unemployment</h2>
<p class="text-neutral-600 mb-4">
Each point represents one state in one year. States that perform well tend to
cluster toward the bottom-right — high participation, low unemployment. The
ACT consistently occupies this position, reflecting its high-income public
sector workforce. Tasmania and South Australia sit toward the upper-left.
The COVID-19 disruption of 2020 appears as a visible break in each state's
trajectory — unemployment spiked as participation fell.
</p>
<%= render Components::Charts::ParticipationScatter.new(
readings: @readings,
height: "420px"
) %>
<p class="text-neutral-500 text-xs mt-2 mb-10">
Each point is one state's annual average. Hover for details. Toggle states
using the legend.
</p>
<%# ── Chart 2 ────────────────────────────────────────────────── %>
<h2 class="text-xl font-semibold mb-2">Employment Volume</h2>
<p class="text-neutral-600 mb-4">
The scale differences between states are striking. New South Wales and Victoria
together employ more than the other six states combined. The long-run growth
trend is visible across all states, with the brief COVID dip in 2020 followed
by a sharp recovery. Western Australia shows the most volatility — driven by
the resources sector's sensitivity to commodity cycles.
</p>
<%= render Components::Charts::EmploymentTrends.new(
readings: @readings,
height: "380px"
) %>
<p class="text-neutral-500 text-xs mt-2 mb-10">
Annual average employed persons ('000). Hover to compare states at a given year.
</p>
<%# ── Chart 3 ────────────────────────────────────────────────── %>
<h2 class="text-xl font-semibold mb-2">Participation Rate Trends</h2>
<p class="text-neutral-600 mb-4">
Participation rate — the share of the working-age population either employed
or actively seeking work — tells a different story to headline unemployment.
The ACT's rate is structurally higher due to its demographic profile. The
national average has been broadly flat over the period, masking diverging
trends: Queensland and Western Australia have lifted, while South Australia
and Tasmania have lagged. The dashed line shows the national average across
all states.
</p>
<%= render Components::Charts::ParticipationTrends.new(
readings: @readings,
height: "380px"
) %>
<p class="text-neutral-500 text-xs mt-2 mb-10">
Annual average participation rate (%). National average shown as dashed line.
</p>
<%# ── Attribution ────────────────────────────────────────────── %>
<div class="border-t border-neutral-200 pt-6 mt-4">
<p class="text-neutral-400 text-xs">
Data: Australian Bureau of Statistics, Labour Force, Australia (ABS cat. 6202.0).
Accessed via ABS Data API. Licensed under
<a href="https://www.abs.gov.au/website-privacy-copyright-and-disclaimer"
class="underline">CC BY 4.0</a>.
</p>
</div>
</div>And we can add an extra card to our index page:
<%= render “charts/gallery_card”, title: “Labour Market Analysis”, description: “Three charts with prose commentary — participation rate, employment volume, and unemployment trends by state, 2012–2024.”, path: charts_labour_market_analysis_path %>
5.7 — Colour Correspondence Across Charts
All three charts on this page use color: "tableau". Because the service returns
states in the same alphabetical order, and ECharts assigns colours from the palette
in series order, ACT always gets the first tableau colour, NSW the second, and so
on — across all three charts on this page.
This is the palette consistency principle from Module 03 in action. A reader can identify “Queensland” in Chart 1 and immediately find Queensland in Charts 2 and 3 by colour without reading the legend again.
The rule: if charts on the same page share the same palette and the same series order, they will share the same colour mapping. The service guarantees consistent ordering by sorting states alphabetically.
Note that Module 04 uses a different palette ("cool") — that is fine. Colour
consistency is a within-page concern. There is no requirement for the bar chart
from Module 04 to match the scatter chart on this page.
5.8 — The National Average Line
The national average series in Chart 3 uses a plain hash for lineStyle and
itemStyle — raw ECharts options passed through the DSL escape hatch:
|
|
symbol: "none" removes the data point markers. lineStyle: { type: "dashed" }
draws a dashed line. These are standard ECharts series options — the **extra
escape hatch in Chart::Series::Base passes them through unchanged.
5.9 — Testing Chart Configuration
Scatter chart option generation is pure Ruby — no browser, no JavaScript, no Rails required. The service and component produce plain hashes that are entirely inspectable in a unit test.
|
|
Running these tests requires no database, no browser, and no ECharts. The component
is instantiated with plain LabourForceReading objects built with .new — no
persistence needed.
|
|
5.10 — Routes and Gallery
|
|
Add to the gallery index:
<%= render "charts/gallery_card",
title: "Labour Market Analysis",
description: "Participation rate, unemployment, and employment trends by state. "\
"Three charts with prose commentary.",
path: charts_labour_market_analysis_path %>5.11 — Module Summary
New files:
| File | Purpose |
|---|---|
app/views/components/charts/participation_scatter.rb |
Scatter — participation vs unemployment |
app/views/components/charts/employment_trends.rb |
Line — employment volume by state |
app/views/components/charts/participation_trends.rb |
Line — participation rate with national average |
app/views/charts/labour_market_analysis.html.erb |
Data story page |
No new service — all three charts reuse Stats::LabourForce from Module 04.
Patterns introduced:
- Scatter chart with per-series data (
[x, y, z]arrays) visualMapencoding a third dimension (year) as colour lightnessmarkLinefor statistical annotations — national average reference line- Multiple charts on one page sharing a palette for colour correspondence
- Charts interspersed with prose — the data story pattern
- Custom tooltip formatter for scatter data including the year dimension
- Service reuse —
Stats::LabourForcefeeds three different chart types - Unit testing chart components in plain Ruby with no database or browser
The data story pattern:
Heading
↓
Prose — what to look for, why it matters
↓
Chart — the visual evidence
↓
Caption — what the chart shows
↓
(repeat)
↓
AttributionThis pattern works for any data-driven page. The prose gives readers the interpretive frame before they encounter the chart — making the chart more legible and more persuasive.
Next: Module 06 — Pie and Donut: CPI Composition