import { Button, CheckBox, ComboBox, ListView, Palette } from "std-widgets.slint";
import {
BarChart,
BubbleChart,
CalendarHeatmap,
ChartGaugeBand,
ChartSunburstItem,
ChartTreemapItem,
ChartWaterfallStep,
DumbbellChart,
FunnelChart,
GaugeChart,
SunburstChart,
TreemapChart,
WaterfallChart,
ChartDataset,
ChartEngine,
ChartLegendPosition,
ChartPalette,
ChartSlice,
ChartTheme,
DoughnutChart,
HeatmapChart,
HorizontalBarChart,
LineChart,
PieChart,
PolarAreaChart,
RadarChart,
ScatterChart,
TimeSeriesChart,
} from "@slintplot";
export { ChartEngine }
// 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;
in property <bool> dark-mode;
// Taller rows, for touch screens.
in property <bool> roomy;
callback select(id: string);
ListView {
for entry in root.samples: Rectangle {
height: (entry.header ? 34px : 28px) + (root.roomy && !entry.header ? 12px : 0px);
background: entry.id == root.sample ? (root.dark-mode ? #ffffff1a : #0000000f) : transparent;
Text {
x: entry.header ? 14px : 22px;
y: entry.header ? parent.height - self.height - 4px : (parent.height - self.height) / 2;
text: entry.title;
color: entry.header ? ChartTheme.text-color : (entry.id == root.sample ? Palette.foreground : ChartTheme.text-color);
font-size: entry.header ? 11px : 13px + (root.roomy ? 2px : 0px);
font-weight: entry.header || entry.id == root.sample ? 700 : 400;
letter-spacing: entry.header ? 0.6px : 0;
}
if !entry.header: TouchArea {
mouse-cursor: pointer;
clicked => {
root.select(entry.id);
}
}
}
}
}
// 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 ? (Palette.color-scheme == ColorScheme.dark ? #ffffff14 : #0000000d) : 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: Palette.foreground;
}
touch := TouchArea {
mouse-cursor: pointer;
clicked => {
root.clicked();
}
}
}
// 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;
background: ChartTheme.background;
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 data of the current sample.
in property <[string]> labels;
in-out property <[ChartDataset]> datasets;
in-out property <[ChartSlice]> slices;
in property <[string]> row-labels;
in property <[float]> grid;
in-out property <[ChartTreemapItem]> tree;
in-out property <[ChartSunburstItem]> sun;
in property <[ChartWaterfallStep]> steps;
in property <float> gauge-value;
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;
// 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;
callback select(id: string);
callback action(index: int);
// Samples with their own light and dark colors update them.
callback dark-mode-changed();
// 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.dark-mode-changed();
}
// 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();
}
changed narrow => {
self.fit-text();
}
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;
}
HorizontalLayout {
if !root.narrow && root.show-samples: SampleList {
width: 230px;
background: root.dark-mode ? #ffffff08 : #00000006;
samples: root.samples;
sample: root.sample;
dark-mode: root.dark-mode;
select(id) => {
root.select(id);
}
}
VerticalLayout {
padding: root.narrow ? 10px : 20px;
padding-top: root.narrow ? 8px : 14px;
spacing: root.narrow ? 8px : 10px;
HorizontalLayout {
spacing: 10px;
MenuButton {
y: (parent.height - self.height) / 2;
clicked => {
if root.narrow {
root.samples-open = !root.samples-open;
} else {
root.show-samples = !root.show-samples;
}
}
}
Text {
text: root.sample-title;
font-size: root.narrow ? 18px : 22px;
font-weight: 700;
color: Palette.foreground;
vertical-alignment: center;
horizontal-stretch: 1;
overflow: elide;
}
if !root.narrow: Text {
text: "Theme";
color: ChartTheme.text-color;
vertical-alignment: center;
}
if !root.narrow: ComboBox {
model: ["Modern", "Material", "Corporate", "Mono", "Pastel"];
current-index <=> root.theme;
}
// The calendar heatmap's color, separate from the theme.
if !root.narrow && root.sample == "calendar": Text {
text: "Colors";
color: ChartTheme.text-color;
vertical-alignment: center;
}
if !root.narrow && root.sample == "calendar": ComboBox {
model: ["Theme", "GitHub green", "Blue", "Orange", "Purple", "Red"];
current-index <=> root.calendar-colors;
}
// The active palette, in the order datasets use it.
if !root.narrow: HorizontalLayout {
spacing: 3px;
alignment: center;
for swatch in ChartTheme.colors: VerticalLayout {
alignment: center;
Rectangle {
width: 14px;
height: 14px;
border-radius: 3px;
background: swatch.with-alpha(ChartTheme.fill-opacity);
border-color: swatch;
border-width: 1px;
}
}
}
if !root.narrow: CheckBox {
text: "Dark mode";
checked <=> root.dark-mode;
}
}
// The pickers on a row of their own on narrow windows.
if root.narrow: HorizontalLayout {
spacing: 8px;
ComboBox {
model: ["Modern", "Material", "Corporate", "Mono", "Pastel"];
current-index <=> root.theme;
}
CheckBox {
text: "Dark";
checked <=> root.dark-mode;
}
}
if root.narrow && root.sample == "calendar": HorizontalLayout {
spacing: 8px;
Text {
text: "Colors";
color: ChartTheme.text-color;
vertical-alignment: center;
}
ComboBox {
model: ["Theme", "GitHub green", "Blue", "Orange", "Purple", "Red"];
current-index <=> root.calendar-colors;
}
}
Text {
text: root.description;
color: ChartTheme.text-color;
wrap: word-wrap;
}
// The chart of the current sample.
Rectangle {
vertical-stretch: 1;
border-radius: 8px;
border-width: 1px;
border-color: ChartTheme.grid-color;
VerticalLayout {
padding: root.narrow ? 6px : 12px;
if root.sample == "bar-vertical": BarChart {
title: "Vertical bar chart";
labels: root.labels;
datasets <=> root.datasets;
}
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;
}
if root.sample == "bar-stacked": BarChart {
title: "Stacked bar chart";
labels: root.labels;
datasets <=> root.datasets;
stacked: true;
}
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;
}
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;
}
if root.sample == "line-points": LineChart {
title: "Line chart without point markers";
labels: root.labels;
datasets <=> root.datasets;
show-points: false;
}
if root.sample == "combo-bar-line": BarChart {
title: "Combo bar and line chart";
labels: root.labels;
datasets <=> root.datasets;
}
if root.sample == "stacked-bar-line": BarChart {
title: "Stacked bar and line chart";
labels: root.labels;
datasets <=> root.datasets;
stacked: true;
}
if root.sample == "area-stacked": LineChart {
title: "Stacked area chart";
labels: root.labels;
datasets <=> root.datasets;
stacked: true;
}
if root.sample == "pie": PieChart {
title: "Pie chart";
slices <=> root.slices;
}
if root.sample == "doughnut": DoughnutChart {
title: "Doughnut chart";
slices <=> root.slices;
}
if root.sample == "polar-area": PolarAreaChart {
title: "Polar area chart";
slices <=> root.slices;
}
if root.sample == "radar" || root.sample == "area-radar": RadarChart {
title: root.sample-title;
labels: root.labels;
datasets <=> root.datasets;
}
if root.sample == "scatter": ScatterChart {
title: "Scatter chart";
datasets <=> root.datasets;
zoom-enabled: true;
}
if root.sample == "bubble": BubbleChart {
title: "Bubble chart";
datasets <=> root.datasets;
zoom-enabled: true;
}
if root.sample == "legend-position": LineChart {
title: "Legend position";
labels: root.labels;
datasets <=> root.datasets;
legend-position: root.legend-position;
}
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;
}
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";
}
if root.sample == "axes-log": LineChart {
title: "Users";
labels: root.labels;
datasets <=> root.datasets;
logarithmic: true;
y-title: "Users (log scale)";
}
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;
}
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;
}
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;
}
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;
}
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";
}
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;
}
if root.sample == "treemap": TreemapChart {
title: "World population by country (millions)";
items <=> root.tree;
value-suffix: " M";
}
if root.sample == "sunburst": SunburstChart {
title: "World population by region (millions)";
items <=> root.sun;
value-suffix: " M";
}
if root.sample == "funnel": FunnelChart {
title: "Visitors to purchases, last 30 days";
stages <=> root.slices;
}
if root.sample == "waterfall": WaterfallChart {
title: "Profit and loss";
steps: root.steps;
show-values: true;
value-prefix: "$";
value-suffix: "k";
}
if root.sample == "dumbbell": DumbbellChart {
title: "Life expectancy at birth (years)";
labels: root.labels;
datasets <=> root.datasets;
show-values: true;
}
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: "%";
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: "%";
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 == "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];
}
if root.sample == "pie-labels": PieChart {
title: "Pie chart with data labels";
slices <=> root.slices;
show-percentages: true;
}
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;
}
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;
}
}
}
// Three buttons per row on narrow windows.
HorizontalLayout {
alignment: start;
GridLayout {
spacing: 8px;
for name[index] in root.actions: Button {
row: root.narrow ? index / 3 : 0;
col: root.narrow ? mod(index, 3) : index;
text: name;
clicked => {
root.action(index);
}
}
}
}
Text {
text: root.status;
color: ChartTheme.text-color;
font-size: 12px;
}
}
}
// 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;
background: ChartTheme.background;
samples: root.samples;
sample: root.sample;
dark-mode: root.dark-mode;
roomy: true;
select(id) => {
root.samples-open = false;
root.select(id);
}
}
}
}