ChartAI.Blazor 1.2.0

dotnet add package ChartAI.Blazor --version 1.2.0
                    
NuGet\Install-Package ChartAI.Blazor -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="ChartAI.Blazor" Version="1.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ChartAI.Blazor" Version="1.2.0" />
                    
Directory.Packages.props
<PackageReference Include="ChartAI.Blazor" />
                    
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 ChartAI.Blazor --version 1.2.0
                    
#r "nuget: ChartAI.Blazor, 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 ChartAI.Blazor@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=ChartAI.Blazor&version=1.2.0
                    
Install as a Cake Addin
#tool nuget:?package=ChartAI.Blazor&version=1.2.0
                    
Install as a Cake Tool

ChartAI.Blazor

NuGet CI

A Blazor component for chartai, a tiny WebGPU chart engine that renders millions of points across thousands of series off the main thread. The engine ships bundled inside the package; there is nothing to install on the JS side. The bundled build is upstream chartai 1.1.0 plus the changes listed in the changelog: in-place data patching, gaps, multiple y axes, anti-aliased lines, GPU hover highlight and axis panning.

Requirements

  • .NET 10 (Blazor WebAssembly or an interactive Server render mode; the component uses JS interop, so it does not render under static SSR).
  • A browser with WebGPU (current Chrome, Edge, Safari and Firefox). Without a WebGPU adapter the host element shows a notice (Chart.UnavailableText) instead of an empty box.

Install

dotnet add package ChartAI.Blazor

Quick start

No service registration and no <script> tag is needed. The component imports its JS module on first render. The host element must have a height; the default Style gives it min-height: 300px.

@using ChartAI.Blazor.Components
@using ChartAI.Blazor.Models

<Chart Config="config" Series="series" Plugins="ChartPlugins.Crosshair | ChartPlugins.Minimap" />

@code {
    private readonly ChartConfig config = new()
    {
        Type = ChartType.Line,
        FormatX = AxisFormat.Index,
        FormatY = AxisFormat.Number,
        Legend = new LegendConfig { DefaultOpen = true },
    };

    private readonly ChartSeries[] series =
    [
        new() { Label = "Signal", Color = "#3b82f6", X = [0, 1, 2, 3, 4], Y = [1, 3, 2, 5, 4] },
        new() { Label = "Noise",  Color = "#f97316", X = [0, 1, 2, 3, 4], Y = [2, 1, 3, 2, 3] },
    ];
}

Chart types

ChartType: Line, Area, Scatter, Bar, Candlestick, Ohlc, Step, Histogram, Heatmap, Bubble, BaselineArea, ErrorBand, Waterfall.

Renderer-specific channels live on ChartSeries (Open/High/Low for candlesticks and OHLC, Lo/Hi for error bands, R for bubbles, Value for heatmaps). ChartSeries.Waterfall(...) builds a waterfall series from a list of deltas. Any other numeric channel can be passed through Extra.

Plugins

Hover tooltips, zoom, legend, annotations, thresholds and watermark are always available and switch on through their ChartConfig properties. Interactive per-chart plugins are attached with the Plugins flags parameter:

ChartPlugins.Crosshair | Stats | Ruler | TooltipPin | Minimap | RangeSelector

Annotation labels are clickable: AnnotationClicked receives the Id of the annotation whose label was clicked. A vertical line's label sits in the bottom margin, or just inside the top of the plot with LabelPosition = AnnotationLabelPosition.Top; neighbouring labels stack in lanes.

Charts on one page can be linked: the module's setSyncViews(mode) mirrors a zoom or pan to every other chart - true or "both" on both axes, "x" or "y" on one. A trend page links the time axis and leaves each plot its own scale. Linked charts share the visible data range, so charts whose data covers different spans still show the same window. ViewChanged is raised for every chart whose view moved: the one the user dragged, the charts that followed it, and a view moved by the minimap, the range selector or ResetViewAsync().

Updating a chart

  • Config changes are detected by comparing its serialized JSON with what was last sent: a new instance with the same content costs nothing, and an object changed in place is picked up the next time the parent renders. RefreshConfigAsync() on a @ref sends it right away.
  • Series changes are detected by reference: assign a new collection to send new data. RefreshAsync() resends Config and Series.
  • SetDataAsync(series) replaces the data; ResetViewAsync() animates back to fit-to-data.
  • The methods can be called as soon as the @ref is set, in OnAfterRenderAsync(firstRender) say: a call made while the chart is still initialising runs once it is up. Ready completes with true then, or with false when the chart never comes up (no WebGPU, or it was disposed); the methods then do nothing and PatchDataAsync returns 0. Await Ready before starting a timer that feeds the chart.
  • Changing Plugins recreates the chart; the data it shows is kept, including data from SetDataAsync and PatchDataAsync. Id is read once, when the chart is created.
  • A missing sample is double.NaN in any channel and renders as a gap. The series cross JS interop as binary float64 (a shared x once), not as JSON text. NaN or an infinity in a config value, a bound or an annotation serializes as null: the value counts as unset.
  • While the pointer is on a line that series is drawn on top and the others fade (ChartConfig.HighlightHover); the tooltip leads with it. Dragging an axis gutter pans that axis, the wheel over it zooms it.
  • ChartConfig.BgColor is the opaque background the axis margins are painted in. By default they are a gradient that fades the data out toward the borders; BgFade = false paints them as plain strips with a hard edge and starts the home view at that edge.

Live data

A chart that follows a stream should not be rebuilt per tick. PatchDataAsync writes columns in place: only the patched columns cross JS interop, and the engine writes them straight into the existing GPU buffers. Every series of such a chart shares the x axis, ascending.

<Chart @ref="chart" Config="config" Series="series" ViewChanged="OnViewChanged" />

@code {
    // A window of 500 columns with room for as many again, see "What a tick costs" below.
    private readonly ChartConfig config = new() { Type = ChartType.Line, Capacity = 1000 };

    private Task Tick(double x, double a, double b) => chart.PatchDataAsync(new ChartPatch
    {
        X = [x],                                          // one new column
        Series = [new() { Y = [a] }, new() { Y = [b] }],  // one entry per series
        DropBefore = x - 500,                             // ring buffer: drop what left the window
        Bounds = new ChartBounds { MinX = x - 500, MaxX = x },
    });

    // The visible range after the user zoomed or panned: fetch that window.
    private Task OnViewChanged(ChartViewRange r) => LoadAsync(r.MinX, r.MaxX);
}
  • Offset rewrites from a column on (the newest sample refreshed); null appends.
  • Drop / DropBefore discard the oldest columns first, so a window of fixed length never grows.
  • Bounds moves the data window with the patch; SetBoundsAsync does only that when nothing new arrived. A side left null keeps its value.
  • ResetView puts the view back to its home transform, so the moved window is what is shown.
  • SetDataAsync(series, capacity, bounds) reloads everything with the buffers sized for capacity. Without bounds the window fits the data (or DefaultBounds) again.
  • With several y axes a patch keeps the secondary axes' ranges; reload with SetDataAsync to rescale them.

What a tick costs. A patch crosses interop as one small binary block, and its columns are copied into the chart's column store and written into the GPU buffers; nothing else is uploaded. A dropped column is only skipped: the chart draws from the first column still in the window. When the buffers are full, the columns of the window are moved back to their start and uploaded once (a full-window write, without recreating anything), or the buffers grow to twice their size (one full upload) when that would free less than a quarter of them. With Capacity twice the window that happens once per window of ticks. What does not shrink with the delta is the GPU's work: after a patch it reruns its per-pixel reduction over the visible columns of every series, so a frame costs the window times the series, as for any redraw. Compute the bounds you send from the samples you have (double.NaN makes Min() and Max() NaN).

Dark mode

ChartConfig.IsDark = true switches the engine to its dark theme, and setting it back to false switches to light. The theme is global: every chart on the page follows it, whichever chart set it, so set it on one chart (or on all of them alike). A chart that leaves IsDark false does not touch the theme. To switch every chart at once from your own JS interop:

var mod = await JS.InvokeAsync<IJSObjectReference>("import", "./_content/ChartAI.Blazor/chartai-blazor.js");
await mod.InvokeVoidAsync("setTheme", isDark);

When the engine initialises it reads a dark class on <html>, unless setTheme was already called.

Multiple Y axes

Configure axes via ChartConfig.YAxes and bind each series with ChartSeries.YAxis. Axes on the same side stack outward without overlapping; YAxisConfig.Width and ChartConfig.YAxisGap control the horizontal distance. The tooltip and legend combine the series of all axes, each value formatted with its own axis format.

var config = new ChartConfig
{
    Type = ChartType.Line,
    YAxisGap = 8, // px between stacked axes (optional, default 6)
    YAxes = new()
    {
        new YAxisConfig { Id = "price", Side = AxisSide.Left,  Format = AxisFormat.Price },
        new YAxisConfig { Id = "temp",  Side = AxisSide.Right, Format = AxisFormat.Degree,
                          Color = "#f97316", Width = 60, Min = -20, Max = 40 },
    },
};

var series = new[]
{
    new ChartSeries { Label = "Price",       X = x, Y = prices, YAxis = "price" },
    new ChartSeries { Label = "Temperature", X = x, Y = temps,  YAxis = "temp" },
};

The first axis is the primary axis: thresholds, annotations and the ruler measure in its units, and unassigned series (YAxis == null) fall back to it. Min/Max are optional exact bounds; otherwise each axis auto-scales to its own series.

Demo

dotnet run --project ChartAI.Blazor.Demo

Building the chart engine

ChartAI.Blazor/wwwroot/chartai.js is generated. Its sources are the TypeScript files under ChartAI.Blazor/engine/src: the chartai engine (upstream 1.1.0) plus this project's changes (in-place data patching, several Y axes, hover highlighting, gap handling, and so on). engine/build.ts bundles them with Bun into that one file, GPU worker inlined, the same way upstream builds its own dist/chart-library.js.

  • dotnet build runs the bundler automatically when Bun is on the PATH and a source file is newer than the bundle. Without Bun the committed bundle is used and a warning is logged. -p:ChartAiSkipEngineBuild=true disables the step.

  • The bundle is built with the Bun version CI pins (1.4.2, ChartAiBunVersion in the csproj). With another version on the PATH the build keeps the committed bundle and warns, since a different Bun can emit a different file; -p:ChartAiAnyBunVersion=true builds with it anyway.

  • To build by hand, run in ChartAI.Blazor/engine:

    bun run build.ts
    
  • bun install then bun run typecheck type-checks the sources (needs nothing else).

  • The bundle is committed so that consumers and CI can build without Bun. CI rebuilds it with a pinned Bun version and fails when the committed file is stale, so rebuild and commit wwwroot/chartai.js together with any change under engine/src.

Releasing

Releases are published by the Release GitHub Actions workflow. Push a tag vX.Y.Z and the workflow packs ChartAI.Blazor X.Y.Z, pushes it to nuget.org and attaches the package to a GitHub release. It authenticates with nuget.org Trusted Publishing, so no API key is stored; the nuget.org account needs a trusted publishing policy for this repository and workflow file. The tagged commit must be on master. A version that nuget.org already has fails the run, and the GitHub release is only created once nuget.org accepted the package, so a re-pushed tag never attaches a package that differs from the published one.

git tag v1.0.0 && git push origin v1.0.0

To build a package locally:

dotnet pack ChartAI.Blazor/ChartAI.Blazor.csproj -c Release -o artifacts

License

MIT. The bundled chartai engine is released into the public domain by its author; see THIRD-PARTY-NOTICES.txt.

Product Compatible and additional computed target framework versions.
.NET 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 38 9/29/2026
1.1.4 116 9/11/2026
1.1.3 85 9/11/2026
1.1.2 87 9/11/2026
1.1.1 94 9/11/2026
1.1.0 91 9/11/2026
1.0.0 117 9/3/2026