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
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

<DocsLink Href="@DemoRouteConstants.Docs_BarChart" />

<Prerequisites PageUrl="@pageUrl" />

Check warning on line 15 in BlazorExpress.ChartJS.Demo.RCL/Pages/Demos/BarChart/BarChartDocumentation.razor

View workflow job for this annotation

GitHub Actions / Build and Deploy Job

Found markup element with unexpected name 'Prerequisites'. If this is intended to be a component, add a @using directive for its namespace.

Check warning on line 15 in BlazorExpress.ChartJS.Demo.RCL/Pages/Demos/BarChart/BarChartDocumentation.razor

View workflow job for this annotation

GitHub Actions / Build and Deploy Job

Found markup element with unexpected name 'Prerequisites'. If this is intended to be a component, add a @using directive for its namespace.

<Section Class="p-0" Size="HeadingSize.H4" Name="How it works" PageUrl="@pageUrl" Link="how-it-works">
<Block>
Expand All @@ -32,6 +32,23 @@
<Demo Type="typeof(BarChart_Demo_01_Examples)" Tabs="true" />
</Section>

<Section Class="p-0" Size="HeadingSize.H4" Name="Combo bar/line" PageUrl="@pageUrl" Link="combo-bar-line">
<Block>
The <strong>Combo bar/line</strong> demo mixes <code>BarChartDataset</code> and <code>LineChartDataset</code> in the same <code>BarChart</code>.
<br /><br />
<strong>How to use:</strong>
<div class="content">
<ol>
<li>Use the <code>BarChart</code> component as the root chart.</li>
<li>Add both <code>BarChartDataset</code> and <code>LineChartDataset</code> instances to the same <code>ChartData.Datasets</code> collection.</li>
<li>Configure interaction options such as <code>Mode = InteractionMode.Index</code> and <code>Intersect = false</code> for a combined tooltip experience.</li>
<li>Refer to the demo code below for a working example with bar columns and a line overlay.</li>
</ol>
</div>
</Block>
<Demo Type="typeof(BarChart_Demo_09_Combo_Bar_Line)" Tabs="true" />
</Section>

<Section Class="p-0" Size="HeadingSize.H4" Name="Horizontal bar chart" PageUrl="@pageUrl" Link="horizontal-bar-chart">
<Block>
The <strong>Horizontal Bar Chart</strong> displays data values as horizontal bars, making it ideal for comparing categories with long labels or when you want to emphasize comparison between values.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<BarChart @ref="barChart" Width="600" />

@code {
private BarChart barChart = default!;
private BarChartOptions barChartOptions = default!;
private ChartData chartData = default!;

protected override void OnInitialized()
{
var revenueColor = ColorUtility.CategoricalTwelveColors[0].ToColor();
var trendColor = ColorUtility.CategoricalTwelveColors[1].ToColor();

chartData = new ChartData
{
Labels = new List<string> { "January", "February", "March", "April", "May", "June" },
Datasets = new List<IChartDataset>
{
new BarChartDataset
{
Label = "Revenue",
Data = new List<double?> { 65, 59, 80, 81, 56, 55 },
BackgroundColor = new List<string> { revenueColor.ToRgbaString() },
BorderColor = new List<string> { revenueColor.ToRgbString() },
BorderWidth = new List<double> { 0 },
},
new LineChartDataset
{
Label = "Target",
Data = new List<double?> { 50, 55, 60, 70, 72, 78 },
BackgroundColor = trendColor.ToRgbaString(),
BorderColor = trendColor.ToRgbString(),
PointRadius = new List<double> { 4 },
PointHoverRadius = new List<double> { 6 },
},
},
};

barChartOptions = new BarChartOptions
{
Responsive = true,
Interaction = new Interaction { Mode = InteractionMode.Index, Intersect = false },
};
}

protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
await barChart.InitializeAsync(chartData, barChartOptions);

await base.OnAfterRenderAsync(firstRender);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

<DocsLink Href="@DemoRouteConstants.Docs_LineChart" />

<Prerequisites PageUrl="@pageUrl" />

Check warning on line 14 in BlazorExpress.ChartJS.Demo.RCL/Pages/Demos/LineChart/LineChartDocumentation.razor

View workflow job for this annotation

GitHub Actions / Build and Deploy Job

Found markup element with unexpected name 'Prerequisites'. If this is intended to be a component, add a @using directive for its namespace.

Check warning on line 14 in BlazorExpress.ChartJS.Demo.RCL/Pages/Demos/LineChart/LineChartDocumentation.razor

View workflow job for this annotation

GitHub Actions / Build and Deploy Job

Found markup element with unexpected name 'Prerequisites'. If this is intended to be a component, add a @using directive for its namespace.

<Section Class="p-0" Size="HeadingSize.H4" Name="How it works" PageUrl="@pageUrl" Link="how-it-works">
<Block>
Expand All @@ -37,6 +37,23 @@
<Demo Type="typeof(LineChart_Demo_01_B_Examples)" Tabs="true" />
</Section>

<Section Class="p-0" Size="HeadingSize.H4" Name="Combo bar/line" PageUrl="@pageUrl" Link="combo-bar-line">
<Block>
The <strong>Combo bar/line</strong> demo mixes <code>LineChartDataset</code> and <code>BarChartDataset</code> in the same <code>LineChart</code>.
<br /><br />
<strong>How to use:</strong>
<div class="content">
<ol>
<li>Use the <code>LineChart</code> component as the root chart.</li>
<li>Add both <code>LineChartDataset</code> and <code>BarChartDataset</code> instances to the same <code>ChartData.Datasets</code> collection.</li>
<li>Configure interaction options such as <code>Mode = InteractionMode.Index</code> and <code>Intersect = false</code> so bar and line points share tooltips cleanly.</li>
<li>Refer to the demo code below for a working example with a line series and bar columns in the same chart area.</li>
</ol>
</div>
</Block>
<Demo Type="typeof(LineChart_Demo_06_Combo_Bar_Line)" Tabs="true" />
</Section>

<Section Class="p-0" Size="HeadingSize.H4" Name="Data labels" PageUrl="@pageUrl" Link="data-labels">
<Block>
The <strong>Line Chart</strong> component supports data labels, allowing you to display values directly on each data point in the chart.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<LineChart @ref="lineChart" Width="600" />

@code {
private LineChart lineChart = default!;
private LineChartOptions lineChartOptions = default!;
private ChartData chartData = default!;

protected override void OnInitialized()
{
var forecastColor = ColorUtility.CategoricalTwelveColors[2].ToColor();
var actualColor = ColorUtility.CategoricalTwelveColors[3].ToColor();

chartData = new ChartData
{
Labels = new List<string> { "January", "February", "March", "April", "May", "June" },
Datasets = new List<IChartDataset>
{
new LineChartDataset
{
Label = "Forecast",
Data = new List<double?> { 45, 52, 61, 66, 73, 79 },
BackgroundColor = forecastColor.ToRgbaString(),
BorderColor = forecastColor.ToRgbString(),
PointRadius = new List<double> { 4 },
PointHoverRadius = new List<double> { 6 },
},
new BarChartDataset
{
Label = "Actual",
Data = new List<double?> { 41, 49, 58, 70, 75, 82 },
BackgroundColor = new List<string> { actualColor.ToRgbaString() },
BorderColor = new List<string> { actualColor.ToRgbString() },
BorderWidth = new List<double> { 0 },
},
},
};

lineChartOptions = new LineChartOptions
{
Responsive = true,
Interaction = new Interaction { Mode = InteractionMode.Index, Intersect = false },
};
}

protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
await lineChart.InitializeAsync(chartData, lineChartOptions);

await base.OnAfterRenderAsync(firstRender);
}
}
61 changes: 44 additions & 17 deletions BlazorExpress.ChartJS/ChartComponents/BarChart.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,30 @@ public override async Task<ChartData> AddDataAsync(ChartData chartData, string d
if (chartData.Datasets is null)
throw new ArgumentNullException(nameof(chartData.Datasets));

if (chartData.Labels is null)
throw new ArgumentNullException(nameof(chartData.Labels));

if (dataLabel is null)
throw new ArgumentNullException(nameof(dataLabel));

if (string.IsNullOrWhiteSpace(dataLabel))
throw new Exception($"{nameof(dataLabel)} cannot be empty.");

if (data is null)
throw new ArgumentNullException(nameof(data));

foreach (var dataset in chartData.Datasets)
if (dataset is BarChartDataset barChartDataset && barChartDataset.Label == dataLabel)
if (data is BarChartDatasetData barChartDatasetData)
barChartDataset.Data?.Add(barChartDatasetData.Data as double?);
var chartDatasetData = BarLineChartSupport.GetSupportedDatasetData(data);

await JSRuntime.InvokeVoidAsync(BarChartInterop.AddDatasetData, Id, dataLabel, data);
if (chartDatasetData is null)
return chartData;

if (!chartData.Labels.Contains(dataLabel))
chartData.Labels.Add(dataLabel);

foreach (var dataset in chartData.Datasets.Where(BarLineChartSupport.IsSupportedDataset))
BarLineChartSupport.AppendDataPoint(dataset, chartDatasetData);

await JSRuntime.InvokeVoidAsync(BarChartInterop.AddDatasetData, Id, dataLabel, chartDatasetData);

return chartData;
}
Expand Down Expand Up @@ -78,24 +93,36 @@ public override async Task<ChartData> AddDataAsync(ChartData chartData, string d
if (!data.Any())
throw new Exception($"{nameof(data)} cannot be empty.");

if (chartData.Datasets.Count != data.Count)
var supportedDatasets = chartData.Datasets.Where(BarLineChartSupport.IsSupportedDataset).ToList();
var supportedData = BarLineChartSupport.GetSupportedDatasetData(data);

if (!supportedDatasets.Any() || !supportedData.Any())
return chartData;

if (supportedDatasets.Count != supportedData.Count)
throw new InvalidDataException("The chart dataset count and the new data points count do not match.");

if (chartData.Labels.Contains(dataLabel))
throw new Exception($"{dataLabel} already exists.");

chartData.Labels.Add(dataLabel);

foreach (var dataset in chartData.Datasets)
if (dataset is BarChartDataset barChartDataset)
foreach (var dataset in supportedDatasets)
{
var chartDataset = dataset switch
{
var chartDatasetData = data.FirstOrDefault(x => x is BarChartDatasetData barChartDatasetData && barChartDatasetData.DatasetLabel == barChartDataset.Label);
BarChartDataset barChartDataset => barChartDataset.Label,
LineChartDataset lineChartDataset => lineChartDataset.Label,
_ => null,
};

if (chartDatasetData is BarChartDatasetData barChartDatasetData)
barChartDataset.Data?.Add(barChartDatasetData.Data as double?);
}
var chartDatasetData = supportedData.FirstOrDefault(x => x.DatasetLabel == chartDataset);

if (chartDatasetData is not null)
BarLineChartSupport.AppendDataPoint(dataset, chartDatasetData);
}

await JSRuntime.InvokeVoidAsync(BarChartInterop.AddDatasetsData, Id, dataLabel, data?.Select(x => (BarChartDatasetData)x));
await JSRuntime.InvokeVoidAsync(BarChartInterop.AddDatasetsData, Id, dataLabel, supportedData);

return chartData;
}
Expand All @@ -122,10 +149,10 @@ public override async Task<ChartData> AddDatasetAsync(ChartData chartData, IChar
if (chartDataset is null)
throw new ArgumentNullException(nameof(chartDataset));

if (chartDataset is BarChartDataset)
if (BarLineChartSupport.IsSupportedDataset(chartDataset))
{
chartData.Datasets.Add(chartDataset);
await JSRuntime.InvokeVoidAsync(BarChartInterop.AddDataset, Id, (BarChartDataset)chartDataset);
await JSRuntime.InvokeVoidAsync(BarChartInterop.AddDataset, Id, chartDataset);
}

return chartData;
Expand All @@ -148,7 +175,7 @@ public override async Task InitializeAsync(ChartData chartData, IChartOptions ch
{
if (chartData is not null && chartData.Datasets is not null)
{
var datasets = chartData.Datasets.OfType<BarChartDataset>();
var datasets = BarLineChartSupport.GetSupportedDatasets(chartData);
var data = new { chartData.Labels, Datasets = datasets };
await JSRuntime.InvokeVoidAsync(BarChartInterop.Initialize, Id, GetChartType(), data, (BarChartOptions)chartOptions, plugins);
}
Expand All @@ -169,7 +196,7 @@ public override async Task UpdateAsync(ChartData chartData, IChartOptions chartO
{
if (chartData is not null && chartData.Datasets is not null)
{
var datasets = chartData.Datasets.OfType<BarChartDataset>();
var datasets = BarLineChartSupport.GetSupportedDatasets(chartData);
var data = new { chartData.Labels, Datasets = datasets };
await JSRuntime.InvokeVoidAsync(BarChartInterop.Update, Id, GetChartType(), data, (BarChartOptions)chartOptions);
}
Expand Down
66 changes: 66 additions & 0 deletions BlazorExpress.ChartJS/ChartComponents/Core/BarLineChartSupport.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
namespace BlazorExpress.ChartJS;

internal static class BarLineChartSupport
{
internal static List<object> GetSupportedDatasets(ChartData chartData)
{
var datasets = new List<object>();

if (chartData?.Datasets?.Any() ?? false)
foreach (var dataset in chartData.Datasets)
if (dataset is BarChartDataset barChartDataset)
datasets.Add(barChartDataset);
else if (dataset is LineChartDataset lineChartDataset)
datasets.Add(lineChartDataset);

return datasets;
}

internal static List<ChartDatasetData> GetSupportedDatasetData(IEnumerable<IChartDatasetData> data) =>
data.Select(GetSupportedDatasetData)
.Where(x => x is not null)
.Cast<ChartDatasetData>()
.ToList();

internal static ChartDatasetData? GetSupportedDatasetData(IChartDatasetData data) =>
data switch
{
BarChartDatasetData barChartDatasetData => barChartDatasetData,
LineChartDatasetData lineChartDatasetData => lineChartDatasetData,
_ => null,
};

internal static bool IsSupportedDataset(IChartDataset dataset) =>
dataset is BarChartDataset or LineChartDataset;

internal static void AppendDataPoint(IChartDataset dataset, ChartDatasetData chartDatasetData)
{
if (!TryGetDataValue(chartDatasetData, out var value))
return;

switch (dataset)
{
case BarChartDataset barChartDataset when barChartDataset.Label == chartDatasetData.DatasetLabel:
barChartDataset.Data ??= new List<double?>();
barChartDataset.Data.Add(value);
break;
case LineChartDataset lineChartDataset when lineChartDataset.Label == chartDatasetData.DatasetLabel:
lineChartDataset.Data ??= new List<double?>();
lineChartDataset.Data.Add(value);
break;
}
}

private static bool TryGetDataValue(ChartDatasetData chartDatasetData, out double? value)
{
switch (chartDatasetData.Data)
{
case double number:
value = number;
return true;
default:
value = null;
return false;
}
}
}
Loading
Loading