更新
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user