import { Button, ComboBox, Palette, ScrollView } from "std-widgets.slint";
import {
BarChart,
BubbleChart,
CalendarHeatmap,
ChartGaugeBand,
ChartSunburstItem,
ChartTreemapItem,
ChartWaterfallStep,
DumbbellChart,
FunnelChart,
GaugeChart,
SunburstChart,
TreemapChart,
WaterfallChart,
ChartDataset,
ChartEngine,
ChartLanguage,
ChartLegendPosition,
ChartPalette,
ChartSlice,
ChartTexts,
ChartTheme,
DoughnutChart,
HeatmapChart,
HorizontalBarChart,
LineChart,
PieChart,
PolarAreaChart,
RadarChart,
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 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;
}
}
}
// 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 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;
// 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);
// 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;
// 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;
}
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-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;
}
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;
}
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 == "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;
}
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: 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 == "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;
}
}
// 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);
}
}
}
Text {
horizontal-stretch: 1;
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 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 { }
}