Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
<PackageReference Include="BootstrapBlazor.BarCode" Version="10.0.0" />
<PackageReference Include="BootstrapBlazor.BarcodeGenerator" Version="10.0.0" />
<PackageReference Include="BootstrapBlazor.BootstrapIcon" Version="10.0.0" />
<PackageReference Include="BootstrapBlazor.Chart" Version="10.0.6" />
<PackageReference Include="BootstrapBlazor.Chart" Version="10.0.7" />
<PackageReference Include="BootstrapBlazor.ChatBot" Version="10.0.0" />
<PackageReference Include="BootstrapBlazor.CherryMarkdown" Version="10.0.1" />
<PackageReference Include="BootstrapBlazor.CodeEditor" Version="10.0.0" />
Expand Down
112 changes: 26 additions & 86 deletions src/BootstrapBlazor.Server/Components/Samples/Charts/Bar.razor
Original file line number Diff line number Diff line change
Expand Up @@ -77,49 +77,27 @@
<ul class="ul-demo">
<li>
<p>@((MarkupString)Localizer["BarTotalDataLabelLi1"].Value)</p>
<Pre class="mb-3">&lt;Chart ChartType="ChartType.Bar" OnInitAsync="OnInitTotalDataLabel" Id="@@TotalDataLabelId" /&gt;</Pre>
<Pre class="mb-3">&lt;Chart ChartType="ChartType.Bar" OnInitAsync="OnInitTotalDataLabel" /&gt;</Pre>
</li>
<li>
<p>@((MarkupString)Localizer["BarTotalDataLabelLi2"].Value)</p>
<Pre class="mb-3">import '../../../_content/BootstrapBlazor.Chart/Components/Chart/Chart.razor.js'

export function customTotalDataLabel(id) {
// chartjs-plugin-datalabels documentation: https://chartjs-plugin-datalabels.netlify.app/guide/options.html
const chart = BootstrapBlazor.Chart;
chart.setOptionsById(id, {
options: {
plugins: {
datalabels: {
labels: {
total: {
color: '#fff',
backgroundColor: 'rgb(75, 192, 192)',
borderRadius: 4,
padding: { top: 2, bottom: 2, left: 6, right: 6 },
font: { weight: 'bold' },
formatter: (value, context) => {
const total = context.chart.data.datasets.reduce(
(sum, v, index) => context.chart.isDatasetVisible(index) ? sum + (Number(v.data[context.dataIndex]) || 0) : sum, 0);
return `Total: ${total}`;
}
}
}
}
}
}
});
}</Pre>
</li>
<li>
<p>@((MarkupString)Localizer["BarTotalDataLabelLi3"].Value)</p>
<Pre>protected override async Task InvokeInitAsync()
<Pre>private Task&lt;ChartDataSource&gt; OnInitTotalDataLabel()
{
await InvokeVoidAsync("customTotalDataLabel", TotalDataLabelId);
var ds = new ChartDataSource();
ds.Options.ShowDataLabel = true;
ds.Options.ShowTotalDataLabel = true;
ds.Options.TotalDataLabelFormatter = "Total: {total}";
ds.Options.TotalDataLabelColor = "#fff";
ds.Options.TotalDataLabelBackgroundColor = "rgb(75, 192, 192)";
ds.Options.TotalDataLabelBorderRadius = 4;
ds.Options.TotalDataLabelPadding = 6;
ds.Options.TotalDataLabelFontWeight = "bold";
return Task.FromResult(ds);
}</Pre>
</li>
</ul>
</section>
<Chart ChartType="ChartType.Bar" OnInitAsync="OnInitTotalDataLabel" Id="@TotalDataLabelId" Height="500px" Width="500px" />
<Chart ChartType="ChartType.Bar" OnInitAsync="OnInitTotalDataLabel" Height="500px" Width="500px" />
</DemoBlock>

<DemoBlock Title="@Localizer["BarColorSeparatelyTitle"]"
Expand Down Expand Up @@ -148,39 +126,21 @@ export function customTotalDataLabel(id) {
<ul class="ul-demo">
<li>
<p>@((MarkupString)Localizer["CustomTooltipLi1"].Value)</p>
<Pre class="mb-3">&lt;Chart ChartType="ChartType.Bar" OnInitAsync="() => OnInit(false)" Id="@@CustomTooltipId" /&gt;</Pre>
<Pre class="mb-3">&lt;Chart ChartType="ChartType.Bar" OnInitAsync="OnInitCustomTooltip" /&gt;</Pre>
</li>
<li>
<p>@((MarkupString)Localizer["CustomTooltipLi2"].Value)</p>
<Pre class="mb-3">import '../../../_content/BootstrapBlazor.Chart/Components/Chart/Chart.razor.js'

export function customTooltip(id) {
// chart.js documentation: https://www.chartjs.org/docs/latest/configuration/tooltip.html
const chart = BootstrapBlazor.Chart;
chart.setOptionsById(id, {
options: {
plugins: {
tooltip: {
callbacks: {
title: tooltipItems => `Day ${tooltipItems[0].label}`,
label: context => ` ${context.dataset.label}: ${context.parsed.y} units`
}
}
}
}
});
}</Pre>
</li>
<li>
<p>@((MarkupString)Localizer["CustomTooltipLi3"].Value)</p>
<Pre>protected override async Task InvokeInitAsync()
<Pre>private Task&lt;ChartDataSource&gt; OnInitCustomTooltip()
{
await InvokeVoidAsync("customTooltip", CustomTooltipId);
var ds = OnInit(false).Result;
ds.Options.TooltipTitleFormatter = "Day {label}";
ds.Options.TooltipLabelFormatter = " {datasetLabel}: {value} units";
return Task.FromResult(ds);
}</Pre>
</li>
</ul>
</section>
<Chart ChartType="ChartType.Bar" OnInitAsync="() => OnInit(false)" Id="@CustomTooltipId" />
<Chart ChartType="ChartType.Bar" OnInitAsync="OnInitCustomTooltip" />
</DemoBlock>

<DemoBlock Title="@Localizer["CustomCategoryLabelTitle"]"
Expand All @@ -190,38 +150,18 @@ export function customTooltip(id) {
<ul class="ul-demo">
<li>
<p>@((MarkupString)Localizer["CustomCategoryLabelLi1"].Value)</p>
<Pre class="mb-3">&lt;Chart ChartType="ChartType.Bar" OnInitAsync="() => OnInit(false)" Id="@@CustomCategoryLabelId" /&gt;</Pre>
<Pre class="mb-3">&lt;Chart ChartType="ChartType.Bar" OnInitAsync="OnInitCustomCategoryLabel" /&gt;</Pre>
</li>
<li>
<p>@((MarkupString)Localizer["CustomCategoryLabelLi2"].Value)</p>
<Pre class="mb-3">import '../../../_content/BootstrapBlazor.Chart/Components/Chart/Chart.razor.js'

export function customCategoryLabel(id) {
// chart.js documentation: https://www.chartjs.org/docs/latest/axes/labelling.html
const chart = BootstrapBlazor.Chart;
chart.setOptionsById(id, {
options: {
scales: {
x: {
ticks: {
callback: function (value) {
return `Day ${this.getLabelForValue(value)}`;
}
}
}
}
}
});
}</Pre>
</li>
<li>
<p>@((MarkupString)Localizer["CustomCategoryLabelLi3"].Value)</p>
<Pre>protected override async Task InvokeInitAsync()
<Pre>private Task&lt;ChartDataSource&gt; OnInitCustomCategoryLabel()
{
await InvokeVoidAsync("customCategoryLabel", CustomCategoryLabelId);
var ds = OnInit(false).Result;
ds.Options.CategoryLabelFormatter = "Day {label}";
return Task.FromResult(ds);
}</Pre>
</li>
</ul>
</section>
<Chart ChartType="ChartType.Bar" OnInitAsync="() => OnInit(false)" Id="@CustomCategoryLabelId" />
<Chart ChartType="ChartType.Bar" OnInitAsync="OnInitCustomCategoryLabel" />
</DemoBlock>
38 changes: 21 additions & 17 deletions src/BootstrapBlazor.Server/Components/Samples/Charts/Bar.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,11 @@ namespace BootstrapBlazor.Server.Components.Samples.Charts;
/// <summary>
/// Bar 图表示例
/// </summary>
[JSModuleAutoLoader("Samples/Charts/Bar.razor.js", JSObjectReference = true)]
public partial class Bar
{
private int _barDatasetCount = 2;
private int _barDataCount = 7;

private string CustomTooltipId => $"custom_tooltip_{Id}";

private string CustomCategoryLabelId => $"custom_category_label_{Id}";

private string TotalDataLabelId => $"total_data_label_{Id}";

private int BarDatasetCount { get; set; } = 2;

private int BarDataCount { get; set; } = 7;
Expand All @@ -44,16 +37,6 @@ protected override void OnAfterRender(bool firstRender)
}
}

/// <summary>
/// <inheritdoc/>
/// </summary>
protected override async Task InvokeInitAsync()
{
await InvokeVoidAsync("customTooltip", CustomTooltipId);
await InvokeVoidAsync("customCategoryLabel", CustomCategoryLabelId);
await InvokeVoidAsync("customTotalDataLabel", TotalDataLabelId);
}

private Task OnAfterInit()
{
Logger.Log("Bar initialization is complete");
Expand Down Expand Up @@ -256,6 +239,12 @@ private Task<ChartDataSource> OnInitTotalDataLabel()
ds.Options.Title = "Stacked total";
ds.Options.ShowDataLabel = true;
ds.Options.ShowTotalDataLabel = true;
ds.Options.TotalDataLabelFormatter = "Total: {total}";
ds.Options.TotalDataLabelColor = "#fff";
ds.Options.TotalDataLabelBackgroundColor = "rgb(75, 192, 192)";
ds.Options.TotalDataLabelBorderRadius = 4;
ds.Options.TotalDataLabelPadding = 6;
ds.Options.TotalDataLabelFontWeight = "bold";
ds.Options.Anchor = ChartDataLabelPosition.Center;
ds.Options.X.Title = "name";
ds.Options.Y.Title = "Numerical value";
Expand All @@ -275,6 +264,21 @@ private Task<ChartDataSource> OnInitTotalDataLabel()
return Task.FromResult(ds);
}

private Task<ChartDataSource> OnInitCustomTooltip()
{
var ds = OnInit(false).Result;
Comment on lines +267 to +269

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Avoid blocking on the async OnInit(false) call with .Result inside OnInitCustomTooltip.

Blocking on OnInit(false).Result in a Blazor component can cause deadlocks and thread-pool exhaustion, and complicates async control flow. Make OnInitCustomTooltip async, use await OnInit(false), and return Task<ChartDataSource> via async/await instead of Task.FromResult to keep the method fully asynchronous.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): OnInitCustomCategoryLabel should also avoid using .Result on the async OnInit(false) method.

This method also blocks synchronously on OnInit(false).Result. Please update it to be fully async (e.g., private async Task<ChartDataSource> OnInitCustomCategoryLabel() { var ds = await OnInit(false); ... }) to avoid potential deadlocks and keep it consistent with the rest of the async code.

ds.Options.TooltipTitleFormatter = "Day {label}";
ds.Options.TooltipLabelFormatter = " {datasetLabel}: {value} units";
return Task.FromResult(ds);
}

private Task<ChartDataSource> OnInitCustomCategoryLabel()
{
var ds = OnInit(false).Result;
ds.Options.CategoryLabelFormatter = "Day {label}";
return Task.FromResult(ds);
}

private Task<ChartDataSource> OnInitAutoSkip(bool autoSkip)
{
var ds = new ChartDataSource();
Expand Down
63 changes: 0 additions & 63 deletions src/BootstrapBlazor.Server/Components/Samples/Charts/Bar.razor.js

This file was deleted.

21 changes: 9 additions & 12 deletions src/BootstrapBlazor.Server/Locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -1265,10 +1265,9 @@
"BarStackedShowDataLabelIntro": "When <code>ShowDataLabel</code> is enabled in stacked mode, the position of the data label is controlled by the <code>Anchor</code> property. The default <code>End</code> displays it at the top of the segment, and <code>Center</code> displays it in the center of the segment. Segments with a value of <code>0</code> do not draw data labels to avoid overlapping with adjacent segment labels. In this example, the top segment of <b>Bob</b> has a value of <code>0</code>, so only the lower segment's data label is displayed",
"BarStackedShowDataLabelTitle": "Stacked Data Label",
"BarStackedTitle": "Stacked",
"BarTotalDataLabelIntro": "In stacked mode, set the <code>ShowTotalDataLabel</code> property to display the total data label above each bar. The total value is automatically recalculated when a dataset is hidden by clicking the legend. In this example, the <code>total</code> label is customized by the client script <code>setOptionsById</code>: the total text is formatted as <code>Total: {X}</code>, and the text color, background color, border radius, padding and font style are customized",
"BarTotalDataLabelLi1": "Set the <b>Id</b> parameter of the <code>Chart</code> component",
"BarTotalDataLabelLi2": "Create a client <code>JavaScript</code> script, override the <code>formatter</code> callback of the <code>total</code> label by the <code>setOptionsById</code> method to customize the total text, and customize the label style by the <code>color</code> <code>backgroundColor</code> <code>borderRadius</code> <code>padding</code> <code>font</code> properties",
"BarTotalDataLabelLi3": "Override the base class method <code>InvokeInitAsync</code> and call the client script by <code>await InvokeVoidAsync(\"customTotalDataLabel\", TotalDataLabelId)</code>",
"BarTotalDataLabelIntro": "In stacked mode, set the <code>ShowTotalDataLabel</code> property to display the total data label above each bar. The total value is automatically recalculated when a dataset is hidden by clicking the legend. In this example, the total text, text color, background color, border radius, padding, and font style are customized by <code>ChartDataSource.Options</code>",
"BarTotalDataLabelLi1": "Set the <code>OnInitAsync</code> delegate of the <code>Chart</code> component",
"BarTotalDataLabelLi2": "Set <code>ShowTotalDataLabel</code>, <code>TotalDataLabelFormatter</code>, and total label style properties in <code>ChartDataSource.Options</code>",
"BarTotalDataLabelTitle": "Stacked Total Label",
"BarTwoYAxesIntro": "Show secondary Y axis: alignment, title and data group",
"BarTwoYAxesTitle": "Double Y axis",
Expand All @@ -1282,15 +1281,13 @@
"BarTypeRemoveData": "Remove data",
"BarTypeRemoveDataSet": "Remove dataset",
"BarTypeTitle": "Bar graph",
"CustomCategoryLabelIntro": "Customize the category label text below each bar on the x-axis by client script",
"CustomCategoryLabelLi1": "Set the <b>Id</b> parameter of the <code>Chart</code> component",
"CustomCategoryLabelLi2": "Create a client <code>JavaScript</code> script, set the <code>ticks</code> callback of the x-axis by <code>setOptionsById</code> method. In this example, the prefix <code>Day</code> is added to each category label",
"CustomCategoryLabelLi3": "Override the base class method <code>InvokeInitAsync</code> and call the client script by <code>await InvokeVoidAsync(\"customCategoryLabel\", CustomCategoryLabelId)</code>",
"CustomCategoryLabelIntro": "Customize the category label text below each bar on the x-axis by <code>ChartDataSource.Options</code>",
"CustomCategoryLabelLi1": "Set the <code>OnInitAsync</code> delegate of the <code>Chart</code> component",
"CustomCategoryLabelLi2": "Set <code>CategoryLabelFormatter</code> in <code>ChartDataSource.Options</code> to format category labels. In this example, the prefix <code>Day</code> is added to each category label",
"CustomCategoryLabelTitle": "Custom Category Label",
"CustomTooltipIntro": "Customize the hover <code>Tooltip</code> text of the bar chart by client script",
"CustomTooltipLi1": "Set the <b>Id</b> parameter of the <code>Chart</code> component",
"CustomTooltipLi2": "Create a client <code>JavaScript</code> script, set the <code>tooltip</code> callbacks by <code>setOptionsById</code> method. In this example, the <code>title</code> and <code>label</code> text are customized",
"CustomTooltipLi3": "Override the base class method <code>InvokeInitAsync</code> and call the client script by <code>await InvokeVoidAsync(\"customTooltip\", CustomTooltipId)</code>",
"CustomTooltipIntro": "Customize the hover <code>Tooltip</code> text of the bar chart by <code>ChartDataSource.Options</code>",
"CustomTooltipLi1": "Set the <code>OnInitAsync</code> delegate of the <code>Chart</code> component",
"CustomTooltipLi2": "Set <code>TooltipTitleFormatter</code> and <code>TooltipLabelFormatter</code> in <code>ChartDataSource.Options</code> to format the <code>title</code> and <code>label</code> text",
"CustomTooltipTitle": "Custom Tooltip"
},
"BootstrapBlazor.Server.Components.Samples.Charts.Bubble": {
Expand Down
Loading
Loading