diff --git a/DentistryHandpieces/TorqueTrendControl.cs b/DentistryHandpieces/TorqueTrendControl.cs index ff8d183..a4f59ea 100644 --- a/DentistryHandpieces/TorqueTrendControl.cs +++ b/DentistryHandpieces/TorqueTrendControl.cs @@ -22,6 +22,7 @@ public sealed class TorqueTrendControl : FrameworkElement private const double SensorMaxTorque = 100; private const double MinimumTorqueRange = 0.1; private const double MinimumSpeedRange = 1; + private const double TorqueTrendBinSize = 0.1; private bool _isManualView; private double _viewMinTorque = SensorMinTorque; @@ -135,10 +136,15 @@ public sealed class TorqueTrendControl : FrameworkElement var points = samples .Select(sample => ToPlotPoint(sample, plot, minTorque, maxTorque, minSpeed, maxSpeed)) .ToList(); + List trendPoints = BuildTrendPoints(samples); drawingContext.PushClip(new RectangleGeometry(plot)); - DrawTrajectory(drawingContext, points); DrawSamplePoints(drawingContext, points); + DrawTrendLine( + drawingContext, + trendPoints + .Select(point => ToPlotPoint(point.Torque, point.Speed, plot, minTorque, maxTorque, minSpeed, maxSpeed)) + .ToList()); DrawCurrentPoint(drawingContext, points[^1]); drawingContext.Pop(); @@ -149,6 +155,16 @@ public sealed class TorqueTrendControl : FrameworkElement 12, Color.FromRgb(15, 118, 110), 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)); + } } protected override void OnMouseWheel(MouseWheelEventArgs e) @@ -297,27 +313,58 @@ public sealed class TorqueTrendControl : FrameworkElement return new Point(x, y); } - private static void DrawTrajectory(DrawingContext drawingContext, IReadOnlyList points) + private static Point ToPlotPoint( + double torque, + double speed, + Rect plot, + double minTorque, + double maxTorque, + double minSpeed, + double maxSpeed) { + 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); + } + + 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) + { + if (points.Count < 2) + { + return; + } + var geometry = new StreamGeometry(); using (StreamGeometryContext context = geometry.Open()) { - for (int i = 0; i < points.Count; i++) + context.BeginFigure(points[0], false, false); + for (int i = 1; i < points.Count; i++) { - if (i == 0) - { - context.BeginFigure(points[i], false, false); - } - else - { - context.LineTo(points[i], true, false); - } + 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); } } geometry.Freeze(); - var lineBrush = new SolidColorBrush(Color.FromArgb(85, 29, 78, 216)); - drawingContext.DrawGeometry(null, new Pen(lineBrush, 1.2), geometry); + var lineBrush = new SolidColorBrush(Color.FromRgb(15, 118, 110)); + drawingContext.DrawGeometry(null, new Pen(lineBrush, 2.4), geometry); } private static void DrawSamplePoints(DrawingContext drawingContext, IReadOnlyList points) @@ -344,6 +391,23 @@ public sealed class TorqueTrendControl : FrameworkElement 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)); + } + private void DrawAxisLabels( DrawingContext drawingContext, Rect plot, @@ -485,4 +549,6 @@ public sealed class TorqueTrendControl : FrameworkElement drawingContext.DrawText(formattedText, origin); } + + private readonly record struct TorqueTrendPoint(double Torque, double Speed); }