Appendix C — The Components::Chart Base Class
Every chart component in this series inherits from Components::Chart. This
appendix explains what the base class provides, how its props work, and how to
extend it.
C.1 — The Base Class
|
|
Props
| Prop | Type | Default | Purpose |
|---|---|---|---|
height |
String | "400px" |
Chart container height |
group |
String | "" |
ECharts group for linked charts — see Module 10 |
color |
String | "" |
Palette override — replaces component’s own palette |
select_url |
String | "" |
URL to visit when a data point is clicked — see Module 10 |
All props default to safe empty values. A chart rendered with no extra props is
standalone, 400px tall, using whatever palette chart_options specifies, with
no click behaviour and no group linking.
view_template
The base view_template renders a two-div structure:
|
|
The outer div provides the card-like appearance. The inner div is the ECharts
mount target — chart_controller.js initialises the ECharts instance on this
element.
**@html passes any additional HTML attributes through to the outer div — the
standard Phlex pattern for attribute forwarding.
The color override
|
|
chart_options sets the palette the component normally uses. When color: is
passed by the caller, it replaces that palette. This allows any component to be
used with a different palette in a dashboard context without changing the
component itself:
|
|
The component’s chart_options method is unchanged — the override happens in
the base class after chart_options returns.
C.2 — Writing a Chart Component
Every chart component follows the same structure:
|
|
Rules:
- Always call
superif overridingview_template— or don’t override it at all - Keep
chart_optionsreadable — extract helpers for series construction - Use
::prefix for model constants —::GdpReadingnotGdpReading - Never call services from
chart_options— the controller passes data in
C.3 — The stream_name Extension
Real-time chart components add one prop and one data attribute:
|
|
Real-time components override view_template to add chart_stream_name_value.
The base class view_template does not include it — static charts don’t need it.
An alternative approach is to add stream_name: to the base class alongside
group: and color:. The tradeoff: simpler for real-time components but adds
an unused data attribute to every static chart. For this series, real-time
components override view_template explicitly — making the intent clear.
C.4 — The Ruby DSL
Chart::Options
|
|
Chart::Options converts snake_case Ruby keys to camelCase ECharts keys:
x_axis → xAxis, y_axis → yAxis. All other keys pass through.
to_h returns the full options hash. to_json serialises it — used in
view_template for the data attribute.
Chart::Series::*
|
|
All series classes accept **extra — any ECharts series option not explicitly
modelled passes through unchanged:
|
|
C.5 — Component Props Reference
Base class props (inherited by all components)
| Prop | Type | Default | Pass as |
|---|---|---|---|
height |
String | "400px" |
height: "600px" |
group |
String | "" |
group: "my_dashboard" |
color |
String | "" |
color: "tableau" |
Usage
<%# Minimal — all defaults %>
<%= render Components::Charts::GdpByIndustry.new(data: @data) %>
<%# Custom height %>
<%= render Components::Charts::GdpByIndustry.new(data: @data, height: "600px") %>
<%# Dashboard — linked and palette-overridden %>
<%= render Components::Charts::GdpByIndustry.new(
data: @data,
group: "gdp_dashboard",
color: "tableau",
height: "320px"
) %>
<%# Real-time %>
<%= render Components::Charts::LiveStocks.new(
prices: @prices,
stream_name: "live_stocks",
height: "400px"
) %>C.6 — The Ruby DSL
The DSL lives in app/lib/chart/ and has one job: produce a Ruby Hash that
serialises to valid ECharts options JSON. It is not a complete wrapper around
ECharts — it wraps the most common options and passes everything else through
unchanged.
Chart::Options
|
|
Key design decisions:
Snake_case to camelCase — x_axis: becomes xAxis, y_axis: becomes
yAxis in the output hash. This is handled by the explicit key mapping in
to_h. Only the options explicitly modelled get this treatment — everything
in **extra passes through with whatever key name you provide.
**extra escape hatch — any ECharts option not modelled by the DSL passes
through unchanged via **extra. This is how visualMap:, calendar:,
toolbox:, graphic:, markLine: etc. are passed — they are not modelled
but work correctly:
|
|
resolve — called on every value before it enters the output hash. Handles
three cases:
Array— maps over elements, resolving each one. Handlesy_axis: [...]for dual axes,calendar: [...]for multi-year calendar,series: [...].Hash— passes through unchanged. Plain hashes are already in the right format.- Everything else — calls
to_hif available (DSL series objects), otherwise passes through as-is (strings, numbers, booleans).
animation: false default — ECharts animates everything by default. For
real-time charts this creates visual noise. The DSL defaults to false — you
can override explicitly:
|
|
Chart::Series::*
Each series type is a thin wrapper that produces a hash with type: set
correctly and common options named explicitly. Uncommon options pass through
via **extra.
|
|
|
|
All series options are passed through **opts and resolved. This means any
ECharts series option works without being explicitly modelled:
|
|
The to_h / to_json Contract
Every DSL object implements to_h and to_json. The base component calls
chart_options.to_h then serialises with .to_json:
|
|
chart_options returns a Chart::Options instance. to_h resolves all nested
DSL objects. to_json produces the JSON string that becomes the Stimulus value.
chart_controller.js receives it as a parsed JavaScript object via the Stimulus
value system.
Adding a New Series Type
To add a Chart::Series::Gauge for use in components:
|
|
Then use it anywhere:
|
|
All gauge-specific options (min:, max:, axisLine:, pointer:, detail:)
pass through **opts unchanged.
Why Not Wrap Everything?
The DSL deliberately wraps only a small subset of ECharts options. ECharts has hundreds of configuration keys — wrapping them all would produce a large, rigid abstraction that trails behind ECharts releases.
The **extra escape hatch means you always have access to the full ECharts API.
The DSL adds value for the most common options (series types, axis configuration)
and gets out of the way for everything else.
If you find yourself repeatedly writing the same **extra hash, that’s a signal
to add it to the DSL. Until then, use the escape hatch.
C.7 — Extending the Base Class
To add a new inherited capability — for example, a theme: prop for ECharts
built-in themes:
1. Add the prop to the base class:
|
|
2. Pass it to the mount div:
|
|
3. Add the Stimulus value to chart_controller.js:
|
|
4. Use it in #initChart:
|
|
Every chart in the application gains the theme: prop automatically — no changes
to individual components. The pattern is identical for any new capability: one prop
on the base class, one data attribute, one Stimulus value.