Charts

chart_panel renders a real Chart.js chart inside a dashboard row, backed by a vendored copy of Chart.js — no CDN, no host gem dependency, works air-gapped and under a strict CSP. If JavaScript never runs (disabled, the asset 404s, CSP blocks it), the panel degrades to the same CSS bar chart AdminSuite has always rendered — nothing is hidden until the real chart has actually been built.

The DSL

Inside portal.dashboard do ... row do ... end end (see Portals & dashboards):

row do
  chart_panel "Signups this week",
    data: -> { Signup.group_by_day(:created_at).count.map { |day, n| { label: day.strftime("%a"), value: n } } },
    type: :bar,
    height: 220,
    color: :indigo,
    span: 6
end

chart_panel(title, data: nil, span: nil, **options, &block):

  • title — panel heading (String)
  • data: — a Proc (or block) returning an Array of rows; see Data contract below
  • type: — :bar (default), :line, :area, or :doughnut — see Chart types
  • height: — pixel height of the chart body; default 192. Applied as an inline style, identically, to both the degraded bars and the live canvas, so there’s no layout shift when Chart.js takes over.
  • color: — a theme color token (:indigo, :amber, :green, :red, :cyan, :violet; defaults to the dashboard’s theme primary). Ignored for :doughnut, which uses a fixed multi-color palette instead.
  • total: — optional value shown as a large number in the panel header (e.g. a running total alongside the chart)
  • span: — grid width, 1-12 (same as every other panel type)

Data contract

data: must resolve (directly, or via the Proc/block) to an Array of rows. Each row should be a Hash with :label and :value keys — but the partial is deliberately forgiving about anything less than that:

  • String-keyed rows are tolerated. {"label" => "Mon", "value" => 3} works exactly like {label: "Mon", value: 3} — rows are symbolized before use, the same normalization AdminSuite::Renderer#data_table applies to JSONB-sourced rows. (Before 0.5.0 this silently rendered a blank/zero-height bar instead of the real value.)
  • Non-Hash rows are dropped, not raised on. If data: returns [{ label: "ok", value: 3 }, 5, nil], the 5 and nil entries are filtered out before rendering; the good row still renders normally.
  • Non-numeric values degrade to zero, not a crash. A value that’s a Boolean, Hash, Array, nil, or a non-numeric String is coerced via Float() with a rescue to 0.0 — so a junk value renders as a zero-height bar (or an empty doughnut slice) instead of 500ing the whole dashboard. The display value (used in the bar’s tooltip and the doughnut list) is kept as originally provided, so a numeric String like "3" still shows "3" in the tooltip, not "3.0".
  • A raising data: proc degrades to the empty state, not a 500. If the proc itself raises, the error is logged via Rails.logger.warn (with a short backtrace) and the panel renders “No chart data.” — the rest of the dashboard is unaffected.

Chart types

  • :bar (default) — vertical bars
  • :line — a line chart
  • :area — a line chart with fill: true (Chart.js has no separate “area” type; this is what :area maps to)
  • :doughnut — a doughnut chart, using a fixed 8-color palette

An unrecognized type: (a typo, or any value outside these four) falls back to :bar and logs a warning naming the panel and the bad value — it never raises, regardless of what’s passed (a Symbol, String, Integer, Boolean, whatever).

Degraded (no-JS) rendering per type

  • :bar / :line / :area — the same percentage-height CSS bar chart, with labels underneath.
  • :doughnut — a doughnut has no meaningful “bar” degraded form, so it renders a plain label/value list instead (one row per data point).

The no-JS degraded state

The CSS bar chart (or, for :doughnut, the label/value list) is rendered directly into the page by the server on every request, regardless of JavaScript. The Stimulus controller (admin-suite--chart) only touches the DOM — building a <canvas>, constructing the Chart instance, and hiding the bars/list — after window.Chart is confirmed loaded and a real chart has been built. If the vendored asset is blocked, 404s, or JS is off entirely, the bars simply stay visible; there is no flash of missing content and no blank box.

Chart.js assets (vendor/chart.umd.min.js) load only on pages that actually render at least one chart with data — a single content_for(:chart_assets) hook, deduplicated across multiple chart panels on the same page.

height: and the 192px default

Before 0.5.0, the chart body was a fixed 64px strip — fine for a sparkline row of CSS bars, but not enough room for a real chart with axes and a legend. As of 0.5.0 the default is 192px, and it’s configurable per panel via height:. This is a host-visible change: an existing dashboard using chart_panel with no height: option will render its charts taller than before. Both the degraded bars and the live canvas read the exact same height value, so there’s no layout shift between the two.

Host partial override

Like every other panel type, chart_panel renders through app/views/admin_suite/panels/_chart.html.erb, resolved via AdminSuite.config.partials[:panel_chart]:

AdminSuite.configure do |config|
  config.partials[:panel_chart] = "admin/panels/custom_chart"
end

If you override the partial entirely, note that the shipped Stimulus controller (admin-suite--chart) expects data-admin-suite--chart-series-value (a JSON array of {label, value}), -type-value, -color-value, and -height-value data attributes on its mount element — reuse that contract (or write your own controller) rather than relying on the built-in one matching a differently-shaped override.