FactFoundry.Blazor.Charts 1.2.0

dotnet add package FactFoundry.Blazor.Charts --version 1.2.0
                    
NuGet\Install-Package FactFoundry.Blazor.Charts -Version 1.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="FactFoundry.Blazor.Charts" Version="1.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="FactFoundry.Blazor.Charts" Version="1.2.0" />
                    
Directory.Packages.props
<PackageReference Include="FactFoundry.Blazor.Charts" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add FactFoundry.Blazor.Charts --version 1.2.0
                    
#r "nuget: FactFoundry.Blazor.Charts, 1.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package FactFoundry.Blazor.Charts@1.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=FactFoundry.Blazor.Charts&version=1.2.0
                    
Install as a Cake Addin
#tool nuget:?package=FactFoundry.Blazor.Charts&version=1.2.0
                    
Install as a Cake Tool

FactFoundry.Blazor.Charts

A zero-dependency, pure .NET charting library for Blazor applications. Charts are rendered as SVG via Razor components — no JavaScript, no interop, no external dependencies.

Features

  • Line Chart — single/multi-series, smooth curves, area fill, crosshair tooltips
  • Bar Chart — vertical/horizontal, grouped/stacked, crosshair tooltips
  • Donut Chart — configurable inner radius, "Top N + Other" grouping, center label, leader line labels
  • Pie Chart — proportional segments with leader line labels
  • World Map Chart — choropleth heatmap with 174 countries, multi-stop color scales
  • Bar List — ranked "top N" breakdown with a bar filling behind each row, share %, status dots
  • Sparkline — bare inline trend (no axes) for KPI tiles; legible down to ~80×30
  • Commit Graph — git-style branch/commit lane view; color-coded lanes, merge curves, ref badges, hover tooltips
  • Calendar Heatmap — GitHub-contributions-style day grid; intensity buckets, month/weekday labels, legend, hover tooltips
  • Sankey Chart — multi-column flow diagram; ribbons sized by value, columns derived from the links (or pinned), hover-to-isolate
  • Matrix Chart — rows × columns grid / heatmap; cells colored by row (or column), optional row/column totals, hover crosshair
  • Theming — built-in light/dark presets, cascading theme provider, fully customizable

All charts include legends, hover tooltips, and accessible SVG output — role="img" with a <title> name and a data-aware <desc> description (override via the Description parameter). Works in both Blazor Server and Blazor WebAssembly.

Installation

dotnet add package FactFoundry.Blazor.Charts

Quick Start

Add the using directives to your _Imports.razor:

@using FactFoundry.Blazor.Charts.Components
@using FactFoundry.Blazor.Charts.Models

Line Chart

<LineChart Title="Sessions Over Time"
           Series="@series"
           XAxisLabels="@labels"
           SmoothLines="true"
           CrosshairTooltip="true"
           Width="700" Height="350" />

@code {
    private List<string> labels = ["Mon", "Tue", "Wed", "Thu", "Fri"];

    private List<ChartSeries> series =
    [
        new() { Label = "Chrome", Color = "#3B82F6", Values = [120, 135, 142, 128, 155] },
        new() { Label = "Firefox", Color = "#F59E0B", Values = [80, 75, 82, 90, 85] }
    ];
}

Bar Chart

<BarChart Title="Revenue by Quarter"
          Series="@revenue"
          XAxisLabels="@quarters"
          Stacked="false"
          Horizontal="false"
          CrosshairTooltip="true"
          Width="700" Height="350" />

@code {
    private List<string> quarters = ["Q1", "Q2", "Q3", "Q4"];

    private List<ChartSeries> revenue =
    [
        new() { Label = "Product A", Color = "#3B82F6", Values = [120, 145, 160, 180] },
        new() { Label = "Product B", Color = "#10B981", Values = [90, 110, 105, 130] }
    ];
}

Donut Chart

<DonutChart Title="Sessions by Browser"
            Data="@browserData"
            MaxSegments="5"
            ShowLabels="true"
            ShowLegend="true"
            CenterLabel="Total"
            Width="700" Height="400" />

@code {
    private List<ChartSegment> browserData =
    [
        new() { Label = "Chrome", Value = 4521 },
        new() { Label = "Safari", Value = 1832 },
        new() { Label = "Firefox", Value = 987 },
        new() { Label = "Edge", Value = 645 }
    ];
}

Pie Chart

<PieChart Title="Sessions by Device"
          Data="@deviceData"
          ShowLabels="true"
          ShowLegend="true"
          Width="700" Height="400" />

World Map Chart

@using FactFoundry.Blazor.Charts.Geo

<WorldMapChart Title="Users by Country"
               Data="@countryData"
               ColorScale="@(new[] { "#dbeafe", "#2563eb" })"
               ShowLegend="true"
               Width="900" Height="450" />

@code {
    private List<MapDataPoint> countryData =
    [
        new() { CountryCode = "US", Value = 45000 },
        new() { CountryCode = "GB", Value = 12000 },
        new() { CountryCode = "DE", Value = 9500 },
        new() { CountryCode = "BR", Value = 5200 },
        new() { CountryCode = "JP", Value = 4200 }
    ];
}

Multi-stop color scales are supported:

<WorldMapChart ColorScale="@(new[] { "#fef08a", "#f97316", "#dc2626" })" ... />

Bar List

A ranked "top N" breakdown — a bar fills behind each row, proportional to its value. Use it for categorical breakdowns (top pages, browsers, countries) where a donut wastes space. See Building an analytics dashboard for the full pattern (KPI tiles, area-trend hero, and bar lists together).

<BarList Title="Top pages"
         Caption="by page views"
         Data="@pages"
         MaxItems="7"
         ShowShare="true"
         MoreText="View all pages"
         MoreHref="/analytics/pages" />

@code {
    private List<ChartSegment> pages =
    [
        new() { Label = "/", Value = 8 },
        new() { Label = "/docs/api", Value = 7 },
        new() { Label = "/download", Value = 6 },
        new() { Label = "/changelog", Value = 6 },
        new() { Label = "/features", Value = 5 }
    ];
}

Commit Graph

A git-style branch/commit lane view — commits as dots, branches as color-coded lanes, merges and branch points as curves. The component knows nothing about git; it takes any DAG of nodes-with-parents (CommitNode) already in display order (newest first), which is exactly what a LibGit2Sharp topological/time-sorted walk yields. Hover a row for the author, short id and date; wire OnCommitClick to select a commit.

<CommitGraph Commits="@history" OnCommitClick="c => selected = c" />

@code {
    private List<CommitNode> history =
    [
        new() { Id = "9f3a1c", ParentIds = ["7b2d4f", "5c8e9a"], Message = "Merge feature/x",
                Author = "Kevin", Date = DateTimeOffset.Now, Refs = ["HEAD -> main"] },
        new() { Id = "5c8e9a", ParentIds = ["7b2d4f"], Message = "Feature work",
                Author = "Kevin", Refs = ["feature/x"] },
        new() { Id = "7b2d4f", ParentIds = [], Message = "Initial commit", Refs = ["tag: v1.0.0"] }
    ];
}

A ref prefixed tag: (the git log --decorate convention) is styled as a tag; every other ref is styled as a branch.

Sizing. FontSize (default 12) is the one knob for scale: row height, lane width, dot radius, and the badge and tooltip typography all derive from it, so the whole graph stays in proportion. Set it to match the surrounding UI. RowHeight, LaneWidth, and DotRadius still accept explicit overrides. Width controls how much horizontal room the message column gets — raise it to fit longer messages, not to enlarge the text (that's FontSize). Unlike the other charts, a responsive CommitGraph fills a narrower container but caps at Width pixels rather than magnifying in a wide one, so text never balloons when the panel stretches.

Calendar Heatmap

A GitHub-contributions-style grid — one cell per day in week columns, colored by each day's value. Hand it a flat list of CalendarPoint (day + value); same-day points are summed, and the grid spans the data's date range (or an explicit StartDate/EndDate).

<CalendarHeatmap Data="@days" ValueFormat="N0" OnDayClick="d => selected = d" />

@code {
    private List<CalendarPoint> days =
    [
        new() { Date = new DateOnly(2026, 1, 4), Value = 3 },
        new() { Date = new DateOnly(2026, 1, 5), Value = 7 },
        new() { Date = new DateOnly(2026, 1, 6), Value = 1 }
    ];
}

Tune it with WeekStart, Levels (intensity buckets), Color, CellSize/CellGap, and the ShowMonthLabels/ShowWeekdayLabels/ShowLegend/ShowTooltip toggles.

Sankey Chart

A flow diagram — nodes in left-to-right columns joined by ribbons whose thickness is proportional to a value. Hand it any SankeyNodes and weighted SankeyLinks; the columns are worked out from the links (longest path from a source, terminal nodes pushed right), or pin a node's column with Layer. Hovering a node or ribbon isolates its flows.

<SankeyChart Nodes="@nodes" Links="@links" ShowValues="true" ValueFormat="N0" />

@code {
    private List<SankeyNode> nodes =
    [
        new() { Id = "organic", Label = "Organic search" },
        new() { Id = "social",  Label = "Social" },
        new() { Id = "landing", Label = "Landing" },
        new() { Id = "signup",  Label = "Sign-up" }
    ];

    private List<SankeyLink> links =
    [
        new() { Source = "organic", Target = "landing", Value = 90 },
        new() { Source = "social",  Target = "landing", Value = 62 },
        new() { Source = "landing", Target = "signup",  Value = 120 }
    ];
}

Each link's Value sets its ribbon thickness — leave it at the default 1 for a uniform membership diagram, or pass a real magnitude for a true flow. Node height is the larger of a node's inflow and outflow. Tune it with NodeWidth, NodePadding, FontSize, LinkOpacity, Iterations (crossing-reduction passes), and the ShowNodeLabels/ShowValues/ShowTooltip toggles; OnNodeClick reports the selected node.

Matrix Chart

A rows × columns grid, filled where a row and column connect — read a row for everything it touches, or a column for everything that touches it. With uniform cell values it's a categorical membership grid; with varying values the fill intensity makes it a heatmap.

<MatrixChart Rows="@rows" Columns="@cols" Cells="@cells" ShowRowTotals="true" />

@code {
    private List<MatrixRow> rows =
    [
        new() { Id = "sales", Label = "Sales" },
        new() { Id = "finance", Label = "Finance" }
    ];

    private List<MatrixColumn> cols =
    [
        new() { Id = "order", Label = "Order" },
        new() { Id = "invoice", Label = "Invoice" }
    ];

    private List<MatrixCell> cells =
    [
        new() { Row = "sales",   Column = "order" },
        new() { Row = "sales",   Column = "invoice" },
        new() { Row = "finance", Column = "invoice" }
    ];
}

Cells are colored by their row (or column, with ColorByColumn), and fill intensity scales with Value against the busiest cell — so set real magnitudes for a heatmap or leave the default 1 for a plain membership grid. Tune it with CellSize/CellGap/CellRadius, FontSize, MaxValue, and the ShowValues/ShowRowTotals/ShowColumnTotals/ShowTooltip toggles; OnCellClick reports the selected cell.

Theming

Wrap any section of your app with ChartThemeProvider to theme all charts within it:

@using FactFoundry.Blazor.Charts.Themes

<ChartThemeProvider Theme="ChartTheme.Dark">
    <LineChart ... />
    <BarChart ... />
    <WorldMapChart ... />
</ChartThemeProvider>

Built-in presets: ChartTheme.Light (default) and ChartTheme.Dark.

Create custom themes for full control:

@code {
    private ChartTheme custom = new()
    {
        TextColor = "#333333",
        GridColor = "#dddddd",
        GridOpacity = 0.2,
        TooltipBackground = "#2d2d2d",
        TooltipTextColor = "#ffffff",
        Palette = ["#ff6384", "#36a2eb", "#ffce56", "#4bc0c0"]
    };
}

Individual charts can override the cascading theme via the Theme parameter:

<LineChart Theme="@custom" ... />

Responsive Sizing

All charts support fluid sizing via the Responsive parameter. When enabled, charts scale to fill their container width while maintaining aspect ratio — no JavaScript required:

<LineChart Responsive="true" Width="700" Height="350" Series="@series" ... />

Width and Height define the aspect ratio (viewBox) when responsive. The chart scales up/down to fit its parent container. Wrap in a <div> to constrain:

<div style="max-width: 600px;">
    <BarChart Responsive="true" Width="700" Height="350" ... />
</div>

Note: CommitGraph is the exception — being text-heavy, a responsive CommitGraph scales down to fit a narrow container but caps at Width pixels rather than magnifying in a wide one, keeping text at FontSize. See Commit Graph for its sizing model.

Label Auto-Thinning

When a chart has many data points (e.g., 30 days of data), X-axis labels are automatically thinned to prevent overlap. The algorithm estimates label widths and shows every Nth label to maintain readability.

To manually cap the number of visible labels:

<LineChart MaxXAxisLabels="10" XAxisLabels="@thirtyDayLabels" ... />

Design Principles

  • Zero dependencies — pure .NET and Blazor, nothing else
  • No JavaScript — not even for tooltips or hover interactions
  • SVG rendering — clean, scalable output via Razor component templates
  • Accessible — SVG titles for screen readers
  • Themeable — cascading theme provider with light/dark presets and full customization
  • Multi-target — supports .NET 8.0, 9.0, and 10.0
  • Works everywhere — Blazor Server and Blazor WebAssembly

Target Frameworks

Framework Status
.NET 8.0 Supported
.NET 9.0 Supported
.NET 10.0 Supported

Project Structure

src/FactFoundry.Blazor.Charts/     # The library
samples/Charts.Sample.Server/      # Blazor Server demo app
samples/Charts.Sample.Wasm/        # Blazor WebAssembly demo app
tests/FactFoundry.Blazor.Charts.Tests/  # Unit tests (xUnit + bUnit)
docs/design/                       # Design documentation
tools/                             # Build/maintenance scripts
  convert_geojson.py               # Python script to regenerate WorldGeometry.cs
                                   #   from Natural Earth GeoJSON data (not a
                                   #   runtime dependency — code-gen only)

Contributing

Contributions are welcome. Please open an issue first to discuss what you'd like to change.

  1. Fork the repo
  2. Create a feature branch (git checkout -b feature/my-feature)
  3. Make your changes
  4. Run tests (dotnet test)
  5. Open a pull request

License

MIT

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.2.0 243 7/21/2026
1.1.0 130 7/18/2026
1.0.3 120 7/18/2026
1.0.2 112 7/9/2026
1.0.1 138 6/8/2026
1.0.0 125 6/8/2026