This commit is contained in:
GukSang.Jin
2026-05-12 18:00:57 +08:00
parent 9f205264e7
commit 6f4a730e86
3 changed files with 119 additions and 23 deletions

View File

@@ -17,11 +17,13 @@ public partial class MainWindow : Window
{
InitializeComponent();
DataContext = _viewModel;
_viewModel.RealtimeChartInvalidated += ViewModel_RealtimeChartInvalidated;
QueueRealtimeChartLayoutRefresh();
}
protected override void OnClosed(EventArgs e)
{
_viewModel.RealtimeChartInvalidated -= ViewModel_RealtimeChartInvalidated;
_viewModel.Dispose();
base.OnClosed(e);
}
@@ -41,6 +43,7 @@ public partial class MainWindow : Window
private void ShowRealtimeTab_Click(object sender, RoutedEventArgs e)
{
MainWorkspaceTabs.SelectedIndex = 0;
QueueRealtimeChartLayoutRefresh();
}
private void ShowHistoryTab_Click(object sender, RoutedEventArgs e)
@@ -63,6 +66,17 @@ public partial class MainWindow : Window
QueueRealtimeChartLayoutRefresh();
}
private void ViewModel_RealtimeChartInvalidated(object? sender, EventArgs e)
{
if (!Dispatcher.CheckAccess())
{
Dispatcher.BeginInvoke(new Action(QueueRealtimeChartLayoutRefresh), DispatcherPriority.Render);
return;
}
QueueRealtimeChartLayoutRefresh();
}
private void TableMotionButton_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (sender is not Button button || button.Tag is not string direction)

View File

@@ -354,7 +354,7 @@ public sealed class RunExportService
worksheet.Cell(row, 3).Value = runs[index].CurveColorHex;
worksheet.Cell(row, 4).Value = runs[index].ValidSamples.Count;
worksheet.Cell(row, 5).Value = IsChartableRun(runs[index])
? "已绘制"
? "已绘制总览与单图"
: "采样点不足,未绘制曲线";
worksheet.Cell(row, 3).Style.Fill.BackgroundColor = ToXlColor(runs[index].CurveColorHex);
worksheet.Cell(row, 3).Style.Font.FontColor = XLColor.White;
@@ -445,11 +445,16 @@ public sealed class RunExportService
var titleRef = BuildCellReference(sheetName, startRow, xColumn);
var startDataRow = startRow + 2;
var endDataRow = Math.Max(startDataRow, currentRow - 1);
var xValues = item.ValidSamples.Select(sample => sample.DisplacementMm).ToArray();
var yValues = item.ValidSamples.Select(sample => sample.ForceN).ToArray();
var layout = new SeriesLayout(
titleRef,
BuildRangeReference(sheetName, startDataRow, xColumn, endDataRow, xColumn),
BuildRangeReference(sheetName, startDataRow, yColumn, endDataRow, yColumn),
item.CurveColorHex);
item.CurveColorHex,
BuildCurveLabel(item),
xValues,
yValues);
if (IsChartableRun(item))
{
@@ -458,10 +463,26 @@ public sealed class RunExportService
}
var charts = new List<ChartDefinition>();
var perRunStartRow = Math.Max(10, runs.Count + 8);
var overviewStartRow = Math.Max(10, runs.Count + 8);
const int overviewChartHeight = 13;
const int chartHeight = 11;
const int chartWidth = 19;
const int rowGap = 2;
var perRunStartRow = overviewStartRow;
if (perRunSeries.Count > 0)
{
charts.Add(new ChartDefinition(
"历史实时摩擦曲线总览",
"位移 (mm)",
"力值 (N)",
true,
perRunSeries.Select(item => item.Series).ToArray(),
new ChartAnchor(0, overviewStartRow, chartWidth, overviewStartRow + overviewChartHeight),
"历史实时摩擦曲线总览"));
perRunStartRow = overviewStartRow + overviewChartHeight + rowGap;
}
for (var index = 0; index < perRunSeries.Count; index++)
{
@@ -492,7 +513,8 @@ public sealed class RunExportService
private static int CalculateChartSheetLastRow(IReadOnlyList<ExportCurveData> runs)
{
var chartCount = runs.Count(IsChartableRun);
return Math.Max(24, runs.Count + 22 + chartCount * 13);
var renderedChartCount = chartCount == 0 ? 0 : chartCount + 1;
return Math.Max(24, runs.Count + 22 + renderedChartCount * 13);
}
private static void ApplyDefaultWorksheetStyle(IXLWorksheet worksheet)
@@ -695,11 +717,11 @@ public sealed class RunExportService
var scatterSeries = new C.ScatterChartSeries(
new C.Index { Val = (uint)index },
new C.Order { Val = (uint)index },
new C.SeriesText(new C.StringReference(new C.Formula(series.TitleReference))),
new C.SeriesText(CreateStringReference(series.TitleReference, series.TitleText)),
chartShape,
new C.Marker(new C.Symbol { Val = C.MarkerStyleValues.None }),
new C.XValues(new C.NumberReference(new C.Formula(series.XValuesReference))),
new C.YValues(new C.NumberReference(new C.Formula(series.YValuesReference))),
new C.XValues(CreateNumberReference(series.XValuesReference, series.XValues)),
new C.YValues(CreateNumberReference(series.YValuesReference, series.YValues)),
new C.Smooth { Val = false });
scatterChart.Append(scatterSeries);
@@ -754,6 +776,28 @@ public sealed class RunExportService
chartPart.ChartSpace.Save();
}
private static C.StringReference CreateStringReference(string formula, string value)
{
var stringCache = new C.StringCache(new C.PointCount { Val = 1U });
stringCache.Append(new C.StringPoint(new C.NumericValue(value)) { Index = 0U });
return new C.StringReference(new C.Formula(formula), stringCache);
}
private static C.NumberReference CreateNumberReference(string formula, IReadOnlyList<double> values)
{
var numberingCache = new C.NumberingCache(new C.FormatCode("General"), new C.PointCount { Val = (uint)values.Count });
for (var index = 0; index < values.Count; index++)
{
numberingCache.Append(new C.NumericPoint(
new C.NumericValue(values[index].ToString("G17", CultureInfo.InvariantCulture)))
{
Index = (uint)index
});
}
return new C.NumberReference(new C.Formula(formula), numberingCache);
}
private static void AppendChartAnchor(Xdr.WorksheetDrawing worksheetDrawing, string chartRelationshipId, ChartAnchor anchor, string shapeName)
{
var graphicFrameId = worksheetDrawing.Descendants<Xdr.NonVisualDrawingProperties>()
@@ -952,7 +996,14 @@ public sealed class RunExportService
int ExcludedRunCount,
int ValidSampleCount);
private sealed record SeriesLayout(string TitleReference, string XValuesReference, string YValuesReference, string ColorHex);
private sealed record SeriesLayout(
string TitleReference,
string XValuesReference,
string YValuesReference,
string ColorHex,
string TitleText,
IReadOnlyList<double> XValues,
IReadOnlyList<double> YValues);
private sealed record PerRunSeriesLayout(ExportCurveData Data, SeriesLayout Series);

View File

@@ -258,6 +258,8 @@ public sealed class MainViewModel : ObservableObject, IDisposable
_ = InitializeDeviceConnectionAsync();
}
public event EventHandler? RealtimeChartInvalidated;
public string StandardVersion => "静摩擦与动摩擦系数工控界面";
public TestRecipe Recipe { get; }
@@ -735,12 +737,9 @@ public sealed class MainViewModel : ObservableObject, IDisposable
_activeRecipeSnapshot = TestRecipeSnapshot.FromRecipe(Recipe);
_activeRunStartedByPlc = startedByPlc;
_displayedRecipeSnapshot = _activeRecipeSnapshot;
_isShowingHistoricalRun = false;
ClearHistoricalSelectionForLiveRun();
_currentRunSamples.Clear();
_forceSamples.Clear();
_peakLineSamples.Clear();
_averageLineSamples.Clear();
_currentPointSample.Clear();
ClearRealtimeChartSamples();
ResetRealtimeSamplingMetrics();
CurrentForceN = 0;
CurrentDisplacementMm = 0;
@@ -763,13 +762,15 @@ public sealed class MainViewModel : ObservableObject, IDisposable
_kineticBand.Xj = Recipe.TravelMm;
_kineticBand.Yi = 0;
_kineticBand.Yj = 1;
SetAxisLimits(GetEmptyChartXAxisMaxLimit(), EmptyChartYAxisMaxLimit);
NotifyRealtimeChartChanged();
_deviceDataReader.Initialize(Recipe);
AddInfoEvent($"批次 {Recipe.BatchNumber} 第 {NextRunIndex} 轮开始。模式={Recipe.TestMode}, 水平速度={Recipe.SpeedMmPerMin:F0} mm/min");
}
else if (isRecoveringActiveRun)
{
_activeRunStartedByPlc |= startedByPlc;
_isShowingHistoricalRun = false;
ClearHistoricalSelectionForLiveRun();
_displayedRecipeSnapshot = _activeRecipeSnapshot;
AddInfoEvent($"试验通信恢复后继续运行,已保留 {_currentRunSamples.Count} 个采样点。");
}
@@ -785,6 +786,7 @@ public sealed class MainViewModel : ObservableObject, IDisposable
InterlockMessage = "采样进行中。实时监控力值与摩擦系数。";
_timer.Start();
RaiseStatusProperties();
NotifyRealtimeChartChanged();
}
private async void Pause()
@@ -1078,6 +1080,8 @@ public sealed class MainViewModel : ObservableObject, IDisposable
_exportReportCommand.RaiseCanExecuteChanged();
}
NotifyRealtimeChartChanged();
return true;
}
@@ -1206,6 +1210,7 @@ public sealed class MainViewModel : ObservableObject, IDisposable
ForceYAxes[0].MaxLimit = yMax;
OnPropertyChanged(nameof(ForceXAxes));
OnPropertyChanged(nameof(ForceYAxes));
NotifyRealtimeChartChanged();
}
private static bool ShouldUpdateAxisLimit(double currentLimit, double nextLimit)
@@ -1324,6 +1329,35 @@ public sealed class MainViewModel : ObservableObject, IDisposable
_lastRealtimeChartRefreshAt = DateTime.MinValue;
}
private void ClearRealtimeChartSamples()
{
_forceSamples.Clear();
_peakLineSamples.Clear();
_averageLineSamples.Clear();
_currentPointSample.Clear();
OnPropertyChanged(nameof(ForceSeries));
}
private void ClearHistoricalSelectionForLiveRun()
{
_isShowingHistoricalRun = false;
if (_selectedRunRecord is null)
{
return;
}
_selectedRunRecord = null;
OnPropertyChanged(nameof(SelectedRunRecord));
OnPropertyChanged(nameof(ExportReportSummary));
OnPropertyChanged(nameof(AnalysisModeSummary));
RaiseCommandStates();
}
private void NotifyRealtimeChartChanged()
{
RealtimeChartInvalidated?.Invoke(this, EventArgs.Empty);
}
private TestRecipeSnapshot GetDisplayRecipeSnapshot()
{
return _isShowingHistoricalRun && _displayedRecipeSnapshot is not null
@@ -1652,7 +1686,7 @@ public sealed class MainViewModel : ObservableObject, IDisposable
private void LoadSamplesIntoChart(IReadOnlyList<RawSampleRecord> samples)
{
_forceSamples.Clear();
ClearRealtimeChartSamples();
foreach (var sample in samples)
{
_forceSamples.Add(new ObservablePoint(sample.DisplacementMm, sample.ForceN));
@@ -1667,14 +1701,13 @@ public sealed class MainViewModel : ObservableObject, IDisposable
{
ShowEmptyRealtimeChartFrame();
}
NotifyRealtimeChartChanged();
}
private void ShowEmptyRealtimeChartFrame()
{
_forceSamples.Clear();
_peakLineSamples.Clear();
_averageLineSamples.Clear();
_currentPointSample.Clear();
ClearRealtimeChartSamples();
_currentPointSample.Add(new ObservablePoint(0, EmptyChartPointForceN));
SetAxisLimits(GetEmptyChartXAxisMaxLimit(), EmptyChartYAxisMaxLimit);
_kineticBand.Xi = Recipe.TravelMm * 0.35;
@@ -1682,6 +1715,7 @@ public sealed class MainViewModel : ObservableObject, IDisposable
_kineticBand.Yi = 0;
_kineticBand.Yj = EmptyChartYAxisMaxLimit;
OnPropertyChanged(nameof(ForceSections));
NotifyRealtimeChartChanged();
}
private void RefreshEmptyRealtimeChartFrameIfVisible()
@@ -1704,10 +1738,7 @@ public sealed class MainViewModel : ObservableObject, IDisposable
private void ResetRealtimePresentation()
{
_forceSamples.Clear();
_peakLineSamples.Clear();
_averageLineSamples.Clear();
_currentPointSample.Clear();
ClearRealtimeChartSamples();
ResetRealtimeSamplingMetrics();
CurrentForceN = 0;