soli-pdf

Charts

A chart element draws a bar, line, pie or donut chart from your data, as vector graphics in the PDF. It takes width × height points at the cursor, plus an optional title above it, and moves the cursor below itself.

{ "type": "chart", "kind": "bar", "title": "Revenue by month",
  "data": "months", "label": "name", "value": "revenue",
  "width": 481, "height": 180, "axis": true, "gridlines": true }
data = {
  'months' => [
    { 'name' => 'Jan', 'revenue' => 42_000 },
    { 'name' => 'Feb', 'revenue' => 38_500 },
    { 'name' => 'Mar', 'revenue' => 51_200 }
  ]
}
Soli::PDF.render(template: template, data: data)

See them in the annual report (bar, line and pie) and the subscription invoice (donut).

#Kinds

kindDrawsUseful options
barVertical bars, one per point, or grouped and stacked with several seriesaxis, gridlines, mode
lineA line through the points, one line per seriesaxis, gridlines
pieSlices proportional to the valueslegend
donutA pie with a ring cut out: whatever is behind the chart shows through the holelegend

#Options

OptionTypeMeaning
kindStringbar, line, pie or donut.
width, heightNumber (pt)The size of the plot area.
titleStringA caption drawn above the chart.
dataStringThe name of an array in the data document.
labelStringThe field of each item that holds the category label.
valueStringThe field of each item that holds the number (single series).
valuesArraySeveral series: [{ "field", "name"?, "color"? }]. Replaces value.
pointsArrayInline points [{ "label", "value" }] instead of data.
colorsArray of hexColours cycled across points (or series). A built-in palette is used without it.
legendBoolPie and donut: a swatch list with each slice's percentage.
axisBoolBar and line: axis lines and category labels.
gridlinesBoolBar and line: horizontal gridlines with value labels.
modeStringBar with several series: "stacked". The default is grouped.

Warning Values must be JSON numbers: 42000 or 42000.5, not "42,000" or "€42k". Format the labels as you like, but send the figures raw. Everywhere else in a template you send pre-formatted strings, so this is easy to miss.

#Data-bound or inline

Bind to an array in the data (data + label + value), or write the points into the template when they never change:

{ "type": "chart", "kind": "pie", "width": 300, "height": 150, "legend": true,
  "points": [
    { "label": "Rent", "value": 1200 },
    { "label": "Payroll", "value": 3400 },
    { "label": "Cloud", "value": 800 }
  ] }

#Several series

Give values instead of value. Each series reads its own field from every item, and a legend shows the series names:

{ "type": "chart", "kind": "bar", "data": "quarters", "label": "q",
  "gridlines": true, "axis": true, "width": 481, "height": 170,
  "values": [
    { "field": "fy24", "name": "FY 2024", "color": "94a3b8" },
    { "field": "fy25", "name": "FY 2025", "color": "0f766e" }
  ] }
'quarters' => [
  { 'q' => 'Q1', 'fy24' => 120, 'fy25' => 150 },
  { 'q' => 'Q2', 'fy24' => 135, 'fy25' => 162 },
  { 'q' => 'Q3', 'fy24' => 128, 'fy25' => 171 },
  { 'q' => 'Q4', 'fy24' => 160, 'fy25' => 198 }
]

Bars are grouped side by side. Add "mode": "stacked" to stack them. With kind: "line", each series is its own line.

#A donut with a legend

{ "type": "chart", "kind": "donut",
  "data": "spend", "label": "name", "value": "amount",
  "width": 210, "height": 106, "legend": true,
  "colors": ["312E81", "5B54C4", "8B84E8", "C3BEF6"] }

#Placing charts

A chart flows like a paragraph: it starts at the cursor and pushes what follows down. To put two charts side by side, use columns or place each with an at element. Charts also flow inside columns, and a chart inside a repeat still reads its data from the current item first. See Data binding.