Draw charts in the terminal
Use the charting helpers for sparklines, line and area charts, histograms, heatmaps, pie charts, and sequence diagrams that belong naturally in terminal output. They draw into Ultraviolet buffers and can be printed as text or placed inside a widget app.
The API is currently exported by package:artisanal/artisanal.dart.
Quick Start (Sparkline)
import 'package:artisanal/artisanal.dart' as chart;
void main() {
final values = [12, 18, 22, 19, 25, 29, 31, 28, 24, 26, 30, 34];
final lines = chart.renderChartLines(40, 3, (screen, area) {
chart.drawSparkline(
screen,
area,
values,
style: chart.uvStyleFromHex('#56ccf2'),
showGrid: true,
gridStyle: chart.uvStyleFromHex('#3b4252'),
);
});
print(lines.join('\n'));
}
Line Chart
import 'dart:math' as math;
import 'package:artisanal/artisanal.dart' as chart;
void main() {
final series = _series(60, seed: 13, min: 20, max: 90);
final lines = chart.renderChartLines(64, 12, (screen, area) {
chart.drawLineChart(
screen,
area,
series,
lineStyle: chart.uvStyleFromHex('#56ccf2'),
showGrid: true,
gridRows: 3,
gridCols: 3,
gridStyle: chart.uvStyleFromHex('#3b4252'),
xLabels: const ['-60s', '-30s', 'now'],
yLabels: const ['100%', '50%', '0%'],
labelStyle: chart.uvStyleFromHex('#3b4252'),
);
});
print(lines.join('\n'));
}
List<double> _series(
int count, {
required int seed,
required double min,
required double max,
}) {
final rng = math.Random(seed);
var value = (min + max) / 2;
return List<double>.generate(count, (i) {
value += (rng.nextDouble() * 2 - 1) * ((max - min) * 0.08);
value = value.clamp(min, max).toDouble();
return value;
}, growable: false);
}
Ribbon / Area Chart
import 'dart:math' as math;
import 'package:artisanal/artisanal.dart' as chart;
void main() {
final seriesA = _series(60, seed: 13, min: 20, max: 90);
final seriesB = _series(60, seed: 42, min: 10, max: 75);
final seriesC = _series(60, seed: 77, min: 5, max: 55);
final lines = chart.renderChartLines(64, 12, (screen, area) {
final styles = [
chart.uvStyleFromHex('#00bbf9'),
chart.uvStyleFromHex('#00f5d4'),
chart.uvStyleFromHex('#f15bb5'),
];
chart.drawRibbonChart(
screen,
area,
[seriesA, seriesB, seriesC],
styles: styles,
showGrid: true,
gridRows: 2,
gridStyle: chart.uvStyleFromHex('#3b4252'),
);
});
print(lines.join('\n'));
}
List<double> _series(
int count, {
required int seed,
required double min,
required double max,
}) {
final rng = math.Random(seed);
var value = (min + max) / 2;
return List<double>.generate(count, (i) {
value += (rng.nextDouble() * 2 - 1) * ((max - min) * 0.08);
value = value.clamp(min, max).toDouble();
return value;
}, growable: false);
}
Histogram
import 'dart:math' as math;
import 'package:artisanal/artisanal.dart' as chart;
void main() {
final values = _series(50, seed: 99, min: 0, max: 100);
final lines = chart.renderChartLines(64, 10, (screen, area) {
chart.drawHistogram(
screen,
area,
values,
barStyle: chart.uvStyleFromHex('#9b5de5'),
axisStyle: chart.uvStyleFromHex('#3b4252'),
showGrid: true,
gridRows: 3,
gridCols: 2,
gridStyle: chart.uvStyleFromHex('#3b4252'),
xLabels: const ['0', 'mid', 'max'],
yLabels: const ['100', '50', '0'],
labelStyle: chart.uvStyleFromHex('#3b4252'),
);
});
print(lines.join('\n'));
}
List<double> _series(
int count, {
required int seed,
required double min,
required double max,
}) {
final rng = math.Random(seed);
return List<double>.generate(
count,
(i) => rng.nextDouble() * (max - min) + min,
growable: false,
);
}
Heatmap
import 'dart:math' as math;
import 'package:artisanal/artisanal.dart' as chart;
void main() {
final grid = _heatmap(28, 12);
final lines = chart.renderChartLines(64, 12, (screen, area) {
chart.drawHeatmap(
screen,
area,
grid,
ramp: chart.ChartRamp.thermal(),
showGrid: true,
gridRows: 3,
gridCols: 4,
gridStyle: chart.uvStyleFromHex('#3b4252'),
);
});
print(lines.join('\n'));
}
List<List<double>> _heatmap(int width, int height) {
final rng = math.Random(7);
return List.generate(
height,
(y) => List<double>.generate(
width,
(x) => (math.sin(x * 0.22) + math.cos(y * 0.31)) * 0.25 +
rng.nextDouble() * 0.5,
growable: false,
),
growable: false,
);
}
Pie Chart
import 'package:artisanal/artisanal.dart' as chart;
void main() {
final lines = chart.renderChartLines(40, 12, (screen, area) {
final styles = [
chart.uvStyleFromHex('#ff006e'),
chart.uvStyleFromHex('#8338ec'),
chart.uvStyleFromHex('#3a86ff'),
chart.uvStyleFromHex('#ffbe0b'),
];
chart.drawPieChart(
screen,
area,
[30, 20, 15, 35],
styles: styles,
donut: true,
useBackground: true,
);
});
print(lines.join('\n'));
}
Notes:
- Pie/donut rendering uses sub-cell sampling with quarter/half block glyphs, which gives smoother circular edges than full-cell fill alone.
Crosshair Overlay
import 'package:artisanal/artisanal.dart' as chart;
void main() {
final lines = chart.renderChartLines(40, 12, (screen, area) {
chart.drawLineChart(screen, area, [12, 20, 18, 30, 22]);
// Standard mode: draw line glyphs on empty cells and tint chart cells.
chart.drawCrosshair(
screen,
area,
18,
5,
style: chart.uvStyleFromHex('#ffff66'),
);
});
print(lines.join('\n'));
}
Use drawOnEmpty: false when you only want to tint existing chart cells
(useful for compact pie/donut overlays):
chart.drawCrosshair(
screen,
area,
x,
y,
style: chart.uvStyleFromHex('#ffff66'),
drawOnEmpty: false,
);
Legend and Palette
Legend entries are typically shown in a framed panel so labels stay readable over dense chart regions.
import 'package:artisanal/artisanal.dart' as chart;
void main() {
final ramp = chart.ChartRamp.fromHexes(
['#0b132b', '#1b2a6b', '#3a86ff', '#88ffcc', '#ffbe0b', '#ff006e'],
);
final entries = List<chart.ChartLegendEntry>.generate(
ramp.colors.length,
(i) {
final t = ramp.colors.length == 1 ? 0.0 : i / (ramp.colors.length - 1);
final style = ramp.styleFor(t, background: true);
return chart.ChartLegendEntry(label: 'step $i', style: style);
},
growable: false,
);
final lines = chart.renderChartLines(30, 6, (screen, area) {
chart.drawLegend(screen, area, entries, columns: 2, rowGap: 0);
});
print(lines.join('\n'));
}
Sequence Diagrams
Sequence diagrams visualize interactions between actors/participants over time. Artisanal renders a supported subset of Mermaid sequence syntax as terminal cells; it is not a complete implementation of Mermaid's browser renderer.
Overview
Sequence diagrams are ideal for documenting:
- API call flows between services
- User authentication workflows
- Request/response patterns
- State machine transitions
- Protocol negotiations
The renderer produces clean ASCII-art output with Unicode box-drawing characters, suitable for terminal display.
API Options
String Convenience API
import 'package:artisanal/artisanal.dart' as chart;
void main() {
final text = chart.renderSequenceDiagram('''
sequenceDiagram
participant U as User
participant S as Server
U->>S: GET /api
S-->>U: 200 OK
''');
print(text);
}
UV Canvas API
For fine-grained control over rendering:
import 'package:artisanal/artisanal.dart' as chart;
import 'package:ultraviolet/ultraviolet.dart' show Canvas, rect;
void main() {
final source = '''
sequenceDiagram
participant A as Alice
participant B as Bob
A->>B: Hello Bob
''';
final diagram = chart.parseSequenceDiagram(source);
if (diagram != null) {
final layout = chart.layoutSequenceDiagram(diagram);
final canvas = Canvas(layout.width, layout.height);
try {
chart.drawSequenceDiagram(
canvas,
rect(0, 0, layout.width, layout.height),
diagram,
);
print(canvas.render());
} finally {
canvas.dispose();
}
}
}
Widget API
For TUI applications using artisanal_widgets:
import 'package:artisanal_widgets/charting.dart';
// In your widget tree:
SequenceDiagramChart(
mermaid: '''
sequenceDiagram
participant C as Client
participant S as Server
C->>S: Request
S-->>C: Response
''',
width: 80,
height: 20,
)
Mermaid Syntax
Supported Mermaid sequence diagram syntax:
Participant Declarations
sequenceDiagram
participant A as Alice
participant B as Bob
participant C
Participants can be declared with or without an alias using as. Undeclared
participants are auto-created when referenced in messages. actor A as Alice
uses a stick figure rather than a participant box. Labels support <br/> line
breaks.
Message Arrows
| Arrow | Style | Description |
|---|---|---|
->> | solid | Arrowhead |
-->> | dashed | Arrowhead |
-> | solid | Line without an arrowhead |
--> | dashed | Line without an arrowhead |
<<->> | solid | Bidirectional arrowheads |
<<-->> | dashed | Bidirectional arrowheads |
-x | solid | Lost message |
--x | dashed | Lost response |
-) | solid | Open asynchronous arrow |
--) | dashed | Open asynchronous arrow |
Activate/deactivate lifelines with + and -:
sequenceDiagram
A->>+B: Activate B
B-->>-A: Return and deactivate B
+ activates the receiver; - deactivates the sender. Explicit activate B /
deactivate B directives are also supported, including nested activation bars.
An unmatched deactivation is an error. An activation left open extends to the
end of the diagram.
Notes
sequenceDiagram
participant A as Alice
participant B as Bob
note right of A: Local note
note over A,B: Spanning both
Position notes with right of, left of, or over participants. Notes and
message labels support <br/> line breaks.
Control Fragments
sequenceDiagram
alt Authentication Success
A->>B: Authenticated
else Authentication Failed
A->>B: Rejected
end
loop Retry Logic
A->>B: Try again
B-->>A: Response
end
opt Cache Miss
A->>C: Fetch data
end
critical Database Write
A->>D: Commit
option Unavailable
D-->>A: Retry later
end
break Cancelled
A-->>B: Stop
end
Supported fragments: alt/else/end, loop, opt, critical/option/end,
par/and/end, break/end, and colored rect/end regions. Blocks can nest.
Branches must belong to the current block: else to alt, and to par,
and option to critical. Unmatched or unclosed blocks are errors.
Participant groups use box COLOR Label followed by declarations and end.
Colors precede labels; CSS names, rgb(...), and rgba(...) are supported.
Rect and group backgrounds preserve the messages and lifelines drawn inside.
Autonumbering
sequenceDiagram
autonumber
A->>B: First
B-->>A: Second
autonumber 10 5
A->>B: Tenth (starts at 10, increments by 5)
Artisanal Color Extensions
Use CSS color names for inline styling:
sequenceDiagram
A->>B: Blue message #blue
B-->>A: Red response #red
The inline arrow-color suffix and style declarations are Artisanal extensions;
do not assume every Mermaid implementation accepts them.
Customization
SequenceDiagramTheme
Configure visual appearance with a custom theme:
import 'package:artisanal/artisanal.dart' as chart;
import 'package:ultraviolet/ultraviolet.dart' show UvColor, UvStyle;
final customTheme = const chart.SequenceDiagramTheme(
participantBox: UvStyle(fg: UvColor.rgb(100, 150, 200)),
participantLabel: UvStyle(fg: UvColor.rgb(255, 255, 255)),
lifeline: UvStyle(fg: UvColor.rgb(80, 80, 80)),
request: UvStyle(fg: UvColor.rgb(100, 255, 150)),
response: UvStyle(fg: UvColor.rgb(255, 150, 100)),
note: UvStyle(fg: UvColor.rgb(200, 200, 150)),
noteBackground: UvStyle(bg: UvColor.rgb(40, 40, 40)),
fragment: UvStyle(fg: UvColor.rgb(150, 200, 150)),
fragmentLabel: UvStyle(fg: UvColor.rgb(150, 200, 150), bg: UvColor.rgb(30, 45, 35)),
group: UvStyle(fg: UvColor.rgb(120, 140, 130)),
rect: UvStyle(fg: UvColor.rgb(150, 150, 150), bg: UvColor.rgb(35, 35, 35)),
);
final text = chart.renderSequenceDiagram(source, theme: customTheme);
Participant Ordering
Participants appear in declaration order. For custom ordering, declare participants explicitly before messages:
sequenceDiagram
participant C as Database
participant B as Service
participant A as Client
A->>B: Call service
B->>C: Query database
RTL Support
The renderer respects implicit participant ordering. For right-to-left diagrams, declare participants in reverse order:
sequenceDiagram
participant C
participant B
participant A
A->>C: Flows right-to-left
Width/Height Constraints
Control diagram dimensions:
import 'package:artisanal/artisanal.dart' as chart;
final layout = chart.layoutSequenceDiagram(
diagram,
options: const chart.SequenceDiagramOptions(
minParticipantGap: 20,
),
);
For a string that fits a terminal width, use
renderSequenceDiagram(source, maxWidth: 80). Participant and message labels
wrap onto additional rows instead of being shortened with ellipses. Allocate
enough height for the resulting diagram. A canvas that cannot accommodate the
geometry gets an explicit diagnostic instead of a clipped partial diagram.
The convenience renderer rejects canvases above one million cells before
allocation; use narrower wrapping or a smaller diagram for large inputs.
Unknown sequence statements produce FormatException. The GitHub CLI displays
the diagnostic alongside literal source rather than replacing the whole app with
an error screen. Other Mermaid diagram types fall back to their code block.
Currently unsupported Mermaid features include participant stereotype/inline
JSON configuration, participant creation/destruction, half-arrows, central ()
connections, actor link menus, and decimal autonumber values. Mermaid entity-code
notation and HSL/HSLA colors are not implemented. No full Mermaid compatibility
or browser-identical appearance is claimed.
Examples
Basic Sequence Diagram
import 'package:artisanal/artisanal.dart' as chart;
void main() {
final text = chart.renderSequenceDiagram('''
sequenceDiagram
participant U as User
participant A as Auth Service
participant D as Database
U->>A: Login request
A->>D: Query user
D-->>A: User data
A-->>U: Auth token
''');
print(text);
}
Parsed Diagram with Custom Rendering
import 'package:artisanal/artisanal.dart' as chart;
import 'package:ultraviolet/ultraviolet.dart' show Canvas, rect, UvColor, UvStyle;
void main() {
final source = '''
sequenceDiagram
participant W as WebClient
participant S as Server
participant D as Database
autonumber
W->>S: POST /users
S->>+D: INSERT user
D-->>-S: OK
S-->>W: 201 Created
''';
final diagram = chart.parseSequenceDiagram(source);
if (diagram != null) {
final layout = chart.layoutSequenceDiagram(diagram);
final canvas = Canvas(layout.width, layout.height);
chart.drawSequenceDiagram(canvas, rect(0, 0, layout.width, layout.height), diagram);
print(canvas.render());
}
}
Widget Usage in TUI App
import 'package:artisanal/bubbles.dart';
void main() {
final diagram = SequenceDiagramModel(
mermaid: '''
sequenceDiagram
participant C as Client
participant S as Server
C->>S: GET /health
S-->>C: 200 OK
''',
width: 60,
height: 15,
);
print(diagram.view());
}
Theme Customization
import 'package:artisanal/artisanal.dart' as chart;
import 'package:ultraviolet/ultraviolet.dart' show UvColor, UvStyle;
void main() {
final darkTheme = const chart.SequenceDiagramTheme(
participantBox: UvStyle(fg: UvColor.rgb(80, 120, 100)),
participantLabel: UvStyle(fg: UvColor.rgb(200, 220, 210)),
lifeline: UvStyle(fg: UvColor.rgb(60, 80, 70)),
request: UvStyle(fg: UvColor.rgb(100, 255, 180)),
response: UvStyle(fg: UvColor.rgb(255, 200, 100)),
note: UvStyle(fg: UvColor.rgb(180, 200, 170), bg: UvColor.rgb(30, 45, 35)),
fragment: UvStyle(fg: UvColor.rgb(120, 180, 140)),
fragmentLabel: UvStyle(fg: UvColor.rgb(120, 180, 140), bg: UvColor.rgb(25, 40, 30)),
group: UvStyle(fg: UvColor.rgb(90, 110, 100)),
rect: UvStyle(fg: UvColor.rgb(140, 140, 140), bg: UvColor.rgb(35, 35, 35)),
);
final text = chart.renderSequenceDiagram('''
sequenceDiagram
participant A as Alice
participant B as Bob
A->>B: Hello #green
B-->>A: Hi back #blue
''', theme: darkTheme);
print(text);
}
Integration
UV-based Rendering
Use in any context where you have a UV Canvas:
import 'package:artisanal/artisanal.dart' as chart;
import 'package:ultraviolet/ultraviolet.dart' show Canvas, rect;
String renderDiagram(String mermaid) {
final diagram = chart.parseSequenceDiagram(mermaid);
if (diagram == null) return 'Invalid diagram';
final layout = chart.layoutSequenceDiagram(diagram);
final canvas = Canvas(layout.width, layout.height);
chart.drawSequenceDiagram(
canvas,
rect(0, 0, layout.width, layout.height),
diagram,
);
return canvas.render();
}
Widget-based TUI Apps
The SequenceDiagramChart widget integrates with artisanal_widgets:
import 'package:artisanal_widgets/charting.dart';
// Direct usage
SequenceDiagramChart(
mermaid: source,
width: 100,
height: 30,
)
Combine with other charting primitives in a single view:
import 'package:artisanal/artisanal.dart' as chart;
// Render multiple diagrams
for (final source in sources) {
print(chart.renderSequenceDiagram(source));
print(''); // blank line between
}
Where to go next
- docs_index.md - Full documentation index
- uv.md