更新12123
This commit is contained in:
@@ -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<TorqueSamplePayload> samples,
|
||||
Rect plot,
|
||||
double minTime,
|
||||
double maxTime,
|
||||
double minValue,
|
||||
double maxValue,
|
||||
Func<TorqueSamplePayload, double> 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<TorqueSamplePayload> 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<TorqueSamplePayload> 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<ushort, bool> coilValues,
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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<TorqueSamplePayload> visibleSamples = DownsampleForDrawing(
|
||||
samples.Where(sample => sample.ElapsedSeconds >= minTime && sample.ElapsedSeconds <= maxTime).ToList(),
|
||||
plot.Width);
|
||||
List<Point> torquePoints = visibleSamples
|
||||
.Select(sample => ToPlotPoint(sample.ElapsedSeconds, sample.TorqueMilliNewtonMeters, plot, minTime, maxTime, minTorque, maxTorque))
|
||||
.ToList();
|
||||
List<Point> speedPoints = visibleSamples
|
||||
.Select(sample => ToPlotPoint(sample.ElapsedSeconds, sample.SpeedRpm, plot, minTime, maxTime, minSpeed, maxSpeed))
|
||||
.ToList();
|
||||
List<TorqueTrendPoint> 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<TorqueSamplePayload> samples)
|
||||
private (double MinTime, double MaxTime, double MinTorque, double MaxTorque, double MinSpeed, double MaxSpeed) GetVisibleRanges(List<TorqueSamplePayload> 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<TorqueSamplePayload> samples)
|
||||
{
|
||||
double maxTime = Math.Max(MinimumTimeRange, samples.Max(sample => sample.ElapsedSeconds));
|
||||
return (0, maxTime);
|
||||
}
|
||||
|
||||
private static (double MinTorque, double MaxTorque) GetAutoTorqueRange(List<TorqueSamplePayload> 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<TorqueSamplePayload> 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<TorqueSamplePayload> DownsampleForDrawing(List<TorqueSamplePayload> 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<TorqueSamplePayload>(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<TorqueTrendPoint> BuildTrendPoints(IReadOnlyList<TorqueSamplePayload> 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<Point> points)
|
||||
private static void DrawLine(DrawingContext drawingContext, IReadOnlyList<Point> 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<Point> 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<TorqueSamplePayload> 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<TorqueSamplePayload> 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<TorqueSamplePayload> samples)
|
||||
{
|
||||
if (_isManualView)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<TorqueSamplePayload> 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<TorqueSamplePayload> 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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user