Files
lux/Lux.UI/Components/Compo/Stats/HistoricalStats.razor.cs
2026-03-25 09:05:06 +01:00

365 lines
13 KiB
C#

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<double> avgData = new();
private bool isLoading = false;
// Dati da passare a Chart.js
private List<string> labels = new();
private List<double> maxData = new();
private bool needReload = false;
private List<double> 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<double> 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
/// <summary>
/// Helper creazione grafico Chart.js single line
/// </summary>
/// <param name="canvasId"></param>
/// <param name="label"></param>
/// <param name="data"></param>
/// <param name="borderColor"></param>
/// <returns></returns>
private async Task CreateChartAsync(string canvasId, string label, List<double> data, string borderColor)
{
var config = new
{
type = "line",
data = new
{
labels = (IEnumerable<object>)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);
}
/// <summary>
/// Helper creazione grafico Chart.js multi line
/// </summary>
/// <param name="canvasId"></param>
/// <param name="labels"></param>
/// <param name="avgData"></param>
/// <param name="maxData"></param>
/// <returns></returns>
private async Task CreateChartMultiAsync(string canvasId, string yAxisType, List<string> labels, List<double> avgData, List<double> 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);
}
/// <summary>
/// Creazione di un barchart stacked tra chiamate OK e perse (no answer)
/// </summary>
/// <param name="canvasId"></param>
/// <param name="label"></param>
/// <param name="listResp"></param>
/// <param name="listMissed"></param>
/// <param name="borderColorResp"></param>
/// <param name="borderColorMissed"></param>
/// <returns></returns>
private async Task CreateMultiBarChartAsync(string canvasId, string yAxisType, string label, List<double> listResp, List<double> listMissed, string borderColorResp = "rgba(0, 0, 255, 1)", string borderColorMissed = "rgba(255, 0, 0, 1)")
{
var config = new
{
type = "bar",
data = new
{
labels = (IEnumerable<object>)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);
}
/// <summary>
/// Caricamento e conversione dati per plotting
/// </summary>
/// <returns></returns>
private async Task DoReload()
{
// [1] Recupero dei dati aggregati
List<StatsAggregatedModel> 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;
}
/// <summary>
/// Ricalcolo delle statistiche (esporta su DB)
/// </summary>
/// <returns></returns>
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
}
}