From 072a5de083ef311afb56b67bc7d3b4652a4c14cd Mon Sep 17 00:00:00 2001 From: Samuele Locatelli Date: Thu, 18 Dec 2025 15:03:57 +0100 Subject: [PATCH] Modifiche stacked bar chart --- EgwCoreLib.Lux.Core/ColorHelper.cs | 57 ++++++++ Lux.API/Lux.API.csproj | 2 +- Lux.UI/Components/Compo/Charts/PieChart.razor | 114 +-------------- .../Components/Compo/Charts/PieChart.razor.cs | 138 ++++++++++++++++++ .../Compo/Charts/StackedBarChart.razor | 34 ++++- .../Compo/Stats/RealTimeStats.razor | 8 +- .../Compo/Stats/RealTimeStats.razor.cs | 71 +++++---- Lux.UI/wwwroot/lib/js/chartsInterop.js | 41 ++++-- Resources/ChangeLog.html | 2 +- Resources/VersNum.txt | 2 +- Resources/manifest.xml | 2 +- 11 files changed, 293 insertions(+), 178 deletions(-) create mode 100644 EgwCoreLib.Lux.Core/ColorHelper.cs create mode 100644 Lux.UI/Components/Compo/Charts/PieChart.razor.cs diff --git a/EgwCoreLib.Lux.Core/ColorHelper.cs b/EgwCoreLib.Lux.Core/ColorHelper.cs new file mode 100644 index 00000000..cdf7d6f3 --- /dev/null +++ b/EgwCoreLib.Lux.Core/ColorHelper.cs @@ -0,0 +1,57 @@ +using System.Globalization; +using System.Text.RegularExpressions; + +namespace EgwCoreLib.Lux.Core +{ + /// + /// Helper gestione colori RGB(A) + /// + public static class ColorHelper + { + #region Public Methods + + /// + /// Imposta un valore di alpha specifico. Se la stringa non è valida, ritorna un fallback nero. + /// + /// Stringa tipo "rgb(0,0,0)" o "rgba(0,0,0,0.5)" + /// Valore da 0.0 a 1.0 + public static string SetAlpha(string color, double alpha) + { + if (string.IsNullOrWhiteSpace(color)) return $"rgba(0,0,0,{alpha.ToString(CultureInfo.InvariantCulture)})"; + + // Cerchiamo i componenti R, G, B + var match = RgbComponentsRegex.Match(color); + + if (match.Success) + { + // Estraiamo i primi 3 gruppi (R, G, B) + string r = match.Groups[1].Value; + string g = match.Groups[2].Value; + string b = match.Groups[3].Value; + + // Usiamo InvariantCulture per assicurarci che il punto decimale sia sempre '.' e non ',' + string alphaStr = alpha.ToString("0.0", CultureInfo.InvariantCulture); + + return $"rgba({r},{g},{b},{alphaStr})"; + } + + // Se la stringa non è in formato rgb/rgba (magari è un nome colore o hex), + // per ora restituiamo l'originale o potresti gestire l'errore. + return color; + } + + /// + /// Forza l'alpha a 1, indipendentemente dal formato originale (rgb o rgba). + /// + public static string ToOpaqueRgba(string color) => SetAlpha(color, 1.0); + + #endregion Public Methods + + #region Private Fields + + // Pattern per catturare i tre componenti R, G, B ignorando l'eventuale A esistente + private static readonly Regex RgbComponentsRegex = new Regex(@"rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)", RegexOptions.Compiled); + + #endregion Private Fields + } +} \ No newline at end of file diff --git a/Lux.API/Lux.API.csproj b/Lux.API/Lux.API.csproj index 57f8ba47..89401fc7 100644 --- a/Lux.API/Lux.API.csproj +++ b/Lux.API/Lux.API.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 0.9.2512.1812 + 0.9.2512.1815 diff --git a/Lux.UI/Components/Compo/Charts/PieChart.razor b/Lux.UI/Components/Compo/Charts/PieChart.razor index 657ea923..3615b3a3 100644 --- a/Lux.UI/Components/Compo/Charts/PieChart.razor +++ b/Lux.UI/Components/Compo/Charts/PieChart.razor @@ -1,113 +1 @@ -@inject IJSRuntime JSRuntime -@implements IDisposable - -@code { - [Parameter] - public Dictionary PieDict { get; set; } = new(); - - [Parameter] - public Dictionary PieColor { get; set; } = new(); - - [Parameter] - public string CanvasId { get; set; } = "pieChart"; - - [Parameter] - public string Label { get; set; } = "#"; - - private bool created = false; - - protected override async Task OnAfterRenderAsync(bool firstRender) - { - // if (firstRender && PieDict.Any()) - if (firstRender || !created) - { - created = true; - pieLabels = PieDict.Keys.ToList(); - pieValues = PieDict.Values.ToList(); - bgColors = pieLabels.Select(l => PieColor.ContainsKey(l) ? PieColor[l] : "rgba(0,0,0,0.3)").ToList(); - bordColors = bgColors.Select(c => c.Replace("0.3", "1")).ToList(); - - await JSRuntime.InvokeVoidAsync("chartsInterop.createPieChart", CanvasId, new - { - type = "pie", - data = new - { - labels = pieLabels, - datasets = new[] - { - new { - label = Label, - data = pieValues, - backgroundColor= bgColors, - borderColor= bordColors, - borderWidth= 2 - } - } - }, - options = new - { - responsive = true, - animation = false - } - }); - } - } - public void Dispose() - { - // Chiama il metodo JS per pulire il dizionario - _ = JSRuntime.InvokeVoidAsync("chartsInterop.destroyChart", CanvasId); - } - private List pieLabels = new(); - private List pieValues = new(); - private List bgColors = new(); - private List bordColors = new(); - - protected override async Task OnParametersSetAsync() - { - if (created) - { - - - pieLabels = PieDict.Keys.ToList(); - pieValues = PieDict.Values.ToList(); - bgColors = pieLabels.Select(l => PieColor.ContainsKey(l) ? PieColor[l] : "rgba(0,0,0,0.3)").ToList(); - bordColors = bgColors.Select(c => c.Replace("0.3", "1")).ToList(); - - // await JSRuntime.InvokeVoidAsync("chartsInterop.createPieChart", CanvasId, pieLabels, values, bgColors); - - // await JSRuntime.InvokeVoidAsync("chartsInterop.createChart", CanvasId, new - await JSRuntime.InvokeVoidAsync("chartsInterop.createPieChart", CanvasId, new - { - type = "pie", - data = new - { - labels = pieLabels, - datasets = new[] - { - new { - label = Label, - data = pieValues, - backgroundColor= bgColors, - borderColor= bordColors, - borderWidth= 2 - } - } - }, - options = new - { - responsive = true, - animation = false - } - }); - } - else - { - pieLabels = PieDict.Keys.ToList(); - pieValues = PieDict.Values.ToList(); - bgColors = pieLabels.Select(l => PieColor.ContainsKey(l) ? PieColor[l] : "rgba(0,0,0,0.3)").ToList(); - bordColors = bgColors.Select(c => c.Replace("0.3", "1")).ToList(); - } - } -} - - + \ No newline at end of file diff --git a/Lux.UI/Components/Compo/Charts/PieChart.razor.cs b/Lux.UI/Components/Compo/Charts/PieChart.razor.cs new file mode 100644 index 00000000..0ae71f23 --- /dev/null +++ b/Lux.UI/Components/Compo/Charts/PieChart.razor.cs @@ -0,0 +1,138 @@ +using EgwCoreLib.Lux.Core; +using Microsoft.AspNetCore.Components; +using Microsoft.JSInterop; + +namespace Lux.UI.Components.Compo.Charts +{ + public partial class PieChart : IDisposable + { + #region Public Properties + + [Parameter] + public bool Animate { get; set; } = false; + + [Parameter] + public string CanvasId { get; set; } = "pieChart"; + + [Parameter] + public string Label { get; set; } = "#"; + + /// + /// Lista colori in formato RGBA + /// es: rgba(0,0,0,0.3) + /// + [Parameter] + public Dictionary PieColor { get; set; } = new(); + + [Parameter] + public Dictionary PieDict { get; set; } = new(); + + [Parameter] + public bool Responsive { get; set; } = true; + + [Parameter] + public double Alpha { get; set; } = 0.5; + + #endregion Public Properties + + #region Public Methods + + public void Dispose() + { + // Chiama il metodo JS per pulire il dizionario + _ = JSRuntime.InvokeVoidAsync("chartsInterop.destroyChart", CanvasId); + } + + #endregion Public Methods + + #region Protected Properties + + [Inject] + protected IJSRuntime JSRuntime { get; set; } = null!; + + #endregion Protected Properties + + #region Protected Methods + + /// + /// Setup dati per plotting + /// + /// + protected override async Task OnParametersSetAsync() + { + // solo se ho tutti i dati... + if (PieDict != null && PieDict.Count > 0) + { + await DoRenderChart(); + } + } + + #endregion Protected Methods + + #region Private Fields + + private List bgColors = new(); + + private List bordColors = new(); + + private List pieLabels = new(); + + private List pieValues = new(); + + #endregion Private Fields + + #region Private Methods + + /// + /// Conversione/trasformazione dati + chiamata js x rendering + /// + /// + private async Task DoRenderChart() + { + pieLabels = PieDict.Keys.ToList(); + pieValues = PieDict.Values.ToList(); +#if false + bgColors = pieLabels.Select(l => PieColor.ContainsKey(l) ? PieColor[l] : "rgba(0,0,0,0.3)").ToList(); + bordColors = bgColors.Select(c => c.Replace("0.3", "1")).ToList(); +#endif + // Sfondo: usiamo SetAlpha per garantire che, qualunque sia il colore nel dizionario, l'alpha sia Alpha + bgColors = pieLabels + .Select(l => { + string baseColor = PieColor.ContainsKey(l) ? PieColor[l] : "rgba(0,0,0,0)"; + return ColorHelper.SetAlpha(baseColor, Alpha); + }) + .ToList(); + + // Bordo: partiamo dai bgColors e forziamo l'opacità totale + bordColors = bgColors + .Select(c => ColorHelper.ToOpaqueRgba(c)) + .ToList(); + + await JSRuntime.InvokeVoidAsync("chartsInterop.createPieChart", CanvasId, new + { + type = "pie", + data = new + { + labels = pieLabels, + datasets = new[] + { + new { + label = Label, + data = pieValues, + backgroundColor= bgColors, + borderColor= bordColors, + borderWidth= 2 + } + } + }, + options = new + { + responsive = Responsive, + animation = Animate + } + }); + } + + #endregion Private Methods + } +} \ No newline at end of file diff --git a/Lux.UI/Components/Compo/Charts/StackedBarChart.razor b/Lux.UI/Components/Compo/Charts/StackedBarChart.razor index 9b8b3fdd..e81225a4 100644 --- a/Lux.UI/Components/Compo/Charts/StackedBarChart.razor +++ b/Lux.UI/Components/Compo/Charts/StackedBarChart.razor @@ -1,5 +1,6 @@ @inject IJSRuntime JSRuntime + @code { [Parameter] public Dictionary BarColor { get; set; } = new(); [Parameter] public List ListX { get; set; } = new(); // puoi passare DateTime.ToString() @@ -8,20 +9,45 @@ protected override async Task OnAfterRenderAsync(bool firstRender) { - if (firstRender && DictY.Any()) + // if (firstRender && DictY.Any()) + if (DictY.Any()) { var datasets = DictY.Select(kvp => new { label = kvp.Key, data = kvp.Value, - backgroundColor = BarColor.ContainsKey(kvp.Key) ? BarColor[kvp.Key] : "rgba(0,0,0,0.3)", - borderColor = BarColor.ContainsKey(kvp.Key) ? BarColor[kvp.Key] : "rgba(0,0,0,1)", + // Usiamo il tuo nuovo ColorHelper! + backgroundColor = ColorHelper.SetAlpha(BarColor.GetValueOrDefault(kvp.Key, "rgba(0,0,0,0.3)"), 0.3), + borderColor = ColorHelper.ToOpaqueRgba(BarColor.GetValueOrDefault(kvp.Key, "rgba(0,0,0,1)")), + // backgroundColor = BarColor.ContainsKey(kvp.Key) ? BarColor[kvp.Key] : "rgba(0,0,0,0.3)", + // borderColor = BarColor.ContainsKey(kvp.Key) ? BarColor[kvp.Key] : "rgba(0,0,0,1)", borderWidth = 2 }).ToList(); + #if false await JSRuntime.InvokeVoidAsync("chartsInterop.createBarChart", CanvasId, ListX, datasets); + #endif + + // Passiamo l'intera configurazione per gestire lo 'stacked' + await JSRuntime.InvokeVoidAsync("chartsInterop.createBarChart", CanvasId, new + { + type = "bar", + data = new + { + labels = ListX, + datasets = datasets + }, + options = new + { + responsive = true, + scales = new + { + x = new { stacked = true }, // Abilita lo stack su X + y = new { stacked = true } // Abilita lo stack su Y + } + } + }); } } } - diff --git a/Lux.UI/Components/Compo/Stats/RealTimeStats.razor b/Lux.UI/Components/Compo/Stats/RealTimeStats.razor index 005704ba..64846af8 100644 --- a/Lux.UI/Components/Compo/Stats/RealTimeStats.razor +++ b/Lux.UI/Components/Compo/Stats/RealTimeStats.razor @@ -9,20 +9,20 @@
- +
- @* *@ +
- +
- @* *@ + @* *@
diff --git a/Lux.UI/Components/Compo/Stats/RealTimeStats.razor.cs b/Lux.UI/Components/Compo/Stats/RealTimeStats.razor.cs index 3a86e8ec..6f41e730 100644 --- a/Lux.UI/Components/Compo/Stats/RealTimeStats.razor.cs +++ b/Lux.UI/Components/Compo/Stats/RealTimeStats.razor.cs @@ -37,9 +37,6 @@ namespace Lux.UI.Components.Compo.Stats { if (firstRender) { -#if false - InitConfig(); -#endif await InitCharts(); _cts = new CancellationTokenSource(); _ = UpdateLoop(_cts.Token); @@ -140,37 +137,17 @@ namespace Lux.UI.Components.Compo.Stats { PieColors.Clear(); PieColors.Add("WINDOW", "rgba(54,162,235,0.6)"); - PieColors.Add("BEAM", "rgba(255,99,132,0.6)"); -#if false - Labels.Clear(); - Labels.Add("WINDOW"); - Labels.Add("BEAM"); - - - PieCount.Clear(); - PieCount.Add("WINDOW", 208); - PieCount.Add("BEAM", 13); - - ListX.Clear(); - ListX = new List { "2025-12-17T00:00:00" }; - - DictY.Clear(); - DictY.Add("WINDOW", new List { 208, 150 }); - DictY.Add("BEAM", new List { 13, 20 }); -#endif - PieCount.Clear(); - PieCount.Add("NONE", 0); - PieElapsed.Clear(); - PieElapsed.Add("NONE", 0); + PieColors.Add("BEAM", "rgba(69,195,132,0.6)"); + DatasetCount.Clear(); + DatasetCount.Add("NONE", 0); + DataseElapsed.Clear(); + DataseElapsed.Add("NONE", 0); } private Dictionary PieColors = new Dictionary(); - private Dictionary PieCount = new Dictionary(); - private Dictionary PieElapsed = new Dictionary(); -#if false - private List Labels = new List(); -#endif + private Dictionary DatasetCount = new Dictionary(); + private Dictionary DataseElapsed = new Dictionary(); private List ListX = new List(); private Dictionary> DictY = new Dictionary>(); @@ -220,27 +197,45 @@ namespace Lux.UI.Components.Compo.Stats // calcolo i valori da mostrare... if (rtProcStats != null && rtProcStats.Count > 0) { -#if false - PieCount.Clear(); - PieElapsed.Clear(); - await InvokeAsync(StateHasChanged); -#endif - - PieCount = rtProcStats + DatasetCount = rtProcStats .GroupBy(p => p.TagClass) .ToDictionary( g => g.Key, g => (double)g.Sum(p => p.EventCount) ); - PieElapsed = rtProcStats + DataseElapsed = rtProcStats .GroupBy(p => p.TagClass) .ToDictionary( g => g.Key, g => g.Sum(p => p.Elapsed) ); + + // 1. Definiamo le etichette dell'asse X (es. le ultime 24 ore o quelle presenti nei dati) + ListX = rtProcStats + .Select(p => p.Hour.ToString("HH:mm")) + .Distinct() + .OrderBy(t => t) + .ToList(); + + // 2. Identifichiamo tutti i TagClass unici (saranno i nostri dataset) + var allTags = rtProcStats.Select(p => p.TagClass).Distinct().ToList(); + + // 3. Costruiamo il DictY: per ogni Tag, creiamo una lista di valori allineata a ListX + DictY = allTags.ToDictionary( + tag => tag, + tag => ListX.Select(hour => + { + // Cerchiamo il valore per quel tag in quell'ora specifica + return (decimal)(rtProcStats + .FirstOrDefault(p => p.TagClass == tag && p.Hour.ToString("HH:mm") == hour) + ?.EventCount ?? 0); // Se non esiste, mettiamo 0 + }).ToList() + ); + + // forzatura update await InvokeAsync(StateHasChanged); } } diff --git a/Lux.UI/wwwroot/lib/js/chartsInterop.js b/Lux.UI/wwwroot/lib/js/chartsInterop.js index 822f4d3e..cdec61d7 100644 --- a/Lux.UI/wwwroot/lib/js/chartsInterop.js +++ b/Lux.UI/wwwroot/lib/js/chartsInterop.js @@ -76,20 +76,31 @@ } }, - - createBarChart: function (canvasId, labels, datasets) { - const ctx = document.getElementById(canvasId); - new Chart(ctx, { - type: "bar", - data: { labels: labels, datasets: datasets }, - options: { - responsive: true, - plugins: { legend: { position: "top" } }, - scales: { - x: { stacked: true, title: { display: true, text: "X Axis" } }, - y: { stacked: true, title: { display: true, text: "Values" } } - } - } - }); + createBarChart: function (canvasId, config) { + if (this.charts[canvasId]) { + const chart = this.charts[canvasId]; + chart.data.labels = config.data.labels; + chart.data.datasets = config.data.datasets; // Sostituisce i dataset con i nuovi + chart.update('none'); // Update senza animazione per aggiornamenti real-time + } else { + const ctx = document.getElementById(canvasId).getContext('2d'); + this.charts[canvasId] = new Chart(ctx, config); + } } + + //createBarChart: function (canvasId, labels, datasets) { + // const ctx = document.getElementById(canvasId); + // new Chart(ctx, { + // type: "bar", + // data: { labels: labels, datasets: datasets }, + // options: { + // responsive: true, + // plugins: { legend: { position: "top" } }, + // scales: { + // x: { stacked: true, title: { display: true, text: "X Axis" } }, + // y: { stacked: true, title: { display: true, text: "Values" } } + // } + // } + // }); + //} }; diff --git a/Resources/ChangeLog.html b/Resources/ChangeLog.html index 623ba636..63a1c685 100644 --- a/Resources/ChangeLog.html +++ b/Resources/ChangeLog.html @@ -1,6 +1,6 @@ LUX - Web Windows MES -

Versione: 0.9.2512.1812

+

Versione: 0.9.2512.1815


Note di rilascio:
  • diff --git a/Resources/VersNum.txt b/Resources/VersNum.txt index e99e64e3..1d5d1ca1 100644 --- a/Resources/VersNum.txt +++ b/Resources/VersNum.txt @@ -1 +1 @@ -0.9.2512.1812 +0.9.2512.1815 diff --git a/Resources/manifest.xml b/Resources/manifest.xml index a0814553..f7c50c5f 100644 --- a/Resources/manifest.xml +++ b/Resources/manifest.xml @@ -1,6 +1,6 @@ - 0.9.2512.1812 + 0.9.2512.1815 http://nexus.steamware.net/repository/SWS/GPW/stable/GPW.UI.zip http://nexus.steamware.net/repository/SWS/GPW/stable/ChangeLog.html false