Draw the bandmap the way N1MM draws it
The bandmap was a list of frequencies and callsigns. It is now a frequency scale with the stations written out beside it and a leader line from each callsign back to where the station really is. BandmapLayout does the placing and has no UI reference, so the rule is unit tested: put the label centred on its frequency, and if it would cover the one above, push it below instead. That is N1MM's CalculateOffset, and it is what keeps three stations a hundred hertz apart readable. The scale stops short of the top and bottom edges, or the first and last frequency numbers come out cut in half. VfoMarker draws the receiver as a bar as wide as the mode passes, rather than a line. Only the receiver is fed: the radio does not report a transmit VFO or a second radio yet, so VfoRole has the other two roles ready and nothing draws them. Band-plan colouring of the scale is left out because the segments differ by ITU region and there is no band-plan table to read them from. Checked by running the program against a fake cluster node under Xvfb and looking at the result: twelve spots, three of them stacked with fanned leaders. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
95
tests/Nonemm.Spotting.Tests/BandmapLayoutTests.cs
Normal file
95
tests/Nonemm.Spotting.Tests/BandmapLayoutTests.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using Nonemm.Core;
|
||||
|
||||
namespace Nonemm.Spotting.Tests;
|
||||
|
||||
public class BandmapLayoutTests
|
||||
{
|
||||
private const double LabelHeight = 10;
|
||||
|
||||
private static readonly DateTime Now = new(2026, 5, 30, 12, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
/// 14000 to 14100 kHz over 100 pixels, so one pixel is one kilohertz.
|
||||
private static BandmapScale Scale() =>
|
||||
new(Frequency.FromKilohertz(14_000), Frequency.FromKilohertz(14_100), 100);
|
||||
|
||||
private static Spot At(double kilohertz, string call = "DL1ABC") =>
|
||||
new(Callsign.Parse(call), Frequency.FromKilohertz(kilohertz), Now, SpotSource.Cluster);
|
||||
|
||||
[Fact]
|
||||
public void ALabelSitsCentredOnItsFrequency()
|
||||
{
|
||||
IReadOnlyList<BandmapLabel> placed = BandmapLayout.Place([At(14_050)], Scale(), LabelHeight);
|
||||
|
||||
Assert.Equal(50, placed[0].MarkY);
|
||||
Assert.Equal(45, placed[0].LabelY);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LabelsComeOutInFrequencyOrder()
|
||||
{
|
||||
IReadOnlyList<BandmapLabel> placed = BandmapLayout.Place(
|
||||
[At(14_080, "JA1XYZ"), At(14_010, "DL1ABC")],
|
||||
Scale(),
|
||||
LabelHeight);
|
||||
|
||||
Assert.Equal(["DL1ABC", "JA1XYZ"], placed.Select(l => l.Spot.Call.Text));
|
||||
}
|
||||
|
||||
/// Two stations a hundred hertz apart would have their text on top of each
|
||||
/// other, so the lower one moves down and its leader line does the work.
|
||||
[Fact]
|
||||
public void ALabelThatWouldCoverTheOneAboveIsPushedDown()
|
||||
{
|
||||
IReadOnlyList<BandmapLabel> placed = BandmapLayout.Place(
|
||||
[At(14_050, "DL1ABC"), At(14_050.1, "JA1XYZ")],
|
||||
Scale(),
|
||||
LabelHeight);
|
||||
|
||||
Assert.Equal(45, placed[0].LabelY);
|
||||
Assert.Equal(55.5, placed[1].LabelY);
|
||||
// the leader still points at where the station really is
|
||||
Assert.Equal(50.1, placed[1].MarkY, 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ALabelWithRoomKeepsItsOwnPlace()
|
||||
{
|
||||
IReadOnlyList<BandmapLabel> placed = BandmapLayout.Place(
|
||||
[At(14_010, "DL1ABC"), At(14_090, "JA1XYZ")],
|
||||
Scale(),
|
||||
LabelHeight);
|
||||
|
||||
Assert.Equal(5, placed[0].LabelY);
|
||||
Assert.Equal(85, placed[1].LabelY);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AStationOutsideTheSliceIsNotDrawn() =>
|
||||
Assert.Empty(BandmapLayout.Place([At(14_200)], Scale(), LabelHeight));
|
||||
|
||||
[Fact]
|
||||
public void TheTicksStartAtTheFirstRoundFrequencyInTheSlice()
|
||||
{
|
||||
IReadOnlyList<Frequency> ticks = BandmapLayout.Ticks(
|
||||
new BandmapScale(Frequency.FromKilohertz(14_003), Frequency.FromKilohertz(14_012), 100),
|
||||
Frequency.FromKilohertz(5));
|
||||
|
||||
Assert.Equal([14_005_000, 14_010_000], ticks.Select(t => t.Hertz));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TheScaleTurnsPixelsBackIntoFrequencies() =>
|
||||
Assert.Equal(14_050_000, Scale().At(50).Hertz);
|
||||
|
||||
[Fact]
|
||||
public void AReceiverBarIsAsWideAsTheModeItIsIn()
|
||||
{
|
||||
VfoMarker vfo = VfoMarker.Centred(
|
||||
Frequency.FromKilohertz(14_025),
|
||||
Frequency.FromHertz(500),
|
||||
VfoRole.Receive);
|
||||
|
||||
Assert.Equal(14_024_750, vfo.Low.Hertz);
|
||||
Assert.Equal(14_025_250, vfo.High.Hertz);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user