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.


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:

1
2
3
4
5
6
7
git clone https://github.com/your-org/phlex-echarts-tutorial
cd phlex-echarts-tutorial
bundle install
ruby db/seeds/fetch_abs_data.rb
ruby db/seeds/generate_daily_activity.rb
rails db:migrate db:seed
rails server

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 browser

Dataset: 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 copies

Next: Module 01 — Foundation: Getting Started