Search by

io / charts

Lento Leisenfeld

Server-rendered SVG charts for Io: bar and line charts with a data table, legend and accessible markup, tooltips as a progressive enhancement.

v0.1.0 2026-10-24 16:23 UTC

This package is auto-updated.

Last update: 2026-10-25 09:43:27 UTC


README

Bar and line charts for Io, rendered as SVG on the server. They work without JavaScript, print, go into PDFs and e-mails, and ship their data as a real table. io.js adds tooltips, a crosshair and keyboard navigation on top.

Setup

$app->plugin(new Io\Charts\ChartsPlugin());   // before attach()

The layout renders the stylesheet and script with $this->pluginAssetTags().

Usage

The sales page of Sonnenkorn, the bakery in the Io example app, draws two charts from its sales report (from src/Controllers/BackofficeController.php):

use Io\Charts\Chart;

$revenue = Chart::fromRows($this->sales->revenueByDay(28, $locale), x: 'day', y: 'revenue')
    ->title('Revenue per day, last four weeks')
    ->unit('€');

$products = Chart::fromRows($this->sales->topProducts(28, $locale), x: 'product', y: 'quantity')
    ->title('Sold in the last four weeks')
    ->size(640, 280);
  • In a view: {{ $this->chart($model->revenue) }} renders a <figure> with caption, the SVG, a legend (two or more series) and the data table in a <details>.
  • From a route: return $chart; answers with image/svg+xml. Use it as <img src="/backoffice/sales/revenue.svg"> or inside a PDF; the SVG carries its own dark-mode styles.
  • From a query: Chart::fromRows($rows, x: 'day', y: 'quantity', series: 'product') pivots rows (arrays or objects) into labels and series (duplicates add up).
  • By hand: Chart::bar()->labels(['Mon', 'Tue'])->series('Croissants', [84, 76])->series('Pretzels', [60, 58]).

Builder options: line() or bar(), stacked() (bars), markers(false) (lines), unit('€', decimals: 2), description('...') for the screen-reader sentence (generated from the data when omitted), size(width, height), withTable(false). At most eight series: fold the rest into "Other" or draw a second chart; never two value axes on one chart.

What you get by design

  • Thin marks: bars at most 24px wide with a rounded data end and a square baseline, 2px lines, 8px markers with a surface ring, 2px surface gaps in stacks. Gridlines are hairlines.
  • Colors are assigned to series in a fixed, validated colorblind-safe order and follow the series, never its rank. Text never wears a series color.
  • A legend for two or more series; the maximum of a single-series bar chart and the end value of each line are labelled directly. Everything else is in the tooltip and the table.
  • role="img" with <title> and a generated <desc>; every x position is a focusable hit area with the values in its aria-label; arrow keys walk the positions once the script is loaded.
  • Light and dark mode from the same eight hues, stepped for each surface.

Options

Setting Default Description
width, height 640, 320 Drawing size; the SVG scales with its container.
maxBarWidth 24 Bars never get thicker than this.
format null Closure(float $value, int $decimals): string; default number_format with . and ,.
table true Render the data table under every chart.
tableLabel 'Show data as table' Text of the <summary>.
palette, paletteDark validated defaults Eight categorical colors per surface, in slot order. Validate a replacement before shipping it.

Development

composer install && composer test && composer stan