using EgwCoreLib.Lux.Data.DbModel.Stats; namespace Lux.UI.Components.Compo.Stats { public partial class HistoricalStats { #region Protected Methods protected override async Task OnInitializedAsync() { // Se il servizio restituisce il “periodo di aggregazione” (es. ultimi 7 gg) periodoLimit = await Calc.StatsAggrRangeAsync(); // aggiungo padding 1h pre-post... periodoLimit.Inizio = periodoLimit.Inizio.AddDays(-1); periodoLimit.Fine = periodoLimit.Fine.AddDays(1); // Normalizzo i bound dei picker periodoSel.Inizio = periodoSel.Inizio < periodoLimit.Inizio ? periodoLimit.Inizio : periodoSel.Inizio; // carica! await DoReload(); } #endregion Protected Methods #region Private Fields private List avgData = new(); private bool isLoading = false; // Dati da passare a Chart.js private List labels = new(); private List maxData = new(); private bool needReload = false; private List noData = new(); private EgwCoreLib.Utils.DtUtils.Periodo periodoLimit = new EgwCoreLib.Utils.DtUtils.Periodo(EgwCoreLib.Utils.DtUtils.PeriodSet.LastWeek); private EgwCoreLib.Utils.DtUtils.Periodo periodoSel = new EgwCoreLib.Utils.DtUtils.Periodo(DateTime.Today.AddHours(DateTime.Now.Hour - 24 * 2), DateTime.Today.AddHours(DateTime.Now.Hour + 1)); private List reqData = new(); #endregion Private Fields #region Private Properties private string btnReload => needReload ? "btn-primary" : "btn-secondary"; [Inject] private ICalcRuidService Calc { get; set; } = null!; private DateTime dtFrom { get => periodoSel.Inizio; set { if (periodoSel.Inizio != value) { periodoSel.Inizio = value; needReload = true; } } } private DateTime dtTo { get => periodoSel.Fine; set { if (periodoSel.Fine != value) { periodoSel.Fine = value; needReload = true; } } } [Inject] private IJSRuntime JSRuntime { get; set; } = null!; #endregion Private Properties #region Private Methods /// /// Helper creazione grafico Chart.js single line /// /// /// /// /// /// private async Task CreateChartAsync(string canvasId, string label, List data, string borderColor) { var config = new { type = "line", data = new { labels = (IEnumerable)labels, datasets = new[] { new { label = label, data = data, borderColor = borderColor, fill = false, tension = 0 } } }, options = new { responsive = true, plugins = new { legend = new { display = true }, tooltip = new { mode = "index", intersect = false } }, scales = new { x = new { type = "time", time = new { unit = "hour", displayFormats = new { hour = "MM.DD HH:mm" } }, title = new { display = true, text = "Data/Ora" } }, y = new { display = true, title = new { display = true, text = "Call (#/h)" } } } } }; await JSRuntime.InvokeVoidAsync("chartsInterop.createChart", canvasId, config); } /// /// Helper creazione grafico Chart.js multi line /// /// /// /// /// /// private async Task CreateChartMultiAsync(string canvasId, string yAxisType, List labels, List avgData, List maxData) { var config = new { type = "line", data = new { labels = labels, datasets = new[] { new { label = "Tempo medio (s)", data = avgData, borderColor = "green", fill = true, tension = 0 }, new { label = "Tempo massimo (s)", data = maxData, borderColor = "red", fill = false, tension = 0 } } }, options = new { responsive = true, plugins = new { legend = new { display = true }, tooltip = new { mode = "index", intersect = false } }, scales = new { x = new { type = "time", time = new { unit = "hour", displayFormats = new { hour = "MM.DD HH:mm" } }, title = new { display = true, text = "Data/Ora" } }, y = new { display = true, type = yAxisType, // <-- Questa linea attiva l'asse Y lineare/logaritmico title = new { display = true, text = "Durata (s)" } } } } }; await JSRuntime.InvokeVoidAsync("chartsInterop.createChart", canvasId, config); } /// /// Creazione di un barchart stacked tra chiamate OK e perse (no answer) /// /// /// /// /// /// /// /// private async Task CreateMultiBarChartAsync(string canvasId, string yAxisType, string label, List listResp, List listMissed, string borderColorResp = "rgba(0, 0, 255, 1)", string borderColorMissed = "rgba(255, 0, 0, 1)") { var config = new { type = "bar", data = new { labels = (IEnumerable)labels, // Assicurati di avere la lista labels definita datasets = new[] { new { label = "Ok", data = listResp, backgroundColor = "rgba(0, 0, 255, 0.3)", // blu semitrasparente borderColor = borderColorResp, borderWidth = 2 }, new { label = "Miss", data = listMissed, backgroundColor = "rgba(255, 0, 0, 0.3)", // rosso semitrasparente borderColor = borderColorMissed, borderWidth = 2 } } }, options = new { responsive = true, plugins = new { legend = new { display = true }, tooltip = new { mode = "index", intersect = false } }, scales = new { x = new { stacked = true, type = "time", time = new { unit = "hour", displayFormats = new { hour = "MM.DD HH:mm" } }, title = new { display = true, text = "Data/Ora" } }, y = new { stacked = true, display = true, type = yAxisType, // <-- Questa linea attiva l'asse Y lineare/logaritmico title = new { display = true, text = "Call (#/h)" } } } } }; await JSRuntime.InvokeVoidAsync("chartsInterop.createChart", canvasId, config); } /// /// Caricamento e conversione dati per plotting /// /// private async Task DoReload() { // [1] Recupero dei dati aggregati List stats = await Calc.StatsAggrListAsync(periodoSel.Inizio, periodoSel.Fine); // [2] Conversione in liste compatibili con Chart.js labels.Clear(); reqData.Clear(); noData.Clear(); avgData.Clear(); maxData.Clear(); // Calcolo dei massimi/minimi per decidere tipo asse double globalMax = 0; double globalMin = double.MaxValue; foreach (var s in stats) { //if (s.RequestCount > 0) globalMin = Math.Min(globalMin, s.RequestCount); //if (s.NoReply > 0) globalMin = Math.Min(globalMin, s.NoReply); if (s.AvgDuration > 0) globalMin = Math.Min(globalMin, s.AvgDuration); if (s.MaxDuration > 0) globalMin = Math.Min(globalMin, s.MaxDuration); globalMax = Math.Max(globalMax, Math.Max(s.AvgDuration, s.MaxDuration)); } double factor = (globalMin > 0) ? globalMax / globalMin : globalMax; string yAxisType = factor > 20 ? "logarithmic" : "linear"; // Valore di riempimento per missing data double fillValue = yAxisType == "logarithmic" ? 0.01 : 0; // [2b] Popolamento liste for (var t = periodoSel.Inizio; t <= periodoSel.Fine; t = t.AddHours(1)) { var s = stats.FirstOrDefault(x => x.Hour == t); labels.Add(t.ToString("yyyy-MM-ddTHH:mm:ss")); // ISO string if (s != null) { reqData.Add(s.RequestCount); noData.Add(s.NoReply); avgData.Add(s.AvgDuration); maxData.Add(s.MaxDuration); } else { //reqData.Add(fillValue); //noData.Add(fillValue); reqData.Add(0); noData.Add(0); avgData.Add(fillValue); maxData.Add(fillValue); } } // [3] Creazione grafici con tipo asse passato await CreateMultiBarChartAsync("chartBarRequests", "linear", "Richieste/ora", reqData, noData, "blue", "red"); await CreateChartMultiAsync("chartHistoricalTimes", yAxisType, labels, avgData, maxData); needReload = false; } /// /// Ricalcolo delle statistiche (esporta su DB) /// /// private async Task RicalcolaStats() { isLoading = true; await InvokeAsync(StateHasChanged); // non fa mai eliminazione la chiamata interattiva await Calc.ExportStatsToDbAsync(false); isLoading = false; await InvokeAsync(StateHasChanged); await Task.Delay(50); // Dopo il ricalcolo possiamo ricaricare i dati così da mostrarli subito await DoReload(); } #endregion Private Methods } }