-
-
Notifications
You must be signed in to change notification settings - Fork 264
Apply BitChart improvements (#13154) #13158
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
msynk
merged 10 commits into
bitfoundation:develop
from
msynk:13154-blazorui-chart-improvements
Sep 14, 2026
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
ec42b9d
apply BitChart improvements #13154
msynk e2661b3
code review
msynk eda76c0
resolve review comments
msynk cc99f73
resolve review comments
msynk f46a2bb
code review
msynk 3d5f021
Merge branch 'develop' into 13154-blazorui-chart-improvements
msynk 41dd4fd
fix siderail
msynk 3060623
code formatting
msynk 4d2f8b8
fix animation
msynk 9b72b39
fix demo animation
msynk File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
172 changes: 172 additions & 0 deletions
172
src/BlazorUI/Bit.BlazorUI.Extras/Components/Chart/BitChart.Export.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,172 @@ | ||
| using System.Globalization; | ||
| using System.Text; | ||
|
|
||
| namespace Bit.BlazorUI; | ||
|
|
||
| /// <summary> | ||
| /// Export helpers. The chart is already a live SVG element, so exporting is a matter of serializing it | ||
| /// (SVG), rasterizing that serialization (PNG), or writing the underlying values out (CSV). | ||
| /// </summary> | ||
| public partial class BitChart | ||
| { | ||
| /// <summary> | ||
| /// Downloads the chart as a standalone <c>.svg</c> file. The theme tokens the chart references are | ||
| /// resolved into the exported file so it looks the same outside the app. | ||
| /// </summary> | ||
| /// <param name="fileName">File name to save as; defaults to <c>chart.svg</c>.</param> | ||
| /// <param name="backgroundColor">Optional background painted behind the chart (SVG is transparent by default).</param> | ||
| /// <returns>True when the file was produced.</returns> | ||
| public async Task<bool> ExportSvgAsync(string? fileName = null, string? backgroundColor = null) | ||
| { | ||
| try | ||
| { | ||
| return await JS.BitChartExportSvg(_plotEl, fileName ?? "chart.svg", backgroundColor); | ||
| } | ||
| catch | ||
| { | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Downloads the chart as a <c>.png</c> image, rasterized from the live SVG. | ||
| /// </summary> | ||
| /// <param name="fileName">File name to save as; defaults to <c>chart.png</c>.</param> | ||
| /// <param name="scale">Pixel ratio; 2 (the default) produces a crisp image on high-density displays.</param> | ||
| /// <param name="backgroundColor">Background painted behind the chart; PNG is transparent without it.</param> | ||
| /// <returns>True when the file was produced.</returns> | ||
| public async Task<bool> ExportPngAsync(string? fileName = null, double scale = 2, string? backgroundColor = "#ffffff") | ||
| { | ||
| try | ||
| { | ||
| return await JS.BitChartExportPng(_plotEl, fileName ?? "chart.png", scale <= 0 ? 1 : scale, backgroundColor); | ||
| } | ||
| catch | ||
| { | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Returns the chart as standalone SVG markup instead of downloading it - for embedding it in a | ||
| /// report, mailing it, or storing it - with the theme tokens it references resolved into the markup | ||
| /// so it looks the same outside the app. | ||
| /// </summary> | ||
| /// <param name="backgroundColor">Optional background painted behind the chart (SVG is transparent by default).</param> | ||
| /// <returns>The SVG markup, or null when the chart has not been rendered in a browser yet.</returns> | ||
| public async Task<string?> ToSvgStringAsync(string? backgroundColor = null) | ||
| { | ||
| try | ||
| { | ||
| return await JS.BitChartToSvgString(_plotEl, backgroundColor); | ||
| } | ||
| catch | ||
| { | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Returns the chart as a rasterized <c>data:</c> URL - the same picture <see cref="ExportPngAsync"/> | ||
| /// downloads - ready to drop into an <c>img</c> src or a PDF. Mirrors Chart.js's <c>toBase64Image</c>. | ||
| /// </summary> | ||
| /// <param name="mimeType">Image type to encode; <c>image/png</c> by default (<c>image/jpeg</c> and <c>image/webp</c> also work).</param> | ||
| /// <param name="scale">Pixel ratio; 2 (the default) produces a crisp image on high-density displays.</param> | ||
| /// <param name="backgroundColor">Background painted behind the chart; PNG is transparent without it.</param> | ||
| /// <returns>The data URL, or null when the chart has not been rendered in a browser yet.</returns> | ||
| public async Task<string?> ToBase64ImageAsync(string mimeType = "image/png", double scale = 2, | ||
| string? backgroundColor = "#ffffff") | ||
| { | ||
| try | ||
| { | ||
| return await JS.BitChartToDataUrl(_plotEl, mimeType, scale <= 0 ? 1 : scale, backgroundColor); | ||
| } | ||
| catch | ||
| { | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| /// <summary>Downloads the chart's data as a <c>.csv</c> file.</summary> | ||
| /// <param name="fileName">File name to save as; defaults to <c>chart.csv</c>.</param> | ||
| /// <returns>True when the file was produced.</returns> | ||
| public async Task<bool> ExportCsvAsync(string? fileName = null) | ||
| { | ||
| try | ||
| { | ||
| await JS.BitChartDownloadText(fileName ?? "chart.csv", ToCsv(), "text/csv;charset=utf-8"); | ||
| return true; | ||
| } | ||
| catch | ||
| { | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Renders the chart's data as CSV. Value datasets become one row per series with a column per | ||
| /// label; point datasets (scatter/bubble) become one row per point. | ||
| /// </summary> | ||
| public string ToCsv() | ||
| { | ||
| var data = _config.Data; | ||
| var culture = Culture; | ||
| var sb = new StringBuilder(); | ||
|
|
||
| if (HasPointData) | ||
| { | ||
| sb.AppendLine("Series,X,Y,R"); | ||
| foreach (var ds in data.Datasets) | ||
| { | ||
| if (ds.Points is not { } pts) continue; | ||
| foreach (var p in pts) | ||
| sb.Append(CsvText(ds.Label ?? "Series")).Append(',') | ||
| .Append(Csv(p.X.ToString(culture))).Append(',') | ||
| .Append(Csv(p.Y.ToString(culture))).Append(',') | ||
| .AppendLine(p.R is { } r ? Csv(r.ToString(culture)) : ""); | ||
| } | ||
| return sb.ToString(); | ||
| } | ||
|
|
||
| sb.Append("Series"); | ||
| foreach (var label in data.Labels) sb.Append(',').Append(CsvText(label)); | ||
| sb.AppendLine(); | ||
|
|
||
| foreach (var ds in data.Datasets) | ||
| { | ||
| sb.Append(CsvText(ds.Label ?? "Series")); | ||
| if (ds.RangeData is { } ranges) | ||
| { | ||
| foreach (var r in ranges) | ||
| sb.Append(',').Append(r is { } rr ? Csv($"{rr.Low.ToString(culture)} - {rr.High.ToString(culture)}") : ""); | ||
| } | ||
| else | ||
| { | ||
| foreach (var v in ds.Data) | ||
| sb.Append(',').Append(v is { } vv ? Csv(vv.ToString(culture)) : ""); | ||
| } | ||
| sb.AppendLine(); | ||
| } | ||
| return sb.ToString(); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Quotes a caller-supplied text field - a series or category label - and neutralizes the leading | ||
| /// characters a spreadsheet reads as the start of a formula, so a label taken from user data cannot | ||
| /// become executable content when the file is opened. Formatted numbers keep going through | ||
| /// <see cref="Csv"/>, where a leading minus sign is a sign rather than an injection. | ||
| /// </summary> | ||
| private static string CsvText(string value) | ||
| { | ||
| if (value.Length > 0 && value[0] is '=' or '+' or '-' or '@' or '\t' or '\r') | ||
| value = "'" + value; | ||
| return Csv(value); | ||
| } | ||
|
|
||
| /// <summary>Quotes a CSV field when it contains a separator, quote or newline.</summary> | ||
| private static string Csv(string value) | ||
| { | ||
| if (value.IndexOfAny([',', '"', '\n', '\r']) < 0) return value; | ||
| return '"' + value.Replace("\"", "\"\"") + '"'; | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.