A chart is a code block
A chart in Ubimate is an ordinary code block whose language is
chartjs. Its text is the chart's configuration. A few
languages are ones the app can draw as well as show, and this is one of
them: instead of the text, the block can show the chart the text
describes. Mermaid, on Diagrams and
charts, is another.
There are three ways to make one:
- Type
/Chart.jsand pick it from the slash menu. - At the start of an empty line, type
```chartjsand press Space or Enter, as for any other code block. - Turn a code block you already have into one: hover over it, open Select language on its toolbar and pick Chart.js.
A new block shows its text, empty. Write the configuration in it, or paste one of the examples below. Exported as Markdown, or copied into another app, the block is a fenced code block like any other, which is why it shows there as text:
```chartjs
const config = {
type: 'bar',
…
};
```
To switch between the text and the chart, hover over the block and use the button at its top right. The eye, Preview chart, draws the chart; the pencil, Edit JSON, goes back to the text. The block keeps whichever it was left showing, so a page opens with its charts drawn.
How a block reads a chart
A chart block holds one configuration: the kind of chart, its data and
its options. It can be written as JSON, with every name in double quotes, or as
JavaScript, the way the examples on this page are. In JavaScript the
configuration needs a name, and config is the one the
block looks for first:
const config = {
type: 'bar',
data: {
labels: ['Q1', 'Q2', 'Q3', 'Q4'],
datasets: [{
label: 'Sales',
data: [12, 19, 8, 15],
backgroundColor: 'rgb(54, 162, 235)',
}],
},
};
Other names can stand for values you use more than once, as long as each is written above the place it is used:
const blue = 'rgb(54, 162, 235)';
const quarters = ['Q1', 'Q2', 'Q3', 'Q4'];
const config = {
type: 'line',
data: {
labels: quarters,
datasets: [{ label: 'Sales', data: [12, 19, 8, 15], borderColor: blue }],
},
};
The configuration is read, never run. Write every value out in full: numbers, text, lists and objects. Arithmetic, functions and anything else that would need running are left out of the chart, along with the setting they belong to. What a block cannot do has the details.
A dataset with no colors of its own gets one from Chart.js's palette,
a different one for each dataset. To choose them, set
backgroundColor and borderColor. The examples
here use the colors of the Chart.js documentation, as
rgb() for lines and borders and as a half-transparent
rgba() for fills.
A chart is the full width of the text and 360 pixels tall, whatever its options say about size. Its text and grid lines follow the app's light or dark theme.
Bar
One bar per label, and one color per dataset. With two datasets, the bars stand side by side:
const config = {
type: 'bar',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
datasets: [
{
label: '2025',
data: [65, 59, 80, 81, 56, 55],
backgroundColor: 'rgba(255, 99, 132, 0.5)',
borderColor: 'rgb(255, 99, 132)',
borderWidth: 1,
},
{
label: '2026',
data: [28, 48, 40, 19, 86, 27],
backgroundColor: 'rgba(54, 162, 235, 0.5)',
borderColor: 'rgb(54, 162, 235)',
borderWidth: 1,
},
],
},
};
For bars that run sideways, add indexAxis: 'y' to the
options. To stack the datasets on each other instead of side by side,
stack both axes:
options: {
indexAxis: 'y',
scales: {
x: { stacked: true },
y: { stacked: true },
},
},
options goes beside type and
data, inside config. Rounded bars take
borderRadius: 6 on the dataset, and a value below zero draws
a bar down from the axis.
Line and area
The same shape as a bar chart, with type: 'line'.
tension curves the line, from 0 for straight segments to
about 0.4:
const config = {
type: 'line',
data: {
labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
datasets: [
{
label: 'This week',
data: [12, 19, 14, 21, 17, 9, 11],
borderColor: 'rgb(75, 192, 192)',
backgroundColor: 'rgba(75, 192, 192, 0.5)',
tension: 0.3,
},
{
label: 'Last week',
data: [10, 15, 16, 18, 20, 12, 8],
borderColor: 'rgb(201, 203, 207)',
backgroundColor: 'rgba(201, 203, 207, 0.5)',
borderDash: [6, 4],
tension: 0.3,
},
],
},
};
A few more settings on a line's dataset:
fill: truecolors the area under the line with itsbackgroundColor, which makes it an area chart.fill: '-1'fills down to the dataset before it instead, and stacking the y axis stacks the areas.borderDash: [6, 4]dashes the line: 6 pixels drawn, 4 skipped.stepped: truedraws steps instead of slopes.pointRadius: 0hides the dots;pointStyle: 'rect','triangle','star'and others change their shape.nullin the data leaves a gap in the line, andspanGaps: truejoins across it.
Pie and doughnut
One dataset, and a color for each slice, in the order of the labels:
const config = {
type: 'doughnut',
data: {
labels: ['Design', 'Build', 'Test', 'Launch'],
datasets: [{
label: 'Hours',
data: [30, 50, 15, 5],
backgroundColor: [
'rgb(255, 99, 132)',
'rgb(54, 162, 235)',
'rgb(255, 205, 86)',
'rgb(75, 192, 192)',
],
}],
},
};
type: 'pie' is the same chart with no hole.
cutout: '70%' in the options sets the size of the hole, and
rotation: -90 with circumference: 180 draws a
half doughnut, a gauge. Clicking a label in the legend hides its slice.
Polar area
Like a pie, but every slice has the same angle and the value sets how far it reaches. Half-transparent colors keep the rings behind visible:
const config = {
type: 'polarArea',
data: {
labels: ['Email', 'Chat', 'Phone', 'Forum', 'Other'],
datasets: [{
label: 'Requests',
data: [11, 16, 7, 3, 14],
backgroundColor: [
'rgba(255, 99, 132, 0.5)',
'rgba(75, 192, 192, 0.5)',
'rgba(255, 205, 86, 0.5)',
'rgba(201, 203, 207, 0.5)',
'rgba(54, 162, 235, 0.5)',
],
}],
},
};
Radar
One spoke per label, for comparing a few things on the same measures.
The scale of a radar chart is called r:
const config = {
type: 'radar',
data: {
labels: ['Speed', 'Price', 'Support', 'Features', 'Design'],
datasets: [
{
label: 'Option A',
data: [8, 5, 7, 9, 6],
borderColor: 'rgb(255, 99, 132)',
backgroundColor: 'rgba(255, 99, 132, 0.2)',
fill: true,
},
{
label: 'Option B',
data: [6, 9, 6, 5, 8],
borderColor: 'rgb(54, 162, 235)',
backgroundColor: 'rgba(54, 162, 235, 0.2)',
fill: true,
},
],
},
options: {
scales: {
r: { min: 0, max: 10 },
},
},
};
Scatter and bubble
For values on two numeric axes. There are no labels: every point is an
object with its x and its y.
const config = {
type: 'scatter',
data: {
datasets: [{
label: 'Orders',
data: [
{ x: 12, y: 340 },
{ x: 18, y: 410 },
{ x: 25, y: 520 },
{ x: 31, y: 480 },
{ x: 40, y: 690 },
],
backgroundColor: 'rgb(153, 102, 255)',
}],
},
options: {
scales: {
x: { title: { display: true, text: 'Temperature (°C)' } },
y: { title: { display: true, text: 'Ice creams sold' } },
},
},
};
A bubble chart is a scatter chart with a third value,
r, the radius of each bubble in pixels:
const config = {
type: 'bubble',
data: {
datasets: [{
label: 'Projects',
data: [
{ x: 20, y: 30, r: 15 },
{ x: 40, y: 10, r: 10 },
{ x: 30, y: 22, r: 25 },
],
backgroundColor: 'rgba(255, 159, 64, 0.5)',
borderColor: 'rgb(255, 159, 64)',
}],
},
};
Mixed charts
A dataset can be a different kind of chart from the rest by giving it a
type of its own: bars with a line over them, say. Here the
line also has an axis of its own on the right, named by
yAxisID:
const config = {
type: 'bar',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
datasets: [
{
label: 'Revenue',
data: [12000, 15000, 11000, 18000, 21000, 19000],
backgroundColor: 'rgba(54, 162, 235, 0.5)',
},
{
type: 'line',
label: 'Customers',
data: [80, 95, 70, 120, 140, 130],
borderColor: 'rgb(255, 99, 132)',
backgroundColor: 'rgb(255, 99, 132)',
yAxisID: 'y1',
},
],
},
options: {
scales: {
y: { beginAtZero: true },
y1: {
position: 'right',
beginAtZero: true,
grid: { drawOnChartArea: false },
},
},
},
};
Bar and line mix this way, as do bar or line with scatter and bubble.
Titles, legends and axes
Most of a chart's look is in options. The settings people
reach for first, together:
options: {
plugins: {
title: { display: true, text: 'Sales by quarter' },
subtitle: { display: true, text: 'In thousands of euros' },
legend: { position: 'bottom' },
tooltip: { mode: 'index', intersect: false },
},
scales: {
x: { title: { display: true, text: 'Quarter' } },
y: {
beginAtZero: true,
max: 100,
ticks: { stepSize: 20 },
title: { display: true, text: 'Sales' },
},
},
},
legend: { display: false }hides the legend; itspositioncan be'top','bottom','left'or'right'.tooltipwithmode: 'index'shows every dataset's value for the label under the pointer, not only the one touched.beginAtZero,minandmaxfix an axis's range;suggestedMinandsuggestedMaxwiden it without cutting data off.type: 'logarithmic'on an axis spaces it by powers of ten.grid: { display: false }on an axis hides its grid lines.animation: falsedraws the chart at once, without its entrance.
From the Chart.js samples
The samples in the Chart.js documentation do not paste into a block as they are. Each is split into tabs, and its data is made by code that only exists on that website. To bring one across:
- Copy the Setup tab first, then the Config tab
under it. Setup makes
data, which Config uses, so it has to come first. - Leave out the Actions tab. It is the code behind the sample's buttons.
- Replace every
Utilscall with the values it stands for, as in the table below. Numbers fromUtils.numbersare random: write your own. - Leave out functions:
callback, anything written(ctx) => …, and settings computed from others.
| In the sample | In the block |
|---|---|
Utils.months({ count: 7 }) |
['January', 'February', … 'July'] |
Utils.numbers(NUMBER_CFG) |
[12, -30, 45, 8, 70, -12, 33], one per label |
Utils.CHART_COLORS.red |
'rgb(255, 99, 132)' |
Utils.transparentize(Utils.CHART_COLORS.red, 0.5) |
'rgba(255, 99, 132, 0.5)' |
The rest of Utils.CHART_COLORS:
orange |
'rgb(255, 159, 64)' |
yellow |
'rgb(255, 205, 86)' |
green |
'rgb(75, 192, 192)' |
blue |
'rgb(54, 162, 235)' |
purple |
'rgb(153, 102, 255)' |
grey |
'rgb(201, 203, 207)' |
The documentation's reference pages are still the place for every option a chart has; the options are the same in a block.
What a block cannot do
Nothing in a chart block is run, so a chart that arrives with a shared page cannot do anything but draw. That rules out:
- Functions. A
callbackthat formats a tick or a tooltip, and options that change point by point, are left out. Put units in an axis title instead:'Revenue (€)'. - Computed values.
10 * 2,.map(),new Date()and a template string with a value inserted into it are left out, and so is what holds them: one computed number drops its whole list of data. - Names used before they are written. A setting that uses a name the block has not read yet is left out.
- Time axes.
type: 'time'and'timeseries'need a date library the app does not include. Write dates as labels instead, such as'3 Mar'. - Gradients and patterns, which are drawn with code.
- Plugins outside Chart.js itself, such as data labels, zoom or annotations. Their options are ignored.
What was left out is left out silently: the chart draws without it. When a setting seems to have no effect, check it against this list. A mistake that stops the chart altogether, such as a missing bracket, shows its error in red where the chart would be.