Skip to content

Charts

Visualize data with column charts, bar charts, breakdown charts, donut/pie charts, time series line charts, and scatter plots. All chart widgets use a generic data-binding pattern that works with any data type, with convenience overloads for ad-hoc data using ChartItem. Use the standalone Legend widget to display labeled color swatches alongside any chart.

Column Chart

Display vertical columns for comparing values across categories.

Basic Usage

Use ChartItem for quick ad-hoc charts. The label and value selectors are pre-wired automatically:

csharp
using Hex1b;
using Hex1b.Charts;

var sales = new[]
{
    new ChartItem("Jan", 42),
    new ChartItem("Feb", 58),
    new ChartItem("Mar", 35),
    new ChartItem("Apr", 71),
    new ChartItem("May", 49),
    new ChartItem("Jun", 63),
};

await using var terminal = Hex1bTerminal.CreateBuilder()
    .WithHex1bApp((app, options) => ctx => ctx.ColumnChart(sales)
        .Title("Monthly Sales")
        .ShowValues()
    )
    .Build();

await terminal.RunAsync();

Multi-Series

Add multiple series for comparative analysis. Use .Layout() to control how series are arranged:

csharp
using Hex1b;
using Hex1b.Charts;
using Hex1b.Theming;

var data = new[]
{
    new SalesRecord("Jan", 50, 30, 20),
    new SalesRecord("Feb", 65, 40, 25),
    new SalesRecord("Mar", 45, 35, 30),
    new SalesRecord("Apr", 70, 50, 35),
};

var blue = Hex1bColor.FromRgb(66, 133, 244);
var red = Hex1bColor.FromRgb(234, 67, 53);
var green = Hex1bColor.FromRgb(52, 168, 83);

await using var terminal = Hex1bTerminal.CreateBuilder()
    .WithHex1bApp((app, options) => ctx => ctx.ColumnChart(data)
        .Label(s => s.Month)
        .Series("Electronics", s => s.Electronics, blue)
        .Series("Clothing", s => s.Clothing, red)
        .Series("Food", s => s.Food, green)
        .Layout(ChartLayout.Stacked)
        .Title("Sales by Category")
        .ShowValues()
    )
    .Build();

await terminal.RunAsync();

record SalesRecord(string Month, double Electronics, double Clothing, double Food);

Layouts

The ChartLayout enum controls how multi-series data is displayed:

LayoutDescription
SimpleSingle series — one column per category (default)
StackedSegments stacked end-to-end; column height = sum of values
Stacked100Stacked and normalized — every column fills to 100%
GroupedSeries placed side-by-side within each category

Bar Chart

Display horizontal bars — the same data-binding API as ColumnChart, oriented horizontally. Bars automatically scale to fill available vertical space with fractional block character edges for sub-cell precision.

csharp
using Hex1b;
using Hex1b.Charts;

var sales = new[]
{
    new ChartItem("Jan", 42),
    new ChartItem("Feb", 58),
    new ChartItem("Mar", 35),
    new ChartItem("Apr", 71),
    new ChartItem("May", 49),
    new ChartItem("Jun", 63),
};

await using var terminal = Hex1bTerminal.CreateBuilder()
    .WithHex1bApp((app, options) => ctx => ctx.BarChart(sales)
        .Title("Monthly Sales")
        .ShowValues()
    )
    .Build();

await terminal.RunAsync();

Bar charts support all the same layouts and multi-series features as column charts.

Breakdown Chart

Display a proportional segmented bar showing how parts contribute to a whole. Each segment's width is proportional to its value relative to the total. Unlike column and bar charts, breakdown charts only support a single series. Pair with a Legend widget to display labels, values, and percentages.

csharp
using Hex1b;
using Hex1b.Charts;

var diskUsage = new[]
{
    new ChartItem("Data", 42),
    new ChartItem("Packages", 18),
    new ChartItem("Temp", 9),
    new ChartItem("System", 15),
    new ChartItem("Other", 3),
};

await using var terminal = Hex1bTerminal.CreateBuilder()
    .WithHex1bApp((app, options) => ctx => ctx.VStack(v => [
        v.BreakdownChart(diskUsage)
            .Title("Disk Usage"),
        v.Legend(diskUsage)
            .ShowPercentages()
            .ShowValues(),
    ]))
    .Build();

await terminal.RunAsync();

Donut Chart

Display proportional data as colored arc segments around a ring. Each segment's arc length is proportional to its value relative to the total. The chart uses Unicode half-block characters (▀/▄) with independent foreground and background colors to achieve smooth circular shapes in the terminal.

Basic Usage

csharp
using Hex1b;
using Hex1b.Charts;

var languages = new[]
{
    new ChartItem("Go", 42),
    new ChartItem("Rust", 28),
    new ChartItem("C#", 30),
    new ChartItem("Python", 55),
    new ChartItem("Java", 38),
};

await using var terminal = Hex1bTerminal.CreateBuilder()
    .WithHex1bApp((app, options) => ctx => ctx.VStack(v => [
        v.DonutChart(languages)
            .Title("Language Popularity")
            .FillHeight(),
        v.Legend(languages)
            .ShowPercentages(),
    ]))
    .Build();

await terminal.RunAsync();

Pie Chart

Set .HoleSize(0) to fill the center and render a solid pie chart instead of a ring:

csharp
using Hex1b;
using Hex1b.Charts;

var expenses = new[]
{
    new ChartItem("Rent", 1200),
    new ChartItem("Food", 450),
    new ChartItem("Transport", 180),
    new ChartItem("Utilities", 120),
    new ChartItem("Entertainment", 200),
};

await using var terminal = Hex1bTerminal.CreateBuilder()
    .WithHex1bApp((app, options) => ctx => ctx.VStack(v => [
        v.DonutChart(expenses)
            .HoleSize(0)
            .Title("Monthly Expenses")
            .FillHeight(),
        v.Legend(expenses)
            .ShowValues()
            .ShowPercentages()
            .FormatValue(v => "$" + v.ToString("N0")),
    ]))
    .Build();

await terminal.RunAsync();

Hole Size

The .HoleSize() method controls the inner radius as a fraction of the outer radius:

ValueEffect
0.0Solid pie chart (no hole)
0.3Thick ring with small center hole
0.5Classic donut (default)
0.8Thin ring with large center hole

Legend

The Legend widget displays labeled color swatches that can be placed anywhere in the widget tree. It uses the same data-binding pattern as chart widgets, making it easy to pair a legend with any chart. The legend uses a colored square swatch (■) for each item.

csharp
using Hex1b;
using Hex1b.Charts;

var data = new[]
{
    new ChartItem("Engineering", 2_450_000),
    new ChartItem("Marketing", 875_000),
    new ChartItem("Sales", 1_200_000),
    new ChartItem("Operations", 340_000),
};

await using var terminal = Hex1bTerminal.CreateBuilder()
    .WithHex1bApp((app, options) => ctx => ctx.VStack(v => [
        v.HStack(h => [
            h.DonutChart(data)
                .Title("Budget Allocation")
                .FillHeight(),
            h.Legend(data)
                .ShowValues()
                .ShowPercentages()
                .FormatValue(v => "$" + (v / 1_000).ToString("N0") + "K"),
        ]),
        v.BreakdownChart(data)
            .Title("Budget Breakdown"),
        v.Legend(data)
            .Horizontal()
            .ShowPercentages(),
    ]))
    .Build();

await terminal.RunAsync();

Orientation

By default, legend items are displayed vertically (one item per row). Call .Horizontal() to render all items on a single row, separated by spacing — useful when placing a legend below a chart.

Display Options

MethodDescription
.ShowValues()Display the absolute numeric value next to each label
.ShowPercentages()Display the percentage of each value relative to the total
.FormatValue(double → string)Custom formatter for displayed values
.Horizontal()Render items on a single row instead of one per row

Time Series Chart

Plot one or more value series over an ordered X axis using braille characters for sub-cell precision line drawing. Each terminal cell provides a 2×4 dot grid (8 sub-pixels).

Basic Usage

csharp
using Hex1b;
using Hex1b.Charts;

var data = new[]
{
    new ChartItem("Jan", 2), new ChartItem("Feb", 4),
    new ChartItem("Mar", 9), new ChartItem("Apr", 14),
    new ChartItem("May", 18), new ChartItem("Jun", 22),
    new ChartItem("Jul", 25), new ChartItem("Aug", 24),
    new ChartItem("Sep", 20), new ChartItem("Oct", 14),
    new ChartItem("Nov", 8), new ChartItem("Dec", 3),
};

await using var terminal = Hex1bTerminal.CreateBuilder()
    .WithHex1bApp((app, options) => ctx => ctx.TimeSeriesChart(data)
        .Title("Monthly Temperature (°C)")
        .ShowGridLines()
    )
    .Build();

await terminal.RunAsync();

Multi-Series

Add multiple series to compare trends. Lines are drawn independently and colors blend at intersection points:

csharp
using Hex1b;
using Hex1b.Charts;
using Hex1b.Theming;

var data = new[]
{
    new FinRec("Jan", 150, 90), new FinRec("Feb", 130, 110),
    new FinRec("Mar", 105, 120), new FinRec("Apr", 95, 135),
    new FinRec("May", 120, 125), new FinRec("Jun", 160, 110),
    new FinRec("Jul", 175, 130), new FinRec("Aug", 140, 145),
    new FinRec("Sep", 110, 150), new FinRec("Oct", 130, 125),
    new FinRec("Nov", 170, 115), new FinRec("Dec", 190, 100),
};

var blue = Hex1bColor.FromRgb(66, 133, 244);
var red = Hex1bColor.FromRgb(234, 67, 53);

await using var terminal = Hex1bTerminal.CreateBuilder()
    .WithHex1bApp((app, options) => ctx => ctx.TimeSeriesChart(data)
        .Label(d => d.Month)
        .Series("Revenue", d => d.Revenue, blue)
        .Series("Expenses", d => d.Expenses, red)
        .Title("Revenue vs Expenses")
        .ShowGridLines()
    )
    .Build();

await terminal.RunAsync();

record FinRec(string Month, double Revenue, double Expenses);

Area Fill

Use .Fill() to shade the area below the line. FillStyle.Braille fills with braille dots; FillStyle.Solid uses block characters:

csharp
using Hex1b;
using Hex1b.Charts;

var data = new[]
{
    new ChartItem("00:00", 120), new ChartItem("04:00", 60),
    new ChartItem("08:00", 450), new ChartItem("12:00", 580),
    new ChartItem("16:00", 490), new ChartItem("20:00", 310),
};

await using var terminal = Hex1bTerminal.CreateBuilder()
    .WithHex1bApp((app, options) => ctx => ctx.TimeSeriesChart(data)
        .Fill(FillStyle.Braille)
        .Title("Request Volume (24h)")
        .ShowGridLines()
    )
    .Build();

await terminal.RunAsync();

Stacked Area

Use .Layout(ChartLayout.Stacked) with .Fill() to create stacked area charts. Each series' area sits on top of the previous, showing cumulative totals. Switch between FillStyle.Braille (dot-based) and FillStyle.Solid (block-character) rendering:

csharp
using Hex1b;
using Hex1b.Charts;
using Hex1b.Theming;

var data = new[]
{
    new RegionSales("Jan", 40, 25, 15), new RegionSales("Feb", 45, 30, 20),
    new RegionSales("Mar", 38, 35, 25), new RegionSales("Apr", 55, 32, 18),
    new RegionSales("May", 48, 40, 30), new RegionSales("Jun", 60, 38, 22),
    new RegionSales("Jul", 52, 45, 28), new RegionSales("Aug", 58, 42, 35),
    new RegionSales("Sep", 50, 48, 30), new RegionSales("Oct", 65, 40, 25),
    new RegionSales("Nov", 55, 50, 32), new RegionSales("Dec", 70, 45, 28),
};

var blue = Hex1bColor.FromRgb(66, 133, 244);
var red = Hex1bColor.FromRgb(234, 67, 53);
var green = Hex1bColor.FromRgb(52, 168, 83);

await using var terminal = Hex1bTerminal.CreateBuilder()
    .WithHex1bApp((app, options) => ctx => ctx.TimeSeriesChart(data)
        .Label(d => d.Month)
        .Series("North", d => d.North, blue)
        .Series("South", d => d.South, red)
        .Series("West", d => d.West, green)
        .Layout(ChartLayout.Stacked)
        .Fill(FillStyle.Braille)
        .Title("Regional Sales (Stacked)")
        .ShowGridLines()
    )
    .Build();

await terminal.RunAsync();

record RegionSales(string Month, double North, double South, double West);

Scatter Chart

Plot independent (x, y) data points using braille dots. Points are NOT connected by lines — each plots a single dot. Both axes are numeric.

Basic Usage

csharp
using Hex1b;
using Hex1b.Charts;

var random = new Random(42);
var data = Enumerable.Range(0, 60).Select(_ =>
{
    var height = 150 + random.NextDouble() * 40;
    var weight = (height - 100) * 0.8 + random.NextDouble() * 20 - 10;
    return new Measurement(height, weight);
}).ToArray();

await using var terminal = Hex1bTerminal.CreateBuilder()
    .WithHex1bApp((app, options) => ctx => ctx.ScatterChart(data)
        .X(d => d.Height)
        .Y(d => d.Weight)
        .Title("Height vs Weight")
        .ShowGridLines()
    )
    .Build();

await terminal.RunAsync();

record Measurement(double Height, double Weight);

Grouped Series

Use .GroupBy() to color-code data points by category:

csharp
using Hex1b;
using Hex1b.Charts;

var random = new Random(42);
var data = Enumerable.Range(0, 90).Select(i =>
{
    var group = i < 30 ? "Young" : i < 60 ? "Middle" : "Senior";
    var income = (group switch { "Young" => 30, "Middle" => 55, _ => 45 })
        + random.NextDouble() * 30;
    var spending = income * (0.5 + random.NextDouble() * 0.4);
    return new DemoPoint(income, spending, group);
}).ToArray();

await using var terminal = Hex1bTerminal.CreateBuilder()
    .WithHex1bApp((app, options) => ctx => ctx.ScatterChart(data)
        .X(d => d.Income)
        .Y(d => d.Spending)
        .GroupBy(d => d.Group)
        .Title("Income vs Spending by Age Group")
        .ShowGridLines()
    )
    .Build();

await terminal.RunAsync();

record DemoPoint(double Income, double Spending, string Group);

Value Formatting

All chart types use smart default formatting for numeric labels — values are displayed with grouped digits, and large values use compact suffixes:

ValueDefault Display
12341,234
1234512.3K
15000001.5M
25000000002.5B
0.420.42

Custom Formatters

Override the default formatting with .FormatValue() to display values in any format — currency, percentages, units, etc. Every chart type supports this:

csharp
using Hex1b;
using Hex1b.Charts;
using Hex1b.Theming;

var budgets = new[]
{
    new Department("Engineering", 2_450_000),
    new Department("Marketing", 875_000),
    new Department("Sales", 1_200_000),
    new Department("Operations", 340_000),
};

var blue = Hex1bColor.FromRgb(66, 133, 244);
var red = Hex1bColor.FromRgb(234, 67, 53);

await using var terminal = Hex1bTerminal.CreateBuilder()
    .WithHex1bApp((app, options) => ctx => ctx.VStack(v => [
        v.ColumnChart(budgets)
            .Label(d => d.Name)
            .Value(d => d.Budget)
            .Title("Department Budgets")
            .ShowValues()
            .FormatValue(v => "$" + (v / 1_000_000).ToString("F1") + "M"),
        v.BreakdownChart(budgets)
            .Label(d => d.Name)
            .Value(d => d.Budget)
            .Title("Budget Allocation"),
        v.Legend(budgets)
            .Label(d => d.Name)
            .Value(d => d.Budget)
            .ShowValues()
            .ShowPercentages()
            .FormatValue(v => "$" + (v / 1_000).ToString("N0") + "K"),
    ]))
    .Build();

await terminal.RunAsync();

record Department(string Name, double Budget);

Column, bar, and time series charts apply the formatter to Y-axis labels and data point values. Scatter charts provide separate .FormatX() and .FormatY() for each axis. The Legend widget applies the formatter to values displayed alongside each label.

Generic Data Binding

All chart widgets are generic (ColumnChartWidget<T>, BarChartWidget<T>, BreakdownChartWidget<T>, DonutChartWidget<T>, TimeSeriesChartWidget<T>, ScatterChartWidget<T>). Bind any data type by providing selector functions:

csharp
using Hex1b;
using Hex1b.Charts;
using Hex1b.Theming;

var metrics = new[]
{
    new ServerMetric("web-01", 78.5, 62.3),
    new ServerMetric("web-02", 45.1, 38.7),
    new ServerMetric("db-01", 92.0, 85.4),
    new ServerMetric("cache", 23.8, 51.2),
};

await using var terminal = Hex1bTerminal.CreateBuilder()
    .WithHex1bApp((app, options) => ctx => ctx.BarChart(metrics)
        .Label(m =&gt; m.Host)
        .Series("CPU %", m =&gt; m.Cpu, Hex1bColor.FromRgb(234, 67, 53))
        .Series("Memory %", m =&gt; m.Memory, Hex1bColor.FromRgb(66, 133, 244))
        .Layout(ChartLayout.Grouped)
        .Title("Server Resources")
        .Range(0, 100)
    )
    .Build();

await terminal.RunAsync();

record ServerMetric(string Host, double Cpu, double Memory);

Data Binding Approaches

MethodUse Case
.Label(T → string)Extract category label from each item
.Value(T → double)Single-series value extraction
.Series(name, T → double, color?)Multiple named series from flat/wide data
.GroupBy(T → string)Pivot long-form data into series at runtime
.X(T → double)X-axis numeric value (scatter charts)
.Y(T → double)Y-axis numeric value (scatter charts)

Single series: Use .Label() and .Value() together.

Multi-series (flat/wide): Use .Label() and multiple .Series() calls. Each series extracts a different property from the same data items.

Multi-series (long/normalized): Use .Label(), .Value(), and .GroupBy(). The group-by selector determines which series each data point belongs to.

Scatter charts: Use .X() and .Y() for numeric axes. Optionally add .GroupBy() for color-coded series.

Animation

Charts work well with live data. Use .RedrawAfter(milliseconds) to schedule periodic re-renders. The widget builder runs on each frame, so update your data source and the chart re-draws automatically:

csharp
var liveData = new List<ChartItem>();
var lastUpdate = DateTime.MinValue;

var terminal = Hex1bTerminal.CreateBuilder()
    .WithHex1bApp((app, options) => ctx =>
    {
        // Throttle data updates to one per interval
        if ((DateTime.Now - lastUpdate).TotalMilliseconds >= 450)
        {
            lastUpdate = DateTime.Now;
            liveData.Add(new ChartItem(DateTime.Now.ToString("ss"), Random.Shared.Next(100)));
            if (liveData.Count > 40) liveData.RemoveAt(0);
        }

        return ctx.TimeSeriesChart(liveData.ToArray())
            .Fill(FillStyle.Braille)
            .Title("Live Data")
            .RedrawAfter(500);
    })
    .Build();

TIP

RedrawAfter is a one-shot timer — it schedules a single re-render after the specified delay. Since the widget builder calls it on every frame, the effect is continuous animation. Throttle your data mutations to avoid adding points on re-renders triggered by resize or input events.

API Reference

Common Fluent Methods (Column & Bar)

MethodDescription
.Label(T → string)Category label selector
.Value(T → double)Single-series value selector
.Series(name, T → double, color?)Add a named series
.GroupBy(T → string)Pivot long-form data into series
.Layout(ChartLayout)Set layout: Simple, Stacked, Stacked100, Grouped
.Title(string)Chart title
.ShowValues(bool)Display numeric values
.ShowGridLines(bool)Display grid lines
.Min(double)Explicit axis minimum
.Max(double)Explicit axis maximum
.Range(min, max)Explicit axis range
.FormatValue(double → string)Custom value formatter

Breakdown-Specific Methods

MethodDescription
.Label(T → string)Segment label selector
.Value(T → double)Segment value selector
.Title(string)Chart title

Donut Chart Methods

MethodDescription
.Label(T → string)Segment label selector
.Value(T → double)Segment value selector
.Title(string)Chart title
.HoleSize(double)Inner radius ratio (0.0 = pie, 0.5 = donut, default)

Legend Methods

MethodDescription
.Label(T → string)Item label selector
.Value(T → double)Item value selector
.ShowValues(bool)Display absolute values
.ShowPercentages(bool)Display percentages
.FormatValue(double → string)Custom value formatter
.Horizontal(bool)Render items on a single row

Time Series Methods

MethodDescription
.Label(T → string)X-axis label selector
.Value(T → double)Single-series Y value selector
.Series(name, T → double, color?)Add a named Y series
.Fill(FillStyle)Area fill: Solid (block chars) or Braille (dot fill)
.Layout(ChartLayout)Stacked for cumulative stacked areas
.Title(string)Chart title
.ShowValues(bool)Display Y values at data points
.ShowGridLines(bool)Display horizontal grid lines
.Min(double) / .Max(double)Explicit Y-axis range
.Range(min, max)Explicit Y-axis range
.FormatValue(double → string)Custom Y value formatter

Scatter Chart Methods

MethodDescription
.X(T → double)X-axis value selector
.Y(T → double)Y-axis value selector
.GroupBy(T → string)Series grouping selector
.Title(string)Chart title
.ShowGridLines(bool)Display grid lines
.XRange(min, max)Explicit X-axis range
.YRange(min, max)Explicit Y-axis range
.FormatX(double → string)Custom X value formatter
.FormatY(double → string)Custom Y value formatter

Rendering Details

Charts render using the Surface API internally. They use Unicode block characters for sub-cell precision:

  • Column charts: Vertical blocks (▁▂▃▄▅▆▇█) for fractional top edges
  • Bar charts: Horizontal blocks (▏▎▍▌▋▊▉█) for fractional right edges, vertical blocks for bar height edges
  • Donut/pie charts: Half-block characters (▀▄) with independent foreground/background colors for 2× vertical resolution, producing smooth circular shapes
  • Time series & scatter charts: Braille characters (U+2800–U+28FF) with a 2×4 dot grid per cell for sub-cell point and line plotting
  • Stacked segments: Complementary foreground/background colors at segment boundaries for smooth visual transitions
  • Table — For displaying the underlying data alongside charts
  • Surface — The low-level rendering API charts are built on
  • Progress — For single-value progress visualization
  • TabPanel — For organizing multiple charts into tabs

Released under the MIT License.