更新
This commit is contained in:
@@ -47,6 +47,7 @@ public sealed class RunExportService
|
||||
throw new InvalidOperationException("未找到可导出的有效历史数据。");
|
||||
}
|
||||
|
||||
var exportStatistics = BuildExportStatistics(runs.Count, exportRuns);
|
||||
var outputDirectory = Path.GetDirectoryName(outputPath);
|
||||
if (!string.IsNullOrWhiteSpace(outputDirectory))
|
||||
{
|
||||
@@ -56,13 +57,17 @@ public sealed class RunExportService
|
||||
WorksheetChartLayout chartLayout;
|
||||
using (var workbook = new XLWorkbook())
|
||||
{
|
||||
BuildSummarySheet(workbook.Worksheets.Add("报表汇总"), exportRuns);
|
||||
BuildSummarySheet(workbook.Worksheets.Add("报表汇总"), exportRuns, exportStatistics);
|
||||
BuildRawDataSheet(workbook.Worksheets.Add("原始数据"), exportRuns);
|
||||
chartLayout = BuildChartSheet(workbook.Worksheets.Add("曲线图"), exportRuns);
|
||||
workbook.SaveAs(outputPath);
|
||||
}
|
||||
|
||||
InsertNativeCharts(outputPath, chartLayout);
|
||||
if (chartLayout.Charts.Count > 0)
|
||||
{
|
||||
InsertNativeCharts(outputPath, chartLayout);
|
||||
}
|
||||
|
||||
return outputPath;
|
||||
}
|
||||
|
||||
@@ -96,7 +101,17 @@ public sealed class RunExportService
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private static bool IsValidSample(RawSampleRecord sample)
|
||||
private static ExportReportStatistics BuildExportStatistics(int sourceRunCount, IReadOnlyList<ExportCurveData> exportRuns)
|
||||
{
|
||||
return new ExportReportStatistics(
|
||||
sourceRunCount,
|
||||
exportRuns.Count,
|
||||
exportRuns.Count(IsChartableRun),
|
||||
Math.Max(0, sourceRunCount - exportRuns.Count),
|
||||
exportRuns.Sum(item => item.ValidSamples.Count));
|
||||
}
|
||||
|
||||
internal static bool IsValidSample(RawSampleRecord sample)
|
||||
{
|
||||
return sample.SampleIndex > 0
|
||||
&& IsFinite(sample.DisplacementMm)
|
||||
@@ -109,7 +124,10 @@ public sealed class RunExportService
|
||||
return !double.IsNaN(value) && !double.IsInfinity(value);
|
||||
}
|
||||
|
||||
private static void BuildSummarySheet(IXLWorksheet worksheet, IReadOnlyList<ExportCurveData> runs)
|
||||
private static void BuildSummarySheet(
|
||||
IXLWorksheet worksheet,
|
||||
IReadOnlyList<ExportCurveData> runs,
|
||||
ExportReportStatistics exportStatistics)
|
||||
{
|
||||
ApplyDefaultWorksheetStyle(worksheet);
|
||||
|
||||
@@ -122,11 +140,19 @@ public sealed class RunExportService
|
||||
worksheet.Cell("A3").Value = "导出时间";
|
||||
worksheet.Cell("B3").Value = DateTime.Now;
|
||||
worksheet.Cell("B3").Style.DateFormat.Format = "yyyy-mm-dd hh:mm:ss";
|
||||
worksheet.Cell("A4").Value = "有效历史数量";
|
||||
worksheet.Cell("B4").Value = runs.Count;
|
||||
worksheet.Cell("A5").Value = "汇总说明";
|
||||
worksheet.Cell("B5").Value = "本表仅统计含有效采样点的试验记录,包含结果汇总、标准差统计和原始力值统计。";
|
||||
worksheet.Range("B5:F5").Merge();
|
||||
worksheet.Cell("A4").Value = "历史记录总数";
|
||||
worksheet.Cell("B4").Value = exportStatistics.SourceRunCount;
|
||||
worksheet.Cell("A5").Value = "有效导出记录数";
|
||||
worksheet.Cell("B5").Value = exportStatistics.ExportRunCount;
|
||||
worksheet.Cell("A6").Value = "曲线图数量";
|
||||
worksheet.Cell("B6").Value = exportStatistics.ChartRunCount;
|
||||
worksheet.Cell("A7").Value = "排除记录数";
|
||||
worksheet.Cell("B7").Value = exportStatistics.ExcludedRunCount;
|
||||
worksheet.Cell("A8").Value = "有效采样点总数";
|
||||
worksheet.Cell("B8").Value = exportStatistics.ValidSampleCount;
|
||||
worksheet.Cell("A9").Value = "汇总说明";
|
||||
worksheet.Cell("B9").Value = "本表仅统计含有效采样点的试验记录;单点记录保留数据但不生成曲线图。";
|
||||
worksheet.Range("B9:F9").Merge();
|
||||
|
||||
BuildStatisticsPanel(worksheet, runs);
|
||||
|
||||
@@ -309,14 +335,14 @@ public sealed class RunExportService
|
||||
worksheet.Cell("A1").Style.Font.FontColor = XLColor.FromHtml("#21313D");
|
||||
|
||||
worksheet.Cell("A3").Value = "图表说明";
|
||||
worksheet.Cell("B3").Value = "包含历史总览曲线、静动摩擦趋势图,以及每轮有效数据的单独曲线图。";
|
||||
worksheet.Cell("B3").Value = "每条含至少 2 个有效采样点的历史试验记录对应一个独立曲线图,横轴按真实位移数值比例绘制。";
|
||||
worksheet.Range("B3:H3").Merge();
|
||||
|
||||
worksheet.Cell("A5").Value = "曲线清单";
|
||||
worksheet.Cell("A5").Style.Font.Bold = true;
|
||||
worksheet.Cell("A5").Style.Font.FontSize = 12;
|
||||
|
||||
WriteHeaderRow(worksheet, 6, ["曲线标识", "图例标签", "颜色", "有效采样点数"]);
|
||||
WriteHeaderRow(worksheet, 6, ["曲线标识", "图例标签", "颜色", "有效采样点数", "曲线状态"]);
|
||||
|
||||
for (var index = 0; index < runs.Count; index++)
|
||||
{
|
||||
@@ -325,13 +351,16 @@ public sealed class RunExportService
|
||||
worksheet.Cell(row, 2).Value = BuildLegendLabel(runs[index]);
|
||||
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;
|
||||
}
|
||||
|
||||
ApplyChartColumnWidths(worksheet);
|
||||
var chartLayout = BuildChartDataArea(worksheet, runs);
|
||||
ApplyPageLayout(worksheet, Math.Max(40, 25 + ((runs.Count + 1) / 2) * 14), "$A$1:$T$");
|
||||
ApplyPageLayout(worksheet, CalculateChartSheetLastRow(runs), "$A$1:$T$");
|
||||
worksheet.SheetView.FreezeRows(1);
|
||||
return chartLayout;
|
||||
}
|
||||
@@ -371,8 +400,9 @@ public sealed class RunExportService
|
||||
{
|
||||
var row = 4 + index;
|
||||
worksheet.Cell(row, 8).Value = rows[index].Label;
|
||||
worksheet.Cell(row, 8).Style.Fill.BackgroundColor = XLColor.FromHtml("#EEF3F7");
|
||||
worksheet.Cell(row, 8).Style.Fill.BackgroundColor = XLColor.FromHtml("#2F4A5C");
|
||||
worksheet.Cell(row, 8).Style.Font.Bold = true;
|
||||
worksheet.Cell(row, 8).Style.Font.FontColor = XLColor.White;
|
||||
worksheet.Cell(row, 9).Value = rows[index].Value;
|
||||
worksheet.Cell(row, 9).Style.Border.BottomBorder = XLBorderStyleValues.Thin;
|
||||
worksheet.Cell(row, 9).Style.Border.BottomBorderColor = XLColor.FromHtml("#D3DDE5");
|
||||
@@ -384,7 +414,6 @@ public sealed class RunExportService
|
||||
const int startColumn = 30;
|
||||
const int startRow = 1;
|
||||
|
||||
var seriesLayouts = new List<SeriesLayout>();
|
||||
var perRunSeries = new List<PerRunSeriesLayout>();
|
||||
var currentColumn = startColumn;
|
||||
var sheetName = worksheet.Name;
|
||||
@@ -420,74 +449,14 @@ public sealed class RunExportService
|
||||
BuildRangeReference(sheetName, startDataRow, yColumn, endDataRow, yColumn),
|
||||
item.CurveColorHex);
|
||||
|
||||
seriesLayouts.Add(layout);
|
||||
perRunSeries.Add(new PerRunSeriesLayout(item, layout));
|
||||
if (IsChartableRun(item))
|
||||
{
|
||||
perRunSeries.Add(new PerRunSeriesLayout(item, layout));
|
||||
}
|
||||
}
|
||||
|
||||
var trendHeaderRow = startRow;
|
||||
var trendDataStartRow = startRow + 1;
|
||||
var runIndexColumn = currentColumn;
|
||||
var staticColumn = runIndexColumn + 1;
|
||||
var kineticColumn = runIndexColumn + 2;
|
||||
|
||||
worksheet.Cell(trendHeaderRow, runIndexColumn).Value = "试验轮次";
|
||||
worksheet.Cell(trendHeaderRow, staticColumn).Value = "静摩擦系数 μs";
|
||||
worksheet.Cell(trendHeaderRow, kineticColumn).Value = "动摩擦系数 μk";
|
||||
|
||||
for (var index = 0; index < runs.Count; index++)
|
||||
{
|
||||
var row = trendDataStartRow + index;
|
||||
worksheet.Cell(row, runIndexColumn).Value = runs[index].Data.Run.RunIndex;
|
||||
worksheet.Cell(row, staticColumn).Value = runs[index].Data.Run.StaticCoefficient;
|
||||
worksheet.Cell(row, kineticColumn).Value = runs[index].Data.Run.KineticCoefficient;
|
||||
}
|
||||
|
||||
worksheet.Column(runIndexColumn).Hide();
|
||||
worksheet.Column(staticColumn).Hide();
|
||||
worksheet.Column(kineticColumn).Hide();
|
||||
|
||||
var trendEndRow = Math.Max(trendDataStartRow, trendDataStartRow + runs.Count - 1);
|
||||
var charts = new List<ChartDefinition>
|
||||
{
|
||||
new(
|
||||
"历史曲线总览",
|
||||
"位移 (mm)",
|
||||
"力值 (N)",
|
||||
true,
|
||||
seriesLayouts,
|
||||
new ChartAnchor(0, 20, 11, 36),
|
||||
"历史曲线总览"),
|
||||
new(
|
||||
"静摩擦系数趋势",
|
||||
"试验轮次",
|
||||
"μs",
|
||||
true,
|
||||
[
|
||||
new SeriesLayout(
|
||||
BuildCellReference(sheetName, trendHeaderRow, staticColumn),
|
||||
BuildRangeReference(sheetName, trendDataStartRow, runIndexColumn, trendEndRow, runIndexColumn),
|
||||
BuildRangeReference(sheetName, trendDataStartRow, staticColumn, trendEndRow, staticColumn),
|
||||
"#5E7DA0")
|
||||
],
|
||||
new ChartAnchor(12, 20, 19, 28),
|
||||
"静摩擦趋势图"),
|
||||
new(
|
||||
"动摩擦系数趋势",
|
||||
"试验轮次",
|
||||
"μk",
|
||||
true,
|
||||
[
|
||||
new SeriesLayout(
|
||||
BuildCellReference(sheetName, trendHeaderRow, kineticColumn),
|
||||
BuildRangeReference(sheetName, trendDataStartRow, runIndexColumn, trendEndRow, runIndexColumn),
|
||||
BuildRangeReference(sheetName, trendDataStartRow, kineticColumn, trendEndRow, kineticColumn),
|
||||
"#00839A")
|
||||
],
|
||||
new ChartAnchor(12, 29, 19, 37),
|
||||
"动摩擦趋势图")
|
||||
};
|
||||
|
||||
const int perRunStartRow = 39;
|
||||
var charts = new List<ChartDefinition>();
|
||||
var perRunStartRow = Math.Max(10, runs.Count + 8);
|
||||
const int chartHeight = 11;
|
||||
const int chartWidth = 9;
|
||||
const int rowGap = 2;
|
||||
@@ -515,6 +484,17 @@ public sealed class RunExportService
|
||||
return new WorksheetChartLayout(worksheet.Name, charts);
|
||||
}
|
||||
|
||||
private static bool IsChartableRun(ExportCurveData item)
|
||||
{
|
||||
return item.ValidSamples.Count >= 2;
|
||||
}
|
||||
|
||||
private static int CalculateChartSheetLastRow(IReadOnlyList<ExportCurveData> runs)
|
||||
{
|
||||
var chartCount = runs.Count(IsChartableRun);
|
||||
return Math.Max(24, runs.Count + 22 + ((chartCount + 1) / 2) * 13);
|
||||
}
|
||||
|
||||
private static void ApplyDefaultWorksheetStyle(IXLWorksheet worksheet)
|
||||
{
|
||||
worksheet.Style.Font.FontName = "Microsoft YaHei UI";
|
||||
@@ -530,9 +510,10 @@ public sealed class RunExportService
|
||||
var cell = worksheet.Cell(row, index + 1);
|
||||
cell.Value = headers[index];
|
||||
cell.Style.Font.Bold = true;
|
||||
cell.Style.Fill.BackgroundColor = XLColor.FromHtml("#E1EAF1");
|
||||
cell.Style.Font.FontColor = XLColor.White;
|
||||
cell.Style.Fill.BackgroundColor = XLColor.FromHtml("#2F4A5C");
|
||||
cell.Style.Border.BottomBorder = XLBorderStyleValues.Thin;
|
||||
cell.Style.Border.BottomBorderColor = XLColor.FromHtml("#B7C4CE");
|
||||
cell.Style.Border.BottomBorderColor = XLColor.FromHtml("#1F3342");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -595,6 +576,7 @@ public sealed class RunExportService
|
||||
2 => 28,
|
||||
3 => 12,
|
||||
4 => 12,
|
||||
5 => 22,
|
||||
_ => 14
|
||||
};
|
||||
}
|
||||
@@ -681,7 +663,7 @@ public sealed class RunExportService
|
||||
|
||||
private static void BuildChartPart(ChartPart chartPart, ChartDefinition chartDefinition)
|
||||
{
|
||||
const uint categoryAxisId = 48650112U;
|
||||
const uint xAxisId = 48650112U;
|
||||
const uint valueAxisId = 48672768U;
|
||||
|
||||
var chartSpace = new C.ChartSpace();
|
||||
@@ -694,8 +676,8 @@ public sealed class RunExportService
|
||||
var plotArea = new C.PlotArea();
|
||||
plotArea.Append(new C.Layout());
|
||||
|
||||
var lineChart = new C.LineChart(
|
||||
new C.Grouping { Val = C.GroupingValues.Standard },
|
||||
var scatterChart = new C.ScatterChart(
|
||||
new C.ScatterStyle { Val = C.ScatterStyleValues.Line },
|
||||
new C.VaryColors { Val = false });
|
||||
|
||||
for (var index = 0; index < chartDefinition.Series.Count; index++)
|
||||
@@ -710,25 +692,25 @@ public sealed class RunExportService
|
||||
Width = 19050
|
||||
});
|
||||
|
||||
var lineSeries = new C.LineChartSeries(
|
||||
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))),
|
||||
chartShape,
|
||||
new C.Marker(new C.Symbol { Val = C.MarkerStyleValues.None }),
|
||||
new C.CategoryAxisData(new C.NumberReference(new C.Formula(series.XValuesReference))),
|
||||
new C.Values(new C.NumberReference(new C.Formula(series.YValuesReference))),
|
||||
new C.XValues(new C.NumberReference(new C.Formula(series.XValuesReference))),
|
||||
new C.YValues(new C.NumberReference(new C.Formula(series.YValuesReference))),
|
||||
new C.Smooth { Val = false });
|
||||
|
||||
lineChart.Append(lineSeries);
|
||||
scatterChart.Append(scatterSeries);
|
||||
}
|
||||
|
||||
lineChart.Append(new C.AxisId { Val = categoryAxisId });
|
||||
lineChart.Append(new C.AxisId { Val = valueAxisId });
|
||||
plotArea.Append(lineChart);
|
||||
scatterChart.Append(new C.AxisId { Val = xAxisId });
|
||||
scatterChart.Append(new C.AxisId { Val = valueAxisId });
|
||||
plotArea.Append(scatterChart);
|
||||
|
||||
var categoryAxis = new C.CategoryAxis(
|
||||
new C.AxisId { Val = categoryAxisId },
|
||||
var xAxis = new C.ValueAxis(
|
||||
new C.AxisId { Val = xAxisId },
|
||||
new C.Scaling(new C.Orientation { Val = C.OrientationValues.MinMax }),
|
||||
new C.Delete { Val = false },
|
||||
new C.AxisPosition { Val = C.AxisPositionValues.Bottom },
|
||||
@@ -737,9 +719,7 @@ public sealed class RunExportService
|
||||
new C.TickLabelPosition { Val = C.TickLabelPositionValues.NextTo },
|
||||
new C.CrossingAxis { Val = valueAxisId },
|
||||
new C.Crosses { Val = C.CrossesValues.AutoZero },
|
||||
new C.AutoLabeled { Val = true },
|
||||
new C.LabelAlignment { Val = C.LabelAlignmentValues.Center },
|
||||
new C.LabelOffset { Val = 100 });
|
||||
new C.CrossBetween { Val = C.CrossBetweenValues.Between });
|
||||
|
||||
var valueAxis = new C.ValueAxis(
|
||||
new C.AxisId { Val = valueAxisId },
|
||||
@@ -750,11 +730,11 @@ public sealed class RunExportService
|
||||
new C.NumberingFormat { FormatCode = "0.000", SourceLinked = true },
|
||||
CreateAxisTitle(chartDefinition.YAxisTitle),
|
||||
new C.TickLabelPosition { Val = C.TickLabelPositionValues.NextTo },
|
||||
new C.CrossingAxis { Val = categoryAxisId },
|
||||
new C.CrossingAxis { Val = xAxisId },
|
||||
new C.Crosses { Val = C.CrossesValues.AutoZero },
|
||||
new C.CrossBetween { Val = C.CrossBetweenValues.Between });
|
||||
|
||||
plotArea.Append(categoryAxis);
|
||||
plotArea.Append(xAxis);
|
||||
plotArea.Append(valueAxis);
|
||||
|
||||
chart.Append(plotArea);
|
||||
@@ -965,6 +945,13 @@ public sealed class RunExportService
|
||||
|
||||
private sealed record ExportCurveData(PersistedRunData Data, IReadOnlyList<RawSampleRecord> ValidSamples, int Sequence, string CurveColorHex);
|
||||
|
||||
private sealed record ExportReportStatistics(
|
||||
int SourceRunCount,
|
||||
int ExportRunCount,
|
||||
int ChartRunCount,
|
||||
int ExcludedRunCount,
|
||||
int ValidSampleCount);
|
||||
|
||||
private sealed record SeriesLayout(string TitleReference, string XValuesReference, string YValuesReference, string ColorHex);
|
||||
|
||||
private sealed record PerRunSeriesLayout(ExportCurveData Data, SeriesLayout Series);
|
||||
|
||||
@@ -1663,19 +1663,21 @@ public sealed class MainViewModel : ObservableObject, IDisposable
|
||||
private bool TryResolveHistoryExportData(out IReadOnlyList<PersistedRunData> exportData)
|
||||
{
|
||||
var historyData = new List<PersistedRunData>();
|
||||
var hasExportableSamples = false;
|
||||
foreach (var run in RunHistory.OrderBy(item => item.CompletedAt))
|
||||
{
|
||||
var data = LoadRunData(run);
|
||||
if (data is not null && data.Samples.Count > 0)
|
||||
if (data is not null)
|
||||
{
|
||||
historyData.Add(data);
|
||||
hasExportableSamples |= data.Samples.Any(RunExportService.IsValidSample);
|
||||
}
|
||||
}
|
||||
|
||||
exportData = historyData;
|
||||
if (historyData.Count == 0)
|
||||
if (!hasExportableSamples)
|
||||
{
|
||||
AddWarningEvent("未找到可导出的历史采样数据。");
|
||||
AddWarningEvent("未找到可导出的有效历史采样数据。");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1710,7 +1712,7 @@ public sealed class MainViewModel : ObservableObject, IDisposable
|
||||
}
|
||||
|
||||
var workbookPath = _runExportService.ExportHistoricalComparisonReport(exportData, saveFileDialog.FileName);
|
||||
foreach (var item in exportData)
|
||||
foreach (var item in exportData.Where(data => data.Samples.Any(RunExportService.IsValidSample)))
|
||||
{
|
||||
_dataRepository.UpdateExportPaths(item.Run.RunId, null, workbookPath);
|
||||
item.Run.ReportExportPath = workbookPath;
|
||||
|
||||
Reference in New Issue
Block a user