更新20260515
This commit is contained in:
@@ -251,31 +251,96 @@
|
||||
<RowDefinition Height="300" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<ScrollViewer x:Name="RealtimeChartScrollViewer"
|
||||
Grid.Row="0"
|
||||
Margin="0,0,0,8"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Disabled"
|
||||
CanContentScroll="False"
|
||||
Loaded="RealtimeChartScrollViewer_Loaded"
|
||||
ScrollChanged="RealtimeChartScrollViewer_ScrollChanged">
|
||||
<controls:RealtimeForceChart x:Name="RealtimeForceChart"
|
||||
Width="{Binding RealtimeChartCanvasWidth}"
|
||||
MinWidth="{Binding ViewportWidth, ElementName=RealtimeChartScrollViewer}"
|
||||
Height="{Binding ViewportHeight, ElementName=RealtimeChartScrollViewer}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Stretch"
|
||||
Samples="{Binding RealtimeForceSamples}"
|
||||
PeakLineSamples="{Binding RealtimePeakLineSamples}"
|
||||
AverageLineSamples="{Binding RealtimeAverageLineSamples}"
|
||||
CurrentPointSamples="{Binding RealtimeCurrentPointSamples}"
|
||||
XMax="{Binding RealtimeChartXMax}"
|
||||
YMax="{Binding RealtimeChartYMax}"
|
||||
TravelMm="{Binding Recipe.TravelMm}"
|
||||
SledMassGrams="{Binding Recipe.SledMassGrams}"
|
||||
Loaded="RealtimeForceChart_Loaded"
|
||||
SizeChanged="RealtimeForceChart_SizeChanged" />
|
||||
</ScrollViewer>
|
||||
<Grid x:Name="RealtimeChartHost"
|
||||
Grid.Row="0"
|
||||
Margin="0,0,0,8"
|
||||
PreviewMouseWheel="RealtimeChartHost_PreviewMouseWheel">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Border Grid.Row="0" Style="{StaticResource PanelBorderStyle}" Padding="8" Margin="0,0,0,6">
|
||||
<DockPanel LastChildFill="False">
|
||||
<TextBlock DockPanel.Dock="Left"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource CaptionStyle}"
|
||||
Text="曲线缩放:Ctrl + 鼠标滚轮,或使用右侧按钮" />
|
||||
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Margin="0,0,12,4"
|
||||
Style="{StaticResource CaptionStyle}"
|
||||
Text="{Binding RealtimeChartZoomText}" />
|
||||
<Button Width="70"
|
||||
Margin="0,0,6,0"
|
||||
Style="{StaticResource CompactButtonStyle}"
|
||||
Command="{Binding RealtimeChartZoomOutCommand}"
|
||||
Content="缩小" />
|
||||
<Button Width="70"
|
||||
Margin="0,0,6,0"
|
||||
Style="{StaticResource CompactButtonStyle}"
|
||||
Command="{Binding RealtimeChartZoomInCommand}"
|
||||
Content="放大" />
|
||||
<Button Width="70"
|
||||
Margin="0"
|
||||
Style="{StaticResource CompactButtonStyle}"
|
||||
Command="{Binding RealtimeChartResetZoomCommand}"
|
||||
Content="重置" />
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="128" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="158" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<controls:RealtimeForceChart Grid.Column="0"
|
||||
ChartPart="LeftAxis"
|
||||
VerticalAlignment="Stretch"
|
||||
Samples="{Binding RealtimeForceSamples}"
|
||||
XMax="{Binding RealtimeChartXMax}"
|
||||
YMax="{Binding RealtimeChartYMax}"
|
||||
TravelMm="{Binding Recipe.TravelMm}"
|
||||
SledMassGrams="{Binding Recipe.SledMassGrams}" />
|
||||
|
||||
<ScrollViewer x:Name="RealtimeChartScrollViewer"
|
||||
Grid.Column="1"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Disabled"
|
||||
CanContentScroll="False"
|
||||
Loaded="RealtimeChartScrollViewer_Loaded"
|
||||
ScrollChanged="RealtimeChartScrollViewer_ScrollChanged">
|
||||
<controls:RealtimeForceChart x:Name="RealtimeForceChart"
|
||||
ChartPart="Plot"
|
||||
Width="{Binding RealtimeChartCanvasWidth}"
|
||||
Height="{Binding ViewportHeight, ElementName=RealtimeChartScrollViewer}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Stretch"
|
||||
Samples="{Binding RealtimeForceSamples}"
|
||||
PeakLineSamples="{Binding RealtimePeakLineSamples}"
|
||||
AverageLineSamples="{Binding RealtimeAverageLineSamples}"
|
||||
CurrentPointSamples="{Binding RealtimeCurrentPointSamples}"
|
||||
XMax="{Binding RealtimeChartXMax}"
|
||||
YMax="{Binding RealtimeChartYMax}"
|
||||
TravelMm="{Binding Recipe.TravelMm}"
|
||||
SledMassGrams="{Binding Recipe.SledMassGrams}"
|
||||
Loaded="RealtimeForceChart_Loaded"
|
||||
SizeChanged="RealtimeForceChart_SizeChanged" />
|
||||
</ScrollViewer>
|
||||
|
||||
<controls:RealtimeForceChart Grid.Column="2"
|
||||
ChartPart="RightAxis"
|
||||
VerticalAlignment="Stretch"
|
||||
Samples="{Binding RealtimeForceSamples}"
|
||||
XMax="{Binding RealtimeChartXMax}"
|
||||
YMax="{Binding RealtimeChartYMax}"
|
||||
TravelMm="{Binding Recipe.TravelMm}"
|
||||
SledMassGrams="{Binding Recipe.SledMassGrams}" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Border Grid.Row="1" Style="{StaticResource PanelBorderStyle}" Padding="10" Margin="0,0,0,8">
|
||||
<Grid>
|
||||
@@ -601,8 +666,11 @@
|
||||
<DataGridTextColumn Header="轮次" Binding="{Binding RunIndex}" Width="0.7*" />
|
||||
<DataGridTextColumn Header="时间" Binding="{Binding CompletedAtLabel}" Width="1.2*" />
|
||||
<DataGridTextColumn Header="批次" Binding="{Binding BatchNumber}" Width="1.2*" />
|
||||
<DataGridTextColumn Header="统计范围" Binding="{Binding AverageScopeLabel}" Width="2.1*" />
|
||||
<DataGridTextColumn Header="COFs" Binding="{Binding StaticCoefficient, StringFormat={}{0:F3}}" Width="0.8*" />
|
||||
<DataGridTextColumn Header="COFk" Binding="{Binding KineticCoefficient, StringFormat={}{0:F3}}" Width="0.8*" />
|
||||
<DataGridTextColumn Header="Fs[N]" Binding="{Binding AverageStaticForceLabel}" Width="0.8*" />
|
||||
<DataGridTextColumn Header="Fk[N]" Binding="{Binding AverageKineticForceLabel}" Width="0.8*" />
|
||||
<DataGridTextColumn Header="模式" Binding="{Binding TestMode}" Width="1.1*" />
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
@@ -615,90 +683,20 @@
|
||||
<Border Grid.Column="1" Style="{StaticResource PanelBorderStyle}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="1.2*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<DockPanel Grid.Row="0" Margin="0,0,0,6">
|
||||
<TextBlock DockPanel.Dock="Left" Style="{StaticResource SectionTitleStyle}" Text="每次摩擦系数数据" />
|
||||
<TextBlock DockPanel.Dock="Right" Style="{StaticResource HintStyle}" Text="{Binding ReciprocatingRecordSummary, Mode=OneWay}" />
|
||||
</DockPanel>
|
||||
<ScrollViewer Grid.Row="1"
|
||||
Margin="0,0,0,10"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<ItemsControl ItemsSource="{Binding ReciprocatingRecordSections}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ItemsControl ItemsSource="{Binding Rows}" Margin="0,0,0,8">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid Height="22">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="58" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Grid.Column="0"
|
||||
Background="#D6EFC3"
|
||||
BorderBrush="#AEB69D"
|
||||
BorderThickness="1,1,1,0">
|
||||
<TextBlock HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="11"
|
||||
FontWeight="SemiBold"
|
||||
Text="{Binding Header}" />
|
||||
</Border>
|
||||
<ItemsControl Grid.Column="1"
|
||||
ItemsSource="{Binding Cells}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<UniformGrid Rows="1" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border x:Name="HistoryRecordCellBorder"
|
||||
Background="#FFFFFF"
|
||||
BorderBrush="#AEB69D"
|
||||
BorderThickness="0,1,1,0">
|
||||
<TextBlock x:Name="HistoryRecordCellText"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Margin="4,0,6,0"
|
||||
FontSize="11"
|
||||
Text="{Binding Value}" />
|
||||
</Border>
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding IsHeader}" Value="True">
|
||||
<Setter TargetName="HistoryRecordCellBorder" Property="Background" Value="#BDE7A2" />
|
||||
<Setter TargetName="HistoryRecordCellText" Property="HorizontalAlignment" Value="Center" />
|
||||
<Setter TargetName="HistoryRecordCellText" Property="FontWeight" Value="SemiBold" />
|
||||
<Setter TargetName="HistoryRecordCellText" Property="Margin" Value="0" />
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
|
||||
<TextBlock Grid.Row="2" Style="{StaticResource SectionTitleStyle}" Text="系统事件" />
|
||||
<DataGrid Grid.Row="3" ItemsSource="{Binding EventLog}">
|
||||
<TextBlock Grid.Row="0" Style="{StaticResource SectionTitleStyle}" Text="系统事件" />
|
||||
<DataGrid Grid.Row="1" ItemsSource="{Binding EventLog}">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="时间" Binding="{Binding TimestampLabel}" Width="1.1*" />
|
||||
<DataGridTextColumn Header="级别" Binding="{Binding LevelLabel}" Width="0.7*" />
|
||||
<DataGridTextColumn Header="内容" Binding="{Binding Message}" Width="2.5*" />
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
<Border Grid.Row="4" Style="{StaticResource InsetCardStyle}">
|
||||
<Border Grid.Row="2" Style="{StaticResource InsetCardStyle}">
|
||||
<StackPanel>
|
||||
<TextBlock FontWeight="SemiBold" TextWrapping="Wrap" Text="{Binding DeviceConnectionSummary, Mode=OneWay}" />
|
||||
<TextBlock Margin="0,6,0,0" Style="{StaticResource HintStyle}" Text="{Binding PlcCommandSummary, Mode=OneWay}" />
|
||||
|
||||
@@ -98,6 +98,17 @@ public partial class MainWindow : Window
|
||||
_isRealtimeChartAutoScrollPinned = IsRealtimeChartScrolledToEnd();
|
||||
}
|
||||
|
||||
private void RealtimeChartHost_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
|
||||
{
|
||||
if ((Keyboard.Modifiers & ModifierKeys.Control) != ModifierKeys.Control)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_viewModel.ZoomRealtimeChartFromWheel(e.Delta);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void RealtimeForceChart_SizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
if (e.NewSize.Width <= 0 || e.NewSize.Height <= 0)
|
||||
|
||||
@@ -260,20 +260,41 @@ public sealed class TestDataRepository
|
||||
command.CommandText =
|
||||
"""
|
||||
SELECT run_id, run_index, completed_at, batch_number, test_mode,
|
||||
COALESCE(NULLIF(static_coefficient, 0), (
|
||||
COALESCE((
|
||||
SELECT AVG(static_coefficient)
|
||||
FROM reciprocating_records
|
||||
WHERE run_id = test_runs.run_id AND static_coefficient IS NOT NULL
|
||||
), static_coefficient) AS static_coefficient,
|
||||
), NULLIF(static_coefficient, 0), static_coefficient) AS static_coefficient,
|
||||
static_coefficient_1, static_coefficient_2,
|
||||
COALESCE(NULLIF(kinetic_coefficient, 0), (
|
||||
COALESCE((
|
||||
SELECT AVG(kinetic_coefficient)
|
||||
FROM reciprocating_records
|
||||
WHERE run_id = test_runs.run_id AND kinetic_coefficient IS NOT NULL
|
||||
), kinetic_coefficient) AS kinetic_coefficient,
|
||||
), NULLIF(kinetic_coefficient, 0), kinetic_coefficient) AS kinetic_coefficient,
|
||||
kinetic_coefficient_1, kinetic_coefficient_2, standard_deviation,
|
||||
standard_deviation_1, standard_deviation_2, peak_force_n, average_force_n,
|
||||
judgement, csv_export_path, report_export_path, sample_count
|
||||
judgement, csv_export_path, report_export_path, sample_count,
|
||||
(
|
||||
SELECT AVG(static_force_n)
|
||||
FROM reciprocating_records
|
||||
WHERE run_id = test_runs.run_id AND static_force_n IS NOT NULL
|
||||
) AS average_static_force_n,
|
||||
(
|
||||
SELECT AVG(kinetic_force_n)
|
||||
FROM reciprocating_records
|
||||
WHERE run_id = test_runs.run_id AND kinetic_force_n IS NOT NULL
|
||||
) AS average_kinetic_force_n,
|
||||
COALESCE((
|
||||
SELECT MAX(record_index)
|
||||
FROM reciprocating_records
|
||||
WHERE run_id = test_runs.run_id
|
||||
AND (
|
||||
static_coefficient IS NOT NULL OR
|
||||
kinetic_coefficient IS NOT NULL OR
|
||||
static_force_n IS NOT NULL OR
|
||||
kinetic_force_n IS NOT NULL
|
||||
)
|
||||
), 0) AS reciprocating_average_end_index
|
||||
FROM test_runs
|
||||
ORDER BY completed_at DESC
|
||||
LIMIT $limit;
|
||||
@@ -302,22 +323,43 @@ public sealed class TestDataRepository
|
||||
counterface_material, direction, sled_mass_grams, lift_speed_mm_per_min,
|
||||
lift_displacement_mm, speed_mm_per_min, travel_mm, replicate_count,
|
||||
reciprocating_count, specimen_description,
|
||||
COALESCE(NULLIF(static_coefficient, 0), (
|
||||
COALESCE((
|
||||
SELECT AVG(static_coefficient)
|
||||
FROM reciprocating_records
|
||||
WHERE run_id = test_runs.run_id AND static_coefficient IS NOT NULL
|
||||
), static_coefficient) AS static_coefficient,
|
||||
), NULLIF(static_coefficient, 0), static_coefficient) AS static_coefficient,
|
||||
static_coefficient_1,
|
||||
static_coefficient_2,
|
||||
COALESCE(NULLIF(kinetic_coefficient, 0), (
|
||||
COALESCE((
|
||||
SELECT AVG(kinetic_coefficient)
|
||||
FROM reciprocating_records
|
||||
WHERE run_id = test_runs.run_id AND kinetic_coefficient IS NOT NULL
|
||||
), kinetic_coefficient) AS kinetic_coefficient,
|
||||
), NULLIF(kinetic_coefficient, 0), kinetic_coefficient) AS kinetic_coefficient,
|
||||
kinetic_coefficient_1,
|
||||
kinetic_coefficient_2, standard_deviation, standard_deviation_1, standard_deviation_2,
|
||||
peak_force_n, average_force_n, judgement, csv_export_path,
|
||||
report_export_path, sample_count
|
||||
report_export_path, sample_count,
|
||||
(
|
||||
SELECT AVG(static_force_n)
|
||||
FROM reciprocating_records
|
||||
WHERE run_id = test_runs.run_id AND static_force_n IS NOT NULL
|
||||
) AS average_static_force_n,
|
||||
(
|
||||
SELECT AVG(kinetic_force_n)
|
||||
FROM reciprocating_records
|
||||
WHERE run_id = test_runs.run_id AND kinetic_force_n IS NOT NULL
|
||||
) AS average_kinetic_force_n,
|
||||
COALESCE((
|
||||
SELECT MAX(record_index)
|
||||
FROM reciprocating_records
|
||||
WHERE run_id = test_runs.run_id
|
||||
AND (
|
||||
static_coefficient IS NOT NULL OR
|
||||
kinetic_coefficient IS NOT NULL OR
|
||||
static_force_n IS NOT NULL OR
|
||||
kinetic_force_n IS NOT NULL
|
||||
)
|
||||
), 0) AS reciprocating_average_end_index
|
||||
FROM test_runs
|
||||
WHERE run_id = $runId;
|
||||
""";
|
||||
@@ -350,7 +392,10 @@ public sealed class TestDataRepository
|
||||
Judgement = reader.GetString(27),
|
||||
CsvExportPath = reader.GetString(28),
|
||||
ReportExportPath = reader.GetString(29),
|
||||
SampleCount = reader.GetInt32(30)
|
||||
SampleCount = reader.GetInt32(30),
|
||||
AverageStaticForceN = ReadNullableDouble(reader, 31),
|
||||
AverageKineticForceN = ReadNullableDouble(reader, 32),
|
||||
ReciprocatingAverageEndIndex = reader.GetInt32(33)
|
||||
};
|
||||
var recipe = new TestRecipeSnapshot
|
||||
{
|
||||
@@ -813,7 +858,11 @@ public sealed class TestDataRepository
|
||||
Judgement = reader.GetString(16),
|
||||
CsvExportPath = reader.GetString(17),
|
||||
ReportExportPath = reader.GetString(18),
|
||||
SampleCount = reader.GetInt32(19)
|
||||
SampleCount = reader.GetInt32(19),
|
||||
AverageStaticForceN = ReadNullableDouble(reader, 20),
|
||||
AverageKineticForceN = ReadNullableDouble(reader, 21),
|
||||
ReciprocatingAverageEndIndex = reader.GetInt32(22)
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1071,6 +1071,13 @@ public sealed class MainViewModel : ObservableObject, IDisposable
|
||||
var finalStandardDeviation = ResolveCompletedMeasurementValue(
|
||||
ResolveRepresentativeValue(GetActiveReplicateCount(), StandardDeviation1, StandardDeviation2),
|
||||
BuildStandardDeviationFallback(ReciprocatingRecords.Select(record => record.StaticCoefficient)));
|
||||
var averageStaticForceN = AverageFiniteNullableValues(ReciprocatingRecords.Select(record => record.StaticForceN));
|
||||
var averageKineticForceN = AverageFiniteNullableValues(ReciprocatingRecords.Select(record => record.KineticForceN));
|
||||
var reciprocatingAverageEndIndex = ReciprocatingRecords
|
||||
.Where(record => record.HasData)
|
||||
.Select(record => record.Index)
|
||||
.DefaultIfEmpty(0)
|
||||
.Max();
|
||||
|
||||
CurrentStaticCoefficient = finalStaticCoefficient;
|
||||
CurrentKineticCoefficient = finalKineticCoefficient;
|
||||
@@ -1093,6 +1100,9 @@ public sealed class MainViewModel : ObservableObject, IDisposable
|
||||
StandardDeviation2 = StandardDeviation2,
|
||||
PeakForceN = CurrentPeakForceN,
|
||||
AverageForceN = CurrentAverageForceN,
|
||||
AverageStaticForceN = averageStaticForceN,
|
||||
AverageKineticForceN = averageKineticForceN,
|
||||
ReciprocatingAverageEndIndex = reciprocatingAverageEndIndex,
|
||||
Judgement = "完成",
|
||||
SampleCount = _currentRunSamples.Count
|
||||
};
|
||||
@@ -1307,6 +1317,15 @@ public sealed class MainViewModel : ObservableObject, IDisposable
|
||||
return values.Length == 0 ? 0 : values.Average();
|
||||
}
|
||||
|
||||
private static double? AverageFiniteNullableValues(IEnumerable<double?> values)
|
||||
{
|
||||
var finiteValues = values
|
||||
.Where(value => value is { } number && IsFinite(number))
|
||||
.Select(value => value!.Value)
|
||||
.ToArray();
|
||||
return finiteValues.Length == 0 ? null : finiteValues.Average();
|
||||
}
|
||||
|
||||
private static IEnumerable<double?> BuildStandardDeviationFallback(IEnumerable<double?> values)
|
||||
{
|
||||
var samples = values
|
||||
|
||||
Reference in New Issue
Block a user