From c477c7ba7b8e6b33b225f6988fb06dfa70f00232 Mon Sep 17 00:00:00 2001
From: Oleg <18466855+EvaTheSalmon@users.noreply.github.com>
Date: Wed, 7 Oct 2026 14:23:04 +0300
Subject: [PATCH] refactor(pens): suffix the start scale pair _on_start
SemiBase v0.5.0 renamed semiplot_tags.scale_min/scale_max to
scale_min_on_start/scale_max_on_start: a column holding what a pen
opens with carries the _on_start suffix, as enabled_on_start does. The
catalogue reads, the editor's update and the bench seeder name the new
columns, so the viewer reads a v0.5.0 database again.
The C# members named for the stored pair follow the column: Pen,
StoredPen and SyntheticPen expose ScaleMinOnStart/ScaleMaxOnStart,
PenSettingChange.Scale and PenField.Scale become ScaleOnStart, and the
pen editor's columns, drafts and resource keys take the same suffix.
The operator text does not change.
---
SemiPlot/SemiPlot.Core/Data/PenCatalogue.cs | 4 +-
.../SemiPlot.Core/Data/PenSettingChange.cs | 2 +-
SemiPlot/SemiPlot.Core/Trends/Pen.cs | 12 ++---
.../SemiPlot.Core/Trends/PenScaleSettings.cs | 2 +-
.../ArchiveStatements.cs | 18 ++++---
.../PostgresDataProvider.cs | 4 +-
.../PostgresPenCatalogueEditor.cs | 9 ++--
.../PenCatalogueEditorTests.cs | 14 ++---
.../PenRegistrationTests.cs | 4 +-
.../PostgresCatalogReadTests.cs | 12 ++---
.../TagCatalogWriterTests.cs | 6 ++-
.../Core/Trends/PenListDeltaTests.cs | 14 ++---
.../RawLayerGeneratorTests.cs | 4 +-
.../UI/Chart/AxisScalePanelViewModelTests.cs | 6 ++-
.../UI/Chart/AxisScalePanelViewTests.cs | 2 +-
.../UI/Chart/TrendChartCatalogueTests.cs | 7 ++-
.../UI/Chart/TrendChartViewModelTests.cs | 21 ++++----
.../UI/MainWindow/AppMenuBarTests.cs | 4 +-
.../UI/PenEditor/PenEditorViewModelTests.cs | 4 +-
.../UI/PenEditor/PenEditorViewTests.cs | 38 ++++++-------
.../UI/PenEditor/PenFormViewModelTests.cs | 50 ++++++++---------
.../SyntheticPen.cs | 4 +-
.../TagCatalogWriter.cs | 13 ++---
.../SemiPlot.UI/Localization/Resources.resx | 4 +-
.../Localization/Resources.ru.resx | 4 +-
.../PenEditor/PenEditorViewModel.cs | 10 ++--
.../PenEditor/PenEditorWindow.axaml | 36 ++++++-------
.../PenEditor/PenEditorWindow.axaml.cs | 6 +--
SemiPlot/SemiPlot.UI/PenEditor/PenField.cs | 2 +-
.../SemiPlot.UI/PenEditor/PenFormViewModel.cs | 54 +++++++++++--------
.../SemiPlot.UI/PenEditor/PenRowViewModel.cs | 3 +-
docs/architecture/charting.md | 8 +--
docs/architecture/data-integration.md | 10 ++--
docs/architecture/postgres-instance.md | 2 +-
docs/architecture/trend-feature-spec.md | 4 +-
docs/architecture/trend-interaction.md | 6 +--
docs/architecture/ui-text.md | 4 +-
37 files changed, 217 insertions(+), 190 deletions(-)
diff --git a/SemiPlot/SemiPlot.Core/Data/PenCatalogue.cs b/SemiPlot/SemiPlot.Core/Data/PenCatalogue.cs
index 51f33b5..d8615a9 100644
--- a/SemiPlot/SemiPlot.Core/Data/PenCatalogue.cs
+++ b/SemiPlot/SemiPlot.Core/Data/PenCatalogue.cs
@@ -14,8 +14,8 @@ public sealed record StoredPen(
string? Color,
PenLineStyle LineStyle,
bool EnabledOnStart,
- double? ScaleMin,
- double? ScaleMax);
+ double? ScaleMinOnStart,
+ double? ScaleMaxOnStart);
/// One semiplot_groups row and the ids of its member pens, in ascending order.
public sealed record StoredGroup(int Id, string Name, IReadOnlyList MemberPenIds);
diff --git a/SemiPlot/SemiPlot.Core/Data/PenSettingChange.cs b/SemiPlot/SemiPlot.Core/Data/PenSettingChange.cs
index 945e751..35eb68f 100644
--- a/SemiPlot/SemiPlot.Core/Data/PenSettingChange.cs
+++ b/SemiPlot/SemiPlot.Core/Data/PenSettingChange.cs
@@ -24,5 +24,5 @@ public sealed record LineStyle(PenLineStyle Value) : PenSettingChange;
public sealed record EnabledOnStart(bool Value) : PenSettingChange;
- public sealed record Scale(double? Min, double? Max) : PenSettingChange;
+ public sealed record ScaleOnStart(double? Min, double? Max) : PenSettingChange;
}
diff --git a/SemiPlot/SemiPlot.Core/Trends/Pen.cs b/SemiPlot/SemiPlot.Core/Trends/Pen.cs
index 07bc870..064d97b 100644
--- a/SemiPlot/SemiPlot.Core/Trends/Pen.cs
+++ b/SemiPlot/SemiPlot.Core/Trends/Pen.cs
@@ -8,8 +8,8 @@ public sealed record Pen(
string? Unit = null,
string? Format = null,
bool EnabledOnStart = true,
- double? ScaleMin = null,
- double? ScaleMax = null,
+ double? ScaleMinOnStart = null,
+ double? ScaleMaxOnStart = null,
PenLineStyle LineStyle = PenLineStyle.Interpolated)
{
/// Compares element by element, so two reads of one stored pen are equal.
@@ -23,8 +23,8 @@ public bool Equals(Pen? other)
&& string.Equals(Unit, other.Unit, StringComparison.Ordinal)
&& string.Equals(Format, other.Format, StringComparison.Ordinal)
&& EnabledOnStart == other.EnabledOnStart
- && Nullable.Equals(ScaleMin, other.ScaleMin)
- && Nullable.Equals(ScaleMax, other.ScaleMax)
+ && Nullable.Equals(ScaleMinOnStart, other.ScaleMinOnStart)
+ && Nullable.Equals(ScaleMaxOnStart, other.ScaleMaxOnStart)
&& LineStyle == other.LineStyle;
}
@@ -44,8 +44,8 @@ public override int GetHashCode()
hash.Add(Unit, StringComparer.Ordinal);
hash.Add(Format, StringComparer.Ordinal);
hash.Add(EnabledOnStart);
- hash.Add(ScaleMin);
- hash.Add(ScaleMax);
+ hash.Add(ScaleMinOnStart);
+ hash.Add(ScaleMaxOnStart);
hash.Add(LineStyle);
return hash.ToHashCode();
diff --git a/SemiPlot/SemiPlot.Core/Trends/PenScaleSettings.cs b/SemiPlot/SemiPlot.Core/Trends/PenScaleSettings.cs
index 4ed22bc..26de818 100644
--- a/SemiPlot/SemiPlot.Core/Trends/PenScaleSettings.cs
+++ b/SemiPlot/SemiPlot.Core/Trends/PenScaleSettings.cs
@@ -11,7 +11,7 @@ public static PenScaleSettings InitialFor(Pen pen)
{
var settings = new PenScaleSettings(pen.PenId);
- if (pen.ScaleMin is { } min && pen.ScaleMax is { } max)
+ if (pen.ScaleMinOnStart is { } min && pen.ScaleMaxOnStart is { } max)
{
settings = settings with { Mode = ScaleMode.Manual, ManualMin = min, ManualMax = max };
}
diff --git a/SemiPlot/SemiPlot.DataSource.Postgres/ArchiveStatements.cs b/SemiPlot/SemiPlot.DataSource.Postgres/ArchiveStatements.cs
index e65e01f..61ea841 100644
--- a/SemiPlot/SemiPlot.DataSource.Postgres/ArchiveStatements.cs
+++ b/SemiPlot/SemiPlot.DataSource.Postgres/ArchiveStatements.cs
@@ -1,7 +1,7 @@
namespace SemiPlot.DataSource.Postgres;
///
-/// The column order of and, up to , of
+/// The column order of and, up to , of
/// , named so a transposition shows.
///
internal static class PenCatalogColumn
@@ -13,8 +13,8 @@ internal static class PenCatalogColumn
public const int Color = 4;
public const int LineStyle = 5;
public const int EnabledOnStart = 6;
- public const int ScaleMin = 7;
- public const int ScaleMax = 8;
+ public const int ScaleMinOnStart = 7;
+ public const int ScaleMaxOnStart = 8;
public const int Groups = 9;
}
@@ -65,7 +65,7 @@ internal static class ArchiveStatements
///
public const string StoredPens = """
SELECT id, name, unit, format, color, line_style, enabled_on_start,
- scale_min, scale_max
+ scale_min_on_start, scale_max_on_start
FROM semiplot_tags
ORDER BY id;
""";
@@ -99,9 +99,11 @@ GROUP BY grp.id
///
/// Both bounds in one statement: semiplot_tags_scale_paired refuses a half-set pair between two.
///
- public const string UpdatePenScale = """
- UPDATE semiplot_tags SET scale_min = @min, scale_max = @max WHERE id = @id;
- """;
+ public const string UpdatePenScaleOnStart = """
+ UPDATE semiplot_tags
+ SET scale_min_on_start = @min, scale_max_on_start = @max
+ WHERE id = @id;
+ """;
public const string CreateGroup = "INSERT INTO semiplot_groups (name) VALUES (@name) RETURNING id;";
@@ -129,7 +131,7 @@ DELETE FROM semiplot_pen_groups
///
public const string PenCatalog = """
SELECT tag.id, tag.name, tag.unit, tag.format, tag.color, tag.line_style,
- tag.enabled_on_start, tag.scale_min, tag.scale_max,
+ tag.enabled_on_start, tag.scale_min_on_start, tag.scale_max_on_start,
coalesce(array_agg(grp.name ORDER BY grp.name)
FILTER (WHERE grp.name IS NOT NULL), '{}') AS groups
FROM semiplot_tags tag
diff --git a/SemiPlot/SemiPlot.DataSource.Postgres/PostgresDataProvider.cs b/SemiPlot/SemiPlot.DataSource.Postgres/PostgresDataProvider.cs
index 1884e2d..c8e7e0d 100644
--- a/SemiPlot/SemiPlot.DataSource.Postgres/PostgresDataProvider.cs
+++ b/SemiPlot/SemiPlot.DataSource.Postgres/PostgresDataProvider.cs
@@ -481,12 +481,12 @@ private Pen ReadPen(NpgsqlDataReader reader)
// installation without the constraint: it autoscales rather than opening on a bound with no partner.
private static (double? Min, double? Max) ReadScalePair(NpgsqlDataReader reader)
{
- if (reader.IsDBNull(PenCatalogColumn.ScaleMin) || reader.IsDBNull(PenCatalogColumn.ScaleMax))
+ if (reader.IsDBNull(PenCatalogColumn.ScaleMinOnStart) || reader.IsDBNull(PenCatalogColumn.ScaleMaxOnStart))
{
return (null, null);
}
- return (reader.GetDouble(PenCatalogColumn.ScaleMin), reader.GetDouble(PenCatalogColumn.ScaleMax));
+ return (reader.GetDouble(PenCatalogColumn.ScaleMinOnStart), reader.GetDouble(PenCatalogColumn.ScaleMaxOnStart));
}
// The server validates a stored colour against the hex pattern, so only its absence is handled here.
diff --git a/SemiPlot/SemiPlot.DataSource.Postgres/PostgresPenCatalogueEditor.cs b/SemiPlot/SemiPlot.DataSource.Postgres/PostgresPenCatalogueEditor.cs
index 0aebc29..4710daf 100644
--- a/SemiPlot/SemiPlot.DataSource.Postgres/PostgresPenCatalogueEditor.cs
+++ b/SemiPlot/SemiPlot.DataSource.Postgres/PostgresPenCatalogueEditor.cs
@@ -166,7 +166,8 @@ PenSettingChange.LineStyle lineStyle
=> Value(ArchiveStatements.UpdatePenLineStyle, NpgsqlDbType.Smallint, (short)lineStyle.Value),
PenSettingChange.EnabledOnStart enabledOnStart
=> Value(ArchiveStatements.UpdatePenEnabledOnStart, NpgsqlDbType.Boolean, enabledOnStart.Value),
- PenSettingChange.Scale scale => (ArchiveStatements.UpdatePenScale, ScaleParameters(scale)),
+ PenSettingChange.ScaleOnStart scale
+ => (ArchiveStatements.UpdatePenScaleOnStart, ScaleOnStartParameters(scale)),
_ => throw new ArgumentOutOfRangeException(nameof(change), change, "Unknown pen setting change.")
};
}
@@ -179,7 +180,7 @@ private static (string Statement, NpgsqlParameter[] Parameters) Value(
return (statement, [new NpgsqlParameter("value", type) { Value = value }]);
}
- private static NpgsqlParameter[] ScaleParameters(PenSettingChange.Scale scale)
+ private static NpgsqlParameter[] ScaleOnStartParameters(PenSettingChange.ScaleOnStart scale)
{
return
[
@@ -256,8 +257,8 @@ private StoredPen ReadStoredPen(NpgsqlDataReader reader)
ReadText(reader, PenCatalogColumn.Color),
StoredLineStyle.Read(storedLineStyle, penId, _logger, () => LogLevel.Warning),
reader.GetBoolean(PenCatalogColumn.EnabledOnStart),
- ReadBound(reader, PenCatalogColumn.ScaleMin),
- ReadBound(reader, PenCatalogColumn.ScaleMax));
+ ReadBound(reader, PenCatalogColumn.ScaleMinOnStart),
+ ReadBound(reader, PenCatalogColumn.ScaleMaxOnStart));
}
private static string? ReadText(NpgsqlDataReader reader, int ordinal)
diff --git a/SemiPlot/SemiPlot.Tests.Integration/PenCatalogueEditorTests.cs b/SemiPlot/SemiPlot.Tests.Integration/PenCatalogueEditorTests.cs
index 2716a55..97aff56 100644
--- a/SemiPlot/SemiPlot.Tests.Integration/PenCatalogueEditorTests.cs
+++ b/SemiPlot/SemiPlot.Tests.Integration/PenCatalogueEditorTests.cs
@@ -89,7 +89,7 @@ public async Task ReadReturnsANullColourAsNullAndAnUnusableMaskAsItIsStored()
[InlineData(nameof(PenSettingChange.Color))]
[InlineData(nameof(PenSettingChange.LineStyle))]
[InlineData(nameof(PenSettingChange.EnabledOnStart))]
- [InlineData(nameof(PenSettingChange.Scale))]
+ [InlineData(nameof(PenSettingChange.ScaleOnStart))]
public async Task EachChangeWritesItsOwnColumnAndNothingElse(string arm)
{
using var services = Build();
@@ -151,12 +151,12 @@ public async Task TheScalePairWritesBothBoundsInOneStatement()
var editor = Editor(services);
var pen = await PenAsync(editor, UncommissionedId);
- var set = await editor.ChangeAsync(pen, new PenSettingChange.Scale(-5, 5));
+ var set = await editor.ChangeAsync(pen, new PenSettingChange.ScaleOnStart(-5, 5));
set.IsSuccess.Should().BeTrue(ArchiveReadSupport.Describe(set));
- (await PenAsync(editor, UncommissionedId)).Should().Be(pen with { ScaleMin = -5, ScaleMax = 5 });
+ (await PenAsync(editor, UncommissionedId)).Should().Be(pen with { ScaleMinOnStart = -5, ScaleMaxOnStart = 5 });
- var cleared = await editor.ChangeAsync(pen, new PenSettingChange.Scale(null, null));
+ var cleared = await editor.ChangeAsync(pen, new PenSettingChange.ScaleOnStart(null, null));
cleared.IsSuccess.Should().BeTrue(ArchiveReadSupport.Describe(cleared));
(await PenAsync(editor, UncommissionedId)).Should().Be(pen);
@@ -173,7 +173,7 @@ public async Task AScalePairTheServerRefusesIsARejectedValueNamingThePen(double?
var editor = Editor(services);
var pen = await PenAsync(editor, ChamberPressureId);
- var result = await editor.ChangeAsync(pen, new PenSettingChange.Scale(min, max));
+ var result = await editor.ChangeAsync(pen, new PenSettingChange.ScaleOnStart(min, max));
FailureOf(result).Should().Be((ArchiveFault.ValueRejected, "Chamber pressure"));
(await PenAsync(editor, ChamberPressureId)).Should().Be(pen);
@@ -422,8 +422,8 @@ private static (PenSettingChange Change, StoredPen Expected) Arm(string arm, Sto
pen with { LineStyle = PenLineStyle.Stepped }),
nameof(PenSettingChange.EnabledOnStart)
=> (new PenSettingChange.EnabledOnStart(false), pen with { EnabledOnStart = false }),
- nameof(PenSettingChange.Scale)
- => (new PenSettingChange.Scale(-1, 1), pen with { ScaleMin = -1, ScaleMax = 1 }),
+ nameof(PenSettingChange.ScaleOnStart)
+ => (new PenSettingChange.ScaleOnStart(-1, 1), pen with { ScaleMinOnStart = -1, ScaleMaxOnStart = 1 }),
_ => throw new ArgumentOutOfRangeException(nameof(arm), arm, "Unknown change arm.")
};
}
diff --git a/SemiPlot/SemiPlot.Tests.Integration/PenRegistrationTests.cs b/SemiPlot/SemiPlot.Tests.Integration/PenRegistrationTests.cs
index 56a6b2e..2af3087 100644
--- a/SemiPlot/SemiPlot.Tests.Integration/PenRegistrationTests.cs
+++ b/SemiPlot/SemiPlot.Tests.Integration/PenRegistrationTests.cs
@@ -64,8 +64,8 @@ public async Task AKeyWithNoPenBecomesAHiddenDefaultPenNamedByItsNumber()
added.Color.Should().BeOneOf(_registrationColors);
added.Unit.Should().BeNull();
added.Format.Should().BeNull();
- added.ScaleMin.Should().BeNull();
- added.ScaleMax.Should().BeNull();
+ added.ScaleMinOnStart.Should().BeNull();
+ added.ScaleMaxOnStart.Should().BeNull();
pens.Select(pen => pen.Id).Should().Equal(_unregisteredPenIds.Prepend(RegisteredPenId));
}
diff --git a/SemiPlot/SemiPlot.Tests.Integration/PostgresCatalogReadTests.cs b/SemiPlot/SemiPlot.Tests.Integration/PostgresCatalogReadTests.cs
index 1afab29..edf3725 100644
--- a/SemiPlot/SemiPlot.Tests.Integration/PostgresCatalogReadTests.cs
+++ b/SemiPlot/SemiPlot.Tests.Integration/PostgresCatalogReadTests.cs
@@ -100,8 +100,8 @@ public async Task SeededCatalogueCarriesTheCommissionedFieldsOfEachPen()
commissioned.Unit.Should().Be("degC");
commissioned.Format.Should().Be("0.0");
- commissioned.ScaleMin.Should().Be(20.0);
- commissioned.ScaleMax.Should().Be(850.0);
+ commissioned.ScaleMinOnStart.Should().Be(20.0);
+ commissioned.ScaleMaxOnStart.Should().Be(850.0);
commissioned.EnabledOnStart.Should().BeTrue();
Single(result.Value, SyntheticPenCatalog.HiddenOnStartPenId).EnabledOnStart.Should().BeFalse();
@@ -147,8 +147,8 @@ public async Task APenInNoGroupReadsAsAnEmptyGroupList()
var uncommissioned = Single(result.Value, SyntheticPenCatalog.UncommissionedPenId);
uncommissioned.Groups.Should().BeEmpty();
- uncommissioned.ScaleMin.Should().BeNull();
- uncommissioned.ScaleMax.Should().BeNull();
+ uncommissioned.ScaleMinOnStart.Should().BeNull();
+ uncommissioned.ScaleMaxOnStart.Should().BeNull();
}
[Fact]
@@ -289,8 +289,8 @@ private static Pen ToPen(SyntheticPen pen)
pen.Unit,
pen.Format,
pen.EnabledOnStart,
- pen.ScaleMin,
- pen.ScaleMax,
+ pen.ScaleMinOnStart,
+ pen.ScaleMaxOnStart,
pen.LineStyle);
}
diff --git a/SemiPlot/SemiPlot.Tests.Integration/TagCatalogWriterTests.cs b/SemiPlot/SemiPlot.Tests.Integration/TagCatalogWriterTests.cs
index 41a4a77..1ad6a56 100644
--- a/SemiPlot/SemiPlot.Tests.Integration/TagCatalogWriterTests.cs
+++ b/SemiPlot/SemiPlot.Tests.Integration/TagCatalogWriterTests.cs
@@ -27,7 +27,7 @@ FROM public.semiplot_pen_groups membership
""";
private const string StoredScalesCommand =
- "SELECT id, scale_min, scale_max FROM public.semiplot_tags ORDER BY id;";
+ "SELECT id, scale_min_on_start, scale_max_on_start FROM public.semiplot_tags ORDER BY id;";
[Fact]
public async Task TheGroupTableHoldsEveryGroupTheSliceNames()
@@ -60,7 +60,9 @@ public async Task TheStoredScalePairIsTheSyntheticPensOwnBounds()
written.Should().Equal(
[
- .. Slice().Select(pen => (pen.PenId, pen.ScaleMin, pen.ScaleMax)).OrderBy(scale => scale.PenId)
+ .. Slice()
+ .Select(pen => (pen.PenId, pen.ScaleMinOnStart, pen.ScaleMaxOnStart))
+ .OrderBy(scale => scale.PenId)
]);
written.Should().Contain(scale => scale.PenId == SyntheticPenCatalog.UncommissionedPenId
&& scale.Min == null
diff --git a/SemiPlot/SemiPlot.Tests.Unit/Core/Trends/PenListDeltaTests.cs b/SemiPlot/SemiPlot.Tests.Unit/Core/Trends/PenListDeltaTests.cs
index 3ab3236..be875c4 100644
--- a/SemiPlot/SemiPlot.Tests.Unit/Core/Trends/PenListDeltaTests.cs
+++ b/SemiPlot/SemiPlot.Tests.Unit/Core/Trends/PenListDeltaTests.cs
@@ -19,8 +19,8 @@ public sealed class PenListDeltaTests
"Pa",
"0.0",
EnabledOnStart: true,
- ScaleMin: 0,
- ScaleMax: 100);
+ ScaleMinOnStart: 0,
+ ScaleMaxOnStart: 100);
private static readonly Pen _heater = new(2, "Heater", ["Heaters"], "#223344", "degC");
@@ -36,8 +36,8 @@ public sealed class PenListDeltaTests
nameof(Pen.Unit),
nameof(Pen.Format),
nameof(Pen.EnabledOnStart),
- nameof(Pen.ScaleMin),
- nameof(Pen.ScaleMax),
+ nameof(Pen.ScaleMinOnStart),
+ nameof(Pen.ScaleMaxOnStart),
nameof(Pen.LineStyle)
];
@@ -96,7 +96,7 @@ public void AColourChange_GivesOneRevisionAndNoSetChange()
[InlineData(null, null)]
public void AStoredScaleChange_GivesARevision(double? scaleMin, double? scaleMax)
{
- var rescaled = _pressure with { ScaleMin = scaleMin, ScaleMax = scaleMax };
+ var rescaled = _pressure with { ScaleMinOnStart = scaleMin, ScaleMaxOnStart = scaleMax };
var delta = PenListDelta.Between([_pressure], [rescaled]);
@@ -196,8 +196,8 @@ private static Pen WithOneMemberChanged(Pen pen, string member)
nameof(Pen.Unit) => pen with { Unit = "kPa" },
nameof(Pen.Format) => pen with { Format = "0.000" },
nameof(Pen.EnabledOnStart) => pen with { EnabledOnStart = !pen.EnabledOnStart },
- nameof(Pen.ScaleMin) => pen with { ScaleMin = -5.0 },
- nameof(Pen.ScaleMax) => pen with { ScaleMax = 500.0 },
+ nameof(Pen.ScaleMinOnStart) => pen with { ScaleMinOnStart = -5.0 },
+ nameof(Pen.ScaleMaxOnStart) => pen with { ScaleMaxOnStart = 500.0 },
nameof(Pen.LineStyle) => pen with { LineStyle = PenLineStyle.Stepped },
_ => throw new ArgumentOutOfRangeException(nameof(member), member, "No change is defined for it.")
};
diff --git a/SemiPlot/SemiPlot.Tests.Unit/RawLayerGeneratorTests.cs b/SemiPlot/SemiPlot.Tests.Unit/RawLayerGeneratorTests.cs
index 3d02116..91122cb 100644
--- a/SemiPlot/SemiPlot.Tests.Unit/RawLayerGeneratorTests.cs
+++ b/SemiPlot/SemiPlot.Tests.Unit/RawLayerGeneratorTests.cs
@@ -217,8 +217,8 @@ public void TheStandardSliceCarriesTheThreeCommissioningStates()
uncommissioned.Groups.Should().BeEmpty();
uncommissioned.Unit.Should().BeNull();
uncommissioned.Format.Should().BeNull();
- uncommissioned.ScaleMin.Should().BeNull();
- uncommissioned.ScaleMax.Should().BeNull();
+ uncommissioned.ScaleMinOnStart.Should().BeNull();
+ uncommissioned.ScaleMaxOnStart.Should().BeNull();
}
[Fact]
diff --git a/SemiPlot/SemiPlot.Tests.Unit/UI/Chart/AxisScalePanelViewModelTests.cs b/SemiPlot/SemiPlot.Tests.Unit/UI/Chart/AxisScalePanelViewModelTests.cs
index 02c1c70..4f5f20a 100644
--- a/SemiPlot/SemiPlot.Tests.Unit/UI/Chart/AxisScalePanelViewModelTests.cs
+++ b/SemiPlot/SemiPlot.Tests.Unit/UI/Chart/AxisScalePanelViewModelTests.cs
@@ -351,7 +351,8 @@ public void Autoscale_ActsOnTheActivePenAndCloses()
public void InitialScale_ActsOnTheActivePenAndCloses()
{
var (chart, _, _, provider) = CreateViewModel();
- chart.ApplyCatalogue([.. provider.Pens.Select(pen => pen with { ScaleMin = 5.0, ScaleMax = 50.0 })]);
+ chart.ApplyCatalogue(
+ [.. provider.Pens.Select(pen => pen with { ScaleMinOnStart = 5.0, ScaleMaxOnStart = 50.0 })]);
chart.SetAxisLimits(1, 10.0, 90.0);
chart.SetAxisLimits(2, 20.0, 80.0);
chart.SetActivePen(2);
@@ -373,7 +374,8 @@ public void InitialScale_ActsOnTheActivePenAndCloses()
public void WhenThePenIsNoLongerDrawn_ThePanelClosesAndEveryCommandWritesNothing(PanelPenLeaves leaves)
{
var (chart, _, _, provider) = CreateViewModel();
- chart.ApplyCatalogue([.. provider.Pens.Select(pen => pen with { ScaleMin = 5.0, ScaleMax = 50.0 })]);
+ chart.ApplyCatalogue(
+ [.. provider.Pens.Select(pen => pen with { ScaleMinOnStart = 5.0, ScaleMaxOnStart = 50.0 })]);
chart.SetAxisLimits(1, 10.0, 90.0);
chart.SetAxisLimits(2, 20.0, 80.0);
chart.SetActivePen(2);
diff --git a/SemiPlot/SemiPlot.Tests.Unit/UI/Chart/AxisScalePanelViewTests.cs b/SemiPlot/SemiPlot.Tests.Unit/UI/Chart/AxisScalePanelViewTests.cs
index 081760e..ad25a46 100644
--- a/SemiPlot/SemiPlot.Tests.Unit/UI/Chart/AxisScalePanelViewTests.cs
+++ b/SemiPlot/SemiPlot.Tests.Unit/UI/Chart/AxisScalePanelViewTests.cs
@@ -244,7 +244,7 @@ public void TheInitialScaleButton_RestoresTheStoredPairAndClosesThePanel()
{
using var viewModel = CreateLoadedViewModel();
var pen = new Pen(1, "Pen 1", ["Group A"], "#ff0000");
- viewModel.ApplyCatalogue([pen with { ScaleMin = 5.0, ScaleMax = 50.0 }]);
+ viewModel.ApplyCatalogue([pen with { ScaleMinOnStart = 5.0, ScaleMaxOnStart = 50.0 }]);
viewModel.SetAxisLimits(1, 10.0, 90.0);
using var shown = ShowChart(viewModel);
var (flyout, panel) = OpenPanel(viewModel, shown);
diff --git a/SemiPlot/SemiPlot.Tests.Unit/UI/Chart/TrendChartCatalogueTests.cs b/SemiPlot/SemiPlot.Tests.Unit/UI/Chart/TrendChartCatalogueTests.cs
index 0121e5e..e40a2f2 100644
--- a/SemiPlot/SemiPlot.Tests.Unit/UI/Chart/TrendChartCatalogueTests.cs
+++ b/SemiPlot/SemiPlot.Tests.Unit/UI/Chart/TrendChartCatalogueTests.cs
@@ -100,7 +100,10 @@ public void ARevisedStoredScale_LeavesTheShownPenAlone()
var autoSettings = viewModel.ScaleSettings[2];
viewModel.ApplyCatalogue(
- [catalogue[0] with { ScaleMin = 0.0, ScaleMax = 50.0 }, catalogue[1] with { ScaleMin = 5.0, ScaleMax = 6.0 }]);
+ [
+ catalogue[0] with { ScaleMinOnStart = 0.0, ScaleMaxOnStart = 50.0 },
+ catalogue[1] with { ScaleMinOnStart = 5.0, ScaleMaxOnStart = 6.0 }
+ ]);
viewModel.ScaleSettings[1].Mode.Should().Be(ScaleMode.Manual);
viewModel.ScaleRangeForPen(1)!.Value.Should().Be((10.0, 90.0));
@@ -115,7 +118,7 @@ public void APenTheCatalogueAdds_TakesItsStoredPair()
var catalogue = provider.Pens;
viewModel.ApplyCatalogue([catalogue[0]]);
- viewModel.ApplyCatalogue([catalogue[0], catalogue[1] with { ScaleMin = 20.0, ScaleMax = 80.0 }]);
+ viewModel.ApplyCatalogue([catalogue[0], catalogue[1] with { ScaleMinOnStart = 20.0, ScaleMaxOnStart = 80.0 }]);
var settings = viewModel.ScaleSettings[2];
settings.Mode.Should().Be(ScaleMode.Manual);
diff --git a/SemiPlot/SemiPlot.Tests.Unit/UI/Chart/TrendChartViewModelTests.cs b/SemiPlot/SemiPlot.Tests.Unit/UI/Chart/TrendChartViewModelTests.cs
index ece1163..9e39b04 100644
--- a/SemiPlot/SemiPlot.Tests.Unit/UI/Chart/TrendChartViewModelTests.cs
+++ b/SemiPlot/SemiPlot.Tests.Unit/UI/Chart/TrendChartViewModelTests.cs
@@ -165,7 +165,7 @@ public void InitialScale_RestoresTheStoredPair()
var catalogue = provider.Pens;
viewModel.ApplyCatalogue(catalogue);
viewModel.SetAxisLimits(1, 10.0, 90.0);
- viewModel.ApplyCatalogue([catalogue[0] with { ScaleMin = 0.0, ScaleMax = 50.0 }, catalogue[1]]);
+ viewModel.ApplyCatalogue([catalogue[0] with { ScaleMinOnStart = 0.0, ScaleMaxOnStart = 50.0 }, catalogue[1]]);
viewModel.RestoreInitialScale();
@@ -183,8 +183,8 @@ public void InitialScaleWithNoStoredPair_IsAuto()
{
var (viewModel, _, _, provider) = CreateViewModel();
var catalogue = provider.Pens;
- viewModel.ApplyCatalogue([catalogue[0] with { ScaleMin = 0.0, ScaleMax = 50.0 }, catalogue[1]]);
- viewModel.ApplyCatalogue([catalogue[0] with { ScaleMin = null, ScaleMax = null }, catalogue[1]]);
+ viewModel.ApplyCatalogue([catalogue[0] with { ScaleMinOnStart = 0.0, ScaleMaxOnStart = 50.0 }, catalogue[1]]);
+ viewModel.ApplyCatalogue([catalogue[0] with { ScaleMinOnStart = null, ScaleMaxOnStart = null }, catalogue[1]]);
viewModel.RestoreInitialScale();
@@ -206,7 +206,8 @@ public void TheScaleCommandsWithNoPens_ChangeNothing()
public void InitialScale_ActsOnTheActivePenOnly()
{
var (viewModel, _, _, provider) = CreateViewModel();
- viewModel.ApplyCatalogue([.. provider.Pens.Select(pen => pen with { ScaleMin = 5.0, ScaleMax = 50.0 })]);
+ viewModel.ApplyCatalogue(
+ [.. provider.Pens.Select(pen => pen with { ScaleMinOnStart = 5.0, ScaleMaxOnStart = 50.0 })]);
viewModel.SetAxisLimits(1, 10.0, 90.0);
viewModel.SetAxisLimits(2, 20.0, 80.0);
viewModel.SetActivePen(2);
@@ -236,7 +237,8 @@ public void Autoscale_ActsOnTheActivePenOnly()
public void TheScaleCommands_LeaveAHiddenActivePenAlone()
{
var (viewModel, _, _, provider) = CreateViewModel();
- viewModel.ApplyCatalogue([.. provider.Pens.Select(pen => pen with { ScaleMin = 5.0, ScaleMax = 50.0 })]);
+ viewModel.ApplyCatalogue(
+ [.. provider.Pens.Select(pen => pen with { ScaleMinOnStart = 5.0, ScaleMaxOnStart = 50.0 })]);
viewModel.SetAxisLimits(1, 10.0, 90.0);
viewModel.SetPenVisibility(2, false);
viewModel.SetPenVisibility(1, false);
@@ -295,7 +297,8 @@ public void AutoscalePen_ActsOnTheNamedPenWhateverIsActive()
public void RestoreInitialScale_ForANamedPen_ActsOnThatPenAndRefusesAHiddenOne()
{
var (viewModel, _, _, provider) = CreateViewModel();
- viewModel.ApplyCatalogue([.. provider.Pens.Select(pen => pen with { ScaleMin = 5.0, ScaleMax = 50.0 })]);
+ viewModel.ApplyCatalogue(
+ [.. provider.Pens.Select(pen => pen with { ScaleMinOnStart = 5.0, ScaleMaxOnStart = 50.0 })]);
viewModel.SetAxisLimits(1, 10.0, 90.0);
viewModel.SetAxisLimits(2, 20.0, 80.0);
@@ -315,7 +318,7 @@ public void InitialScale_RequestsARedraw()
{
var scheduler = new TestScheduler();
using var viewModel = CreateChart(scheduler);
- viewModel.AddPen(new Pen(1, "Pen 1", ["Group A"], "#ff0000", ScaleMin: 5.0, ScaleMax: 50.0));
+ viewModel.AddPen(new Pen(1, "Pen 1", ["Group A"], "#ff0000", ScaleMinOnStart: 5.0, ScaleMaxOnStart: 50.0));
viewModel.SetAxisLimits(1, 10.0, 90.0);
scheduler.AdvanceBy(BatchWindow.Ticks * 2);
var redraws = 0;
@@ -423,8 +426,8 @@ public void SameGroupPens_GetSeparateYAxes()
public void StoredScalePair_OpensEachPenOnItsOwnBoundsWithNoPadding()
{
var (viewModel, _, _, _) = CreateViewModel();
- viewModel.AddPen(new Pen(1, "Pen 1", ["Group A"], "#ff0000", ScaleMin: 0.0, ScaleMax: 50.0));
- viewModel.AddPen(new Pen(2, "Pen 2", ["Group A"], "#00ff00", ScaleMin: -10.0, ScaleMax: 10.0));
+ viewModel.AddPen(new Pen(1, "Pen 1", ["Group A"], "#ff0000", ScaleMinOnStart: 0.0, ScaleMaxOnStart: 50.0));
+ viewModel.AddPen(new Pen(2, "Pen 2", ["Group A"], "#00ff00", ScaleMinOnStart: -10.0, ScaleMaxOnStart: 10.0));
viewModel.AxisCount.Should().Be(2);
viewModel.ScaleSettings[1].Mode.Should().Be(ScaleMode.Manual);
diff --git a/SemiPlot/SemiPlot.Tests.Unit/UI/MainWindow/AppMenuBarTests.cs b/SemiPlot/SemiPlot.Tests.Unit/UI/MainWindow/AppMenuBarTests.cs
index be755b5..8b1e080 100644
--- a/SemiPlot/SemiPlot.Tests.Unit/UI/MainWindow/AppMenuBarTests.cs
+++ b/SemiPlot/SemiPlot.Tests.Unit/UI/MainWindow/AppMenuBarTests.cs
@@ -337,8 +337,8 @@ private static List StoredPairPens(double min, double max)
{
return
[
- new Pen(1, "Pen 1", ["Group A"], "#ff0000", ScaleMin: min, ScaleMax: max),
- new Pen(2, "Pen 2", ["Group A"], "#00ff00", ScaleMin: min, ScaleMax: max)
+ new Pen(1, "Pen 1", ["Group A"], "#ff0000", ScaleMinOnStart: min, ScaleMaxOnStart: max),
+ new Pen(2, "Pen 2", ["Group A"], "#00ff00", ScaleMinOnStart: min, ScaleMaxOnStart: max)
];
}
diff --git a/SemiPlot/SemiPlot.Tests.Unit/UI/PenEditor/PenEditorViewModelTests.cs b/SemiPlot/SemiPlot.Tests.Unit/UI/PenEditor/PenEditorViewModelTests.cs
index d3b02cb..04ca1d5 100644
--- a/SemiPlot/SemiPlot.Tests.Unit/UI/PenEditor/PenEditorViewModelTests.cs
+++ b/SemiPlot/SemiPlot.Tests.Unit/UI/PenEditor/PenEditorViewModelTests.cs
@@ -298,8 +298,8 @@ public async Task ARefreshIssuedWhileACommitIsHeld_RegistersOnlyAfterTheCommitCo
[InlineData(PenColumn.Unit, new[] { 7, 3, 12 })]
[InlineData(PenColumn.Mask, new[] { 3, 12, 7 })]
[InlineData(PenColumn.LineStyle, new[] { 7, 12, 3 })]
- [InlineData(PenColumn.ScaleMin, new[] { 3, 7, 12 })]
- [InlineData(PenColumn.ScaleMax, new[] { 3, 7, 12 })]
+ [InlineData(PenColumn.ScaleMinOnStart, new[] { 3, 7, 12 })]
+ [InlineData(PenColumn.ScaleMaxOnStart, new[] { 3, 7, 12 })]
[InlineData(PenColumn.Groups, new[] { 12, 7, 3 })]
public void ASort_OrdersTheRowsByTheColumnThenById(PenColumn column, int[] expectedIds)
{
diff --git a/SemiPlot/SemiPlot.Tests.Unit/UI/PenEditor/PenEditorViewTests.cs b/SemiPlot/SemiPlot.Tests.Unit/UI/PenEditor/PenEditorViewTests.cs
index 5e37e12..deb2732 100644
--- a/SemiPlot/SemiPlot.Tests.Unit/UI/PenEditor/PenEditorViewTests.cs
+++ b/SemiPlot/SemiPlot.Tests.Unit/UI/PenEditor/PenEditorViewTests.cs
@@ -116,7 +116,7 @@ public void ARow_ShowsThePenAsStoredUnderItsHeader()
Cell(argon, "RowColor").Bounds.Width.Should().BePositive("the swatch renders");
TextOf(power, "RowLineStyle").Should().Be(Resources.PenLineStyleInterpolated);
- TextOf(power, "RowScaleMax").Should().Be(500.ToString(CultureInfo.CurrentCulture));
+ TextOf(power, "RowScaleMaxOnStart").Should().Be(500.ToString(CultureInfo.CurrentCulture));
Cell(power, "RowEnabledOnStart").IsChecked.Should().BeTrue();
Cell(power, "RowColor").Background.Should()
.Be(Brushes.Transparent, "a pen with no colour draws no swatch");
@@ -285,8 +285,8 @@ public void TheForm_IsEmptyAndDisabledWithNoRowAndShowsTheClickedPen()
Named(window, "FormColorPicker").Color.Should().Be(Color.Parse(Pressure.Color!));
Named(window, "FormLineStyle").SelectedItem.Should().Be(Pressure.LineStyle);
Named(window, "FormEnabledOnStart").IsChecked.Should().Be(Pressure.EnabledOnStart);
- Named(window, "FormScaleMin").Text.Should().Be(0.ToString(CultureInfo.CurrentCulture));
- Named(window, "FormScaleMax").Text.Should().Be(100.ToString(CultureInfo.CurrentCulture));
+ Named(window, "FormScaleMinOnStart").Text.Should().Be(0.ToString(CultureInfo.CurrentCulture));
+ Named(window, "FormScaleMaxOnStart").Text.Should().Be(100.ToString(CultureInfo.CurrentCulture));
Named(window, "FormMessage").Text.Should().BeEmpty();
_editor.Calls.Should().BeEmpty();
}
@@ -384,18 +384,18 @@ public void TheScalePair_WritesOnceWhenFocusLeavesBothBounds()
{
HeadlessInput.Click(window, RowAt(window, 0));
- HeadlessInput.Type(window, Named(window, "FormScaleMin"), "5");
+ HeadlessInput.Type(window, Named(window, "FormScaleMinOnStart"), "5");
HeadlessInput.Press(window, PhysicalKey.Tab);
- Named(window, "FormScaleMax").IsFocused.Should()
+ Named(window, "FormScaleMaxOnStart").IsFocused.Should()
.BeTrue("Tab moves from the minimum to the maximum");
_editor.Calls.Should().BeEmpty("half a pair is never written");
- HeadlessInput.Type(window, Named(window, "FormScaleMax"), "50");
+ HeadlessInput.Type(window, Named(window, "FormScaleMaxOnStart"), "50");
HeadlessInput.Press(window, PhysicalKey.Tab);
Named(window, "FormScale").IsKeyboardFocusWithin.Should().BeFalse();
- _editor.Changes.Should().Equal(new FakeEditorCall.Change(Argon, new PenSettingChange.Scale(5, 50)));
+ _editor.Changes.Should().Equal(new FakeEditorCall.Change(Argon, new PenSettingChange.ScaleOnStart(5, 50)));
}
finally
{
@@ -434,12 +434,12 @@ public void BothBoundsTypedThenAnotherRowClicked_WritesOneScaleChangeToTheFirstP
try
{
HeadlessInput.Click(window, RowAt(window, 0));
- HeadlessInput.Type(window, Named(window, "FormScaleMin"), "1");
- HeadlessInput.Type(window, Named(window, "FormScaleMax"), "2");
+ HeadlessInput.Type(window, Named(window, "FormScaleMinOnStart"), "1");
+ HeadlessInput.Type(window, Named(window, "FormScaleMaxOnStart"), "2");
HeadlessInput.Click(window, RowAt(window, 1));
- _editor.Changes.Should().Equal(new FakeEditorCall.Change(Argon, new PenSettingChange.Scale(1, 2)));
+ _editor.Changes.Should().Equal(new FakeEditorCall.Change(Argon, new PenSettingChange.ScaleOnStart(1, 2)));
}
finally
{
@@ -481,14 +481,14 @@ public void BothBoundsTypedThenTheFormsEmptyAreaClicked_WritesOneScaleChange()
{
HeadlessInput.Click(window, RowAt(window, 0));
var message = Named(window, "FormMessage");
- HeadlessInput.Type(window, Named(window, "FormScaleMin"), "1");
- HeadlessInput.Type(window, Named(window, "FormScaleMax"), "2");
+ HeadlessInput.Type(window, Named(window, "FormScaleMinOnStart"), "1");
+ HeadlessInput.Type(window, Named(window, "FormScaleMaxOnStart"), "2");
message.Text.Should().BeNullOrEmpty("the empty message line is the form's empty area");
HeadlessInput.Click(window, message);
Named(window, "FormScale").IsKeyboardFocusWithin.Should().BeFalse();
- _editor.Changes.Should().Equal(new FakeEditorCall.Change(Argon, new PenSettingChange.Scale(1, 2)));
+ _editor.Changes.Should().Equal(new FakeEditorCall.Change(Argon, new PenSettingChange.ScaleOnStart(1, 2)));
}
finally
{
@@ -808,8 +808,8 @@ public void AScalePairEnteredWhileItsWriteIsHeldThenTabbedOut_WritesOnce()
try
{
HeadlessInput.Click(window, RowAt(window, 0));
- HeadlessInput.Type(window, Named(window, "FormScaleMin"), "1");
- HeadlessInput.Type(window, Named(window, "FormScaleMax"), "2");
+ HeadlessInput.Type(window, Named(window, "FormScaleMinOnStart"), "1");
+ HeadlessInput.Type(window, Named(window, "FormScaleMaxOnStart"), "2");
var write = _editor.HoldNextCall();
HeadlessInput.Press(window, PhysicalKey.Enter);
@@ -818,7 +818,7 @@ public void AScalePairEnteredWhileItsWriteIsHeldThenTabbedOut_WritesOnce()
Dispatcher.UIThread.RunJobs();
Named(window, "FormScale").IsKeyboardFocusWithin.Should().BeFalse();
- _editor.Changes.Should().Equal(new FakeEditorCall.Change(Argon, new PenSettingChange.Scale(1, 2)));
+ _editor.Changes.Should().Equal(new FakeEditorCall.Change(Argon, new PenSettingChange.ScaleOnStart(1, 2)));
}
finally
{
@@ -835,15 +835,15 @@ public void AScalePairTypedThenTheWindowClosed_WritesOnceBeforeTheWindowIsGone()
window.Closed += (_, _) => changesWhenClosed = _editor.Changes.Count();
HeadlessInput.Click(window, RowAt(window, 0));
- HeadlessInput.Type(window, Named(window, "FormScaleMin"), "1");
- HeadlessInput.Type(window, Named(window, "FormScaleMax"), "2");
+ HeadlessInput.Type(window, Named(window, "FormScaleMinOnStart"), "1");
+ HeadlessInput.Type(window, Named(window, "FormScaleMaxOnStart"), "2");
var write = _editor.HoldNextCall();
window.Close();
Dispatcher.UIThread.RunJobs();
window.IsVisible.Should().BeTrue("the close waits for the write in flight");
- _editor.Changes.Should().Equal(new FakeEditorCall.Change(Argon, new PenSettingChange.Scale(1, 2)));
+ _editor.Changes.Should().Equal(new FakeEditorCall.Change(Argon, new PenSettingChange.ScaleOnStart(1, 2)));
write.SetResult();
Dispatcher.UIThread.RunJobs();
diff --git a/SemiPlot/SemiPlot.Tests.Unit/UI/PenEditor/PenFormViewModelTests.cs b/SemiPlot/SemiPlot.Tests.Unit/UI/PenEditor/PenFormViewModelTests.cs
index 337de88..ed4ab7a 100644
--- a/SemiPlot/SemiPlot.Tests.Unit/UI/PenEditor/PenFormViewModelTests.cs
+++ b/SemiPlot/SemiPlot.Tests.Unit/UI/PenEditor/PenFormViewModelTests.cs
@@ -63,7 +63,7 @@ public async Task AValidEdit_WritesTheOneChangeItNamesAndUpdatesTheRow()
[InlineData(PenField.Color)]
[InlineData(PenField.LineStyle)]
[InlineData(PenField.EnabledOnStart)]
- [InlineData(PenField.Scale)]
+ [InlineData(PenField.ScaleOnStart)]
public async Task AnUnchangedField_WritesNothing(PenField field)
{
var form = FormFor(Pressure);
@@ -108,15 +108,15 @@ public async Task ARefusedScalePair_NeverReachesTheEditorAndRevertsBothBounds(
{
var form = FormFor(Pressure);
- form.ScaleMin = minimum;
- form.ScaleMax = maximum;
- await form.EndEditAsync(PenField.Scale);
+ form.ScaleMinOnStart = minimum;
+ form.ScaleMaxOnStart = maximum;
+ await form.EndEditAsync(PenField.ScaleOnStart);
_editor.Calls.Should().BeEmpty();
- form.ScaleMin.Should().Be("0");
- form.ScaleMax.Should().Be("100");
- form.IsScaleMinValid.Should().BeFalse();
- form.IsScaleMaxValid.Should().BeFalse();
+ form.ScaleMinOnStart.Should().Be("0");
+ form.ScaleMaxOnStart.Should().Be("100");
+ form.IsScaleMinOnStartValid.Should().BeFalse();
+ form.IsScaleMaxOnStartValid.Should().BeFalse();
form.Message.Should().Be(Text(ruleKey));
}
@@ -125,12 +125,12 @@ public async Task AValidScalePair_WritesBothBoundsInOneChange()
{
var form = FormFor(Pressure);
- form.ScaleMin = "-20";
- form.ScaleMax = "80";
- await form.EndEditAsync(PenField.Scale);
+ form.ScaleMinOnStart = "-20";
+ form.ScaleMaxOnStart = "80";
+ await form.EndEditAsync(PenField.ScaleOnStart);
- _editor.Changes.Select(call => call.Setting).Should().Equal(new PenSettingChange.Scale(-20, 80));
- form.Row.Pen.Should().Be(Pressure with { ScaleMin = -20, ScaleMax = 80 });
+ _editor.Changes.Select(call => call.Setting).Should().Equal(new PenSettingChange.ScaleOnStart(-20, 80));
+ form.Row.Pen.Should().Be(Pressure with { ScaleMinOnStart = -20, ScaleMaxOnStart = 80 });
}
[AvaloniaFact]
@@ -138,11 +138,11 @@ public async Task EmptyingBothScaleBounds_WritesAPairOfNulls()
{
var form = FormFor(Pressure);
- form.ScaleMin = string.Empty;
- form.ScaleMax = string.Empty;
- await form.EndEditAsync(PenField.Scale);
+ form.ScaleMinOnStart = string.Empty;
+ form.ScaleMaxOnStart = string.Empty;
+ await form.EndEditAsync(PenField.ScaleOnStart);
- _editor.Changes.Select(call => call.Setting).Should().Equal(new PenSettingChange.Scale(null, null));
+ _editor.Changes.Select(call => call.Setting).Should().Equal(new PenSettingChange.ScaleOnStart(null, null));
}
[AvaloniaFact]
@@ -179,13 +179,13 @@ public async Task UnderTheRussianCulture_ACommaBoundCommitsItsValue()
try
{
CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("ru-RU");
- var form = FormFor(Pressure with { ScaleMin = null, ScaleMax = null });
+ var form = FormFor(Pressure with { ScaleMinOnStart = null, ScaleMaxOnStart = null });
- form.ScaleMin = "1,5";
- form.ScaleMax = "2,25";
- await form.EndEditAsync(PenField.Scale);
+ form.ScaleMinOnStart = "1,5";
+ form.ScaleMaxOnStart = "2,25";
+ await form.EndEditAsync(PenField.ScaleOnStart);
- _editor.Changes.Select(call => call.Setting).Should().Equal(new PenSettingChange.Scale(1.5, 2.25));
+ _editor.Changes.Select(call => call.Setting).Should().Equal(new PenSettingChange.ScaleOnStart(1.5, 2.25));
}
finally
{
@@ -207,16 +207,16 @@ public async Task UnderTheRussianCulture_ANonFiniteBoundIsRefused(string bound)
CultureInfo.CurrentCulture = russian;
var form = FormFor(Pressure);
- form.ScaleMax = bound switch
+ form.ScaleMaxOnStart = bound switch
{
"NaNSymbol" => russian.NumberFormat.NaNSymbol,
"PositiveInfinitySymbol" => russian.NumberFormat.PositiveInfinitySymbol,
_ => bound
};
- await form.EndEditAsync(PenField.Scale);
+ await form.EndEditAsync(PenField.ScaleOnStart);
_editor.Calls.Should().BeEmpty();
- form.IsScaleMaxValid.Should().BeFalse();
+ form.IsScaleMaxOnStartValid.Should().BeFalse();
form.Message.Should().Be(Resources.PenFormScaleBoundInvalid);
}
finally
diff --git a/SemiPlot/SemiPlot.Tools.ArchiveSeeder/SyntheticPen.cs b/SemiPlot/SemiPlot.Tools.ArchiveSeeder/SyntheticPen.cs
index 052886f..86bcbce 100644
--- a/SemiPlot/SemiPlot.Tools.ArchiveSeeder/SyntheticPen.cs
+++ b/SemiPlot/SemiPlot.Tools.ArchiveSeeder/SyntheticPen.cs
@@ -19,8 +19,8 @@ public sealed record SyntheticPen(
public string PrimaryGroup => Groups.Count > 0 ? Groups[0] : string.Empty;
/// The commissioned lower bound, or none for a pen the operator left autoscaling.
- public double? ScaleMin => StoresScale ? MinValue : null;
+ public double? ScaleMinOnStart => StoresScale ? MinValue : null;
/// The commissioned upper bound, or none for a pen the operator left autoscaling.
- public double? ScaleMax => StoresScale ? MaxValue : null;
+ public double? ScaleMaxOnStart => StoresScale ? MaxValue : null;
}
diff --git a/SemiPlot/SemiPlot.Tools.ArchiveSeeder/TagCatalogWriter.cs b/SemiPlot/SemiPlot.Tools.ArchiveSeeder/TagCatalogWriter.cs
index a38e9ec..5313f31 100644
--- a/SemiPlot/SemiPlot.Tools.ArchiveSeeder/TagCatalogWriter.cs
+++ b/SemiPlot/SemiPlot.Tools.ArchiveSeeder/TagCatalogWriter.cs
@@ -10,8 +10,9 @@ public sealed class TagCatalogWriter(string adminConnectionString)
private const string UpsertTagCommand =
"""
INSERT INTO public.semiplot_tags
- (id, name, unit, format, color, line_style, enabled_on_start, scale_min, scale_max)
- VALUES (@id, @name, @unit, @format, @color, @line_style, @enabled_on_start, @scale_min, @scale_max)
+ (id, name, unit, format, color, line_style, enabled_on_start, scale_min_on_start, scale_max_on_start)
+ VALUES
+ (@id, @name, @unit, @format, @color, @line_style, @enabled_on_start, @scale_min_on_start, @scale_max_on_start)
ON CONFLICT (id) DO UPDATE
SET name = EXCLUDED.name,
unit = EXCLUDED.unit,
@@ -19,8 +20,8 @@ ON CONFLICT (id) DO UPDATE
color = EXCLUDED.color,
line_style = EXCLUDED.line_style,
enabled_on_start = EXCLUDED.enabled_on_start,
- scale_min = EXCLUDED.scale_min,
- scale_max = EXCLUDED.scale_max;
+ scale_min_on_start = EXCLUDED.scale_min_on_start,
+ scale_max_on_start = EXCLUDED.scale_max_on_start;
""";
private const string InsertGroupsCommand =
@@ -124,8 +125,8 @@ private static async Task UpsertTagAsync(
command.Parameters.AddWithValue("color", pen.Color);
command.Parameters.AddWithValue("line_style", (short)pen.LineStyle);
command.Parameters.AddWithValue("enabled_on_start", pen.EnabledOnStart);
- command.Parameters.AddWithValue("scale_min", (object?)pen.ScaleMin ?? DBNull.Value);
- command.Parameters.AddWithValue("scale_max", (object?)pen.ScaleMax ?? DBNull.Value);
+ command.Parameters.AddWithValue("scale_min_on_start", (object?)pen.ScaleMinOnStart ?? DBNull.Value);
+ command.Parameters.AddWithValue("scale_max_on_start", (object?)pen.ScaleMaxOnStart ?? DBNull.Value);
await command.ExecuteNonQueryAsync(cancellationToken);
}
diff --git a/SemiPlot/SemiPlot.UI/Localization/Resources.resx b/SemiPlot/SemiPlot.UI/Localization/Resources.resx
index fa04036..9c4b867 100644
--- a/SemiPlot/SemiPlot.UI/Localization/Resources.resx
+++ b/SemiPlot/SemiPlot.UI/Localization/Resources.resx
@@ -210,10 +210,10 @@
Line
-
+
Initial scale, min
-
+
Initial scale, max
diff --git a/SemiPlot/SemiPlot.UI/Localization/Resources.ru.resx b/SemiPlot/SemiPlot.UI/Localization/Resources.ru.resx
index ac5cbc2..7757176 100644
--- a/SemiPlot/SemiPlot.UI/Localization/Resources.ru.resx
+++ b/SemiPlot/SemiPlot.UI/Localization/Resources.ru.resx
@@ -210,10 +210,10 @@
Линия
-
+
Начальная шкала, от
-
+
Начальная шкала, до
diff --git a/SemiPlot/SemiPlot.UI/PenEditor/PenEditorViewModel.cs b/SemiPlot/SemiPlot.UI/PenEditor/PenEditorViewModel.cs
index bb682f2..84c8725 100644
--- a/SemiPlot/SemiPlot.UI/PenEditor/PenEditorViewModel.cs
+++ b/SemiPlot/SemiPlot.UI/PenEditor/PenEditorViewModel.cs
@@ -24,8 +24,8 @@ public enum PenColumn
Unit,
Mask,
LineStyle,
- ScaleMin,
- ScaleMax,
+ ScaleMinOnStart,
+ ScaleMaxOnStart,
Groups
}
@@ -258,8 +258,10 @@ private static Comparison ComparisonOf(PenColumn column)
PenColumn.Unit => ByText(row => row.Pen.Unit),
PenColumn.Mask => ByText(row => row.Pen.Format),
PenColumn.LineStyle => (left, right) => left.Pen.LineStyle.CompareTo(right.Pen.LineStyle),
- PenColumn.ScaleMin => (left, right) => Nullable.Compare(left.Pen.ScaleMin, right.Pen.ScaleMin),
- PenColumn.ScaleMax => (left, right) => Nullable.Compare(left.Pen.ScaleMax, right.Pen.ScaleMax),
+ PenColumn.ScaleMinOnStart
+ => (left, right) => Nullable.Compare(left.Pen.ScaleMinOnStart, right.Pen.ScaleMinOnStart),
+ PenColumn.ScaleMaxOnStart
+ => (left, right) => Nullable.Compare(left.Pen.ScaleMaxOnStart, right.Pen.ScaleMaxOnStart),
PenColumn.Groups => ByText(row => row.GroupsText),
_ => throw new ArgumentOutOfRangeException(nameof(column), column, null)
};
diff --git a/SemiPlot/SemiPlot.UI/PenEditor/PenEditorWindow.axaml b/SemiPlot/SemiPlot.UI/PenEditor/PenEditorWindow.axaml
index 7364654..7ee5365 100644
--- a/SemiPlot/SemiPlot.UI/PenEditor/PenEditorWindow.axaml
+++ b/SemiPlot/SemiPlot.UI/PenEditor/PenEditorWindow.axaml
@@ -139,18 +139,18 @@
CommandParameter="{x:Static local:PenColumn.LineStyle}" />
+ CommandParameter="{x:Static local:PenColumn.ScaleMinOnStart}" />
+ CommandParameter="{x:Static local:PenColumn.ScaleMaxOnStart}" />
diff --git a/SemiPlot/SemiPlot.UI/PenEditor/PenEditorWindow.axaml.cs b/SemiPlot/SemiPlot.UI/PenEditor/PenEditorWindow.axaml.cs
index c7e0dbc..dc8fd02 100644
--- a/SemiPlot/SemiPlot.UI/PenEditor/PenEditorWindow.axaml.cs
+++ b/SemiPlot/SemiPlot.UI/PenEditor/PenEditorWindow.axaml.cs
@@ -115,7 +115,7 @@ private async Task DrainAsync()
if (TakeScaleForm() is { } scaleForm)
{
- await scaleForm.EndEditAsync(PenField.Scale);
+ await scaleForm.EndEditAsync(PenField.ScaleOnStart);
}
if (DataContext is PenEditorViewModel editor)
@@ -191,7 +191,7 @@ private async void OnFieldKeyDown(object? sender, KeyEventArgs e)
}
else if (_scaleForm is { } scaleForm && field.GetVisualAncestors().Contains(FormScale))
{
- await scaleForm.EndEditAsync(PenField.Scale);
+ await scaleForm.EndEditAsync(PenField.ScaleOnStart);
}
}
catch (Exception exception)
@@ -211,7 +211,7 @@ private async void OnScaleFocusWithinChanged(bool isFocusWithin)
}
else if (TakeScaleForm() is { } scaleForm)
{
- await scaleForm.EndEditAsync(PenField.Scale);
+ await scaleForm.EndEditAsync(PenField.ScaleOnStart);
}
}
catch (Exception exception)
diff --git a/SemiPlot/SemiPlot.UI/PenEditor/PenField.cs b/SemiPlot/SemiPlot.UI/PenEditor/PenField.cs
index e014c00..36582ac 100644
--- a/SemiPlot/SemiPlot.UI/PenEditor/PenField.cs
+++ b/SemiPlot/SemiPlot.UI/PenEditor/PenField.cs
@@ -9,5 +9,5 @@ public enum PenField
Color,
LineStyle,
EnabledOnStart,
- Scale
+ ScaleOnStart
}
diff --git a/SemiPlot/SemiPlot.UI/PenEditor/PenFormViewModel.cs b/SemiPlot/SemiPlot.UI/PenEditor/PenFormViewModel.cs
index d3c5481..c400725 100644
--- a/SemiPlot/SemiPlot.UI/PenEditor/PenFormViewModel.cs
+++ b/SemiPlot/SemiPlot.UI/PenEditor/PenFormViewModel.cs
@@ -91,16 +91,16 @@ public bool EnabledOnStart
}
/// The lower scale bound as the operator types it, read under the current culture.
- public string ScaleMin
+ public string ScaleMinOnStart
{
get;
- set => SetDraft(ref field, value, PenField.Scale);
+ set => SetDraft(ref field, value, PenField.ScaleOnStart);
} = string.Empty;
- public string ScaleMax
+ public string ScaleMaxOnStart
{
get;
- set => SetDraft(ref field, value, PenField.Scale);
+ set => SetDraft(ref field, value, PenField.ScaleOnStart);
} = string.Empty;
public bool IsNameValid => IsValid(PenField.Name, RuleOf(PenField.Name));
@@ -111,11 +111,15 @@ public string ScaleMax
public bool IsColorValid => IsValid(PenField.Color, RuleOf(PenField.Color));
- public bool IsScaleMinValid =>
- IsValid(PenField.Scale, PenFormRules.BoundRule(ScaleMin) ?? PenFormRules.PairRule(ScaleMin, ScaleMax));
+ public bool IsScaleMinOnStartValid =>
+ IsValid(
+ PenField.ScaleOnStart,
+ PenFormRules.BoundRule(ScaleMinOnStart) ?? PenFormRules.PairRule(ScaleMinOnStart, ScaleMaxOnStart));
- public bool IsScaleMaxValid =>
- IsValid(PenField.Scale, PenFormRules.BoundRule(ScaleMax) ?? PenFormRules.PairRule(ScaleMin, ScaleMax));
+ public bool IsScaleMaxOnStartValid =>
+ IsValid(
+ PenField.ScaleOnStart,
+ PenFormRules.BoundRule(ScaleMaxOnStart) ?? PenFormRules.PairRule(ScaleMinOnStart, ScaleMaxOnStart));
/// The first rule a draft breaks, in form order; otherwise the last refusal; otherwise empty.
public string Message => FirstBrokenRule() ?? _refusal?.Text ?? string.Empty;
@@ -270,9 +274,9 @@ private void Seed(PenField penField, StoredPen pen)
case PenField.EnabledOnStart:
EnabledOnStart = pen.EnabledOnStart;
break;
- case PenField.Scale:
- ScaleMin = TextOf(pen.ScaleMin);
- ScaleMax = TextOf(pen.ScaleMax);
+ case PenField.ScaleOnStart:
+ ScaleMinOnStart = TextOf(pen.ScaleMinOnStart);
+ ScaleMaxOnStart = TextOf(pen.ScaleMaxOnStart);
break;
default:
throw new ArgumentOutOfRangeException(nameof(penField), penField, null);
@@ -290,7 +294,8 @@ private bool Shows(PenField penField, StoredPen pen)
PenField.Color => Color == TextOf(pen.Color),
PenField.LineStyle => LineStyle == pen.LineStyle,
PenField.EnabledOnStart => EnabledOnStart == pen.EnabledOnStart,
- PenField.Scale => ScaleMin == TextOf(pen.ScaleMin) && ScaleMax == TextOf(pen.ScaleMax),
+ PenField.ScaleOnStart => ScaleMinOnStart == TextOf(pen.ScaleMinOnStart)
+ && ScaleMaxOnStart == TextOf(pen.ScaleMaxOnStart),
_ => throw new ArgumentOutOfRangeException(nameof(penField), penField, null)
};
}
@@ -301,8 +306,8 @@ private void RaiseValidity()
this.RaisePropertyChanged(nameof(IsUnitValid));
this.RaisePropertyChanged(nameof(IsMaskValid));
this.RaisePropertyChanged(nameof(IsColorValid));
- this.RaisePropertyChanged(nameof(IsScaleMinValid));
- this.RaisePropertyChanged(nameof(IsScaleMaxValid));
+ this.RaisePropertyChanged(nameof(IsScaleMinOnStartValid));
+ this.RaisePropertyChanged(nameof(IsScaleMaxOnStartValid));
this.RaisePropertyChanged(nameof(Message));
this.RaisePropertyChanged(nameof(MaskPreview));
}
@@ -314,7 +319,10 @@ private bool IsValid(PenField penField, string? brokenRule)
private string? FirstBrokenRule()
{
- return RuleOf(PenField.Name) ?? RuleOf(PenField.Mask) ?? RuleOf(PenField.Color) ?? RuleOf(PenField.Scale);
+ return RuleOf(PenField.Name)
+ ?? RuleOf(PenField.Mask)
+ ?? RuleOf(PenField.Color)
+ ?? RuleOf(PenField.ScaleOnStart);
}
private string? RuleOf(PenField penField)
@@ -324,7 +332,7 @@ private bool IsValid(PenField penField, string? brokenRule)
PenField.Name => PenFormRules.NameRule(Name),
PenField.Mask => PenFormRules.MaskRule(Mask),
PenField.Color => PenFormRules.ColorRule(Color),
- PenField.Scale => PenFormRules.ScaleRule(ScaleMin, ScaleMax),
+ PenField.ScaleOnStart => PenFormRules.ScaleRule(ScaleMinOnStart, ScaleMaxOnStart),
_ => null
};
}
@@ -335,9 +343,9 @@ private bool IsValid(PenField penField, string? brokenRule)
{
var queued = Row.QueuedPen;
- if (penField == PenField.Scale)
+ if (penField == PenField.ScaleOnStart)
{
- return ScaleChangeOf(queued);
+ return ScaleOnStartChangeOf(queued);
}
if (Shows(penField, queued))
@@ -357,12 +365,14 @@ private bool IsValid(PenField penField, string? brokenRule)
};
}
- private PenSettingChange.Scale? ScaleChangeOf(StoredPen queued)
+ private PenSettingChange.ScaleOnStart? ScaleOnStartChangeOf(StoredPen queued)
{
- PenFormRules.TryReadBound(ScaleMin, out var min);
- PenFormRules.TryReadBound(ScaleMax, out var max);
+ PenFormRules.TryReadBound(ScaleMinOnStart, out var min);
+ PenFormRules.TryReadBound(ScaleMaxOnStart, out var max);
- return min == queued.ScaleMin && max == queued.ScaleMax ? null : new PenSettingChange.Scale(min, max);
+ return min == queued.ScaleMinOnStart && max == queued.ScaleMaxOnStart
+ ? null
+ : new PenSettingChange.ScaleOnStart(min, max);
}
private static string TextOf(string? stored)
diff --git a/SemiPlot/SemiPlot.UI/PenEditor/PenRowViewModel.cs b/SemiPlot/SemiPlot.UI/PenEditor/PenRowViewModel.cs
index e8b23ed..a31efe2 100644
--- a/SemiPlot/SemiPlot.UI/PenEditor/PenRowViewModel.cs
+++ b/SemiPlot/SemiPlot.UI/PenEditor/PenRowViewModel.cs
@@ -87,7 +87,8 @@ private static StoredPen Applied(StoredPen pen, PenSettingChange change)
PenSettingChange.Color color => pen with { Color = color.Value },
PenSettingChange.LineStyle lineStyle => pen with { LineStyle = lineStyle.Value },
PenSettingChange.EnabledOnStart enabledOnStart => pen with { EnabledOnStart = enabledOnStart.Value },
- PenSettingChange.Scale scale => pen with { ScaleMin = scale.Min, ScaleMax = scale.Max },
+ PenSettingChange.ScaleOnStart scale
+ => pen with { ScaleMinOnStart = scale.Min, ScaleMaxOnStart = scale.Max },
_ => throw new ArgumentOutOfRangeException(nameof(change), change, null)
};
}
diff --git a/docs/architecture/charting.md b/docs/architecture/charting.md
index f64bfa9..9c06950 100644
--- a/docs/architecture/charting.md
+++ b/docs/architecture/charting.md
@@ -148,8 +148,8 @@ The mapping below routes the major capability groups to their feature IDs:
- **Grouping / layout** — view pen groups separately or together: trend-feature-spec.md §MS-2.
Canonical use cases (acceptance fixtures): 16 dampers + 16 heat sources (dampers viewed separately,
-the 16 heaters reading against the same bounds, which is the same `scale_min`/`scale_max` pair stored
-on each of them rather than a shared axis) and 10 gas lines with different min..max ranges (all on
+the 16 heaters reading against the same bounds, which is the same
+`scale_min_on_start`/`scale_max_on_start` pair stored on each of them rather than a shared axis) and 10 gas lines with different min..max ranges (all on
one chart, each with its own scale — §AY-2).
## Module layout (Avalonia views / view models / Core models)
@@ -349,8 +349,8 @@ models, backed by renderer-agnostic models in `SemiPlot.Core`. Responsibilities:
**Core models (`SemiPlot.Core.Trends`, renderer-agnostic, unit-tested):**
- `PenScaleModel` — one `PenScale` per pen: `(Min, Max)` + autoscale mode + the active flag (Auto over
- the columns inside `[windowStart, windowEnd]` / Manual; log sanitize). A pen whose `scale_min`/`scale_max` are stored
- opens `Manual` on exactly those bounds; a pen without them opens `Auto`. What the operator then does
+ the columns inside `[windowStart, windowEnd]` / Manual; log sanitize). A pen whose
+ `scale_min_on_start`/`scale_max_on_start` are stored opens `Manual` on exactly those bounds; a pen without them opens `Auto`. What the operator then does
to the axis rewrites the settings for the session and reaches no database. The only writer of
`semiplot_tags` is the pen editor (`Edit` -> `Pens and groups`, `PenEditor/`,
`data-integration.md#the-pen-catalogue-editor`). The stored pair is the pen's initial scale: it builds the
diff --git a/docs/architecture/data-integration.md b/docs/architecture/data-integration.md
index aef83b8..976a0ea 100644
--- a/docs/architecture/data-integration.md
+++ b/docs/architecture/data-integration.md
@@ -59,7 +59,7 @@ public interface IDataProvider
| Type | Shape | Notes |
| --- | --- | --- |
-| `Pen` | `PenId`, `Name`, `Groups`, `Color`, `Unit`, `Format`, `EnabledOnStart`, `ScaleMin`, `ScaleMax`, `LineStyle` | `PenId` is the archive's `trends.id`. Equality compares `Groups` element by element, ordinal, so two reads of one stored pen are equal. |
+| `Pen` | `PenId`, `Name`, `Groups`, `Color`, `Unit`, `Format`, `EnabledOnStart`, `ScaleMinOnStart`, `ScaleMaxOnStart`, `LineStyle` | `PenId` is the archive's `trends.id`. Equality compares `Groups` element by element, ordinal, so two reads of one stored pen are equal. |
| `Sample` | `PenId`, `TimestampUtc`, `Value` | Realtime element. Timestamps are UTC by the time they leave the provider. |
| `PenHistoryEnvelope` | parallel `Timestamps` / `Min` / `Max` / `Center`, strictly ascending, `NaN` marks a gap | One per pen per history query. |
| `ArchiveExtent` | `FirstUtc`, `LastUtc`, `IsEmpty` | The span of the configured variables, consumed by the minimap (`TM-4`). `ArchiveExtent.Empty` is the no-span form. |
@@ -119,9 +119,9 @@ singleton, so one role and one connection string serve reads and writes.
| Type | Shape | Notes |
| --- | --- | --- |
| `PenCatalogue` | `Pens`, `Groups` | The catalogue as stored. |
-| `StoredPen` | `Id`, `Name`, `Unit`, `Format`, `Color`, `LineStyle`, `EnabledOnStart`, `ScaleMin`, `ScaleMax` | One `semiplot_tags` row; a null field is a stored `NULL`. |
+| `StoredPen` | `Id`, `Name`, `Unit`, `Format`, `Color`, `LineStyle`, `EnabledOnStart`, `ScaleMinOnStart`, `ScaleMaxOnStart` | One `semiplot_tags` row; a null field is a stored `NULL`. |
| `StoredGroup` | `Id`, `Name`, `MemberPenIds` | Member ids ascending; the member count is `MemberPenIds.Count`. |
-| `PenSettingChange` | `Name`, `Unit`, `Format`, `Color`, `LineStyle`, `EnabledOnStart`, `Scale(Min, Max)` | A closed family, one arm per column; the scale pair is one arm. |
+| `PenSettingChange` | `Name`, `Unit`, `Format`, `Color`, `LineStyle`, `EnabledOnStart`, `ScaleOnStart(Min, Max)` | A closed family, one arm per column; the scale pair is one arm. |
A parameter record carries the id the statement binds and the name a fault names. The implementation
reads only `Id` and `Name` from it, and no statement compares old values in its `WHERE`.
@@ -146,8 +146,8 @@ already replaced.
| Operation | Constant | What the statement keeps |
| --- | --- | --- |
-| `ChangeAsync`, every arm but `Scale` | `UpdatePenName`, `UpdatePenUnit`, `UpdatePenFormat`, `UpdatePenColor`, `UpdatePenLineStyle`, `UpdatePenEnabledOnStart` | `UPDATE semiplot_tags SET = @value WHERE id = @id`, the column fixed in the constant and never interpolated. A null unit or mask binds `NULL`; the form turns an empty field into null. |
-| `ChangeAsync`, `Scale` | `UpdatePenScale` | Both bounds in one statement: `semiplot_tags_scale_paired` refuses a half-set pair between two. |
+| `ChangeAsync`, every arm but `ScaleOnStart` | `UpdatePenName`, `UpdatePenUnit`, `UpdatePenFormat`, `UpdatePenColor`, `UpdatePenLineStyle`, `UpdatePenEnabledOnStart` | `UPDATE semiplot_tags SET = @value WHERE id = @id`, the column fixed in the constant and never interpolated. A null unit or mask binds `NULL`; the form turns an empty field into null. |
+| `ChangeAsync`, `ScaleOnStart` | `UpdatePenScaleOnStart` | Both bounds in one statement: `semiplot_tags_scale_paired` refuses a half-set pair between two. |
| `CreateGroupAsync` | `CreateGroup` | `INSERT ... RETURNING id`; the result is the new group's id. |
| `RenameGroupAsync` | `RenameGroup` | One row by id. |
| `DeleteGroupAsync` | `DeleteGroup` | `ON DELETE CASCADE` on `semiplot_pen_groups.group_id` removes the memberships. The pens stay, and a pen left in no group falls under the Ungrouped header at the next catalogue read. |
diff --git a/docs/architecture/postgres-instance.md b/docs/architecture/postgres-instance.md
index 5110342..4eef2d0 100644
--- a/docs/architecture/postgres-instance.md
+++ b/docs/architecture/postgres-instance.md
@@ -80,7 +80,7 @@ chart within 5 s (`overview.md#the-live-catalogue`).
| `color` | yes | yes | Pen colour; `NULL` draws in the one fallback colour |
| `line_style` | yes | yes | Mapped onto the domain line-style enum |
| `enabled_on_start` | yes | yes | Whether the pen is drawn when the viewer opens |
-| `scale_min`, `scale_max` | yes | yes, in one statement | The pen's initial Y range, set together or not at all; absent means autoscale |
+| `scale_min_on_start`, `scale_max_on_start` | yes | yes, in one statement | The pen's initial Y range, set together or not at all; absent means autoscale |
Group membership is many-to-many. `semiplot_groups` holds one row per group name,
`semiplot_pen_groups` one row per membership, and a pen may sit in several groups or in none. The
diff --git a/docs/architecture/trend-feature-spec.md b/docs/architecture/trend-feature-spec.md
index b28d69c..9dd8770 100644
--- a/docs/architecture/trend-feature-spec.md
+++ b/docs/architecture/trend-feature-spec.md
@@ -57,7 +57,7 @@ Acceptance-criterion labels: "UI" — verified in the running application; "Core
### AY-2 — Per-pen scale ("each on its own axis") (MUST)
**Definition.** The canonical case "N lines, each with its own min..max scale" must work literally: each such pen gets its own axis with its own range, rather than sharing a common min/max with others. The axis is never hard-wired to the pen's group.
**Acceptance.** Core/test: N `PenScaleSettings` build N axes with independent ranges. UI: after loading the "gas lines" catalog, the operator sees each line on its own scale.
-**Note.** Closed: `PenScaleSettings` carries no axis key at all — the axis is the pen. The stored `scale_min`/`scale_max` pair is what forced it: seeded into a group-keyed axis, one pen's range would have been applied to the whole group and the rest silently discarded.
+**Note.** Closed: `PenScaleSettings` carries no axis key at all — the axis is the pen. The stored `scale_min_on_start`/`scale_max_on_start` pair is what forced it: seeded into a group-keyed axis, one pen's range would have been applied to the whole group and the rest silently discarded.
### AY-3 — Manual min/max for an axis (MUST)
**Definition.** `Manual` mode: fixed `ManualMin/ManualMax` for an axis; on swapped bounds — auto-swap. Bound editing is available from the UI: a click on the active pen's axis opens a panel with Maximum and Minimum fields, and Apply writes both.
@@ -85,7 +85,7 @@ Acceptance-criterion labels: "UI" — verified in the running application; "Core
## 3. Pens
### PN-1 — Pen model and Y layer (MUST)
-**Definition.** A pen: `PenId`, `Name`, `Groups`, `Color`, `LineStyle`, `Unit`, `Format`, `EnabledOnStart` and the stored `ScaleMin`/`ScaleMax` pair. A pen's history is a `PenHistoryEnvelope` (parallel `Timestamps/Min/Max/Center`, strictly increasing timestamps, NaN = gap). Render: one `EnvelopeLine` polyline through each column's Min and Max.
+**Definition.** A pen: `PenId`, `Name`, `Groups`, `Color`, `LineStyle`, `Unit`, `Format`, `EnabledOnStart` and the stored `ScaleMinOnStart`/`ScaleMaxOnStart` pair. A pen's history is a `PenHistoryEnvelope` (parallel `Timestamps/Min/Max/Center`, strictly increasing timestamps, NaN = gap). Render: one `EnvelopeLine` polyline through each column's Min and Max.
**Acceptance.** Core/test: the envelope constructor throws on unequal lengths and non-increasing timestamps; NaN breaks the line. UI: a column whose Min differs from its Max is drawn as a vertical segment.
### PN-2 — Resize a pen's Y layer (MUST)
diff --git a/docs/architecture/trend-interaction.md b/docs/architecture/trend-interaction.md
index 6643b7f..bdc7aaa 100644
--- a/docs/architecture/trend-interaction.md
+++ b/docs/architecture/trend-interaction.md
@@ -75,7 +75,7 @@ operator interaction.
**right edge** (centering is at most a transient transition animation).
- **Many-axes management:** **single active Y axis + per-pen autoscale** (legacy SCADA model);
clicking a pen makes it active. Pens that must read against one range carry the same stored
- `scale_min`/`scale_max`; no two pens share an axis.
+ `scale_min_on_start`/`scale_max_on_start`; no two pens share an axis.
- **Axis scaling gestures:** entering values = fixed manual limits. Autoscale and the initial scale
are commands on the active pen: buttons of the axis scale panel and items of the View -> Pen scale
submenu. The original **toolbar duplicate** is superseded: the autoscale button, the two limit
@@ -147,8 +147,8 @@ own axis" case (§AY-2), and the shared-X / independent-Y invariant (§TM-1). As
on the primary axis; non-active pens scale individually with their axes hidden, so many pens do
not spill many visible axes. The literal "N lines, each on its own axis" case (§AY-2) is the
as-built shape: `PenScaleSettings` carries no axis key, and the axis is the pen.
-- 16 heaters reading against one range (dampers separately) is the same `scale_min`/`scale_max` pair
- stored on each of the sixteen, not one axis carrying all of them.
+- 16 heaters reading against one range (dampers separately) is the same
+ `scale_min_on_start`/`scale_max_on_start` pair stored on each of the sixteen, not one axis carrying all of them.
- When panning/zooming time, **all pens move together** — pens are **always time-synchronized**;
Y scales are independent (§TM-1).
diff --git a/docs/architecture/ui-text.md b/docs/architecture/ui-text.md
index 237edf1..edebb9b 100644
--- a/docs/architecture/ui-text.md
+++ b/docs/architecture/ui-text.md
@@ -173,8 +173,8 @@ one label of its own is `PenFormMaskPreviewLabel`. The line-style cell and the c
`FuncValueConverter` consumed through `{x:Static}`. The visibility column and field read
`PenEditorColumnOnStart`, "On start" / "При запуске", because the flag is the visibility a pen starts
with and a change of it switches nothing on a running chart. The scale columns and fields read
-`PenEditorColumnScaleMin` and `PenEditorColumnScaleMax`, "Initial scale, min" / "Начальная шкала, от" and
-"Initial scale, max" / "Начальная шкала, до", because the stored pair is the pen's initial scale, the
+`PenEditorColumnScaleMinOnStart` and `PenEditorColumnScaleMaxOnStart`, "Initial scale, min" / "Начальная шкала, от"
+and "Initial scale, max" / "Начальная шкала, до", because the stored pair is the pen's initial scale, the
term the axis scale panel's button `AxisScaleInitialScale` ("Restore initial scale" / "Вернуть начальную")
and the `View` menu item `MenuViewInitialScale` ("Restore initial scale" / "Вернуть начальную шкалу") use: it applies
when the pen enters a chart and on that command, and it changes no pen already shown. The three scale