72 lines
2.7 KiB
C#
72 lines
2.7 KiB
C#
using Schaad.Accounting;
|
|
using Schaad.Accounting.Datasets.Charts;
|
|
|
|
namespace Schaad.Accounting.UI.Components.Pages.Charts;
|
|
|
|
/// <summary>
|
|
/// Swiss (de-CH) formatting for chart labels/tooltips, plus the top-N + "Andere"
|
|
/// folding used to keep pies and stacked series legible. Plotly's own d3-format
|
|
/// tokens can't produce the apostrophe thousands separator, so values are
|
|
/// pre-formatted here in C# and passed to Plotly as literal text.
|
|
/// </summary>
|
|
public static class ChartFormatting
|
|
{
|
|
private static readonly string[] MonthAbbreviations =
|
|
[
|
|
"Jan", "Feb", "Mrz", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"
|
|
];
|
|
|
|
public static string MonthLabel(DateOnly date) => $"{MonthAbbreviations[date.Month - 1]} {date.Year % 100:00}";
|
|
|
|
public static string Chf(decimal value) => $"{value.ToFormattedString()} CHF";
|
|
|
|
/// <summary>
|
|
/// Sorts by magnitude descending and, past <see cref="ChartTheme.MaxCategories"/>,
|
|
/// collapses the remainder into a single "Andere" entry so a pie/legend never
|
|
/// has to carry more slices than the categorical palette can distinguish.
|
|
/// </summary>
|
|
public static IReadOnlyList<CategoryTotal> FoldTail(IReadOnlyList<CategoryTotal> items)
|
|
{
|
|
var sorted = items.OrderByDescending(i => Math.Abs(i.Value)).ToList();
|
|
if (sorted.Count <= ChartTheme.MaxCategories)
|
|
{
|
|
return sorted;
|
|
}
|
|
|
|
var head = sorted.Take(ChartTheme.MaxCategories - 1).ToList();
|
|
var tailTotal = sorted.Skip(ChartTheme.MaxCategories - 1).Sum(i => i.Value);
|
|
head.Add(new CategoryTotal(ChartTheme.OtherLabel, tailTotal));
|
|
return head;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Same idea as <see cref="FoldTail(IReadOnlyList{CategoryTotal})"/> for a set of
|
|
/// monthly series (e.g. a stacked bar chart): series are ranked by total magnitude
|
|
/// across all months, and everything past the cap is summed month-by-month into
|
|
/// a single "Andere" series.
|
|
/// </summary>
|
|
public static IReadOnlyList<DataSerie> FoldTail(IReadOnlyList<DataSerie> series)
|
|
{
|
|
if (series.Count <= ChartTheme.MaxCategories)
|
|
{
|
|
return series;
|
|
}
|
|
|
|
var ranked = series.OrderByDescending(s => s.Y.Sum(Math.Abs)).ToList();
|
|
var head = ranked.Take(ChartTheme.MaxCategories - 1).ToList();
|
|
var tail = ranked.Skip(ChartTheme.MaxCategories - 1).ToList();
|
|
|
|
var x = tail[0].X;
|
|
var otherY = new decimal[x.Count];
|
|
foreach (var serie in tail)
|
|
{
|
|
for (var i = 0; i < x.Count; i++)
|
|
{
|
|
otherY[i] += serie.Y[i];
|
|
}
|
|
}
|
|
|
|
head.Add(new DataSerie(ChartTheme.OtherLabel, ChartTheme.OtherLabel, x, otherY));
|
|
return head;
|
|
}
|
|
}
|