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.
Why Use ECharts?
ECharts is an open-source JavaScript charting library maintained by Apache. It is one of the most capable charting libraries available — and unlike many alternatives, it is genuinely free with no commercial licensing restrictions.
Breadth of chart types. ECharts covers the full range: line, bar, scatter, pie, gauge, calendar heatmap, treemap, Sankey, radar, candlestick, choropleth maps, and more. Most charting libraries cover the common types well and become awkward at the edges. ECharts handles the edges as cleanly as the basics.
Interactivity out of the box. Tooltips, legends, zoom, pan, drill-down,
linked charts, and animated transitions are all built in. The magicType
toolbox lets users switch between bar and line charts, stack and unstack series,
and download the chart as an image — without writing a line of JavaScript.
Real-time capable. ECharts’ setOption and appendData methods support
live data updates efficiently. Combined with ActionCable, any chart can become
a live dashboard with minimal additional code.
Performance. ECharts handles large datasets — tens of thousands of points —
through large mode and progressive rendering. The SVG renderer scales cleanly
on high-DPI displays and prints correctly without configuration.
Active development. ECharts is actively maintained with regular releases. The documentation is comprehensive and the option reference is exhaustive. When you need an obscure configuration option, it exists and is documented.
The honest alternative comparison. Chart.js is simpler but significantly more limited. Highcharts is commercial. D3 is powerful but requires you to build everything yourself. Vega-Lite is declarative but opinionated. ECharts sits in the right position for Rails applications: full-featured, free, and configurable without being overwhelming.
Why Use Phlex?
Phlex brings chart components into the Ruby object model — and that changes everything about how you build and maintain them.
Components are Ruby classes. A chart component is a class with props, private methods, and standard Ruby inheritance. You can test it with plain Ruby unit tests, passing data directly and asserting on the options hash — no browser, no JavaScript runtime, no fixtures. This is not possible with ERB partials or ViewComponent without additional setup.
The DSL is Ruby. Chart::Options.new(x_axis: { type: "category" }) is
Ruby. It has Ruby’s syntax highlighting, Ruby’s autocomplete, Ruby’s refactoring
tools. The alternative — building JSON strings or hashes in ERB — is error-prone
and unreadable at scale.
Composition is natural. A chart component can call private helper methods,
extract complex series-building logic into named methods, and share behaviour
through inheritance. The base Components::Chart class provides props and
rendering behaviour that every chart inherits — adding a new capability to all
charts means changing one file.
Props are typed and validated. Literal properties give you typed props with
defaults and validation. A chart that requires data: will raise immediately
if data is nil. A chart with height: defaulting to "400px" just works when
rendered without arguments. No defensive nil checks scattered through the code.
The separation is clean. Phlex draws a clear line: the component builds
chart options and renders a mount div. chart_controller.js owns the ECharts
instance. Neither crosses into the other’s territory. This makes both sides
easier to reason about, test, and change independently.
Familiarity from the main series. If you’ve completed the Phlex v2 on Rails series, you already know how to write Phlex components, how props work, and how Stimulus wires JavaScript behaviour to Ruby-rendered HTML. This series builds directly on that foundation — the chart library is the same patterns applied to a new domain.
What This Series Builds
This series teaches you to build rich, interactive, real-time data visualisations in Rails — written entirely in Ruby.
The central thesis is simple: ECharts is a JavaScript runtime concern. Your job is Ruby. A thin Stimulus controller owns the ECharts instance lifecycle. A Ruby DSL generates the chart configuration. Phlex components assemble everything. You never write chart-specific JavaScript.
By the end of this series you will have built a working chart library for your Rails application:
chart_controller.js— installed once, never touched again. Manages ECharts initialisation, disposal, resize, real-time streaming, and linked chart coordination.- A named formatter and palette registry — Ruby passes a string name, JavaScript resolves it. All formatting logic stays in one place.
Components::Chart— a base Phlex component with built-in support for height, palette overrides, chart linking, real-time streaming, and click-to-select interaction.- A template library — copy-paste starting points for every common chart type, each with sensible defaults and clear customisation points.
- A service layer pattern — SQL-first data transformation that feeds any chart type without modification.
The modules teach you how to use the library. The appendices explain how it works.
Getting Started
Following the tutorial:
|
|
Adding to your own project:
Download echarts-generator.zip from Module 01, extract into your project root,
and run rails generate echarts:install.
The Library in One Diagram
Your Rails app
│
├── Controller
│ calls service → gets shaped data → passes to component
│
├── Service (app/services/stats/)
│ SQL query + transformation → plain Ruby hash
│
├── Phlex Component (app/views/components/charts/)
│ receives data → builds Chart::Options → renders mount div
│
├── chart_controller.js ← installed once
│ resolves palette + formatters → calls ECharts.setOption
│ optionally: subscribes to ActionCable stream (stream_name:)
│ optionally: joins ECharts group (group:)
│ optionally: binds click handler (select_url:)
│
└── ECharts ← renders in the browserDataset: Real ABS Open Data
All examples use a curated slice of ABS open data covering Australia’s economy from 2000–2024 (see: ABS Data Fixtures).
| Dataset | Model | Records | Used in |
|---|---|---|---|
| GDP by Industry (ANA_IND_GVA) | GdpReading |
1,900 | Modules 01, 09, 11 |
| Labour Force by State (LF) | LabourForceReading |
1,248 | Modules 03, 04, 10 |
| CPI — All Groups (CPI) | CpiReading |
100 | Module 11 |
| Wage Price Index (WPI) | WagePriceReading |
100 | Module 11 |
| Business Indicators (QBIS) | BusinessIndicatorReading |
1,200 | Module 05 |
| National Accounts (ANA_AGG) | NationalAccountsReading |
600 | Module 07 |
| Daily Activity (generated) | DailyActivityReading |
2,557 | Module 06 |
ABS data is licensed under CC BY 4.0. Daily activity data is generated — clearly labelled as illustrative throughout.
Module Guide
Module 01 — Foundation: Getting Started
Installs the chart library (via the tutorial repo or the standalone generator), seeds the database with real ABS data, and builds the first chart: GDP by industry as a multi-series line chart. Establishes the controller → service → component pattern that every subsequent module follows.
New: Stats::GdpByIndustry, Components::Charts::GdpByIndustry, gallery index
Module 02 — Formatters and Colour Palettes
The formatter and palette registries make charts readable. Ruby passes a string
name; chart_controller.js resolves it to a JavaScript function before calling
setOption. The trigger key qualifies formatter lookups automatically —
formatter: "billions" resolves to "axis:billions" or "item:billions"
depending on context. Nine built-in palettes; custom formatters and palettes
added to separate files.
Module 03 — Bar and Stacked Bar: Labour Force by State
First multi-series chart. Builds Components::Charts::LabourForce and
Components::Charts::UnemploymentRate using the Labour Force dataset. Covers
grouped vs stacked bars, horizontal bar orientation, and ECharts’ native
magicType toolbox (stack/unstack, bar/line toggle — no custom JavaScript).
Establishes correct SQL-first service pattern.
References: Bar Chart template, Stacked Bar template
Module 04 — Scatter: Labour Market Analysis
Introduces scatter charts and the data story pattern — multiple charts
interspersed with prose on a single page. Three components from one service call:
ParticipationScatter, EmploymentTrends, ParticipationTrends. Covers
visualMap for encoding a third dimension as colour, markLine for annotations,
and within-page colour correspondence. Service reuse demonstrated —
Stats::LabourForce feeds all three chart types unchanged.
References: Scatter Chart template
Module 05 — Pie, Donut, and Rose: Industry Composition
Four chart variants — pie, donut, rose, and half donut — from one dataset and
one service. Covers item trigger tooltips, rich tooltip formatters, graphic
for centre labels, roseType, and startAngle/endAngle. ECharts label
template variables {b}, {c}, {d}.
References: Pie Chart template, Donut Chart template
Module 06 — Calendar Heatmap: Daily Activity
The calendar coordinate system. One component handles any year range: single
year is [2022, 2022], multi-year is [2019, 2021]. Shared visualMap
ensures colour consistency across years. The single component as general case
pattern — one year is a special case of many.
References: Calendar Chart template
Module 07 — Gauge: National Accounts
Three gauge styles — needle, arc/progress, speedometer — driven by a single
time slider. The slider scrubs through 25 years of quarterly data, animating
all three gauges simultaneously via echarts.getInstanceByDom. each_with_object
and parallel series alignment covered in Appendix A.
References: Gauge Chart template
Module 08 — Real-Time Charts via ActionCable
Any chart becomes real-time with one prop: stream_name:. The broadcaster
sends ECharts options JSON; chart_controller.js merges and applies it via
optionsValueChanged. No custom JavaScript per chart. Scalability is determined
by ActionCable — not client code. Demonstrates with four simulated stocks
updating every second. ActionCable setup, broadcaster lifecycle, start/stop
control, and Turbo cache control covered in housekeeping sections.
Module 09 — Advanced Label Formatting
Rich text labels with the rich property and {styleName|text} formatter
syntax. markLine for reference lines (average, threshold, fixed value).
markArea for shaded regions (GFC, COVID). markPoint for min/max callouts.
Label overlap avoidance with labelLayout: { hideOverlap: true }. All
demonstrated with GDP data.
Module 10 — Interactive Dashboard: Labour Market
A three-chart dashboard where clicking a year on the timeline updates two child
charts. Parent: LabourForceTimeline (line). Children: LabourForceStateBar
(grouped bar) and LabourForceSharePie (donut). Click handler added to
chart_controller.js via select_url: prop on the base component. Turbo Morph
updates only what changed — no Turbo Frames, no broadcasts, no custom JavaScript.
Colour correspondence maintained across all three chart types via shared palette.
Module 11 — Mixed Charts and Multiple Axes: Wages vs Inflation
The chart: CPI change (bars) and wage growth (line) on a single axis,
and GDP volume vs CPI change on dual Y axes. The wages vs inflation story
told visually. Covers mixed series (Bar + Line), dual Y axis configuration,
yAxisIndex:, axisPointer: { type: "cross" }, magicType, dataView, and
restore in the toolbox.
References: Mixed Chart template
Postscript - Extending the pattern
Short notes with sample downloads for several additional chart types, all following the same pattern we’ve learned to apply:
- Chloropleth Chart
- Treemap
- Sankey Diagram
- Radar Chart
- Candlestick Chart
- ThemeRiver
- Sunburst
Appendices
Appendix A — The Service Layer
Ruby patterns used throughout the services: extend self, group_by,
transform_values, each_with_object, pluck, SQL aggregation. Dissects
the most interesting services from the series with line-by-line explanation.
Appendix B — The JavaScript Infrastructure
chart_controller.js explained in full — Stimulus lifecycle, ECharts
initialisation, option resolution, real-time streaming, chart linking,
click-to-select. The formatter registry and palette registry: how they work,
how to extend them. Complete reference for all built-in formatters and palettes.
Appendix C — The Components::Chart Base Class
All base class props explained (height:, group:, color:, select_url:,
stream_name:). The view_template structure. How to write a chart component.
The Ruby DSL reference — Chart::Options and Chart::Series::* internals.
Appendix D — Chart Template Library
Copy-paste starting points for every common chart type. Each template is a complete, working component with sensible defaults and comments marking what to customise. Available templates: Line, Stacked Bar, Grouped Bar, Horizontal Bar, Pie, Donut, Scatter, Calendar, Gauge, Mixed (Bar + Line, dual axis).
Architectural Decisions
| Question | Decision |
|---|---|
| Where does the ECharts instance live? | Owned by chart_controller.js |
| How does Ruby pass config to JavaScript? | JSON via data-chart-options-value |
| Where does data transformation live? | Stats::* service modules — SQL first |
| How are formatter functions handled? | Named registry — Ruby string, JS function |
| How are colours kept consistent? | Named palette registry — same name, same order |
| How are real-time updates delivered? | ActionCable → attribute change → setOption |
| How are charts linked? | group: prop → echarts.connect(group) |
| How does click-to-select work? | select_url: prop → Turbo Morph |
| How is chart config tested? | Plain Ruby — no browser, no Rails required |
| What handles stack/line/bar toggles? | ECharts native magicType toolbox |
| Can any chart be real-time? | Yes — add stream_name: prop |
| Can any chart be linked? | Yes — add group: prop |
| Can any chart be clickable? | Yes — add select_url: prop |
Key Files
app/
javascript/
controllers/
chart_controller.js ← install once, never touch
charts/
chart_formatters.js ← base formatter registry
chart_palettes.js ← base palette registry
custom_chart_formatters.js ← your additions
custom_chart_palettes.js ← your additions
channels/
consumer.js ← ActionCable consumer
lib/
chart/
options.rb
series/
base.rb / line.rb / bar.rb / scatter.rb / pie.rb
services/
stats/ ← data transformation services
views/
components/
chart.rb ← base component
charts/ ← chart components
templates/ ← copy-paste starting points
lib/
generators/
echarts/
install_generator.rb ← for use in your own projects
templates/ ← files the generator copiesNext: Module 01 — Foundation: Getting Started