From 3eadf6c0755d36027cc09d6f31de82df17ced1e2 Mon Sep 17 00:00:00 2001 From: "GukSang.Jin" Date: Sat, 13 Jun 2026 20:01:50 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B012123?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DentistryHandpieces/MainWindowViewModel.cs | 247 +++++++-------- DentistryHandpieces/Models.cs | 2 + DentistryHandpieces/TorqueTrendControl.cs | 332 ++++++++++----------- 3 files changed, 283 insertions(+), 298 deletions(-) diff --git a/DentistryHandpieces/MainWindowViewModel.cs b/DentistryHandpieces/MainWindowViewModel.cs index 7f8e49c..30c4db4 100644 --- a/DentistryHandpieces/MainWindowViewModel.cs +++ b/DentistryHandpieces/MainWindowViewModel.cs @@ -99,7 +99,6 @@ public sealed class MainWindowViewModel : ObservableObject private const ushort PressureCoefficientRegister = 1378; private const ushort PressureDisplayRegister = 1388; private const double TenthsRegisterScale = 10.0; - private const int MaxTorqueSampleCount = 300; private const double MinimumTorqueChangeThreshold = 0.05; private const double MinimumSpeedChangeThreshold = 1.0; private const string TorqueUnit = "mN.m"; @@ -252,7 +251,7 @@ public sealed class MainWindowViewModel : ObservableObject private string _speedTorquePeakTorqueText = $"0.00 {TorqueUnit}"; private string _speedTorqueMaxDisplacementText = "0.000 mm"; private string _speedTorqueFinalDisplacementText = "--"; - private string _torqueCurveStatusText = "转速/扭矩保持时间关系曲线:未启动"; + private string _torqueCurveStatusText = "转速/扭矩随时间变化曲线:未启动"; private string _axialConfigSummaryText = "--"; private string _speedTorqueConfigSummaryText = "--"; private string _statusText = "完成测试后可导出报表。"; @@ -1372,14 +1371,14 @@ public sealed class MainWindowViewModel : ObservableObject CreateRecordPoint("转速/扭矩实时测试", "转速/扭矩保持时间关系曲线判定", curve.Result, string.Empty, "记录"), CreateRecordPoint("转速/扭矩实时测试", "扭矩变化阈值", $"{FormatTorque(curve.ChangeThresholdMilliNewtonMeters)} {TorqueUnit}", TorqueUnit), CreateRecordPoint("转速/扭矩实时测试", "转速变化阈值", $"{FormatSpeed(curve.SpeedChangeThresholdRpm)} r/min", "r/min"), - CreateRecordPoint("转速/扭矩实时测试", "保持时间内最小扭矩", curve.Samples.Count >= 2 ? $"{FormatTorque(curve.MinTorqueMilliNewtonMeters)} {TorqueUnit}" : "--", TorqueUnit), - CreateRecordPoint("转速/扭矩实时测试", "保持时间内最大扭矩", curve.Samples.Count >= 2 ? $"{FormatTorque(curve.MaxTorqueMilliNewtonMeters)} {TorqueUnit}" : "--", TorqueUnit), - CreateRecordPoint("转速/扭矩实时测试", "保持时间内平均扭矩", curve.Samples.Count >= 2 ? $"{FormatTorque(curve.AverageTorqueMilliNewtonMeters)} {TorqueUnit}" : "--", TorqueUnit), - CreateRecordPoint("转速/扭矩实时测试", "保持时间内扭矩波动值", curve.Samples.Count >= 2 ? $"{FormatTorque(curve.FluctuationMilliNewtonMeters)} {TorqueUnit}" : "--", TorqueUnit), - CreateRecordPoint("转速/扭矩实时测试", "保持时间内最小转速", curve.Samples.Count >= 2 ? $"{FormatSpeed(curve.MinSpeedRpm)} r/min" : "--", "r/min"), - CreateRecordPoint("转速/扭矩实时测试", "保持时间内最大转速", curve.Samples.Count >= 2 ? $"{FormatSpeed(curve.MaxSpeedRpm)} r/min" : "--", "r/min"), - CreateRecordPoint("转速/扭矩实时测试", "保持时间内平均转速", curve.Samples.Count >= 2 ? $"{FormatSpeed(curve.AverageSpeedRpm)} r/min" : "--", "r/min"), - CreateRecordPoint("转速/扭矩实时测试", "保持时间内转速波动值", curve.Samples.Count >= 2 ? $"{FormatSpeed(curve.SpeedFluctuationRpm)} r/min" : "--", "r/min") + CreateRecordPoint("转速/扭矩实时测试", "保持时间内最小扭矩", curve.EvaluationSampleCount >= 2 ? $"{FormatTorque(curve.MinTorqueMilliNewtonMeters)} {TorqueUnit}" : "--", TorqueUnit), + CreateRecordPoint("转速/扭矩实时测试", "保持时间内最大扭矩", curve.EvaluationSampleCount >= 2 ? $"{FormatTorque(curve.MaxTorqueMilliNewtonMeters)} {TorqueUnit}" : "--", TorqueUnit), + CreateRecordPoint("转速/扭矩实时测试", "保持时间内平均扭矩", curve.EvaluationSampleCount >= 2 ? $"{FormatTorque(curve.AverageTorqueMilliNewtonMeters)} {TorqueUnit}" : "--", TorqueUnit), + CreateRecordPoint("转速/扭矩实时测试", "保持时间内扭矩波动值", curve.EvaluationSampleCount >= 2 ? $"{FormatTorque(curve.FluctuationMilliNewtonMeters)} {TorqueUnit}" : "--", TorqueUnit), + CreateRecordPoint("转速/扭矩实时测试", "保持时间内最小转速", curve.EvaluationSampleCount >= 2 ? $"{FormatSpeed(curve.MinSpeedRpm)} r/min" : "--", "r/min"), + CreateRecordPoint("转速/扭矩实时测试", "保持时间内最大转速", curve.EvaluationSampleCount >= 2 ? $"{FormatSpeed(curve.MaxSpeedRpm)} r/min" : "--", "r/min"), + CreateRecordPoint("转速/扭矩实时测试", "保持时间内平均转速", curve.EvaluationSampleCount >= 2 ? $"{FormatSpeed(curve.AverageSpeedRpm)} r/min" : "--", "r/min"), + CreateRecordPoint("转速/扭矩实时测试", "保持时间内转速波动值", curve.EvaluationSampleCount >= 2 ? $"{FormatSpeed(curve.SpeedFluctuationRpm)} r/min" : "--", "r/min") ] }; } @@ -1799,7 +1798,7 @@ public sealed class MainWindowViewModel : ObservableObject } var sheet = workbook.Worksheets.Add("转速扭矩曲线"); - sheet.Cell(1, 1).Value = "转速/扭矩保持时间关系曲线"; + sheet.Cell(1, 1).Value = "转速/扭矩随时间变化曲线"; sheet.Range(1, 1, 1, 12).Merge().Style.Font.SetBold().Font.SetFontSize(16); sheet.Cell(3, 1).Value = "保持时间(s)"; @@ -1809,29 +1808,29 @@ public sealed class MainWindowViewModel : ObservableObject sheet.Cell(5, 1).Value = "曲线判定"; sheet.Cell(5, 2).Value = curve.Result; sheet.Cell(6, 1).Value = $"最小扭矩({TorqueUnit})"; - sheet.Cell(6, 2).Value = curve.Samples.Count >= 2 ? curve.MinTorqueMilliNewtonMeters : string.Empty; + sheet.Cell(6, 2).Value = curve.EvaluationSampleCount >= 2 ? curve.MinTorqueMilliNewtonMeters : string.Empty; sheet.Cell(7, 1).Value = $"最大扭矩({TorqueUnit})"; - sheet.Cell(7, 2).Value = curve.Samples.Count >= 2 ? curve.MaxTorqueMilliNewtonMeters : string.Empty; + sheet.Cell(7, 2).Value = curve.EvaluationSampleCount >= 2 ? curve.MaxTorqueMilliNewtonMeters : string.Empty; sheet.Cell(8, 1).Value = $"平均扭矩({TorqueUnit})"; - sheet.Cell(8, 2).Value = curve.Samples.Count >= 2 ? curve.AverageTorqueMilliNewtonMeters : string.Empty; + sheet.Cell(8, 2).Value = curve.EvaluationSampleCount >= 2 ? curve.AverageTorqueMilliNewtonMeters : string.Empty; sheet.Cell(9, 1).Value = $"扭矩波动值({TorqueUnit})"; - sheet.Cell(9, 2).Value = curve.Samples.Count >= 2 ? curve.FluctuationMilliNewtonMeters : string.Empty; + sheet.Cell(9, 2).Value = curve.EvaluationSampleCount >= 2 ? curve.FluctuationMilliNewtonMeters : string.Empty; sheet.Range(3, 1, 9, 1).Style.Font.SetBold(); sheet.Cell(3, 4).Value = "X轴"; - sheet.Cell(3, 5).Value = $"扭矩({TorqueUnit})"; - sheet.Cell(4, 4).Value = "Y轴"; - sheet.Cell(4, 5).Value = "转速(r/min)"; + sheet.Cell(3, 5).Value = "时间(s)"; + sheet.Cell(4, 4).Value = "双Y轴"; + sheet.Cell(4, 5).Value = $"左:扭矩({TorqueUnit});右:转速(r/min)"; sheet.Cell(5, 4).Value = "转速变化阈值(r/min)"; sheet.Cell(5, 5).Value = curve.SpeedChangeThresholdRpm; sheet.Cell(6, 4).Value = "最小转速(r/min)"; - sheet.Cell(6, 5).Value = curve.Samples.Count >= 2 ? curve.MinSpeedRpm : string.Empty; + sheet.Cell(6, 5).Value = curve.EvaluationSampleCount >= 2 ? curve.MinSpeedRpm : string.Empty; sheet.Cell(7, 4).Value = "最大转速(r/min)"; - sheet.Cell(7, 5).Value = curve.Samples.Count >= 2 ? curve.MaxSpeedRpm : string.Empty; + sheet.Cell(7, 5).Value = curve.EvaluationSampleCount >= 2 ? curve.MaxSpeedRpm : string.Empty; sheet.Cell(8, 4).Value = "平均转速(r/min)"; - sheet.Cell(8, 5).Value = curve.Samples.Count >= 2 ? curve.AverageSpeedRpm : string.Empty; + sheet.Cell(8, 5).Value = curve.EvaluationSampleCount >= 2 ? curve.AverageSpeedRpm : string.Empty; sheet.Cell(9, 4).Value = "转速波动值(r/min)"; - sheet.Cell(9, 5).Value = curve.Samples.Count >= 2 ? curve.SpeedFluctuationRpm : string.Empty; + sheet.Cell(9, 5).Value = curve.EvaluationSampleCount >= 2 ? curve.SpeedFluctuationRpm : string.Empty; sheet.Range(3, 4, 9, 4).Style.Font.SetBold(); sheet.Cell(11, 1).Value = "时间(s)"; @@ -1862,7 +1861,7 @@ public sealed class MainWindowViewModel : ObservableObject catch (Exception ex) { sheet.Cell(3, 7).Value = "曲线图生成失败,原始曲线数据已完整保留"; - Log.Warning(ex, "转速/扭矩关系曲线图片生成失败,报表继续保留原始曲线数据"); + Log.Warning(ex, "转速/扭矩时间曲线图片生成失败,报表继续保留原始曲线数据"); } } else @@ -1881,15 +1880,15 @@ public sealed class MainWindowViewModel : ObservableObject var background = new SolidColorBrush(Color.FromRgb(248, 250, 252)); var borderPen = new Pen(new SolidColorBrush(Color.FromRgb(197, 210, 222)), 1); var gridPen = new Pen(new SolidColorBrush(Color.FromRgb(219, 229, 238)), 1); - var linePen = new Pen(new SolidColorBrush(Color.FromRgb(29, 78, 216)), 2.6); - var markerBrush = new SolidColorBrush(Color.FromRgb(15, 118, 110)); + var torqueBrush = new SolidColorBrush(Color.FromRgb(29, 78, 216)); + var speedBrush = new SolidColorBrush(Color.FromRgb(15, 118, 110)); var textBrush = new SolidColorBrush(Color.FromRgb(51, 65, 85)); context.DrawRectangle(background, borderPen, new Rect(0, 0, width, height)); - DrawChartText(context, "转速/扭矩保持时间关系曲线", 18, textBrush, new System.Windows.Point(22, 16), true); - DrawChartText(context, $"判定:{curve.Result}", 14, markerBrush, new System.Windows.Point(22, 44), true); + DrawChartText(context, "转速/扭矩随时间变化曲线", 18, textBrush, new System.Windows.Point(22, 16), true); + DrawChartText(context, $"保持时间判定:{curve.Result}", 14, speedBrush, new System.Windows.Point(22, 44), true); - Rect plot = new(62, 78, width - 92, height - 126); + Rect plot = new(70, 78, width - 145, height - 126); for (int i = 0; i <= 4; i++) { double y = plot.Top + plot.Height * i / 4; @@ -1904,70 +1903,33 @@ public sealed class MainWindowViewModel : ObservableObject if (curve.Samples.Count > 0) { - double minTorque = curve.Samples.Min(sample => sample.TorqueMilliNewtonMeters); - double maxTorque = curve.Samples.Max(sample => sample.TorqueMilliNewtonMeters); - double torqueRange = maxTorque - minTorque; - if (torqueRange < 0.01) - { - minTorque -= 0.5; - maxTorque += 0.5; - torqueRange = 1; - } - else - { - double padding = torqueRange * 0.12; - minTorque -= padding; - maxTorque += padding; - torqueRange = maxTorque - minTorque; - } + double minTime = 0; + double maxTime = Math.Max(0.5, curve.Samples.Max(sample => sample.ElapsedSeconds)); + (double minTorque, double maxTorque) = GetChartRange( + curve.Samples.Min(sample => sample.TorqueMilliNewtonMeters), + curve.Samples.Max(sample => sample.TorqueMilliNewtonMeters), + 0.02); + (double minSpeed, double maxSpeed) = GetChartRange( + curve.Samples.Min(sample => sample.SpeedRpm), + curve.Samples.Max(sample => sample.SpeedRpm), + 1); - double minSpeed = curve.Samples.Min(sample => sample.SpeedRpm); - double maxSpeed = curve.Samples.Max(sample => sample.SpeedRpm); - double speedRange = maxSpeed - minSpeed; - if (speedRange < 1) - { - double padding = Math.Max(Math.Abs(minSpeed) * 0.05, 1); - minSpeed -= padding; - maxSpeed += padding; - speedRange = maxSpeed - minSpeed; - } - else - { - double padding = speedRange * 0.05; - minSpeed -= padding; - maxSpeed += padding; - speedRange = maxSpeed - minSpeed; - } + StreamGeometry torqueGeometry = CreateTimeSeriesGeometry( + curve.Samples, plot, minTime, maxTime, minTorque, maxTorque, static sample => sample.TorqueMilliNewtonMeters); + StreamGeometry speedGeometry = CreateTimeSeriesGeometry( + curve.Samples, plot, minTime, maxTime, minSpeed, maxSpeed, static sample => sample.SpeedRpm); + context.DrawGeometry(null, new Pen(torqueBrush, 2.4), torqueGeometry); + context.DrawGeometry(null, new Pen(speedBrush, 2.4), speedGeometry); - var geometry = new StreamGeometry(); - using (StreamGeometryContext geometryContext = geometry.Open()) - { - for (int i = 0; i < curve.Samples.Count; i++) - { - TorqueSamplePayload sample = curve.Samples[i]; - double x = plot.Left + plot.Width * (sample.TorqueMilliNewtonMeters - minTorque) / torqueRange; - double y = plot.Bottom - (sample.SpeedRpm - minSpeed) / speedRange * plot.Height; - var point = new System.Windows.Point(x, y); - if (i == 0) - { - geometryContext.BeginFigure(point, false, false); - } - else - { - geometryContext.LineTo(point, true, false); - } - } - } - - geometry.Freeze(); - context.DrawGeometry(null, linePen, geometry); - - DrawChartText(context, maxSpeed.ToString("0", CultureInfo.InvariantCulture), 12, textBrush, new System.Windows.Point(16, plot.Top - 8), false); - DrawChartText(context, minSpeed.ToString("0", CultureInfo.InvariantCulture), 12, textBrush, new System.Windows.Point(16, plot.Bottom - 10), false); - DrawChartText(context, minTorque.ToString("0.##", CultureInfo.InvariantCulture), 12, textBrush, new System.Windows.Point(plot.Left, plot.Bottom + 10), false); - DrawChartText(context, maxTorque.ToString("0.##", CultureInfo.InvariantCulture), 12, textBrush, new System.Windows.Point(plot.Right - 34, plot.Bottom + 10), false); - DrawChartText(context, "转速(r/min)", 12, textBrush, new System.Windows.Point(12, plot.Top + 16), false); - DrawChartText(context, $"扭矩({TorqueUnit})", 12, textBrush, new System.Windows.Point(plot.Right - 78, plot.Bottom - 18), false); + DrawChartText(context, maxTorque.ToString("0.##", CultureInfo.InvariantCulture), 12, torqueBrush, new System.Windows.Point(14, plot.Top - 8), false); + DrawChartText(context, minTorque.ToString("0.##", CultureInfo.InvariantCulture), 12, torqueBrush, new System.Windows.Point(14, plot.Bottom - 10), false); + DrawChartText(context, maxSpeed.ToString("0", CultureInfo.InvariantCulture), 12, speedBrush, new System.Windows.Point(plot.Right + 8, plot.Top - 8), false); + DrawChartText(context, minSpeed.ToString("0", CultureInfo.InvariantCulture), 12, speedBrush, new System.Windows.Point(plot.Right + 8, plot.Bottom - 10), false); + DrawChartText(context, minTime.ToString("0.0", CultureInfo.InvariantCulture), 12, textBrush, new System.Windows.Point(plot.Left, plot.Bottom + 10), false); + DrawChartText(context, maxTime.ToString("0.0", CultureInfo.InvariantCulture), 12, textBrush, new System.Windows.Point(plot.Right - 34, plot.Bottom + 10), false); + DrawChartText(context, $"扭矩({TorqueUnit})", 12, torqueBrush, new System.Windows.Point(plot.Left + 8, plot.Top + 16), false); + DrawChartText(context, "转速(r/min)", 12, speedBrush, new System.Windows.Point(plot.Right - 82, plot.Top + 16), false); + DrawChartText(context, "时间(s)", 12, textBrush, new System.Windows.Point(plot.Right - 54, plot.Bottom - 18), false); } else { @@ -1986,6 +1948,52 @@ public sealed class MainWindowViewModel : ObservableObject return pngStream.ToArray(); } + private static (double Min, double Max) GetChartRange(double min, double max, double minimumRange) + { + double range = max - min; + if (range < minimumRange) + { + double center = (min + max) / 2; + return (center - minimumRange / 2, center + minimumRange / 2); + } + + double padding = range * 0.12; + return (min - padding, max + padding); + } + + private static StreamGeometry CreateTimeSeriesGeometry( + IReadOnlyList samples, + Rect plot, + double minTime, + double maxTime, + double minValue, + double maxValue, + Func valueSelector) + { + var geometry = new StreamGeometry(); + using (StreamGeometryContext geometryContext = geometry.Open()) + { + for (int i = 0; i < samples.Count; i++) + { + TorqueSamplePayload sample = samples[i]; + double x = plot.Left + plot.Width * (sample.ElapsedSeconds - minTime) / (maxTime - minTime); + double y = plot.Bottom - (valueSelector(sample) - minValue) / (maxValue - minValue) * plot.Height; + var point = new System.Windows.Point(x, y); + if (i == 0) + { + geometryContext.BeginFigure(point, false, false); + } + else + { + geometryContext.LineTo(point, true, false); + } + } + } + + geometry.Freeze(); + return geometry; + } + private static void DrawChartText(DrawingContext context, string text, double fontSize, Brush brush, System.Windows.Point origin, bool bold) { var formattedText = new FormattedText( @@ -3458,12 +3466,6 @@ public sealed class MainWindowViewModel : ObservableObject } double elapsedSeconds = Math.Max(0, (sampledAt - _speedTorqueStartedAt.Value).TotalSeconds); - if (_parameterConfig.TorqueHoldTime <= 0 - || elapsedSeconds > _parameterConfig.TorqueHoldTime) - { - return; - } - TorqueSamples.Add(new TorqueSamplePayload { ElapsedSeconds = elapsedSeconds, @@ -3471,12 +3473,6 @@ public sealed class MainWindowViewModel : ObservableObject TorqueMilliNewtonMeters = torque }); _cachedTorqueCurve = null; - - int sampleLimit = GetTorqueSampleLimit(); - while (TorqueSamples.Count > sampleLimit) - { - TorqueSamples.RemoveAt(0); - } } private void ClearTorqueSamples() @@ -3489,19 +3485,15 @@ public sealed class MainWindowViewModel : ObservableObject private void UpdateTorqueCurveStatus() { if (_isSpeedTorqueRunning - && _speedTorqueStartedAt.HasValue - && _parameterConfig.TorqueHoldTime > 0) + && _speedTorqueStartedAt.HasValue) { double elapsed = Math.Max(0, (DateTime.Now - _speedTorqueStartedAt.Value).TotalSeconds); - if (elapsed < _parameterConfig.TorqueHoldTime) - { - TorqueCurveStatusText = $"实时曲线:采集中 {FormatConfigNumber(elapsed)} / {FormatConfigNumber(_parameterConfig.TorqueHoldTime)} s;X轴扭矩 / Y轴转速"; - return; - } + TorqueCurveStatusText = $"实时曲线:已采集 {FormatConfigNumber(elapsed)} s;X轴时间 / 左Y轴扭矩 / 右Y轴转速"; + return; } TorqueCurvePayload curve = CreateTorqueCurvePayload(); - TorqueCurveStatusText = $"转速/扭矩保持时间关系曲线:{curve.Result}"; + TorqueCurveStatusText = $"转速/扭矩随时间变化曲线:保持时间判定 {curve.Result}"; } private TorqueCurvePayload CreateTorqueCurvePayload() @@ -3512,7 +3504,6 @@ public sealed class MainWindowViewModel : ObservableObject } List samples = TorqueSamples - .Where(sample => sample.ElapsedSeconds <= _parameterConfig.TorqueHoldTime) .Select(sample => new TorqueSamplePayload { ElapsedSeconds = sample.ElapsedSeconds, @@ -3539,7 +3530,6 @@ public sealed class MainWindowViewModel : ObservableObject SpeedRpm = sample.RealtimeSpeedRpm, TorqueMilliNewtonMeters = sample.RealtimeTorqueMilliNewtonMeters }) - .Where(sample => sample.ElapsedSeconds <= holdTime) .ToList(); return BuildTorqueCurvePayload( @@ -3555,11 +3545,16 @@ public sealed class MainWindowViewModel : ObservableObject double torqueThreshold, double speedThreshold) { + List evaluationSamples = samples + .Where(sample => sample.ElapsedSeconds <= holdTime) + .ToList(); + if (holdTime <= 0) { return new TorqueCurvePayload { HoldTimeSeconds = holdTime, + EvaluationSampleCount = evaluationSamples.Count, ChangeThresholdMilliNewtonMeters = torqueThreshold, SpeedChangeThresholdRpm = speedThreshold, Result = "未设置保持时间,未判定", @@ -3567,11 +3562,12 @@ public sealed class MainWindowViewModel : ObservableObject }; } - if (samples.Count < 2) + if (evaluationSamples.Count < 2) { return new TorqueCurvePayload { HoldTimeSeconds = holdTime, + EvaluationSampleCount = evaluationSamples.Count, ChangeThresholdMilliNewtonMeters = torqueThreshold, SpeedChangeThresholdRpm = speedThreshold, Result = "采样不足,未判定", @@ -3579,11 +3575,11 @@ public sealed class MainWindowViewModel : ObservableObject }; } - double minTorque = samples.Min(static sample => sample.TorqueMilliNewtonMeters); - double maxTorque = samples.Max(static sample => sample.TorqueMilliNewtonMeters); + double minTorque = evaluationSamples.Min(static sample => sample.TorqueMilliNewtonMeters); + double maxTorque = evaluationSamples.Max(static sample => sample.TorqueMilliNewtonMeters); double torqueFluctuation = maxTorque - minTorque; - double minSpeed = samples.Min(static sample => sample.SpeedRpm); - double maxSpeed = samples.Max(static sample => sample.SpeedRpm); + double minSpeed = evaluationSamples.Min(static sample => sample.SpeedRpm); + double maxSpeed = evaluationSamples.Max(static sample => sample.SpeedRpm); double speedFluctuation = maxSpeed - minSpeed; bool torqueChanged = torqueFluctuation > torqueThreshold; bool speedChanged = speedFluctuation > speedThreshold; @@ -3598,15 +3594,16 @@ public sealed class MainWindowViewModel : ObservableObject return new TorqueCurvePayload { HoldTimeSeconds = holdTime, + EvaluationSampleCount = evaluationSamples.Count, ChangeThresholdMilliNewtonMeters = torqueThreshold, SpeedChangeThresholdRpm = speedThreshold, MinTorqueMilliNewtonMeters = minTorque, MaxTorqueMilliNewtonMeters = maxTorque, - AverageTorqueMilliNewtonMeters = samples.Average(static sample => sample.TorqueMilliNewtonMeters), + AverageTorqueMilliNewtonMeters = evaluationSamples.Average(static sample => sample.TorqueMilliNewtonMeters), FluctuationMilliNewtonMeters = torqueFluctuation, MinSpeedRpm = minSpeed, MaxSpeedRpm = maxSpeed, - AverageSpeedRpm = samples.Average(static sample => sample.SpeedRpm), + AverageSpeedRpm = evaluationSamples.Average(static sample => sample.SpeedRpm), SpeedFluctuationRpm = speedFluctuation, Result = result, Samples = samples @@ -3623,18 +3620,6 @@ public sealed class MainWindowViewModel : ObservableObject return Math.Max(MinimumSpeedChangeThreshold, Math.Abs(_parameterConfig.LoadSpeedSetting) * 0.01); } - private int GetTorqueSampleLimit() - { - if (_parameterConfig.TorqueHoldTime <= 0) - { - return MaxTorqueSampleCount; - } - - int requiredSamples = (int)Math.Ceiling( - _parameterConfig.TorqueHoldTime / RealtimeRefreshInterval.TotalSeconds) + 2; - return Math.Max(MaxTorqueSampleCount, requiredSamples); - } - private void CaptureRealtimeSample( double dialIndicator, IReadOnlyDictionary coilValues, diff --git a/DentistryHandpieces/Models.cs b/DentistryHandpieces/Models.cs index 2021d01..6842c03 100644 --- a/DentistryHandpieces/Models.cs +++ b/DentistryHandpieces/Models.cs @@ -212,6 +212,8 @@ public sealed class TorqueCurvePayload { public double HoldTimeSeconds { get; init; } + public int EvaluationSampleCount { get; init; } + public double ChangeThresholdMilliNewtonMeters { get; init; } public double SpeedChangeThresholdRpm { get; init; } diff --git a/DentistryHandpieces/TorqueTrendControl.cs b/DentistryHandpieces/TorqueTrendControl.cs index cdacfc4..61b06fb 100644 --- a/DentistryHandpieces/TorqueTrendControl.cs +++ b/DentistryHandpieces/TorqueTrendControl.cs @@ -9,6 +9,8 @@ namespace DentistryHandpieces; public readonly record struct TorqueTrendViewState( bool IsManual, + double MinTime, + double MaxTime, double MinTorque, double MaxTorque, double MinSpeed, @@ -20,16 +22,21 @@ public sealed class TorqueTrendControl : FrameworkElement private const string SpeedUnit = "r/min"; private const double SensorMinTorque = 0; private const double SensorMaxTorque = 100; - private const double MinimumTorqueRange = 0.1; + private const double MinimumTimeRange = 0.5; + private const double MinimumTorqueRange = 0.02; private const double MinimumSpeedRange = 1; - private const double TorqueRangePaddingRatio = 0.12; - private const double TorqueTrendBinSize = 0.01; + private const double RangePaddingRatio = 0.12; + + private static readonly Color TorqueColor = Color.FromRgb(29, 78, 216); + private static readonly Color SpeedColor = Color.FromRgb(15, 118, 110); private bool _isManualView; + private double _viewMinTime; + private double _viewMaxTime = 1; private double _viewMinTorque = SensorMinTorque; - private double _viewMaxTorque = SensorMaxTorque; + private double _viewMaxTorque = MinimumTorqueRange; private double _viewMinSpeed; - private double _viewMaxSpeed = 1; + private double _viewMaxSpeed = MinimumSpeedRange; private Point? _lastDragPoint; public TorqueTrendControl() @@ -56,6 +63,8 @@ public sealed class TorqueTrendControl : FrameworkElement { return new TorqueTrendViewState( _isManualView, + _viewMinTime, + _viewMaxTime, _viewMinTorque, _viewMaxTorque, _viewMinSpeed, @@ -71,11 +80,13 @@ public sealed class TorqueTrendControl : FrameworkElement } _isManualView = true; + _viewMinTime = state.MinTime; + _viewMaxTime = state.MaxTime; _viewMinTorque = state.MinTorque; _viewMaxTorque = state.MaxTorque; _viewMinSpeed = state.MinSpeed; _viewMaxSpeed = state.MaxSpeed; - ClampView(); + ClampView(ReadSamples()); InvalidateVisual(); } @@ -92,8 +103,6 @@ public sealed class TorqueTrendControl : FrameworkElement public void ResetView() { _isManualView = false; - _viewMinTorque = SensorMinTorque; - _viewMaxTorque = SensorMaxTorque; InvalidateVisual(); } @@ -115,7 +124,7 @@ public sealed class TorqueTrendControl : FrameworkElement 6, 6); - if (ActualWidth < 120 || ActualHeight < 80) + if (ActualWidth < 160 || ActualHeight < 80) { return; } @@ -131,41 +140,37 @@ public sealed class TorqueTrendControl : FrameworkElement return; } - (double minTorque, double maxTorque, double minSpeed, double maxSpeed) = GetVisibleRanges(samples); - DrawAxisLabels(drawingContext, plot, minTorque, maxTorque, minSpeed, maxSpeed); + (double minTime, double maxTime, double minTorque, double maxTorque, double minSpeed, double maxSpeed) = GetVisibleRanges(samples); + DrawAxisLabels(drawingContext, plot, minTime, maxTime, minTorque, maxTorque, minSpeed, maxSpeed); - var points = samples - .Select(sample => ToPlotPoint(sample, plot, minTorque, maxTorque, minSpeed, maxSpeed)) + List visibleSamples = DownsampleForDrawing( + samples.Where(sample => sample.ElapsedSeconds >= minTime && sample.ElapsedSeconds <= maxTime).ToList(), + plot.Width); + List torquePoints = visibleSamples + .Select(sample => ToPlotPoint(sample.ElapsedSeconds, sample.TorqueMilliNewtonMeters, plot, minTime, maxTime, minTorque, maxTorque)) + .ToList(); + List speedPoints = visibleSamples + .Select(sample => ToPlotPoint(sample.ElapsedSeconds, sample.SpeedRpm, plot, minTime, maxTime, minSpeed, maxSpeed)) .ToList(); - List trendPoints = BuildTrendPoints(samples); drawingContext.PushClip(new RectangleGeometry(plot)); - DrawSamplePoints(drawingContext, points); - DrawTrendLine( - drawingContext, - trendPoints - .Select(point => ToPlotPoint(point.Torque, point.Speed, plot, minTorque, maxTorque, minSpeed, maxSpeed)) - .ToList()); - DrawCurrentPoint(drawingContext, points[^1]); + DrawLine(drawingContext, torquePoints, TorqueColor); + DrawLine(drawingContext, speedPoints, SpeedColor); + if (torquePoints.Count > 0) + { + DrawCurrentPoint(drawingContext, torquePoints[^1], TorqueColor); + DrawCurrentPoint(drawingContext, speedPoints[^1], SpeedColor); + } drawingContext.Pop(); TorqueSamplePayload current = samples[^1]; DrawText( drawingContext, - $"当前 {current.SpeedRpm:0} {SpeedUnit} / {current.TorqueMilliNewtonMeters:0.##} {TorqueUnit}", + $"当前 {current.ElapsedSeconds:0.0} s / {current.TorqueMilliNewtonMeters:0.##} {TorqueUnit} / {current.SpeedRpm:0} {SpeedUnit}", 12, - Color.FromRgb(15, 118, 110), + SpeedColor, new Point(plot.Left + 6, plot.Top + 5)); - DrawLegend(drawingContext, plot, trendPoints.Count >= 2); - if (trendPoints.Count < 2) - { - DrawText( - drawingContext, - "扭矩变化小于传感器精度,暂无有效趋势线", - 11, - Color.FromRgb(180, 83, 9), - new Point(plot.Left + 6, plot.Bottom - 38)); - } + DrawLegend(drawingContext, plot); } protected override void OnMouseWheel(MouseWheelEventArgs e) @@ -261,7 +266,7 @@ public sealed class TorqueTrendControl : FrameworkElement private Rect GetPlotRect() { - return new Rect(52, 14, Math.Max(1, ActualWidth - 70), Math.Max(1, ActualHeight - 42)); + return new Rect(58, 14, Math.Max(1, ActualWidth - 116), Math.Max(1, ActualHeight - 44)); } private static void DrawGrid(DrawingContext drawingContext, Rect plot) @@ -280,91 +285,96 @@ public sealed class TorqueTrendControl : FrameworkElement } } - private (double MinTorque, double MaxTorque, double MinSpeed, double MaxSpeed) GetVisibleRanges(List samples) + private (double MinTime, double MaxTime, double MinTorque, double MaxTorque, double MinSpeed, double MaxSpeed) GetVisibleRanges(List samples) { if (!_isManualView) { + (_viewMinTime, _viewMaxTime) = GetAutoTimeRange(samples); (_viewMinTorque, _viewMaxTorque) = GetAutoTorqueRange(samples); (_viewMinSpeed, _viewMaxSpeed) = GetAutoSpeedRange(samples); } - return (_viewMinTorque, _viewMaxTorque, _viewMinSpeed, _viewMaxSpeed); + return (_viewMinTime, _viewMaxTime, _viewMinTorque, _viewMaxTorque, _viewMinSpeed, _viewMaxSpeed); + } + + private static (double MinTime, double MaxTime) GetAutoTimeRange(List samples) + { + double maxTime = Math.Max(MinimumTimeRange, samples.Max(sample => sample.ElapsedSeconds)); + return (0, maxTime); } private static (double MinTorque, double MaxTorque) GetAutoTorqueRange(List samples) { double minTorque = Math.Clamp(samples.Min(sample => sample.TorqueMilliNewtonMeters), SensorMinTorque, SensorMaxTorque); double maxTorque = Math.Clamp(samples.Max(sample => sample.TorqueMilliNewtonMeters), SensorMinTorque, SensorMaxTorque); - double range = maxTorque - minTorque; - - if (range < MinimumTorqueRange) - { - double center = (minTorque + maxTorque) / 2; - minTorque = center - MinimumTorqueRange / 2; - maxTorque = center + MinimumTorqueRange / 2; - } - else - { - double padding = range * TorqueRangePaddingRatio; - minTorque -= padding; - maxTorque += padding; - } - - return ClampTorqueRange(minTorque, maxTorque); + return ClampTorqueRangeWithPadding(minTorque, maxTorque); } private static (double MinSpeed, double MaxSpeed) GetAutoSpeedRange(List samples) { - double minSpeed = samples.Min(sample => sample.SpeedRpm); - double maxSpeed = samples.Max(sample => sample.SpeedRpm); - double range = maxSpeed - minSpeed; - double padding = range < MinimumSpeedRange - ? Math.Max(Math.Abs(minSpeed) * 0.05, MinimumSpeedRange) - : range * 0.08; - return (minSpeed - padding, maxSpeed + padding); + return GetPaddedRange( + samples.Min(sample => sample.SpeedRpm), + samples.Max(sample => sample.SpeedRpm), + MinimumSpeedRange); + } + + private static (double Min, double Max) GetPaddedRange(double min, double max, double minimumRange) + { + double range = max - min; + if (range < minimumRange) + { + double center = (min + max) / 2; + return (center - minimumRange / 2, center + minimumRange / 2); + } + + double padding = range * RangePaddingRatio; + return (min - padding, max + padding); + } + + private static (double MinTorque, double MaxTorque) ClampTorqueRangeWithPadding(double minTorque, double maxTorque) + { + (minTorque, maxTorque) = GetPaddedRange(minTorque, maxTorque, MinimumTorqueRange); + return ClampTorqueRange(minTorque, maxTorque); } private static Point ToPlotPoint( - TorqueSamplePayload sample, + double time, + double value, Rect plot, - double minTorque, - double maxTorque, - double minSpeed, - double maxSpeed) + double minTime, + double maxTime, + double minValue, + double maxValue) { - double x = plot.Left + plot.Width * (sample.TorqueMilliNewtonMeters - minTorque) / (maxTorque - minTorque); - double y = plot.Bottom - (sample.SpeedRpm - minSpeed) / (maxSpeed - minSpeed) * plot.Height; + double x = plot.Left + plot.Width * (time - minTime) / (maxTime - minTime); + double y = plot.Bottom - (value - minValue) / (maxValue - minValue) * plot.Height; return new Point(x, y); } - private static Point ToPlotPoint( - double torque, - double speed, - Rect plot, - double minTorque, - double maxTorque, - double minSpeed, - double maxSpeed) + private static List DownsampleForDrawing(List samples, double plotWidth) { - double x = plot.Left + plot.Width * (torque - minTorque) / (maxTorque - minTorque); - double y = plot.Bottom - (speed - minSpeed) / (maxSpeed - minSpeed) * plot.Height; - return new Point(x, y); + int maximumPoints = Math.Max(100, (int)Math.Ceiling(plotWidth * 2)); + if (samples.Count <= maximumPoints) + { + return samples; + } + + int step = (int)Math.Ceiling((double)samples.Count / maximumPoints); + var result = new List(maximumPoints + 1); + for (int i = 0; i < samples.Count; i += step) + { + result.Add(samples[i]); + } + + if (!ReferenceEquals(result[^1], samples[^1])) + { + result.Add(samples[^1]); + } + + return result; } - private static List BuildTrendPoints(IReadOnlyList samples) - { - return samples - .Where(sample => sample.TorqueMilliNewtonMeters >= SensorMinTorque - && sample.TorqueMilliNewtonMeters <= SensorMaxTorque) - .GroupBy(sample => Math.Floor(sample.TorqueMilliNewtonMeters / TorqueTrendBinSize)) - .Select(group => new TorqueTrendPoint( - group.Average(sample => sample.TorqueMilliNewtonMeters), - group.Average(sample => sample.SpeedRpm))) - .OrderBy(point => point.Torque) - .ToList(); - } - - private static void DrawTrendLine(DrawingContext drawingContext, IReadOnlyList points) + private static void DrawLine(DrawingContext drawingContext, IReadOnlyList points, Color color) { if (points.Count < 2) { @@ -377,80 +387,55 @@ public sealed class TorqueTrendControl : FrameworkElement context.BeginFigure(points[0], false, false); for (int i = 1; i < points.Count; i++) { - Point previous = points[i - 1]; - Point current = points[i]; - double deltaX = current.X - previous.X; - var control1 = new Point(previous.X + deltaX / 3, previous.Y); - var control2 = new Point(current.X - deltaX / 3, current.Y); - context.BezierTo(control1, control2, current, true, false); + context.LineTo(points[i], true, false); } } geometry.Freeze(); - var lineBrush = new SolidColorBrush(Color.FromRgb(15, 118, 110)); - drawingContext.DrawGeometry(null, new Pen(lineBrush, 2.4), geometry); + drawingContext.DrawGeometry(null, new Pen(new SolidColorBrush(color), 2.3), geometry); } - private static void DrawSamplePoints(DrawingContext drawingContext, IReadOnlyList points) + private static void DrawCurrentPoint(DrawingContext drawingContext, Point point, Color color) { - var pointBrush = new SolidColorBrush(Color.FromArgb(175, 29, 78, 216)); - Point? lastDrawn = null; - foreach (Point point in points) - { - if (lastDrawn is Point previous - && Math.Abs(point.X - previous.X) < 1.5 - && Math.Abs(point.Y - previous.Y) < 1.5) - { - continue; - } - - drawingContext.DrawEllipse(pointBrush, null, point, 2.2, 2.2); - lastDrawn = point; - } + var brush = new SolidColorBrush(color); + drawingContext.DrawEllipse(brush, new Pen(Brushes.White, 1.5), point, 4.5, 4.5); } - private static void DrawCurrentPoint(DrawingContext drawingContext, Point point) + private void DrawLegend(DrawingContext drawingContext, Rect plot) { - var brush = new SolidColorBrush(Color.FromRgb(15, 118, 110)); - drawingContext.DrawEllipse(brush, new Pen(Brushes.White, 1.5), point, 5, 5); - } - - private void DrawLegend(DrawingContext drawingContext, Rect plot, bool hasTrend) - { - double y = plot.Bottom + 5; - var sampleBrush = new SolidColorBrush(Color.FromArgb(175, 29, 78, 216)); - drawingContext.DrawEllipse(sampleBrush, null, new Point(plot.Left + 116, y + 7), 2.5, 2.5); - DrawText(drawingContext, "原始采样点", 10, Color.FromRgb(82, 97, 111), new Point(plot.Left + 122, y)); - - if (!hasTrend) - { - return; - } - - var trendPen = new Pen(new SolidColorBrush(Color.FromRgb(15, 118, 110)), 2.4); - drawingContext.DrawLine(trendPen, new Point(plot.Left + 194, y + 7), new Point(plot.Left + 214, y + 7)); - DrawText(drawingContext, "显示趋势线", 10, Color.FromRgb(82, 97, 111), new Point(plot.Left + 220, y)); + double y = plot.Bottom + 7; + var torquePen = new Pen(new SolidColorBrush(TorqueColor), 2.3); + var speedPen = new Pen(new SolidColorBrush(SpeedColor), 2.3); + drawingContext.DrawLine(torquePen, new Point(plot.Left + 82, y + 6), new Point(plot.Left + 104, y + 6)); + DrawText(drawingContext, "扭矩", 10, TorqueColor, new Point(plot.Left + 110, y)); + drawingContext.DrawLine(speedPen, new Point(plot.Left + 156, y + 6), new Point(plot.Left + 178, y + 6)); + DrawText(drawingContext, "转速", 10, SpeedColor, new Point(plot.Left + 184, y)); } private void DrawAxisLabels( DrawingContext drawingContext, Rect plot, + double minTime, + double maxTime, double minTorque, double maxTorque, double minSpeed, double maxSpeed) { - DrawText(drawingContext, maxSpeed.ToString("0", CultureInfo.InvariantCulture), 11, Color.FromRgb(82, 97, 111), new Point(5, plot.Top - 2)); - DrawText(drawingContext, minSpeed.ToString("0", CultureInfo.InvariantCulture), 11, Color.FromRgb(82, 97, 111), new Point(5, plot.Bottom - 14)); - DrawText(drawingContext, minTorque.ToString("0.##", CultureInfo.InvariantCulture), 11, Color.FromRgb(82, 97, 111), new Point(plot.Left, plot.Bottom + 5)); - DrawText(drawingContext, maxTorque.ToString("0.##", CultureInfo.InvariantCulture), 11, Color.FromRgb(82, 97, 111), new Point(plot.Right - 34, plot.Bottom + 5)); + DrawText(drawingContext, maxTorque.ToString("0.##", CultureInfo.InvariantCulture), 11, TorqueColor, new Point(3, plot.Top - 2)); + DrawText(drawingContext, minTorque.ToString("0.##", CultureInfo.InvariantCulture), 11, TorqueColor, new Point(3, plot.Bottom - 14)); + DrawText(drawingContext, maxSpeed.ToString("0", CultureInfo.InvariantCulture), 11, SpeedColor, new Point(plot.Right + 7, plot.Top - 2)); + DrawText(drawingContext, minSpeed.ToString("0", CultureInfo.InvariantCulture), 11, SpeedColor, new Point(plot.Right + 7, plot.Bottom - 14)); + DrawText(drawingContext, $"{minTime:0.0}", 11, Color.FromRgb(82, 97, 111), new Point(plot.Left, plot.Bottom + 7)); + DrawText(drawingContext, $"{maxTime:0.0}", 11, Color.FromRgb(82, 97, 111), new Point(plot.Right - 30, plot.Bottom + 7)); DrawAxisTitles(drawingContext, plot); } private void DrawAxisTitles(DrawingContext drawingContext, Rect plot) { - DrawText(drawingContext, $"转速 ({SpeedUnit})", 11, Color.FromRgb(82, 97, 111), new Point(plot.Left + 4, plot.Top + 22)); - DrawText(drawingContext, $"扭矩 ({TorqueUnit})", 11, Color.FromRgb(82, 97, 111), new Point(plot.Right - 82, plot.Bottom - 18)); + DrawText(drawingContext, $"扭矩 ({TorqueUnit})", 10, TorqueColor, new Point(plot.Left + 4, plot.Top + 22)); + DrawText(drawingContext, $"转速 ({SpeedUnit})", 10, SpeedColor, new Point(plot.Right - 78, plot.Top + 22)); + DrawText(drawingContext, "时间 (s)", 10, Color.FromRgb(82, 97, 111), new Point(plot.Right - 52, plot.Bottom - 18)); } private void Zoom(double factor, Point origin) @@ -461,19 +446,14 @@ public sealed class TorqueTrendControl : FrameworkElement return; } - EnsureManualView(); + List samples = ReadSamples(); + EnsureManualView(samples); double originX = Math.Clamp((origin.X - plot.Left) / plot.Width, 0, 1); double originY = Math.Clamp((plot.Bottom - origin.Y) / plot.Height, 0, 1); - double torqueAtOrigin = _viewMinTorque + (_viewMaxTorque - _viewMinTorque) * originX; - double speedAtOrigin = _viewMinSpeed + (_viewMaxSpeed - _viewMinSpeed) * originY; - double torqueRange = Math.Max(MinimumTorqueRange, (_viewMaxTorque - _viewMinTorque) * factor); - double speedRange = Math.Max(MinimumSpeedRange, (_viewMaxSpeed - _viewMinSpeed) * factor); - - _viewMinTorque = torqueAtOrigin - torqueRange * originX; - _viewMaxTorque = _viewMinTorque + torqueRange; - _viewMinSpeed = speedAtOrigin - speedRange * originY; - _viewMaxSpeed = _viewMinSpeed + speedRange; - ClampView(); + (_viewMinTime, _viewMaxTime) = ScaleRange(_viewMinTime, _viewMaxTime, factor, originX, MinimumTimeRange); + (_viewMinTorque, _viewMaxTorque) = ScaleRange(_viewMinTorque, _viewMaxTorque, factor, originY, MinimumTorqueRange); + (_viewMinSpeed, _viewMaxSpeed) = ScaleRange(_viewMinSpeed, _viewMaxSpeed, factor, originY, MinimumSpeedRange); + ClampView(samples); InvalidateVisual(); } @@ -485,34 +465,58 @@ public sealed class TorqueTrendControl : FrameworkElement return; } - EnsureManualView(); - double torqueShift = -deltaX / plot.Width * (_viewMaxTorque - _viewMinTorque); + List samples = ReadSamples(); + EnsureManualView(samples); + double timeShift = -deltaX / plot.Width * (_viewMaxTime - _viewMinTime); + double torqueShift = deltaY / plot.Height * (_viewMaxTorque - _viewMinTorque); double speedShift = deltaY / plot.Height * (_viewMaxSpeed - _viewMinSpeed); + _viewMinTime += timeShift; + _viewMaxTime += timeShift; _viewMinTorque += torqueShift; _viewMaxTorque += torqueShift; _viewMinSpeed += speedShift; _viewMaxSpeed += speedShift; - ClampView(); + ClampView(samples); InvalidateVisual(); } - private void EnsureManualView() + private static (double Min, double Max) ScaleRange(double min, double max, double factor, double originRatio, double minimumRange) + { + double valueAtOrigin = min + (max - min) * originRatio; + double range = Math.Max(minimumRange, (max - min) * factor); + double newMin = valueAtOrigin - range * originRatio; + return (newMin, newMin + range); + } + + private void EnsureManualView(List samples) { if (_isManualView) { return; } - List samples = ReadSamples(); - (_viewMinTorque, _viewMaxTorque) = samples.Count == 0 - ? (SensorMinTorque, SensorMaxTorque) - : GetAutoTorqueRange(samples); - (_viewMinSpeed, _viewMaxSpeed) = samples.Count == 0 ? (0, 1) : GetAutoSpeedRange(samples); + if (samples.Count == 0) + { + (_viewMinTime, _viewMaxTime) = (0, 1); + (_viewMinTorque, _viewMaxTorque) = (SensorMinTorque, MinimumTorqueRange); + (_viewMinSpeed, _viewMaxSpeed) = (0, MinimumSpeedRange); + } + else + { + (_viewMinTime, _viewMaxTime) = GetAutoTimeRange(samples); + (_viewMinTorque, _viewMaxTorque) = GetAutoTorqueRange(samples); + (_viewMinSpeed, _viewMaxSpeed) = GetAutoSpeedRange(samples); + } + _isManualView = true; } - private void ClampView() + private void ClampView(List samples) { + double dataMaxTime = samples.Count == 0 ? 1 : Math.Max(MinimumTimeRange, samples.Max(sample => sample.ElapsedSeconds)); + double timeRange = Math.Clamp(_viewMaxTime - _viewMinTime, MinimumTimeRange, dataMaxTime); + _viewMinTime = Math.Clamp(_viewMinTime, 0, dataMaxTime - timeRange); + _viewMaxTime = _viewMinTime + timeRange; (_viewMinTorque, _viewMaxTorque) = ClampTorqueRange(_viewMinTorque, _viewMaxTorque); if (!double.IsFinite(_viewMinSpeed) @@ -559,10 +563,6 @@ public sealed class TorqueTrendControl : FrameworkElement { values.Add(torqueSample); } - else if (sample is double value && double.IsFinite(value)) - { - values.Add(new TorqueSamplePayload { ElapsedSeconds = values.Count, SpeedRpm = values.Count, TorqueMilliNewtonMeters = value }); - } } return values; @@ -581,6 +581,4 @@ public sealed class TorqueTrendControl : FrameworkElement drawingContext.DrawText(formattedText, origin); } - - private readonly record struct TorqueTrendPoint(double Torque, double Speed); }