Count the log under any two of its fields

N1MM's statistics window. `StatisticsPivot` counts the contacts under one row
field and one column field, with the row total, the running total and the
column totals N1MM prints. The fields are N1MM's own two lists: 29 down the
rows, 20 of them across the columns, sorted the way N1MM's SQL sorts them, so
zone 10 comes after zone 2 and 80M before 20M.

The window has N1MM's two tabs. The table is the grid with `Tot` and `Accum`
and a `Total` line; the graph is a stacked column per row, one segment per
column, in one of N1MM's twelve palettes. "Include Dupes & X-QSOs" counts the
contacts left out of the claimed score and the ones marked as dupes.

Checked against a 3775-contact CQ WW SSB log: 3762 counted, which is what
`select count(*) ... where CLAIMEDQSO = 1 and ContactType <> 'D'` returns, and
the per-band totals match band by band.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PdAYHcdRktqKry7nk414TU
This commit is contained in:
2026-09-04 07:47:30 +00:00
co-authored by Claude Opus 5
parent 4eb8071d57
commit acd22a9b5e
15 changed files with 1145 additions and 1 deletions
+45
View File
@@ -0,0 +1,45 @@
using Avalonia.Media;
namespace Nonemm.App.Windows;
/// The twelve palettes N1MM's statistics graph offers. The names are the ones
/// in N1MM's box, which are the .NET chart control's palettes; the colours are
/// chosen to match the name, not copied out of that control.
public static class ChartPalettes
{
public static readonly IReadOnlyList<string> Names =
[
"Berry", "Bright", "Bright Pastel", "Chocolate", "Earth Tones", "Excel",
"Fire", "Gray Scale", "Light", "Pastel", "Sea Green", "Semi Transparent",
];
private static readonly Dictionary<string, uint[]> Colours = new()
{
["Berry"] = [0x8A2BE2, 0xBA55D3, 0xC71585, 0xDA70D6, 0x7B68EE, 0x9370DB, 0xFF69B4, 0x4B0082],
["Bright"] = [0x1F77B4, 0xFF7F0E, 0x2CA02C, 0xD62728, 0x9467BD, 0x8C564B, 0xE377C2, 0x17BECF],
["Bright Pastel"] = [0x418CF0, 0xFCB441, 0xE0400A, 0x056492, 0xBFBFBF, 0x1A3B69, 0xFFE382, 0x129CDD],
["Chocolate"] = [0x5B3A29, 0x8B4513, 0xA0522D, 0xCD853F, 0xD2B48C, 0x6B4226, 0xB87333, 0xE3C6A0],
["Earth Tones"] = [0x6B8E23, 0x808000, 0xBDB76B, 0x8B7355, 0x556B2F, 0xA0522D, 0xC0A16B, 0x4F5D2F],
["Excel"] = [0x4472C4, 0xED7D31, 0xA5A5A5, 0xFFC000, 0x5B9BD5, 0x70AD47, 0x264478, 0x9E480E],
["Fire"] = [0xB22222, 0xE34A00, 0xFF8C00, 0xFFC000, 0xFFE873, 0x8B0000, 0xFF4500, 0xFFA07A],
["Gray Scale"] = [0x2B2B2B, 0x4D4D4D, 0x6E6E6E, 0x8F8F8F, 0xB0B0B0, 0xC9C9C9, 0xE0E0E0, 0xF2F2F2],
["Light"] = [0xA6CEE3, 0xB2DF8A, 0xFDBF6F, 0xCAB2D6, 0xFB9A99, 0xFFFF99, 0xB3E2CD, 0xD9D9D9],
["Pastel"] = [0xAEC7E8, 0xFFBB78, 0x98DF8A, 0xFF9896, 0xC5B0D5, 0xC49C94, 0xF7B6D2, 0x9EDAE5],
["Sea Green"] = [0x2E8B57, 0x3CB371, 0x20B2AA, 0x66CDAA, 0x008B8B, 0x5F9EA0, 0x8FBC8F, 0x006400],
["Semi Transparent"] = [0x1F77B4, 0xFF7F0E, 0x2CA02C, 0xD62728, 0x9467BD, 0x8C564B, 0xE377C2, 0x17BECF],
};
/// The palette of that name, or the first one when there is no such name.
/// A series past the end of the list takes a colour again from the start,
/// as the chart control does.
public static IReadOnlyList<Color> Of(string name)
{
uint[] values = Colours.TryGetValue(name, out uint[]? found) ? found : Colours["Berry"];
byte alpha = name == "Semi Transparent" ? (byte)0xA0 : (byte)0xFF;
return [.. values.Select(v => Color.FromArgb(
alpha,
(byte)(v >> 16),
(byte)(v >> 8),
(byte)v))];
}
}