import { CheckBox, ComboBox, LineEdit, Palette, ScrollView, Slider, SpinBox } from "std-widgets.slint";
import {
BarChart,
BubbleChart,
BulletChart,
CalendarHeatmap,
ChartGanttTask,
ChartGaugeBand,
ChartSunburstItem,
ChartTreemapItem,
ChartWaterfallStep,
DumbbellChart,
FunnelChart,
GanttChart,
GaugeChart,
SunburstChart,
TreemapChart,
WaterfallChart,
CandlestickChart,
ChartCandle,
ChartDataset,
ChartDateTime,
ChartEngine,
ChartLanguage,
ChartLegendPosition,
ChartMatrixColorMode,
ChartPalette,
ChartSlice,
ChartTexts,
ChartTheme,
DoughnutChart,
HeatmapChart,
HorizontalBarChart,
MatrixChart,
LineChart,
PieChart,
PolarAreaChart,
RadarChart,
RadialBarChart,
ScatterChart,
TimeSeriesChart,
} from "@slintplot";
import { DragArea, ResizeHandles, WindowFrame, WindowsControls } from "window_frame.slint";
export { ChartEngine, ChartLanguage, ChartTheme, WindowFrame }
// The gallery's own colors, apart from the charts': a light gray page with
// white cards, as in macOS and iOS settings, and their dark mode variants.
global Look {
in-out property <bool> dark;
out property <color> page: root.dark ? #161618 : #f5f5f7;
out property <color> surface: root.dark ? #232326 : #ffffff;
out property <color> sidebar: root.dark ? #1c1c1f : #ebebee;
out property <color> border: root.dark ? #ffffff17 : #0000001a;
out property <color> text: root.dark ? #f5f5f7 : #1d1d1f;
out property <color> secondary: root.dark ? #a1a1a6 : #6e6e73;
out property <color> accent: root.dark ? #0a84ff : #007aff;
out property <color> hover: root.dark ? #ffffff12 : #0000000b;
}
// A tab of the code panel: a file of the project that runs the sample.
export struct CodeFile {
name: string,
code: string,
}
// A row of the sample list: a category header, or a sample.
export struct SampleEntry {
id: string,
title: string,
header: bool,
}
// The list of samples, with a header row per category.
component SampleList inherits Rectangle {
in property <[SampleEntry]> samples;
in property <string> sample;
// Taller rows, for touch screens.
in property <bool> roomy;
// Room at the top, for the window buttons on macOS.
in property <length> top-inset;
callback select(id: string);
// The logo links to the website.
callback open-url(url: string);
background: Look.sidebar;
if root.top-inset > 0: DragArea {
y: 0;
height: root.top-inset;
}
// The logo above the list, with the app's name.
property <length> logo-height: 34px;
HorizontalLayout {
x: 20px;
y: root.top-inset + 12px;
height: root.logo-height;
spacing: 8px;
Image {
// The logo is 720 by 218 pixels.
width: root.logo-height * 720 / 218;
source: Look.dark ? @image-url("../logo-dark.png") : @image-url("../logo.png");
image-fit: contain;
}
Text {
text: "Gallery";
color: Look.secondary;
font-size: 13px;
font-weight: 500;
vertical-alignment: center;
}
}
// The logo links to the website.
TouchArea {
x: 20px;
y: root.top-inset + 12px;
width: root.logo-height * 720 / 218;
height: root.logo-height;
mouse-cursor: pointer;
accessible-role: button;
accessible-label: "SlintPlot website";
accessible-action-default => {
root.open-url("https://slintplot.dev/");
}
clicked => {
root.open-url("https://slintplot.dev/");
}
}
// Kept clear of the top and bottom edges, so that scrolled rows are cut
// off before they reach the window's edges.
ScrollView {
y: root.top-inset + 12px + root.logo-height + 8px;
height: parent.height - self.y - 10px;
VerticalLayout {
padding-bottom: 8px;
alignment: start;
for entry in root.samples: Rectangle {
height: (entry.header ? 32px : 30px) + (root.roomy && !entry.header ? 10px : 0px);
// The selected sample in the accent color; the hovered one shaded.
if !entry.header: Rectangle {
x: 10px;
width: parent.width - 20px;
border-radius: 7px;
background: entry.id == root.sample ? Look.accent.with-alpha(Look.dark ? 0.25 : 0.14)
: touch.has-hover ? Look.hover : transparent;
}
Text {
x: 20px;
y: entry.header ? parent.height - self.height - 5px : (parent.height - self.height) / 2;
text: entry.title;
color: entry.header ? Look.secondary : entry.id == root.sample ? Look.accent : Look.text;
font-size: entry.header ? 12px : 13px + (root.roomy ? 2px : 0px);
font-weight: entry.header ? 600 : entry.id == root.sample ? 600 : 400;
}
touch := TouchArea {
enabled: !entry.header;
mouse-cursor: entry.header ? default : pointer;
clicked => {
root.select(entry.id);
}
}
}
}
}
// A hairline between the list and the content.
Rectangle {
x: parent.width - 1px;
width: 1px;
background: Look.border;
}
}
// A theme's colors as a row of small squares.
component Swatches inherits HorizontalLayout {
in property <[color]> colors;
horizontal-stretch: 0;
spacing: 2px;
for c in root.colors: VerticalLayout {
alignment: center;
Rectangle {
width: 10px;
height: 10px;
border-radius: 2px;
background: c;
}
}
}
// A built-in palette, for the theme picker.
struct ThemeOption {
name: string,
light: [color],
dark: [color],
}
// Picks the chart theme; each option in the list shows the theme's eight
// colors. The
// colors are those of `ChartTheme.colors` (crates/slintplot/ui/theme.slint).
component ThemePicker inherits Rectangle {
in-out property <int> current;
private property <[ThemeOption]> options: [
{ name: "Modern", light: [#3b82f6, #10b981, #f97316, #6366f1, #84cc16, #06b6d4, #8b5cf6, #f43f5e], dark: [#60a5fa, #34d399, #fb923c, #818cf8, #a3e635, #22d3ee, #a78bfa, #fb7185] },
{ name: "Material", light: [#2196f3, #e91e63, #ff9800, #9c27b0, #4caf50, #3f51b5, #ff5722, #00bcd4], dark: [#42a5f5, #ec407a, #ffa726, #ab47bc, #66bb6a, #7986cb, #ff7043, #26c6da] },
{ name: "Corporate", light: [#4e79a7, #f28e2b, #76b7b2, #e15759, #ff9da7, #59a14f, #2f4b7c, #b07aa1], dark: [#5f8ac0, #f5993d, #7fc4be, #e8696b, #ffadb5, #6cb262, #4a68a0, #bf8ab1] },
{ name: "Mono", light: [#1e3a8a, #60a5fa, #1d4ed8, #93c5fd, #2563eb, #bfdbfe, #3b82f6, #172554], dark: [#93c5fd, #2563eb, #bfdbfe, #3b82f6, #dbeafe, #1d4ed8, #60a5fa, #1e40af] },
{ name: "Pastel", light: [#a1c9f4, #8de5a1, #f5a8d8, #bae6fd, #ff9f9b, #99e2dc, #ffb482, #d0bbff], dark: [#a1c9f4, #8de5a1, #f5a8d8, #bae6fd, #ff9f9b, #99e2dc, #ffb482, #d0bbff] },
];
height: 32px;
border-radius: 7px;
border-width: 1px;
border-color: Look.border;
background: touch.has-hover ? Look.hover : Look.surface;
accessible-role: combobox;
accessible-label: "Theme";
accessible-value: root.options[root.current].name;
HorizontalLayout {
padding-left: 10px;
padding-right: 10px;
spacing: 8px;
Text {
horizontal-stretch: 1;
text: root.options[root.current].name;
color: Look.text;
font-size: 13px;
vertical-alignment: center;
}
// A chevron.
Path {
width: 10px;
height: 6px;
y: (parent.height - self.height) / 2;
viewbox-width: 10;
viewbox-height: 6;
commands: "M 1 1 L 5 5 L 9 1";
stroke: Look.secondary;
stroke-width: 1.5px;
fill: transparent;
}
}
touch := TouchArea {
mouse-cursor: pointer;
clicked => {
popup.show();
}
}
// Wide enough for the colors, and right-aligned with the picker.
popup := PopupWindow {
width: max(root.width, 220px);
x: root.width - self.width;
y: root.height + 4px;
Rectangle {
border-radius: 8px;
border-width: 1px;
border-color: Look.border;
background: Look.surface;
drop-shadow-blur: 12px;
drop-shadow-offset-y: 4px;
drop-shadow-color: #00000026;
VerticalLayout {
padding: 4px;
for option[index] in root.options: Rectangle {
height: 30px;
border-radius: 5px;
background: index == root.current ? Look.accent.with-alpha(Look.dark ? 0.25 : 0.14)
: row-touch.has-hover ? Look.hover : transparent;
HorizontalLayout {
padding-left: 8px;
padding-right: 8px;
spacing: 8px;
Swatches {
colors: Look.dark ? option.dark : option.light;
}
Text {
horizontal-stretch: 1;
text: option.name;
color: index == root.current ? Look.accent : Look.text;
font-size: 13px;
font-weight: index == root.current ? 600 : 400;
vertical-alignment: center;
}
}
row-touch := TouchArea {
mouse-cursor: pointer;
clicked => {
root.current = index;
popup.close();
}
}
}
}
}
}
}
// Equal stripes, across or down, for the flags.
component Stripes inherits Rectangle {
in property <[color]> colors;
in property <bool> vertical;
for c[i] in root.colors: Rectangle {
x: root.vertical ? i * root.width / root.colors.length : 0;
y: root.vertical ? 0 : i * root.height / root.colors.length;
width: root.vertical ? root.width / root.colors.length : root.width;
height: root.vertical ? root.height : root.height / root.colors.length;
background: c;
}
}
// A Nordic cross flag: the cross, and an inner cross for Norway.
component NordicCross inherits Rectangle {
in property <color> cross;
in property <color> inner: transparent;
Rectangle {
x: 5px;
width: 4px;
background: root.cross;
}
Rectangle {
y: 4px;
height: 4px;
background: root.cross;
}
Rectangle {
x: 6px;
width: 2px;
background: root.inner;
}
Rectangle {
y: 5px;
height: 2px;
background: root.inner;
}
}
// A small flag for each language of the language picker, in the order of
// `LANGUAGES` in main.rs.
component Flag inherits Rectangle {
in property <int> language;
width: 18px;
height: 12px;
border-radius: 2px;
clip: true;
// English: the Union Jack.
if root.language == 0: Rectangle {
background: #012169;
Path {
viewbox-width: 18;
viewbox-height: 12;
commands: "M 0 0 L 18 12 M 18 0 L 0 12";
stroke: #ffffff;
stroke-width: 2.4px;
}
Path {
viewbox-width: 18;
viewbox-height: 12;
commands: "M 0 0 L 18 12 M 18 0 L 0 12";
stroke: #c8102e;
stroke-width: 0.8px;
}
Rectangle {
x: 7px;
width: 4px;
background: #ffffff;
}
Rectangle {
y: 4px;
height: 4px;
background: #ffffff;
}
Rectangle {
x: 8px;
width: 2px;
background: #c8102e;
}
Rectangle {
y: 5px;
height: 2px;
background: #c8102e;
}
}
if root.language == 1: NordicCross {
background: #c8102e;
cross: #ffffff;
}
if root.language == 2: Stripes {
colors: [#000000, #dd0000, #ffce00];
}
if root.language == 3: Stripes {
colors: [#aa151b, #f1bf00, #f1bf00, #aa151b];
}
if root.language == 4: Stripes {
vertical: true;
colors: [#002654, #ffffff, #ce1126];
}
if root.language == 5: Stripes {
vertical: true;
colors: [#009246, #ffffff, #ce2b37];
}
if root.language == 6: Stripes {
colors: [#ae1c28, #ffffff, #21468b];
}
if root.language == 7: NordicCross {
background: #ba0c2f;
cross: #ffffff;
inner: #00205b;
}
if root.language == 8: Stripes {
colors: [#ffffff, #dc143c];
}
if root.language == 9: Stripes {
vertical: true;
colors: [#046a38, #046a38, #da291c, #da291c, #da291c];
}
if root.language == 10: NordicCross {
background: #ffffff;
cross: #002f6c;
}
if root.language == 11: NordicCross {
background: #006aa7;
cross: #fecc02;
}
if root.language == 12: Rectangle {
background: #ffffff;
Rectangle {
width: 7px;
height: 7px;
border-radius: 3.5px;
background: #bc002d;
}
}
if root.language == 13: Rectangle {
background: #ee1c25;
Path {
x: 1.5px;
y: 1.5px;
width: 5px;
height: 5px;
viewbox-width: 10;
viewbox-height: 10;
// A five-pointed star.
commands: "M 5 0 L 6.18 3.38 L 9.76 3.45 L 6.9 5.62 L 7.94 9.05 L 5 7 L 2.06 9.05 L 3.1 5.62 L 0.24 3.45 L 3.82 3.38 Z";
fill: #ffff00;
}
}
// A faint edge, so white flags show on white.
Rectangle {
border-radius: 2px;
border-width: 1px;
border-color: #00000026;
}
}
// Picks the chart language; each option in the list has the language's flag
// after its name.
component LanguagePicker inherits Rectangle {
in property <[string]> names;
in-out property <int> current;
height: 32px;
border-radius: 7px;
border-width: 1px;
border-color: Look.border;
background: touch.has-hover ? Look.hover : Look.surface;
accessible-role: combobox;
accessible-label: "Language";
accessible-value: root.names[root.current];
HorizontalLayout {
padding-left: 10px;
padding-right: 10px;
spacing: 8px;
Text {
horizontal-stretch: 1;
text: root.names[root.current];
color: Look.text;
font-size: 13px;
vertical-alignment: center;
}
Path {
width: 10px;
height: 6px;
y: (parent.height - self.height) / 2;
viewbox-width: 10;
viewbox-height: 6;
commands: "M 1 1 L 5 5 L 9 1";
stroke: Look.secondary;
stroke-width: 1.5px;
fill: transparent;
}
}
touch := TouchArea {
mouse-cursor: pointer;
clicked => {
popup.show();
}
}
popup := PopupWindow {
x: 0;
y: root.height + 4px;
width: root.width;
Rectangle {
border-radius: 8px;
border-width: 1px;
border-color: Look.border;
background: Look.surface;
drop-shadow-blur: 12px;
drop-shadow-offset-y: 4px;
drop-shadow-color: #00000026;
VerticalLayout {
padding: 4px;
for name[index] in root.names: Rectangle {
height: 28px;
border-radius: 5px;
background: index == root.current ? Look.accent.with-alpha(Look.dark ? 0.25 : 0.14)
: row-touch.has-hover ? Look.hover : transparent;
HorizontalLayout {
padding-left: 8px;
padding-right: 8px;
spacing: 8px;
Text {
horizontal-stretch: 1;
text: name;
color: index == root.current ? Look.accent : Look.text;
font-size: 13px;
font-weight: index == root.current ? 600 : 400;
vertical-alignment: center;
}
VerticalLayout {
alignment: center;
Flag {
language: index;
}
}
}
row-touch := TouchArea {
mouse-cursor: pointer;
clicked => {
root.current = index;
popup.close();
}
}
}
}
}
}
}
// A small, quiet button for a sample's actions.
component ActionButton inherits Rectangle {
in property <string> text;
callback clicked();
height: 28px;
min-width: label.preferred-width + 24px;
border-radius: 7px;
border-width: 1px;
border-color: Look.border;
background: touch.pressed ? Look.hover.with-alpha(0.12) : touch.has-hover ? Look.hover : Look.surface;
accessible-role: button;
accessible-label: root.text;
accessible-action-default => {
root.clicked();
}
label := Text {
text: root.text;
color: Look.text;
font-size: 13px;
font-weight: 500;
horizontal-alignment: center;
vertical-alignment: center;
}
touch := TouchArea {
mouse-cursor: pointer;
clicked => {
root.clicked();
}
}
}
// Shows or hides the sample list.
component MenuButton inherits Rectangle {
callback clicked();
width: 36px;
height: 36px;
border-radius: 8px;
background: touch.has-hover || touch.pressed ? Look.hover : transparent;
accessible-role: button;
accessible-label: "Samples";
accessible-action-default => {
root.clicked();
}
for i in 3: Rectangle {
x: (parent.width - self.width) / 2;
y: parent.height / 2 - 6px + i * 5px;
width: 18px;
height: 2px;
border-radius: 1px;
background: Look.text;
}
touch := TouchArea {
mouse-cursor: pointer;
clicked => {
root.clicked();
}
}
}
// An icon button that opens a web page.
component LinkButton inherits Rectangle {
in property <string> label;
in property <string> url;
// A 16 by 16 icon, filled, or outlined with `outline`.
in property <string> icon;
in property <bool> outline;
callback open(url: string);
width: 36px;
height: 36px;
border-radius: 8px;
background: touch.has-hover || touch.pressed ? Look.hover : transparent;
accessible-role: button;
accessible-label: root.label;
accessible-action-default => {
root.open(root.url);
}
Path {
width: 18px;
height: 18px;
viewbox-width: 16;
viewbox-height: 16;
commands: root.icon;
fill: root.outline ? transparent : Look.text;
stroke: root.outline ? Look.text : transparent;
stroke-width: root.outline ? 1.4px : 0px;
}
touch := TouchArea {
mouse-cursor: pointer;
clicked => {
root.open(root.url);
}
}
}
// Switches dark mode: a moon in light mode, a sun in dark mode.
component DarkModeButton inherits Rectangle {
in-out property <bool> dark;
width: 36px;
height: 36px;
border-radius: 8px;
background: touch.has-hover || touch.pressed ? Look.hover : transparent;
accessible-role: checkbox;
accessible-label: "Dark mode";
accessible-checked: root.dark;
accessible-action-default => {
root.dark = !root.dark;
}
Path {
width: 20px;
height: 20px;
viewbox-width: 20;
viewbox-height: 20;
commands: root.dark
// A sun: a circle and eight rays.
? "M 13.5 10 A 3.5 3.5 0 1 1 6.5 10 A 3.5 3.5 0 1 1 13.5 10 Z M 10 1.5 L 10 3.5 M 10 16.5 L 10 18.5 M 1.5 10 L 3.5 10 M 16.5 10 L 18.5 10 M 4 4 L 5.4 5.4 M 14.6 14.6 L 16 16 M 4 16 L 5.4 14.6 M 14.6 5.4 L 16 4"
// A crescent moon.
: "M 16.5 12.5 A 7 7 0 1 1 7.5 3.5 A 5.5 5.5 0 0 0 16.5 12.5 Z";
stroke: Look.text;
stroke-width: 1.5px;
fill: transparent;
}
touch := TouchArea {
mouse-cursor: pointer;
clicked => {
root.dark = !root.dark;
}
}
}
// Opens the code of the current sample: angle brackets and a slash.
component CodeButton inherits Rectangle {
callback clicked();
width: 36px;
height: 36px;
border-radius: 8px;
background: touch.has-hover || touch.pressed ? Look.hover : transparent;
accessible-role: button;
accessible-label: "Code";
accessible-action-default => {
root.clicked();
}
Path {
width: 20px;
height: 20px;
viewbox-width: 20;
viewbox-height: 20;
commands: "M 6.5 5.5 L 2 10 L 6.5 14.5 M 13.5 5.5 L 18 10 L 13.5 14.5 M 11.5 3.5 L 8.5 16.5";
stroke: Look.text;
stroke-width: 1.5px;
fill: transparent;
}
touch := TouchArea {
mouse-cursor: pointer;
clicked => {
root.clicked();
}
}
}
// A tab of the code panel, underlined while it is shown.
component CodeTab inherits Rectangle {
in property <string> text;
in property <bool> current;
callback clicked();
horizontal-stretch: 0;
min-width: label.preferred-width + 24px;
label := Text {
text: root.text;
color: root.current ? Look.text : Look.secondary;
font-size: 13px;
font-weight: root.current ? 600 : 400;
}
Rectangle {
y: parent.height - self.height;
height: 2px;
background: root.current ? Look.accent : touch.has-hover ? Look.border : transparent;
}
touch := TouchArea {
mouse-cursor: pointer;
clicked => {
root.clicked();
}
}
}
// The current sample's code and the Rust project that runs it, a file per
// tab, over the gallery, with a button that copies the shown file. Closes
// with Escape, the close button or a click beside it.
component CodePanel inherits Rectangle {
in property <string> title;
in property <[CodeFile]> files;
// The tab shown.
in-out property <int> current;
// Smaller margins on phone-sized windows.
in property <bool> narrow;
// A monospace font of the system.
in property <string> font-family;
callback close();
// Also called with the code on Copy: the browser copies it from Rust.
callback copy-text(text: string);
// "Copied" on the button for a moment after copying.
private property <bool> copied;
background: #00000066;
TouchArea {
clicked => {
root.close();
}
}
Rectangle {
width: min(880px, root.width - (root.narrow ? 24px : 96px));
// A fixed height, so it doesn't change from tab to tab.
height: min(root.height - (root.narrow ? 24px : 96px), 760px);
border-radius: 12px;
border-width: 1px;
border-color: Look.border;
background: Look.surface;
drop-shadow-blur: 24px;
drop-shadow-offset-y: 8px;
drop-shadow-color: #00000040;
// Clicks on the panel don't close it.
TouchArea { }
keys := FocusScope {
init => {
self.focus();
}
key-pressed(event) => {
if event.text == Key.Escape {
root.close();
return accept;
}
reject
}
VerticalLayout {
HorizontalLayout {
padding: 12px;
padding-left: 16px;
spacing: 8px;
Text {
horizontal-stretch: 1;
text: root.title;
color: Look.text;
font-size: 15px;
font-weight: 600;
vertical-alignment: center;
overflow: elide;
}
ActionButton {
width: 84px;
text: root.copied ? "Copied" : "Copy";
clicked => {
code-text.select-all();
code-text.copy();
code-text.clear-selection();
root.copy-text(root.files[root.current].code);
root.copied = true;
}
}
ActionButton {
text: "Close";
clicked => {
root.close();
}
}
}
HorizontalLayout {
height: 34px;
padding-left: 4px;
alignment: start;
for file[i] in root.files: CodeTab {
text: file.name;
current: i == root.current;
clicked => {
root.current = i;
}
}
}
Rectangle {
height: 1px;
background: Look.border;
}
ScrollView {
content-width: max(self.visible-width, code-text.preferred-width + 32px);
content-height: max(self.visible-height, code-text.preferred-height + 24px);
// Read-only, but selectable: the Copy button selects all of it.
code-text := TextInput {
x: 16px;
y: 12px;
width: self.preferred-width;
height: self.preferred-height;
text: root.files[root.current].code;
read-only: true;
single-line: false;
wrap: no-wrap;
font-family: root.font-family;
font-size: 12px;
color: Look.text;
selection-background-color: Look.accent.with-alpha(0.3);
}
}
}
}
}
Timer {
interval: 1.5s;
running: root.copied;
triggered => {
root.copied = false;
}
}
}
// A label and its field in the task editor.
component EditorRow inherits HorizontalLayout {
in property <string> label;
spacing: 12px;
Text {
width: 76px;
text: root.label;
color: Look.secondary;
font-size: 13px;
vertical-alignment: center;
}
@children
}
// A dialog over the gallery that edits a Gantt task: its name, group, start,
// length in days and progress. Save or Enter hands the changed task to
// `save`; Cancel, Escape or a click beside it closes it unchanged.
component TaskEditor inherits Rectangle {
in property <ChartGanttTask> task;
in property <ChartDateTime> origin;
in property <bool> narrow;
callback close();
callback save(task: ChartGanttTask);
property <float> day: 86400;
// The fields, from the task when the dialog opens.
property <string> name;
property <string> group;
property <int> start-day;
property <int> days;
property <float> percent;
init => {
self.name = self.task.label;
self.group = self.task.group;
self.start-day = round(self.task.start / self.day);
self.days = round((self.task.end - self.task.start) / self.day);
self.percent = round(self.task.progress * 100);
}
function done() {
root.save({
label: root.name,
group: root.group,
start: root.start-day * root.day,
end: (root.start-day + root.days) * root.day,
color: root.task.color,
progress: round(root.percent) / 100,
});
}
background: #00000066;
TouchArea {
clicked => {
root.close();
}
}
Rectangle {
width: min(440px, root.width - (root.narrow ? 24px : 96px));
height: form.preferred-height;
border-radius: 12px;
border-width: 1px;
border-color: Look.border;
background: Look.surface;
drop-shadow-blur: 24px;
drop-shadow-offset-y: 8px;
drop-shadow-color: #00000040;
// Clicks on the dialog don't close it.
TouchArea { }
// Has the focus when the dialog opens, for Escape and Enter.
FocusScope {
init => {
self.focus();
}
key-pressed(event) => {
if event.text == Key.Escape {
root.close();
return accept;
}
if event.text == Key.Return {
root.done();
return accept;
}
reject
}
form := VerticalLayout {
padding: 20px;
spacing: 12px;
Text {
text: root.days == 0 ? "Edit milestone" : "Edit task";
color: Look.text;
font-size: 15px;
font-weight: 600;
}
EditorRow {
label: "Name";
LineEdit {
text <=> root.name;
accepted => {
root.done();
}
}
}
EditorRow {
label: "Group";
LineEdit {
text <=> root.group;
accepted => {
root.done();
}
}
}
EditorRow {
label: "Start";
SpinBox {
width: 110px;
minimum: -365;
maximum: 730;
value <=> root.start-day;
}
Text {
text: ChartEngine.format-time(root.origin, 0, root.start-day * root.day);
color: Look.secondary;
font-size: 13px;
vertical-alignment: center;
}
}
EditorRow {
label: "Days";
SpinBox {
width: 110px;
minimum: 0;
maximum: 365;
value <=> root.days;
}
Text {
text: root.days == 0 ? "A milestone"
: "Ends " + ChartEngine.format-time(root.origin, 0, (root.start-day + root.days) * root.day);
color: Look.secondary;
font-size: 13px;
vertical-alignment: center;
}
}
// A milestone is reached or not; a task is a share done.
if root.days == 0: EditorRow {
label: "Reached";
CheckBox {
checked: root.percent >= 100;
toggled => {
root.percent = self.checked ? 100 : 0;
}
}
}
if root.days > 0: EditorRow {
label: "Progress";
Slider {
minimum: 0;
maximum: 100;
step: 5;
value <=> root.percent;
}
Text {
width: 40px;
text: round(root.percent) + " %";
color: Look.text;
font-size: 13px;
horizontal-alignment: right;
vertical-alignment: center;
}
}
HorizontalLayout {
padding-top: 8px;
spacing: 8px;
alignment: end;
ActionButton {
text: "Cancel";
clicked => {
root.close();
}
}
ActionButton {
text: "Save";
clicked => {
root.done();
}
}
}
}
}
}
}
// A sample browser: pick a sample on
// the left, and change its data with the buttons below the chart.
export component Gallery inherits Window {
title: "SlintPlot gallery";
// Shown in the taskbar, and in the Dock on macOS.
icon: @image-url("../icon.png");
preferred-width: 1280px;
preferred-height: 820px;
// Windows and Linux: the app draws the frame, with rounded corners over
// a transparent window.
no-frame: WindowFrame.drawn;
background: WindowFrame.drawn ? transparent : Look.page;
// The rounded corners of the drawn frame; square when maximized.
private property <length> corner-radius: WindowFrame.drawn && !WindowFrame.maximized ? 8px : 0px;
// The desktop app, with a window frame; the browser has none.
private property <bool> desktop: WindowFrame.mac-titlebar || WindowFrame.drawn;
// macOS: the window buttons sit over the sample list, or, with the list
// hidden, in a title bar strip of their own above the header.
private property <length> title-strip: WindowFrame.mac-titlebar && !root.sidebar-shown ? 28px : 0px;
private property <bool> sidebar-shown: !root.narrow && root.show-samples && !root.chart-only;
// The width of the drawn title bar buttons at the top right.
private property <length> controls-width: WindowFrame.drawn ? 3 * 46px : 0px;
in property <[SampleEntry]> samples;
in-out property <string> sample;
in property <string> sample-title;
in property <string> description;
// Buttons for the current sample.
in property <[string]> actions;
in-out property <string> status;
// The code panel's tabs: the current sample's Slint code with its data,
// and the Rust project that runs it.
in property <[CodeFile]> code-files;
in-out property <bool> code-open;
// A monospace font for the code, set per system from Rust; the browser
// only has Inter.
in property <string> code-font: "Menlo";
// The data of the current sample.
in property <[string]> labels;
in-out property <[ChartDataset]> datasets;
in-out property <[ChartSlice]> slices;
in-out property <[ChartSlice]> channels;
in property <[string]> row-labels;
in property <[float]> grid;
in property <[float]> growth;
in-out property <[ChartTreemapItem]> tree;
in-out property <[ChartSunburstItem]> sun;
in property <[ChartWaterfallStep]> steps;
in property <[ChartCandle]> candles;
// The Gantt sample's tasks, and the one the task editor shows, or -1.
in-out property <[ChartGanttTask]> gantt-tasks;
in-out property <int> editing-task: -1;
// The candlestick sample draws OHLC bars, and skips gaps in time.
in property <bool> ohlc;
in property <bool> skip-gaps: true;
in property <float> gauge-value;
in property <[float]> bullet-values: [270, 22.5, 320, 1650, 4.5, 104, 87, 72, 95, 128, 48, 36, 14, 9, 27];
in-out property <ChartLegendPosition> legend-position;
in-out property <bool> decimate: true;
in-out property <bool> dark-mode: Palette.color-scheme == ColorScheme.dark;
// Index into the theme picker.
in-out property <int> theme;
// Index into the calendar heatmap's color picker; 0 follows the theme.
in-out property <int> calendar-colors;
// Index into the language picker of the "languages" sample, in the order
// of `LANGUAGES` in main.rs. The other samples are in English.
in-out property <int> language;
// Japanese and Chinese need fonts with their characters; in the browser,
// Slint only has Inter.
in property <bool> cjk-fonts: true;
private property <[string]> language-names: root.cjk-fonts
? ["English", "Dansk", "Deutsch", "Español", "Français", "Italiano", "Nederlands", "Norsk", "Polski", "Português", "Suomi", "Svenska", "日本語", "中文"]
: ["English", "Dansk", "Deutsch", "Español", "Français", "Italiano", "Nederlands", "Norsk", "Polski", "Português", "Suomi", "Svenska"];
// Phone-sized windows: the sample list opens over the chart from the
// menu button, and the header, buttons and gauges wrap.
private property <bool> narrow: self.width < 720px;
// The sample list beside the chart, on wide windows.
in-out property <bool> show-samples: true;
// The sample list over the chart, on narrow windows.
in-out property <bool> samples-open: false;
// Only the chart, filling the window, without the card's frame: for the
// chart screenshots on the website (`--snapshot`).
in property <bool> chart-only;
callback select(id: string);
callback action(index: int);
// Opens the website or GitHub in the browser.
callback open-url(url: string);
// The code panel's Copy button: the browser copies the code from Rust.
callback copy-text(text: string);
// The code button: Rust sets `code` to the sample's code with its data
// now, and opens the code panel.
callback show-code();
// A click on a Gantt task: opens the task editor on it.
callback edit-task(index: int);
edit-task(index) => {
root.editing-task = index;
}
// Samples with their own light and dark colors update them.
callback dark-mode-changed();
// The "languages" sample sets the chart language and translates its texts.
callback language-changed();
// The "languages" sample's chart title and axis titles.
in property <string> language-title;
in property <string> language-y-title;
in property <string> language-y2-title;
// The zoom window of the one-week time series.
in-out property <float> view-start: 0;
in-out property <float> view-end: 1;
changed dark-mode => {
Palette.color-scheme = self.dark-mode ? ColorScheme.dark : ColorScheme.light;
root.apply-look();
root.dark-mode-changed();
}
// The charts sit on the cards, so their backdrop (slice borders, label
// backgrounds) is the card's color.
function apply-look() {
Look.dark = self.dark-mode;
ChartTheme.background = Look.surface;
}
// Slightly smaller chart text on narrow windows, for more room.
function fit-text() {
ChartTheme.font-size = self.narrow ? 11px : 12px;
ChartTheme.title-font-size = self.narrow ? 14px : 15px;
}
init => {
self.fit-text();
self.apply-look();
}
changed narrow => {
self.fit-text();
}
changed language => {
root.language-changed();
}
changed theme => {
ChartTheme.palette = self.theme == 0 ? ChartPalette.modern
: self.theme == 1 ? ChartPalette.material
: self.theme == 2 ? ChartPalette.corporate
: self.theme == 3 ? ChartPalette.mono
: ChartPalette.pastel;
}
Rectangle {
border-radius: root.corner-radius;
clip: WindowFrame.drawn;
background: Look.page;
// The empty parts of the top of the window move it.
if root.desktop: DragArea {
y: 0;
height: root.title-strip + (root.narrow ? 60px : 76px);
}
HorizontalLayout {
if root.sidebar-shown: SampleList {
width: 240px;
top-inset: WindowFrame.mac-titlebar ? 32px : 0px;
samples: root.samples;
sample: root.sample;
open-url(url) => {
root.open-url(url);
}
select(id) => {
root.select(id);
}
}
VerticalLayout {
padding: root.chart-only ? 0px : root.narrow ? 12px : 24px;
padding-top: root.chart-only ? 0px : root.title-strip + (root.narrow ? 10px : 18px);
spacing: root.narrow ? 12px : 16px;
// The header: the sample's title and description on the left, the
// gallery's settings on the right.
if !root.chart-only: HorizontalLayout {
spacing: 12px;
VerticalLayout {
alignment: start;
MenuButton {
clicked => {
if root.narrow {
root.samples-open = !root.samples-open;
} else {
root.show-samples = !root.show-samples;
}
}
}
}
VerticalLayout {
horizontal-stretch: 1;
spacing: 2px;
alignment: start;
Text {
height: 36px;
text: root.sample-title;
font-size: root.narrow ? 18px : 20px;
font-weight: 600;
color: Look.text;
vertical-alignment: center;
overflow: elide;
}
if !root.narrow: Text {
text: root.description;
font-size: 13px;
color: Look.secondary;
wrap: word-wrap;
}
}
if !root.narrow: VerticalLayout {
alignment: start;
HorizontalLayout {
spacing: 8px;
// The calendar heatmap's color, separate from the theme.
if root.sample == "calendar": ComboBox {
width: 150px;
model: ["Theme colors", "Green", "Blue", "Orange", "Purple", "Red"];
current-index <=> root.calendar-colors;
}
// The chart language, for the sample that shows it off.
if root.sample == "languages": LanguagePicker {
width: 150px;
names: root.language-names;
current <=> root.language;
}
ThemePicker {
width: 140px;
current <=> root.theme;
}
}
}
VerticalLayout {
alignment: start;
HorizontalLayout {
spacing: 2px;
CodeButton {
clicked => {
root.show-code();
}
}
// The documentation: an open book.
LinkButton {
label: "Documentation";
url: "https://slintplot.dev/#getting-started";
outline: true;
icon: "M 1.5 3 C 3.5 2 6 2 8 3.5 C 10 2 12.5 2 14.5 3 L 14.5 13 C 12.5 12 10 12 8 13.5 C 6 12 3.5 12 1.5 13 Z M 8 3.5 L 8 13.5";
open(url) => {
root.open-url(url);
}
}
// The GitHub mark.
LinkButton {
label: "GitHub";
url: "https://github.com/trondegil/slintplot";
icon: "M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z";
open(url) => {
root.open-url(url);
}
}
DarkModeButton {
dark <=> root.dark-mode;
}
}
}
// Room for the drawn window buttons.
if WindowFrame.drawn: Rectangle {
width: root.controls-width - (root.narrow ? 12px : 24px);
}
}
// Narrow windows: the description and pickers under the title.
if root.narrow && !root.chart-only: Text {
text: root.description;
font-size: 13px;
color: Look.secondary;
wrap: word-wrap;
}
if root.narrow && !root.chart-only: HorizontalLayout {
spacing: 8px;
ThemePicker {
horizontal-stretch: 1;
current <=> root.theme;
}
if root.sample == "languages": LanguagePicker {
horizontal-stretch: 1;
names: root.language-names;
current <=> root.language;
}
if root.sample == "calendar": ComboBox {
model: ["Theme colors", "Green", "Blue", "Orange", "Purple", "Red"];
current-index <=> root.calendar-colors;
}
}
// The chart of the current sample, on a card, with the sample's
// actions in a footer.
Rectangle {
vertical-stretch: 1;
border-radius: root.chart-only ? 0px : 12px;
background: Look.surface;
border-width: root.chart-only ? 0px : 1px;
border-color: Look.border;
drop-shadow-blur: root.chart-only ? 0px : 16px;
drop-shadow-offset-y: 2px;
drop-shadow-color: Look.dark ? #00000040 : #0000000f;
VerticalLayout {
VerticalLayout {
vertical-stretch: 1;
padding: root.narrow ? 8px : 16px;
if root.sample == "bar-vertical": BarChart {
title: "Vertical bar chart";
labels: root.labels;
datasets <=> root.datasets;
category-clicked(index) => {
root.status = "category-clicked: " + self.labels[index];
}
}
if root.sample == "bar-horizontal": HorizontalBarChart {
title: "Horizontal bar chart";
labels: root.labels;
datasets <=> root.datasets;
// Beside the chart there's no room for it on narrow windows.
legend-position: root.narrow ? ChartLegendPosition.top : ChartLegendPosition.right;
category-clicked(index) => {
root.status = "category-clicked: " + self.labels[index];
}
}
if root.sample == "bar-stacked": BarChart {
title: "Stacked bar chart";
labels: root.labels;
datasets <=> root.datasets;
stacked: true;
category-clicked(index) => {
root.status = "category-clicked: " + self.labels[index];
}
}
if root.sample == "bar-border-radius": BarChart {
title: "Bar chart border radius";
labels: root.labels;
datasets <=> root.datasets;
bar-radius: 6px;
bar-border-width: 2px;
category-clicked(index) => {
root.status = "category-clicked: " + self.labels[index];
}
}
if root.sample == "line" || root.sample == "line-interpolation" || root.sample == "line-stepped" || root.sample == "area-line": LineChart {
title: root.sample-title;
labels: root.labels;
datasets <=> root.datasets;
begin-at-zero: false;
category-clicked(index) => {
root.status = "category-clicked: " + self.labels[index];
}
}
if root.sample == "line-points": LineChart {
title: "Line chart without point markers";
labels: root.labels;
datasets <=> root.datasets;
show-points: false;
category-clicked(index) => {
root.status = "category-clicked: " + self.labels[index];
}
}
if root.sample == "combo-bar-line": BarChart {
title: "Combo bar and line chart";
labels: root.labels;
datasets <=> root.datasets;
category-clicked(index) => {
root.status = "category-clicked: " + self.labels[index];
}
}
if root.sample == "stacked-bar-line": BarChart {
title: "Stacked bar and line chart";
labels: root.labels;
datasets <=> root.datasets;
stacked: true;
category-clicked(index) => {
root.status = "category-clicked: " + self.labels[index];
}
}
if root.sample == "area-stacked": LineChart {
title: "Stacked area chart";
labels: root.labels;
datasets <=> root.datasets;
stacked: true;
category-clicked(index) => {
root.status = "category-clicked: " + self.labels[index];
}
}
if root.sample == "pie": PieChart {
title: "Pie chart";
slices <=> root.slices;
slice-clicked(index) => {
root.status = "slice-clicked: " + self.slices[index].label + ", " + ChartEngine.format-value(self.slices[index].value, { decimals: -1 });
}
}
if root.sample == "doughnut": DoughnutChart {
title: "Doughnut chart";
slices <=> root.slices;
slice-clicked(index) => {
root.status = "slice-clicked: " + self.slices[index].label + ", " + ChartEngine.format-value(self.slices[index].value, { decimals: -1 });
}
}
if root.sample == "polar-area": PolarAreaChart {
title: "Polar area chart";
slices <=> root.slices;
slice-clicked(index) => {
root.status = "slice-clicked: " + self.slices[index].label + ", " + ChartEngine.format-value(self.slices[index].value, { decimals: -1 });
}
}
if root.sample == "radar" || root.sample == "area-radar": RadarChart {
title: root.sample-title;
labels: root.labels;
datasets <=> root.datasets;
category-clicked(index) => {
root.status = "category-clicked: " + self.labels[index];
}
}
if root.sample == "scatter": ScatterChart {
title: "Scatter chart";
datasets <=> root.datasets;
zoom-enabled: true;
point-clicked(dataset, index) => {
root.status = "point-clicked: " + self.datasets[dataset].label + " ("
+ ChartEngine.format-value(self.datasets[dataset].points[index].x, { decimals: -1 }) + ", "
+ ChartEngine.format-value(self.datasets[dataset].points[index].y, { decimals: -1 }) + ")";
}
}
if root.sample == "bubble": BubbleChart {
title: "Bubble chart";
datasets <=> root.datasets;
zoom-enabled: true;
point-clicked(dataset, index) => {
root.status = "point-clicked: " + self.datasets[dataset].label + " ("
+ ChartEngine.format-value(self.datasets[dataset].points[index].x, { decimals: -1 }) + ", "
+ ChartEngine.format-value(self.datasets[dataset].points[index].y, { decimals: -1 }) + ")";
}
}
if root.sample == "legend-position": LineChart {
title: "Legend position";
labels: root.labels;
datasets <=> root.datasets;
legend-position: root.legend-position;
category-clicked(index) => {
root.status = "category-clicked: " + self.labels[index];
}
}
if root.sample == "axes-format": BarChart {
title: "Revenue";
labels: root.labels;
datasets <=> root.datasets;
x-title: "Month";
y-title: "Revenue (USD)";
value-prefix: "$";
value-decimals: 0;
show-values: true;
category-clicked(index) => {
root.status = "category-clicked: " + self.labels[index];
}
}
if root.sample == "axes-fixed": LineChart {
title: "Fixed range: -150 to 150";
labels: root.labels;
datasets <=> root.datasets;
fixed-range: true;
min-value: -150;
max-value: 150;
y-title: "Value";
category-clicked(index) => {
root.status = "category-clicked: " + self.labels[index];
}
}
if root.sample == "axes-two": BarChart {
title: "Temperature and rainfall";
labels: root.labels;
datasets <=> root.datasets;
y-title: "Temperature";
value-suffix: " °C";
y2-title: "Rainfall";
y2-value-suffix: " mm";
show-values: false;
category-clicked(index) => {
root.status = "category-clicked: " + self.labels[index];
}
}
if root.sample == "languages": BarChart {
title: root.language-title;
labels: root.labels;
datasets <=> root.datasets;
y-title: root.language-y-title;
y2-title: root.language-y2-title;
y2-value-suffix: ChartTexts.percent;
y2-value-decimals: 1;
category-clicked(index) => {
root.status = "category-clicked: " + self.labels[index];
}
}
if root.sample == "axes-log": LineChart {
title: "Users";
labels: root.labels;
datasets <=> root.datasets;
logarithmic: true;
y-title: "Users (log scale)";
category-clicked(index) => {
root.status = "category-clicked: " + self.labels[index];
}
}
if root.sample == "survey": HorizontalBarChart {
title: "How often do you work from home?";
labels: root.labels;
datasets <=> root.datasets;
stacked-percent: true;
show-values: true;
bar-border-width: 0;
fill-opacity: 1;
category-clicked(index) => {
root.status = "category-clicked: " + self.labels[index];
}
}
if root.sample == "time-hourly": TimeSeriesChart {
title: "Temperature, hourly";
datasets <=> root.datasets;
time-origin: { year: 2026, month: 10, day: 11 };
value-suffix: " °C";
value-decimals: 1;
y-title: "Temperature";
zoom-enabled: true;
point-clicked(dataset, index) => {
root.status = "point-clicked: " + self.datasets[dataset].label + ", "
+ ChartEngine.format-time(self.time-origin, self.utc-offset, self.datasets[dataset].points[index].x) + ", "
+ ChartEngine.format-value(self.datasets[dataset].points[index].y, { suffix: " °C", decimals: 1 });
}
}
if root.sample == "live": TimeSeriesChart {
title: "Live sensor data, last minute";
datasets <=> root.datasets;
time-origin: { year: 2026, month: 10, day: 27, hour: 12 };
y-title: "Reading";
animated: false;
point-clicked(dataset, index) => {
root.status = "point-clicked: " + self.datasets[dataset].label + ", "
+ ChartEngine.format-time(self.time-origin, self.utc-offset, self.datasets[dataset].points[index].x) + ", "
+ ChartEngine.format-value(self.datasets[dataset].points[index].y, { decimals: 1 });
}
}
if root.sample == "time-week": TimeSeriesChart {
title: "Electricity use, one week";
datasets <=> root.datasets;
// Monday 19 October 2026, local time (UTC+2).
time-origin: { year: 2026, month: 10, day: 18, hour: 22 };
utc-offset: 120;
value-suffix: " kW";
begin-at-zero: true;
y-title: "Power";
zoom-enabled: true;
view-start <=> root.view-start;
view-end <=> root.view-end;
point-clicked(dataset, index) => {
root.status = "point-clicked: " + self.datasets[dataset].label + ", "
+ ChartEngine.format-time(self.time-origin, self.utc-offset, self.datasets[dataset].points[index].x) + ", "
+ ChartEngine.format-value(self.datasets[dataset].points[index].y, { suffix: " kW", decimals: -1 });
}
}
if root.sample == "time-daily": TimeSeriesChart {
title: "Temperature, daily";
datasets <=> root.datasets;
time-origin: { year: 2025, month: 10, day: 26 };
value-suffix: " °C";
value-decimals: 1;
zoom-enabled: true;
point-clicked(dataset, index) => {
root.status = "point-clicked: " + self.datasets[dataset].label + ", "
+ ChartEngine.format-time(self.time-origin, self.utc-offset, self.datasets[dataset].points[index].x) + ", "
+ ChartEngine.format-value(self.datasets[dataset].points[index].y, { suffix: " °C", decimals: 1 });
}
}
if root.sample == "candlestick": CandlestickChart {
title: "Acme Robotics, daily";
candles: root.candles;
datasets: [{ label: "20-day average", points: ChartEngine.moving-average(root.candles, 20), color: ChartTheme.text-color }];
ohlc: root.ohlc;
skip-gaps: root.skip-gaps;
show-volume: true;
show-last-price: true;
time-origin: { year: 2026, month: 4, day: 6 };
value-prefix: "$";
value-decimals: 2;
zoom-enabled: true;
// Zoomed out, a candle can stand for several days.
candle-clicked(first, last) => {
root.status = "candle-clicked: " + ChartEngine.format-time(self.time-origin, self.utc-offset, self.candles[first].x)
+ (last > first ? " – " + ChartEngine.format-time(self.time-origin, self.utc-offset, self.candles[last].x) : "")
+ " (close " + ChartEngine.format-value(self.candles[last].close, { prefix: "$", decimals: 2 }) + ")";
}
}
if root.sample == "heatmap": HeatmapChart {
title: "Visits by weekday and hour";
x-labels: root.labels;
y-labels: root.row-labels;
values: root.grid;
value-suffix: " visits";
cell-clicked(row, column) => {
root.status = "cell-clicked: " + self.y-labels[row] + " " + self.x-labels[column] + ":00, "
+ ChartEngine.format-value(self.values[row * self.x-labels.length + column], { suffix: " visits", decimals: -1 });
}
}
if root.sample == "heatmap-diverging": HeatmapChart {
title: "Correlation";
x-labels: root.labels;
y-labels: root.row-labels;
values: root.grid;
diverging: true;
fixed-range: true;
min-value: -1;
max-value: 1;
show-values: true;
value-decimals: 2;
cell-clicked(row, column) => {
root.status = "cell-clicked: " + self.y-labels[row] + " × " + self.x-labels[column] + ", "
+ ChartEngine.format-value(self.values[row * self.x-labels.length + column], { decimals: 2 });
}
}
if root.sample == "matrix": MatrixChart {
title: "Sales by product and region";
x-labels: root.labels;
y-labels: root.row-labels;
values: root.grid;
color-mode: ChartMatrixColorMode.scale;
color-values: root.growth;
size-label: "Sales";
color-label: "Growth";
value-prefix: "$";
value-suffix: "M";
color-value-suffix: ChartTexts.percent;
show-values: true;
show-empty: true;
show-totals: true;
x-labels-top: true;
cell-clicked(row, column) => {
let value = self.values[row * self.x-labels.length + column];
// Empty cells hold NaN, which is not equal to itself.
root.status = "cell-clicked: " + self.y-labels[row] + " × " + self.x-labels[column] + ", "
+ (value == value ? ChartEngine.format-value(value, { prefix: "$", suffix: "M", decimals: -1 }) : "no sales");
}
}
if root.sample == "treemap": TreemapChart {
title: "World population by country (millions)";
items <=> root.tree;
value-suffix: " M";
item-clicked(index) => {
root.status = "item-clicked: " + self.items[index].label + ", "
+ ChartEngine.format-value(self.items[index].value, { suffix: " M", decimals: -1 });
}
}
if root.sample == "sunburst": SunburstChart {
title: "World population by region (millions)";
items <=> root.sun;
value-suffix: " M";
// Parents have no value of their own: their children add up to it.
item-clicked(index) => {
root.status = "item-clicked: " + ChartEngine.sunburst-path(self.items, index)
+ (self.items[index].value > 0 ? ", " + ChartEngine.format-value(self.items[index].value, { suffix: " M", decimals: -1 }) : "");
}
}
if root.sample == "funnel": FunnelChart {
title: "Visitors to purchases, last 30 days";
stages <=> root.slices;
stage-clicked(index) => {
root.status = "stage-clicked: " + self.stages[index].label + ", " + ChartEngine.format-value(self.stages[index].value, { decimals: -1 });
}
}
if root.sample == "waterfall": WaterfallChart {
title: "Profit and loss";
steps: root.steps;
show-values: true;
value-prefix: "$";
value-suffix: "k";
// Totals show the running total; the other steps their change.
step-clicked(index) => {
root.status = "step-clicked: " + self.steps[index].label + ", "
+ (self.steps[index].total ? ChartEngine.format-value(ChartEngine.waterfall-totals(self.steps)[index], { prefix: "$", suffix: "k", decimals: -1 })
: (self.steps[index].value > 0 ? "+" : "") + ChartEngine.format-value(self.steps[index].value, { prefix: "$", suffix: "k", decimals: -1 }));
}
}
if root.sample == "dumbbell": DumbbellChart {
title: "Life expectancy at birth (years)";
labels: root.labels;
datasets <=> root.datasets;
show-values: true;
category-clicked(index) => {
root.status = "category-clicked: " + self.labels[index] + ", "
+ ChartEngine.format-value(self.datasets[0].values[index], { decimals: 1 }) + " → "
+ ChartEngine.format-value(self.datasets[1].values[index], { decimals: 1 });
}
}
if root.sample == "gantt": GanttChart {
property <float> day: 86400;
title: "Release 3.0";
// 15 December 2026; task times are seconds after it.
time-origin: { year: 2026, month: 12, day: 15 };
tasks: root.gantt-tasks;
// Today, 53 days in: 6 February 2027.
show-now: true;
now: 53 * day;
show-values: true;
zoom-enabled: true;
task-clicked(index) => {
root.status = "task-clicked: " + self.tasks[index].label;
root.edit-task(index);
}
view-start <=> root.view-start;
view-end <=> root.view-end;
}
if root.sample == "gauge": GridLayout {
spacing: 8px;
GaugeChart {
row: 0;
col: 0;
title: "Storage used";
value: root.gauge-value;
label: "of 2 TB";
value-suffix: ChartTexts.percent;
value-decimals: 0;
}
GaugeChart {
row: root.narrow ? 1 : 0;
col: root.narrow ? 0 : 1;
title: "Memory pressure";
value: 100 - root.gauge-value * 0.8;
label: "16 GB";
sweep: 240;
value-suffix: ChartTexts.percent;
value-decimals: 0;
bands: [
{ to: 60, label: "Normal", color: ChartTheme.dark ? #34d399 : #10b981 },
{ to: 85, label: "High", color: ChartTheme.dark ? #fbbf24 : #f59e0b },
{ to: 100, label: "Critical", color: ChartTheme.dark ? #fb7185 : #f43f5e },
];
}
GaugeChart {
row: root.narrow ? 2 : 0;
col: root.narrow ? 0 : 2;
title: "CPU temperature";
value: 30 + root.gauge-value * 0.65;
min-value: 20;
max-value: 100;
label: "Package";
sweep: 240;
show-needle: true;
value-suffix: " °C";
value-decimals: 0;
bands: [
{ to: 60, label: "Normal", color: ChartTheme.dark ? #34d399 : #10b981 },
{ to: 85, label: "Warm", color: ChartTheme.dark ? #fbbf24 : #f59e0b },
{ to: 100, label: "Hot", color: ChartTheme.dark ? #fb7185 : #f43f5e },
];
}
}
if root.sample == "radial-bar": GridLayout {
spacing: 8px;
RadialBarChart {
row: 0;
col: 0;
title: "Daily goals reached";
bars <=> root.slices;
max-value: 100;
value-suffix: ChartTexts.percent;
value-decimals: 0;
bar-clicked(index) => {
root.status = "bar-clicked: " + self.bars[index].label + ", "
+ ChartEngine.format-value(self.bars[index].value, { suffix: ChartTexts.percent, decimals: 0 });
}
}
RadialBarChart {
row: root.narrow ? 1 : 0;
col: root.narrow ? 0 : 1;
title: "How customers contacted support";
bars <=> root.channels;
value-suffix: ChartTexts.percent;
value-decimals: 0;
bar-clicked(index) => {
root.status = "bar-clicked: " + self.bars[index].label + ", "
+ ChartEngine.format-value(self.bars[index].value, { suffix: ChartTexts.percent, decimals: 0 });
}
}
}
if root.sample == "bullet": GridLayout {
spacing: 8px;
BulletChart {
row: 0;
col: 0;
colspan: root.narrow ? 1 : 2;
vertical-stretch: 0;
title: "Sales, year to date";
below-target-color: ChartTheme.decrease-color;
rows: [
{ label: "Revenue", sublabel: "US$ (1,000s)", value: root.bullet-values[0], target: 250, prefix: "$",
bands: [{ to: 150, label: "Poor" }, { to: 225, label: "OK" }, { to: 300, label: "Good" }] },
{ label: "Profit", sublabel: "%", value: root.bullet-values[1], target: 26, suffix: "%",
bands: [{ to: 20, label: "Poor" }, { to: 25, label: "OK" }, { to: 30, label: "Good" }] },
{ label: "Order size", sublabel: "US$, average", value: root.bullet-values[2], target: 550, prefix: "$",
bands: [{ to: 350, label: "Poor" }, { to: 500, label: "OK" }, { to: 600, label: "Good" }] },
{ label: "New customers", sublabel: "count", value: root.bullet-values[3], target: 2100,
bands: [{ to: 1400, label: "Poor" }, { to: 2000, label: "OK" }, { to: 2500, label: "Good" }] },
{ label: "Satisfaction", sublabel: "of 5", value: root.bullet-values[4], target: 4.2,
bands: [{ to: 3.5, label: "Poor" }, { to: 4.25, label: "OK" }, { to: 5, label: "Good" }] },
];
// Each row has its own prefix or suffix.
row-clicked(index) => {
root.status = "row-clicked: " + self.rows[index].label + ", " + self.rows[index].prefix
+ ChartEngine.format-value(self.rows[index].value, { decimals: -1 }) + self.rows[index].suffix;
}
}
// Soft tints for the bands, under a dark bar.
BulletChart {
row: 1;
col: 0;
title: "Sales target reached, by region";
vertical: true;
shared-axis: true;
show-legend: true;
fixed-range: true;
min-value: 0;
max-value: 120;
value-suffix: ChartTexts.percent;
below-target-color: ChartTheme.decrease-color;
rows: [
{ label: "North", value: root.bullet-values[5], target: 100 },
{ label: "South", value: root.bullet-values[6], target: 100 },
{ label: "East", value: root.bullet-values[7], target: 100 },
{ label: "West", value: root.bullet-values[8], target: 100 },
{ label: "Online", value: root.bullet-values[9], target: 100 },
];
bands: [
{ to: 80, label: "Behind" },
{ to: 100, label: "Close" },
{ to: 120, label: "Ahead" },
];
row-clicked(index) => {
root.status = "row-clicked: " + self.rows[index].label + ", "
+ ChartEngine.format-value(self.rows[index].value, { suffix: ChartTexts.percent, decimals: -1 });
}
}
// Rows without a target, on one axis.
BulletChart {
row: root.narrow ? 2 : 1;
col: root.narrow ? 0 : 1;
title: "Website visits, thousands";
shared-axis: true;
show-legend: true;
rows: [
{ label: "Search", value: root.bullet-values[10], target: 55 },
{ label: "Direct", value: root.bullet-values[11] },
{ label: "Social", value: root.bullet-values[12], target: 20 },
{ label: "Email", value: root.bullet-values[13] },
{ label: "Ads", value: root.bullet-values[14], target: 25 },
];
bands: [{ to: 20 }, { to: 40 }, { to: 60 }];
row-clicked(index) => {
root.status = "row-clicked: " + self.rows[index].label + ", "
+ ChartEngine.format-value(self.rows[index].value, { decimals: -1 });
}
}
}
if root.sample == "calendar": CalendarHeatmap {
title: "Contributions in the last year";
start-date: { year: 2025, month: 10, day: 26 };
values: root.grid;
value-suffix: " contributions";
// Light and dark mode shades of each choice.
color: root.calendar-colors == 1 ? (ChartTheme.dark ? #39d353 : #216e39)
: root.calendar-colors == 2 ? (ChartTheme.dark ? #60a5fa : #1d4ed8)
: root.calendar-colors == 3 ? (ChartTheme.dark ? #fb923c : #c2410c)
: root.calendar-colors == 4 ? (ChartTheme.dark ? #a78bfa : #6d28d9)
: root.calendar-colors == 5 ? (ChartTheme.dark ? #f87171 : #b91c1c)
: ChartTheme.colors[0];
day-clicked(index) => {
root.status = "day-clicked: " + ChartEngine.format-time(self.start-date, 0, index * 86400) + ", "
+ ChartEngine.format-value(self.values[index], { suffix: " contributions", decimals: -1 });
}
}
if root.sample == "pie-labels": PieChart {
title: "Pie chart with data labels";
slices <=> root.slices;
show-percentages: true;
slice-clicked(index) => {
root.status = "slice-clicked: " + self.slices[index].label + ", " + ChartEngine.format-value(self.slices[index].value, { decimals: -1 });
}
}
if root.sample == "zoom": LineChart {
title: "Zoom and pan: scroll to zoom, drag to pan, double-click to reset";
labels: root.labels;
datasets <=> root.datasets;
begin-at-zero: false;
show-points: false;
zoom-enabled: true;
category-clicked(index) => {
root.status = "category-clicked: " + self.labels[index];
}
}
if root.sample == "decimation": LineChart {
title: "Data decimation (scroll to zoom)";
labels: root.labels;
datasets <=> root.datasets;
begin-at-zero: false;
show-points: false;
decimate: root.decimate;
zoom-enabled: true;
animated: false;
category-clicked(index) => {
root.status = "category-clicked: " + self.labels[index];
}
}
}
// The footer: the sample's actions, and its status.
if !root.chart-only && (root.actions.length > 0 || root.status != ""): Rectangle {
height: 1px;
background: Look.border;
}
if !root.chart-only && (root.actions.length > 0 || root.status != ""): HorizontalLayout {
padding: root.narrow ? 8px : 12px;
padding-left: root.narrow ? 8px : 16px;
padding-right: root.narrow ? 8px : 16px;
spacing: 12px;
// Three buttons per row on narrow windows.
GridLayout {
spacing: 8px;
for name[index] in root.actions: ActionButton {
row: root.narrow ? index / 3 : 0;
col: root.narrow ? mod(index, 3) : index;
text: name;
clicked => {
root.action(index);
}
}
}
// Click readouts and notes, at the right; long ones are cut
// short rather than pushing the buttons.
Text {
horizontal-stretch: 1;
min-width: 0px;
text: root.status;
color: Look.secondary;
font-size: 12px;
horizontal-alignment: right;
vertical-alignment: center;
overflow: elide;
}
}
}
}
}
}
// The sample list over the chart on narrow windows; closes when a
// sample is picked or the shaded area is tapped.
if root.narrow && root.samples-open: Rectangle {
background: #00000066;
TouchArea {
clicked => {
root.samples-open = false;
}
}
SampleList {
x: 0;
y: 0;
width: min(300px, root.width * 0.85);
height: root.height;
samples: root.samples;
sample: root.sample;
open-url(url) => {
root.open-url(url);
}
roomy: true;
top-inset: WindowFrame.mac-titlebar ? 32px : 0px;
select(id) => {
root.samples-open = false;
root.select(id);
}
}
}
if root.code-open && !root.chart-only: CodePanel {
title: "Code: " + root.sample-title;
files: root.code-files;
narrow: root.narrow;
font-family: root.code-font;
close => {
root.code-open = false;
}
copy-text(text) => {
root.copy-text(text);
}
}
if root.editing-task >= 0 && root.editing-task < root.gantt-tasks.length: TaskEditor {
task: root.gantt-tasks[root.editing-task];
// The Gantt sample's time-origin.
origin: { year: 2026, month: 12, day: 15 };
narrow: root.narrow;
close => {
root.editing-task = -1;
}
save(task) => {
root.gantt-tasks[root.editing-task] = task;
root.status = "Saved: " + task.label;
root.editing-task = -1;
}
}
if WindowFrame.drawn: WindowsControls {
width: root.controls-width;
x: root.width - self.width;
y: 0;
height: 32px;
}
}
if WindowFrame.drawn && !WindowFrame.maximized: Rectangle {
border-radius: root.corner-radius;
border-width: 1px;
border-color: root.dark-mode ? #ffffff26 : #00000026;
}
if WindowFrame.drawn && !WindowFrame.maximized: ResizeHandles { }
}