Skip to content

Data Visualisation with Phlex and ECharts

Series Overview

Prerequisites: Completion of the Phlex v2 on Rails series, or equivalent familiarity with Rails 8, Phlex 2.4.1, Literal 1.9.0, Tailwind CSS v4, Stimulus, and importmaps.


What This Series Builds

This series teaches you to integrate ECharts 5.x into a Rails 8 application using Phlex components and a Stimulus controller — arriving at a clean, testable architecture where each layer has a single, well-defined responsibility.

The central thesis: ECharts is a JavaScript runtime concern. Ruby’s job is to produce a well-typed, testable option hash. The Stimulus controller’s job is to own the ECharts instance lifecycle. Phlex bridges them via data attributes — nothing more.

By the end you will have:

  • A chart_controller.js Stimulus controller that manages ECharts initialisation, disposal, and resize correctly
  • A Ruby DSL in app/lib/chart/ for constructing ECharts option hashes — expressive, testable, and deliberately constrained to what Ruby does well
  • A named formatter and colour palette registry that keeps all JavaScript formatting logic out of Ruby
  • A suite of Phlex chart components covering the most common chart types, all driven by the same architectural patterns
  • A real-time update architecture using ActionCable
  • Unit tests for chart configuration that run in plain Ruby, with no browser and no Rails

Dataset: Simulated Australian Economic Data

All examples use a curated slice of ABS open data covering Australia’s economy from 2000–2024 (see: ABS Data Fixtures)

Data Series Granularity Used In
GDP by industry (chain volume measures) Quarterly Line, Area, Mixed
Labour force by state (employment, unemployment, participation rate) Monthly Bar, Scatter
CPI by expenditure category Quarterly Pie, Donut
Leading economic index Monthly Gauge, Real-time
Daily economic activity proxy Daily Calendar Heatmap

Data is generated by a deterministic Ruby script using a seeded LCG random number generator — the same values are produced on every run, so seed data is reproducible without being stored in the repository.


Module Guide

Module 01 — Foundation: ECharts, Stimulus, and the Phlex Bridge

Establishes the core architecture. Pin ECharts via importmap, build chart_controller.js managing the full ECharts lifecycle (init, dispose, resize via ResizeObserver), and create the minimal Components::Chart Phlex base class.

No DSL yet — raw Ruby hashes serialised to JSON via a Stimulus value, exposing the seam cleanly before abstracting over it.

Key questions answered: Where does the ECharts instance live? What destroys it? What resizes it?


Module 02 — The Ruby DSL: Building ECharts Options in Ruby

Introduces the three-layer architecture and the DSL that underpins the rest of the series:

  • app/lib/chart/Chart::Options, Chart::Series::*, Chart::Axis, Chart::Tooltip, Chart::Title
  • app/services/stats/ — data transformation services using extend self
  • app/views/components/charts/ — chart-specific Phlex components

Covers the escape hatch: plain Ruby hashes pass through Chart::Options unchanged. The DSL wraps what benefits from Ruby; everything else stays as hashes.

Key questions answered: What should Ruby wrap? Where does data transformation live? How do you test chart configuration in plain Ruby?


Module 03 — Formatters and Colour Palettes

ECharts formatters are JavaScript functions — they cannot survive JSON serialisation. This module introduces the named formatter registry that solves this cleanly.

  • app/javascript/charts/chart_formatters.js — base formatter library
  • app/javascript/charts/custom_chart_formatters.js — application-specific formatters
  • app/javascript/charts/chart_palettes.js — 9 base colour palettes
  • app/javascript/charts/custom_chart_palettes.js — application-specific palettes

Ruby passes a string name; chart_controller.js resolves it to a function before calling setOption. The trigger key on tooltip options provides context automatically — formatter: "currency" resolves to "axis:currency" or "item:currency" depending on the trigger.

String templates ("{value}kg") pass through unchanged. Named formatters cover everything else.

Key questions answered: How do JavaScript functions cross the Ruby/JSON boundary? How do you ensure colour consistency across linked charts?


Module 04 — Bar and Stacked Bar: Labour Force by State

First multi-series chart. Builds Components::Charts::LabourForce using the Labour Force dataset. Covers grouped vs stacked bars using ECharts’ native magicType toolbox feature, horizontal bar orientation, and bucketing monthly data into annual averages.

Introduces groupdate conceptually — the Labour Force data uses integer year/month columns so we bucket manually, but the daily_activity_readings table in Module 07 uses a proper date column where groupdate earns its place fully.


Module 05 — Scatter: Labour Market Analysis

Introduces scatter charts and the data story pattern — multiple charts interspersed with prose on a single page. Builds three components from one service call: Components::Charts::ParticipationScatter (participation rate vs unemployment rate by state), Components::Charts::EmploymentTrends (employment volume over time), and Components::Charts::ParticipationTrends (participation rate trends with national average reference line).

Covers visualMap for encoding a third dimension as colour, markLine for statistical annotations, and the within-page colour correspondence principle — all charts on the page share the same palette ensuring consistent colour mapping across charts.

Reuses Stats::LabourForce from Module 04 — demonstrating that services are chart-agnostic. Deepens the testing story — scatter chart option generation is entirely testable in plain Ruby.


Module 06 — Pie and Donut: CPI Composition

Builds Components::Charts::CpiPie and a donut variant using the CPI dataset. Covers item-trigger tooltip formatters, label formatting, and the rose chart variant. Introduces trigger: "item" and the "item:percent" formatter pattern.


Module 07 — Calendar Heatmap: Business Activity

Builds Components::Charts::BusinessCalendar using the Business Indicators dataset aggregated to a calendar view. ECharts’ calendar coordinate system is unusual — this module covers explicit date range generation, sparse data handling, and multi-year layouts. Introduces groupdate properly for bucketing quarterly data into a calendar heatmap.


Module 08 — Gauge: National Accounts

Builds Components::Charts::NationalAccountsGauge using the National Accounts dataset — GDP growth, household saving ratio, and terms of trade as gauge indicators. Gauge charts have a deceptively complex option structure; this module shows how the DSL handles nested configuration cleanly. Sets up the real-time update story for Module 09.


Module 09 — Real-Time Updates via ActionCable

Builds EconomicIndicatorChannel and extends chart_controller.js to handle incoming data via mergeOption — bypassing Turbo entirely for chart updates. Covers when to use mergeOption (updating any option) vs full reinitialisation (structural changes).

Includes a simulated broadcaster that replays National Accounts data at a configurable interval — no live data source required.


Module 10 — Advanced Label Formatting

ECharts supports multi-line, mixed-style labels via a rich object — named text styles referenced in formatter strings like "{bold|Mining}: {value|$42B}". This module covers rich text labels in depth: pie chart labels showing category, value, and percentage on separate lines; bar chart labels with multiple metrics; custom legend-style annotations.


Module 11 — Interactivity: Linked Charts, Drill-Down and dataZoom

The most complex interaction patterns:

  • Click events wired via chart.on('click') in chart_controller.js
  • Column chart → click → pie chart drill-down using Turbo Frames
  • dataZoom — slider and inside zoom, toolbox integration
  • Linked charts sharing colour palettes for visual consistency

Module 12 — Complex Multi-Series Mixed Chart

The capstone chart: a mixed line + bar visualisation on shared time axes with dual Y-axes. Pulls together the full DSL, formatter and palette systems, and the toolbox features introduced throughout the series.


Module 13 — Production Concerns

Rounds out the series:

  • SVG and CSV export — extending beyond the built-in saveAsImage
  • Server-side PDF generation from SVG strings
  • Accessibility: ARIA labels and visually hidden data table fallback
  • Performance: large datasets, large mode, progressive rendering
  • Skeleton loading states
  • Testing patterns summary

Architectural Decisions — Quick Reference

Question Decision
Where does the ECharts instance live? Owned entirely by chart_controller.js
How does Ruby pass config to JavaScript? JSON-serialised via data-chart-options-value Stimulus value
What does the Phlex component render? A mount div with the correct data attributes — nothing more
Where does data transformation live? Stats::* service modules in app/services/stats/
How are JavaScript formatter functions handled? Named formatter registry — Ruby passes a string, JavaScript resolves it
How are tooltip formatters contextualised? The sibling trigger key qualifies the lookup automatically
How are colours kept consistent across charts? Named palette registry — same palette name = same colour order
How are real-time updates delivered? Direct to the ECharts instance via ActionCable
How is chart configuration tested? Plain Ruby unit tests on DSL objects — no browser, no Rails required
What handles stack/line/bar/zoom toggles? ECharts native toolbox with magicType — no custom JavaScript

Key Files

app/
  lib/
    chart/
      options.rb          ← top-level builder
      title.rb
      tooltip.rb
      axis.rb
      series/
        base.rb
        line.rb
        bar.rb
        scatter.rb
        pie.rb
  services/
    stats/                ← data transformation services
  views/
    components/
      chart.rb            ← base Phlex chart component
      charts/             ← chart-specific components
app/javascript/
  controllers/
    chart_controller.js   ← ECharts lifecycle + option resolution
  charts/
    chart_formatters.js   ← base formatter registry
    custom_chart_formatters.js
    chart_palettes.js     ← base palette registry
    custom_chart_palettes.js

Next: Module 01 — Foundation: ECharts, Stimulus, and the Phlex Bridge