feat: restore bench section UI with controls, PID RPM ramp, flowmeter charts, and fix CAN IDs
Restore the full bench control panel from the old source with MVVM architecture: - Two-column left panel layout: bench info displays (RPM with target/voltage, temps, pressures, Q-flow, pump live values) and user commands (direction toggle, start/stop with RPM popup and quick-select buttons, oil pump toggle, turn downcounter with CAN send) - PID RPM ramp controller (BenchPidController) with bumpless startup, anti-windup, and derivative-on-measurement for smooth motor speed transitions - Real-time flowmeter charts (LiveChartsCore) for Q-Delivery and Q-Over with tolerance band overlays - Bench/pump CAN liveness detection in PcanAdapter (receive-only IDs) - K-Line connection status indicator (placeholder) - Periodic relay bitmask sender (~21ms) and ElectronicMsg keepalive start on CAN connect, pump sender starts immediately on pump load Fix critical CAN message ID bug: default bench XML values were incorrectly converted from old source (decimal-notation hex parsed as actual hex digits, e.g. "10" -> "A" instead of keeping "10" which parses as 0x10). Corrected all IDs to match hardware: 0x10, 0x11, 0x13, 0x14, 0x15, 0x50, 0x51, 0x55. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
193
ViewModels/BenchControlViewModel.cs
Normal file
193
ViewModels/BenchControlViewModel.cs
Normal file
@@ -0,0 +1,193 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using HC_APTBS.Models;
|
||||
using HC_APTBS.Services;
|
||||
|
||||
namespace HC_APTBS.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// ViewModel for manual bench controls: direction toggle, RPM start/stop with
|
||||
/// PID ramp, oil pump toggle, and turn downcounter.
|
||||
/// Created by <see cref="MainViewModel"/> as a child ViewModel.
|
||||
/// </summary>
|
||||
public sealed partial class BenchControlViewModel : ObservableObject
|
||||
{
|
||||
private readonly IBenchService _bench;
|
||||
private readonly IConfigurationService _config;
|
||||
|
||||
// ── Direction ─────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>True when the bench rotates clockwise (right). False for counter-clockwise (left).</summary>
|
||||
[ObservableProperty] private bool _isDirectionRight = true;
|
||||
|
||||
// ── Oil pump ──────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>True when the oil pump relay is energised.</summary>
|
||||
[ObservableProperty] private bool _isOilPumpOn;
|
||||
|
||||
// ── RPM control ───────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>True when the bench motor is running (last RPM command > 0).</summary>
|
||||
[ObservableProperty] private bool _isBenchRunning;
|
||||
|
||||
/// <summary>Controls the RPM quick-select popup visibility.</summary>
|
||||
[ObservableProperty] private bool _isRpmPopupOpen;
|
||||
|
||||
/// <summary>Text in the RPM input field.</summary>
|
||||
[ObservableProperty] private string _rpmInputText = string.Empty;
|
||||
|
||||
/// <summary>Last RPM setpoint commanded via PID or direct set.</summary>
|
||||
[ObservableProperty] private double _targetRpm;
|
||||
|
||||
/// <summary>Last voltage sent to the motor CAN parameter.</summary>
|
||||
[ObservableProperty] private double _commandVoltage;
|
||||
|
||||
// ── Counter ───────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>Controls the counter popup visibility.</summary>
|
||||
[ObservableProperty] private bool _isCounterPopupOpen;
|
||||
|
||||
/// <summary>Text in the counter input field.</summary>
|
||||
[ObservableProperty] private string _counterInputText = string.Empty;
|
||||
|
||||
/// <summary>Live counter count-down value read from CAN.</summary>
|
||||
[ObservableProperty] private double _benchCounterValue;
|
||||
|
||||
// ── Constructor ───────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Creates the bench control ViewModel and subscribes to service events.
|
||||
/// </summary>
|
||||
/// <param name="benchService">Bench service for RPM, relay, and parameter operations.</param>
|
||||
/// <param name="configService">Configuration service for bench parameters.</param>
|
||||
public BenchControlViewModel(IBenchService benchService, IConfigurationService configService)
|
||||
{
|
||||
_bench = benchService;
|
||||
_config = configService;
|
||||
|
||||
_bench.RpmCommandSent += () =>
|
||||
{
|
||||
TargetRpm = _bench.LastTargetRpm;
|
||||
CommandVoltage = _bench.LastCommandVoltage;
|
||||
};
|
||||
}
|
||||
|
||||
// ── Direction toggle ──────────────────────────────────────────────────────
|
||||
|
||||
partial void OnIsDirectionRightChanged(bool value)
|
||||
{
|
||||
_bench.SetRelay(RelayNames.DirectionRight, value);
|
||||
_bench.SetRelay(RelayNames.DirectionLeft, !value);
|
||||
}
|
||||
|
||||
// ── Oil pump toggle ───────────────────────────────────────────────────────
|
||||
|
||||
partial void OnIsOilPumpOnChanged(bool value)
|
||||
{
|
||||
_bench.SetRelay(RelayNames.OilPump, value);
|
||||
}
|
||||
|
||||
// ── RPM commands ──────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>Opens the RPM quick-select popup.</summary>
|
||||
[RelayCommand]
|
||||
private void OpenRpmPopup()
|
||||
{
|
||||
IsRpmPopupOpen = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the bench motor at the RPM specified in <see cref="RpmInputText"/>.
|
||||
/// Warns the operator if the oil pump is off.
|
||||
/// </summary>
|
||||
[RelayCommand]
|
||||
private void StartBench()
|
||||
{
|
||||
if (!int.TryParse(RpmInputText, out int rpm) || rpm <= 0) return;
|
||||
|
||||
// Safety warning if oil pump is not running.
|
||||
if (!IsOilPumpOn)
|
||||
{
|
||||
var result = MessageBox.Show(
|
||||
"Oil pump is OFF. Start bench without oil circulation?",
|
||||
"Oil Pump Warning",
|
||||
MessageBoxButton.YesNo,
|
||||
MessageBoxImage.Warning);
|
||||
if (result != MessageBoxResult.Yes) return;
|
||||
}
|
||||
|
||||
// Ensure direction relays are set.
|
||||
_bench.SetRelay(RelayNames.DirectionRight, IsDirectionRight);
|
||||
_bench.SetRelay(RelayNames.DirectionLeft, !IsDirectionRight);
|
||||
|
||||
_bench.StartRpmPid(rpm);
|
||||
IsBenchRunning = true;
|
||||
IsRpmPopupOpen = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the bench motor via PID stop and clears direction relays.
|
||||
/// </summary>
|
||||
[RelayCommand]
|
||||
private void StopBench()
|
||||
{
|
||||
_bench.StopRpmPid();
|
||||
_bench.SetRelay(RelayNames.DirectionLeft, false);
|
||||
_bench.SetRelay(RelayNames.DirectionRight, false);
|
||||
IsBenchRunning = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Quick-select button handler: sets the RPM input and starts the bench.
|
||||
/// </summary>
|
||||
/// <param name="rpmString">RPM value as a string from the button content.</param>
|
||||
[RelayCommand]
|
||||
private void SetQuickRpm(string rpmString)
|
||||
{
|
||||
RpmInputText = rpmString;
|
||||
StartBench();
|
||||
}
|
||||
|
||||
// ── Counter commands ──────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>Toggles the counter popup visibility.</summary>
|
||||
[RelayCommand]
|
||||
private void ToggleCounterPopup()
|
||||
{
|
||||
IsCounterPopupOpen = !IsCounterPopupOpen;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends the counter value over CAN and activates the counter relay.
|
||||
/// </summary>
|
||||
[RelayCommand]
|
||||
private void SendCounter()
|
||||
{
|
||||
if (!int.TryParse(CounterInputText, out int count) || count <= 0) return;
|
||||
|
||||
// Set the counter parameter value and transmit.
|
||||
_bench.SetParameter(BenchParameterNames.Counter, count);
|
||||
if (_config.Bench.ParametersByName.TryGetValue(
|
||||
BenchParameterNames.Counter, out var counterParam))
|
||||
{
|
||||
_bench.SendParameters(counterParam.MessageId);
|
||||
}
|
||||
|
||||
// Activate the counter relay.
|
||||
_bench.SetRelay(RelayNames.Counter, true);
|
||||
}
|
||||
|
||||
// ── Refresh (called from MainViewModel timer tick) ────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Updates live counter readback from CAN.
|
||||
/// Called on the UI thread from <see cref="MainViewModel.OnRefreshTick"/>.
|
||||
/// </summary>
|
||||
public void RefreshFromTick()
|
||||
{
|
||||
BenchCounterValue = _bench.ReadBenchParameter(BenchParameterNames.BenchCounter);
|
||||
}
|
||||
}
|
||||
}
|
||||
51
ViewModels/FlowmeterChartViewModel.cs
Normal file
51
ViewModels/FlowmeterChartViewModel.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using HC_APTBS.Models;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace HC_APTBS.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// Container ViewModel holding two real-time flowmeter charts:
|
||||
/// one for Q-Delivery and one for Q-Over.
|
||||
/// </summary>
|
||||
public sealed class FlowmeterChartViewModel
|
||||
{
|
||||
/// <summary>Chart for the Q-Delivery flowmeter (amber line).</summary>
|
||||
public SingleFlowChartViewModel Delivery { get; }
|
||||
= new("Q-Delivery", new SKColor(0xFF, 0xAE, 0x00));
|
||||
|
||||
/// <summary>Chart for the Q-Over flowmeter (blue line).</summary>
|
||||
public SingleFlowChartViewModel Over { get; }
|
||||
= new("Q-Over", new SKColor(0x40, 0x80, 0xFF));
|
||||
|
||||
/// <summary>
|
||||
/// Appends a sample pair to both charts.
|
||||
/// Must be called on the UI thread.
|
||||
/// </summary>
|
||||
public void AddSamples(double qDelivery, double qOver)
|
||||
{
|
||||
Delivery.AddValue(qDelivery);
|
||||
Over.AddValue(qOver);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the tolerance band on the appropriate chart.
|
||||
/// </summary>
|
||||
/// <param name="paramName">Parameter name (use <see cref="BenchParameterNames"/>).</param>
|
||||
/// <param name="target">Center value.</param>
|
||||
/// <param name="tolerance">Half-width.</param>
|
||||
public void SetTolerance(string paramName, double target, double tolerance)
|
||||
{
|
||||
if (paramName == BenchParameterNames.QDelivery)
|
||||
Delivery.SetTolerance(target, tolerance);
|
||||
else if (paramName == BenchParameterNames.QOver)
|
||||
Over.SetTolerance(target, tolerance);
|
||||
}
|
||||
|
||||
/// <summary>Clears both charts.</summary>
|
||||
public void Clear()
|
||||
{
|
||||
Delivery.Clear();
|
||||
Over.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
@@ -59,6 +60,12 @@ namespace HC_APTBS.ViewModels
|
||||
/// <summary>ViewModel for the manual pump control sliders (FBKW, ME, PreIn).</summary>
|
||||
public PumpControlViewModel PumpControl { get; private set; } = null!;
|
||||
|
||||
/// <summary>ViewModel for manual bench controls (direction, RPM, oil pump, counter).</summary>
|
||||
public BenchControlViewModel BenchControl { get; }
|
||||
|
||||
/// <summary>ViewModel for the two flowmeter real-time charts (Q-Delivery, Q-Over).</summary>
|
||||
public FlowmeterChartViewModel FlowmeterChart { get; } = new();
|
||||
|
||||
/// <summary>ViewModel for the first pump status display (Status word).</summary>
|
||||
public StatusDisplayViewModel StatusDisplay1 { get; } = new();
|
||||
|
||||
@@ -90,6 +97,7 @@ namespace HC_APTBS.ViewModels
|
||||
PumpIdentification = new PumpIdentificationViewModel(kwpService, configService, logger);
|
||||
DfiViewModel = new DfiManageViewModel(kwpService, configService);
|
||||
PumpControl = new PumpControlViewModel(benchService);
|
||||
BenchControl = new BenchControlViewModel(benchService, configService);
|
||||
|
||||
// React to pump changes from the identification child VM.
|
||||
PumpIdentification.PumpChanged += OnPumpChanged;
|
||||
@@ -106,6 +114,12 @@ namespace HC_APTBS.ViewModels
|
||||
IsCanConnected = ok;
|
||||
});
|
||||
|
||||
// Bench/pump liveness → connection indicators
|
||||
_can.BenchLivenessChanged += alive =>
|
||||
App.Current.Dispatcher.Invoke(() => IsBenchConnected = alive);
|
||||
_can.PumpLivenessChanged += alive =>
|
||||
App.Current.Dispatcher.Invoke(() => IsPumpConnected = alive);
|
||||
|
||||
// Bench service events
|
||||
_bench.TestStarted += OnTestStarted;
|
||||
_bench.TestFinished += OnTestFinished;
|
||||
@@ -123,8 +137,12 @@ namespace HC_APTBS.ViewModels
|
||||
() => ShowPsgSyncError());
|
||||
_bench.PhaseCompleted += (phase, passed) => App.Current.Dispatcher.Invoke(
|
||||
() => TestPanel.SetPhaseResult(phase, passed));
|
||||
_bench.ToleranceUpdated += (paramName, value, _) => App.Current.Dispatcher.Invoke(
|
||||
() => TestPanel.UpdateLiveIndicator(paramName, value));
|
||||
_bench.ToleranceUpdated += (paramName, value, tolerance) => App.Current.Dispatcher.Invoke(
|
||||
() =>
|
||||
{
|
||||
TestPanel.UpdateLiveIndicator(paramName, value);
|
||||
FlowmeterChart.SetTolerance(paramName, value, tolerance);
|
||||
});
|
||||
|
||||
// Unlock service status → verbose display
|
||||
_unlock.StatusChanged += msg => App.Current.Dispatcher.Invoke(
|
||||
@@ -156,6 +174,7 @@ namespace HC_APTBS.ViewModels
|
||||
|
||||
// Register the pump's CAN parameters with the bus adapter.
|
||||
_can.AddParameters(pump.ParametersById);
|
||||
_can.RegisterPumpMessageIds(GetReceiveMessageIds(pump.ParametersById));
|
||||
|
||||
// Configure pump control sliders.
|
||||
PumpControl.IsPreInVisible = pump.HasPreInjection;
|
||||
@@ -177,8 +196,8 @@ namespace HC_APTBS.ViewModels
|
||||
}
|
||||
|
||||
// Start periodic senders for the new pump.
|
||||
_bench.StartElectronicMsgSender();
|
||||
_bench.StartMemoryRequestSender();
|
||||
_bench.StartPumpSender();
|
||||
|
||||
// Notify commands that depend on pump availability.
|
||||
StartTestCommand.NotifyCanExecuteChanged();
|
||||
@@ -198,9 +217,18 @@ namespace HC_APTBS.ViewModels
|
||||
private void ConnectCan()
|
||||
{
|
||||
_can.SetParameters(_config.Bench.ParametersById);
|
||||
_can.RegisterBenchMessageIds(GetReceiveMessageIds(_config.Bench.ParametersById));
|
||||
bool ok = _can.Connect();
|
||||
CanStatusText = ok ? "Connected" : "Connection failed";
|
||||
IsCanConnected = ok;
|
||||
|
||||
if (ok)
|
||||
{
|
||||
// ElectronicMsg keepalive (0x51) and relay bitmask (0x15) must
|
||||
// begin transmitting as soon as the CAN bus is up.
|
||||
_bench.StartElectronicMsgSender();
|
||||
_bench.StartRelaySender();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Disconnects from the CAN bus adapter.</summary>
|
||||
@@ -208,6 +236,7 @@ namespace HC_APTBS.ViewModels
|
||||
private void DisconnectCan()
|
||||
{
|
||||
_bench.StopElectronicMsgSender();
|
||||
_bench.StopRelaySender();
|
||||
_bench.StopMemoryRequestSender();
|
||||
_bench.StopPumpSender();
|
||||
_can.Disconnect();
|
||||
@@ -269,6 +298,9 @@ namespace HC_APTBS.ViewModels
|
||||
/// <summary>True when oil circulation has been detected.</summary>
|
||||
[ObservableProperty] private bool _isOilCirculating;
|
||||
|
||||
/// <summary>True when a K-Line session is active.</summary>
|
||||
[ObservableProperty] private bool _isKLineConnected;
|
||||
|
||||
// ── Test status ───────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>True while a test sequence is running.</summary>
|
||||
@@ -407,7 +439,14 @@ namespace HC_APTBS.ViewModels
|
||||
|
||||
// Connect CAN bus.
|
||||
_can.SetParameters(_config.Bench.ParametersById);
|
||||
_can.Connect();
|
||||
_can.RegisterBenchMessageIds(GetReceiveMessageIds(_config.Bench.ParametersById));
|
||||
bool canOk = _can.Connect();
|
||||
|
||||
if (canOk)
|
||||
{
|
||||
_bench.StartElectronicMsgSender();
|
||||
_bench.StartRelaySender();
|
||||
}
|
||||
|
||||
// Start the UI refresh timer.
|
||||
StartRefreshTimer();
|
||||
@@ -442,6 +481,10 @@ namespace HC_APTBS.ViewModels
|
||||
Pressure = _bench.ReadBenchParameter(BenchParameterNames.Pressure);
|
||||
PsgEncoderValue = _bench.ReadBenchParameter(BenchParameterNames.PsgEncoderValue);
|
||||
|
||||
// Feed flowmeter charts and refresh bench controls.
|
||||
FlowmeterChart.AddSamples(QDelivery, QOver);
|
||||
BenchControl.RefreshFromTick();
|
||||
|
||||
if (CurrentPump != null)
|
||||
{
|
||||
PumpRpm = _bench.ReadPumpParameter(PumpParameterNames.Rpm);
|
||||
@@ -520,5 +563,24 @@ namespace HC_APTBS.ViewModels
|
||||
=> MessageBox.Show(
|
||||
"PSG sync pulse not detected. Check encoder connection.",
|
||||
"PSG Error", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Returns only the message IDs that contain at least one receive parameter.
|
||||
/// Transmit-only IDs (RPM command, ElectronicMsg, etc.) are excluded because
|
||||
/// they are frames we send, not frames the remote device sends to us.
|
||||
/// </summary>
|
||||
private static HashSet<uint> GetReceiveMessageIds(
|
||||
Dictionary<uint, List<CanBusParameter>> parametersById)
|
||||
{
|
||||
var ids = new HashSet<uint>();
|
||||
foreach (var kv in parametersById)
|
||||
{
|
||||
if (kv.Value.Any(p => p.IsReceive))
|
||||
ids.Add(kv.Key);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
122
ViewModels/SingleFlowChartViewModel.cs
Normal file
122
ViewModels/SingleFlowChartViewModel.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using LiveChartsCore;
|
||||
using LiveChartsCore.Defaults;
|
||||
using LiveChartsCore.SkiaSharpView;
|
||||
using LiveChartsCore.SkiaSharpView.Painting;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace HC_APTBS.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// Reusable ViewModel for a single real-time scrolling line chart.
|
||||
/// Backed by LiveChartsCore with a fixed-width sample window.
|
||||
/// </summary>
|
||||
public sealed partial class SingleFlowChartViewModel : ObservableObject
|
||||
{
|
||||
private const int DefaultMaxSamples = 200;
|
||||
|
||||
private readonly ObservableCollection<double> _values = new();
|
||||
private readonly int _maxSamples;
|
||||
|
||||
/// <summary>Chart title label.</summary>
|
||||
[ObservableProperty] private string _title = string.Empty;
|
||||
|
||||
/// <summary>Series array bound to the CartesianChart.</summary>
|
||||
public ISeries[] Series { get; }
|
||||
|
||||
/// <summary>X axes for the chart (auto-scrolling, no labels).</summary>
|
||||
public Axis[] XAxes { get; }
|
||||
|
||||
/// <summary>Y axes for the chart.</summary>
|
||||
public Axis[] YAxes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Tolerance band sections overlaid on the chart.
|
||||
/// Updated when <see cref="SetTolerance"/> is called.
|
||||
/// </summary>
|
||||
[ObservableProperty] private RectangularSection[] _sections = Array.Empty<RectangularSection>();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new chart ViewModel.
|
||||
/// </summary>
|
||||
/// <param name="title">Display title for the chart.</param>
|
||||
/// <param name="lineColor">SKColor for the line series.</param>
|
||||
/// <param name="maxSamples">Maximum number of samples before the oldest is dropped.</param>
|
||||
public SingleFlowChartViewModel(string title, SKColor lineColor, int maxSamples = DefaultMaxSamples)
|
||||
{
|
||||
_title = title;
|
||||
_maxSamples = maxSamples;
|
||||
|
||||
Series = new ISeries[]
|
||||
{
|
||||
new LineSeries<double>
|
||||
{
|
||||
Values = _values,
|
||||
Fill = null,
|
||||
GeometrySize = 0,
|
||||
Stroke = new SolidColorPaint(lineColor, 2),
|
||||
LineSmoothness = 0,
|
||||
AnimationsSpeed = TimeSpan.Zero
|
||||
}
|
||||
};
|
||||
|
||||
XAxes = new Axis[]
|
||||
{
|
||||
new Axis
|
||||
{
|
||||
IsVisible = false,
|
||||
AnimationsSpeed = TimeSpan.Zero
|
||||
}
|
||||
};
|
||||
|
||||
YAxes = new Axis[]
|
||||
{
|
||||
new Axis
|
||||
{
|
||||
AnimationsSpeed = TimeSpan.Zero,
|
||||
MinLimit = 0
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends a value to the chart. Drops the oldest value when the window is full.
|
||||
/// Must be called on the UI thread.
|
||||
/// </summary>
|
||||
public void AddValue(double value)
|
||||
{
|
||||
_values.Add(value);
|
||||
if (_values.Count > _maxSamples)
|
||||
_values.RemoveAt(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the tolerance band (displayed as a shaded region on the chart).
|
||||
/// </summary>
|
||||
/// <param name="target">Center value of the tolerance band.</param>
|
||||
/// <param name="tolerance">Half-width of the tolerance band.</param>
|
||||
public void SetTolerance(double target, double tolerance)
|
||||
{
|
||||
Sections = new[]
|
||||
{
|
||||
new RectangularSection
|
||||
{
|
||||
Yi = target - tolerance,
|
||||
Yj = target + tolerance,
|
||||
Fill = new SolidColorPaint(SKColors.LimeGreen.WithAlpha(40))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all sample data and tolerance bands.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
_values.Clear();
|
||||
Sections = Array.Empty<RectangularSection>();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user