using Sunny.UI.Win32; using System; using System.Collections.Generic; using System.Data; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using Excel = Microsoft.Office.Interop.Excel; using System.Windows.Forms.DataVisualization; using System.Windows.Forms.DataVisualization.Charting; using System.Drawing.Imaging; using System.Drawing; using DocumentFormat.OpenXml.EMMA; namespace SLC1_N { class Chart { // 生成泄漏量趋势图 public static void Create_TrendChart(int CH, string filepath, int maxProducts = 10) { try { string datatime = DateTime.Now.ToString("yyyyMMdd"); //string filepath = GetExcelFilePath(CH, datatime); if (!File.Exists(filepath)) { mxlLog.Instance.Error($"Excel文件不存在,无法生成图表"); return; } // 读取Excel数据 var Exceldata = ReadExcelData(filepath, maxProducts); if (Exceldata.Rows.Count == 0) { mxlLog.Instance.Error($"没有足够的数据生成趋势图"); return; } // 提取泄漏量数据 List leakValues_list = new List(); List productLabels = new List(); List code_list = new List(); List results_list = new List(); for (int i = 0; i < Exceldata.Rows.Count; i++) { string leakStr = Exceldata.Rows[i]["微漏泄漏量"].ToString(); Console.WriteLine($"Chart:{i}: {leakStr}"); // 移除单位,只保留数值 leakStr = leakStr.Replace("KPa", "") .Replace("Pa/s", "") .Replace("mbar/s", "") .Replace("Pa", "") .Replace("s", "").Trim(); Console.WriteLine($"Chart2:{i}: {leakStr}"); if (double.TryParse(leakStr, out double leakage)) { leakValues_list.Add(leakage); productLabels.Add($"产品{i + 1}"); code_list.Add(Exceldata.Rows[i]["条形码"].ToString()); results_list.Add(Exceldata.Rows[i]["测试结果"].ToString()); } } if (leakValues_list.Count == 0) { mxlLog.Instance.Error($"没有有效的泄漏量数据"); return; } // 创建图表图像 int width = 1000; int height = 600; using (Bitmap bitmap = new Bitmap(width, height)) using (Graphics graphics = Graphics.FromImage(bitmap)) { graphics.Clear(Color.White); graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; // 抗锯齿 // 设置边距 int margin = 50; int chartWidth = width - 2 * margin; int chartHeight = height - 2 * margin; // 计算Y轴数据范围(最大值加10%,最小值减10%,但不小于0) double maxValue = leakValues_list.Max() * 1.1; double minValue = Math.Max(0, leakValues_list.Min() * 0.9); // 绘制标题 using (Font titleFont = new Font("Arial", 16, FontStyle.Bold)) using (Brush titleBrush = new SolidBrush(Color.Black)) { graphics.DrawString($"通道{CH} - 最近{maxProducts}个产品泄漏量趋势", titleFont, titleBrush, new PointF(width / 2 - 150, 10)); // 居中 } // 绘制坐标轴(Y轴坐标是向下的,原点(50,550)) using (Pen axisPen = new Pen(Color.Black, 2)) { // X轴 graphics.DrawLine(axisPen, margin, height - margin, width - margin, height - margin); // Y轴 graphics.DrawLine(axisPen, margin, margin, margin, height - margin); } // 绘制Y轴刻度 using (Font scaleFont = new Font("Arial", 8)) using (Brush scaleBrush = new SolidBrush(Color.Black)) { for (int i = 0; i <= 5; i++) // 在 Y 轴上绘制 5 个等分刻度。 { double value = minValue + (maxValue - minValue) * i / 5; int y = height - margin - (int)(chartHeight * i / 5); graphics.DrawString(value.ToString("F2"), scaleFont, scaleBrush, margin - 45, y - 10); // 刻度标签坐标 } } // 绘制数据点 using (Pen dataPen = new Pen(Color.Blue, 2)) // 连线 using (Brush pointBrush = new SolidBrush(Color.Red)) // 坐标点 using (Font labelFont = new Font("Arial", 8)) using (Brush labelBrush = new SolidBrush(Color.DarkBlue)) // 数据点数值标签 { for (int i = 0; i < leakValues_list.Count; i++) { double leakvalue = leakValues_list[i]; int x = margin + (int)(chartWidth * i / (leakValues_list.Count - 1)); int y = height - margin - (int)(chartHeight * (leakvalue - minValue) / (maxValue - minValue)); // 绘制数据点连接线 if (i > 0) { double prevValue = leakValues_list[i - 1]; // 前一个数据值 int prevX = margin + (int)(chartWidth * (i - 1) / (leakValues_list.Count - 1)); int prevY = height - margin - (int)(chartHeight * (prevValue - minValue) / (maxValue - minValue)); graphics.DrawLine(dataPen, prevX, prevY, x, y); // 连接前一个点和当前点 } // 绘制数据点 graphics.FillEllipse(pointBrush, x - 4, y - 4, 8, 8); // 绘制数值标签 string label = leakvalue.ToString("F2"); graphics.DrawString(label, labelFont, labelBrush, x - 15, y - 20); // 绘制X轴标签 graphics.DrawString(productLabels[i], labelFont, labelBrush, x - 10, height - margin + 10); } } // 绘制图例 using (Font legendFont = new Font("Arial", 10)) using (Brush legendBrush = new SolidBrush(Color.Black)) { graphics.DrawString("泄漏量趋势", legendFont, legendBrush, width - 150, margin); graphics.DrawLine(new Pen(Color.Blue, 2), width - 170, margin + 10, width - 140, margin + 10); graphics.FillEllipse(Brushes.Red, width - 145, margin + 6, 8, 8); } // 保存图表 string chartDir = Path.Combine(Path.GetDirectoryName(filepath), "Charts"); if (!Directory.Exists(chartDir)) Directory.CreateDirectory(chartDir); string chartPath = Path.Combine(chartDir, $"CH{CH}_LeakageTrend_{datatime}.png"); bitmap.Save(chartPath, ImageFormat.Png); //MessageBox.Show($"图表已保存至: {chartPath}"); } } catch (Exception ex) { mxlLog.Instance.Error($"图表生成错误: {ex.Message}", ex); } } // 生成结果统计图 public static void Create_PieChart(int CH, string filepath) { try { string datatime = DateTime.Now.ToString("yyyyMMdd"); //string filepath = GetExcelFilePath(CH, datatime); if (!File.Exists(filepath)) return; var data = ReadExcelData(filepath, 1000); int okCount = 0; int ngCount = 0; foreach (DataRow row in data.Rows) { string result = row["测试结果"].ToString(); if (result.Equals("OK", StringComparison.OrdinalIgnoreCase)) okCount++; else if (result.Equals("NG", StringComparison.OrdinalIgnoreCase)) ngCount++; } int total = okCount + ngCount; if (total == 0) return; // 创建统计图 int width = 600; int height = 400; using (Bitmap bitmap = new Bitmap(width, height)) using (Graphics graphics = Graphics.FromImage(bitmap)) { graphics.Clear(Color.White); graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; // 绘制标题 using (Font titleFont = new Font("Arial", 16, FontStyle.Bold)) { graphics.DrawString($"通道{CH} - 测试结果统计", titleFont, Brushes.Black, 150, 10); graphics.DrawString($"总计: {total} 个产品", titleFont, Brushes.Black, 200, 40); } // 绘制饼图 int centerX = width / 2; int centerY = height / 2 + 20; int radius = 120; if (okCount > 0) { float okAngle = 360f * okCount / total; graphics.FillPie(Brushes.Green, centerX - radius, centerY - radius, radius * 2, radius * 2, 0, okAngle); } if (ngCount > 0) { float ngAngle = 360f * ngCount / total; graphics.FillPie(Brushes.Red, centerX - radius, centerY - radius, radius * 2, radius * 2, 360f * okCount / total, ngAngle); } // 绘制图例 using (Font legendFont = new Font("Arial", 12)) { graphics.FillRectangle(Brushes.Green, 100, height - 80, 20, 20); graphics.DrawString($"OK: {okCount} ({okCount * 100f / total:F1}%)", legendFont, Brushes.Black, 130, height - 80); graphics.FillRectangle(Brushes.Red, 300, height - 80, 20, 20); graphics.DrawString($"NG: {ngCount} ({ngCount * 100f / total:F1}%)", legendFont, Brushes.Black, 330, height - 80); } // 保存图表 string chartDir = Path.Combine(Path.GetDirectoryName(filepath), "Charts"); if (!Directory.Exists(chartDir)) Directory.CreateDirectory(chartDir); string chartPath = Path.Combine(chartDir, $"CH{CH}_ResultStat_{datatime}.png"); bitmap.Save(chartPath, ImageFormat.Png); } } catch (Exception ex) { mxlLog.Instance.Error($"生成统计图错误: {ex.Message}", ex); } } // 读取Excel数据 private static DataTable ReadExcelData(string filePath, int maxRows = 10) { DataTable dt = new DataTable(); Excel.Application xapp = null; Excel.Workbook xbook = null; try { xapp = new Excel.Application(); xapp.Visible = false; xapp.DisplayAlerts = false; xbook = xapp.Workbooks.Open(filePath); Excel.Worksheet xsheet = (Excel.Worksheet)xbook.Sheets[1]; // 获取最后一行 int lastRow = xsheet.Cells.Find("*", System.Reflection.Missing.Value, System.Reflection.Missing.Value, System.Reflection.Missing.Value, Excel.XlSearchOrder.xlByRows, Excel.XlSearchDirection.xlPrevious, false, System.Reflection.Missing.Value, System.Reflection.Missing.Value).Row; // 读取表头创建列 for (int col = 1; col <= 15; col++) { string header = (xsheet.Cells[1, col] as Excel.Range)?.Value2?.ToString() ?? $"Column{col}"; dt.Columns.Add(header); } // 读取数据(从最后maxRows行开始) int startRow = Math.Max(2, lastRow - maxRows + 1); for (int row = startRow; row <= lastRow; row++) { DataRow dr = dt.NewRow(); for (int col = 1; col <= 15; col++) { var cellValue = (xsheet.Cells[row, col] as Excel.Range)?.Value2; dr[col - 1] = cellValue?.ToString() ?? ""; } dt.Rows.Add(dr); } } catch (Exception ex) { mxlLog.Instance.Error($"读取Excel数据错误", ex); } finally { if (xbook != null) { xbook.Close(false); System.Runtime.InteropServices.Marshal.ReleaseComObject(xbook); } if (xapp != null) { xapp.Quit(); System.Runtime.InteropServices.Marshal.ReleaseComObject(xapp); } GC.Collect(); GC.WaitForPendingFinalizers(); } return dt; } // 获取Excel文件路径 private static string GetExcelFilePath(int CH, string date) { string basePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop); string chPath = CH == 1 ? "CH1" : "CH2"; return Path.Combine(basePath, chPath, $"{CH}_{date}.xls"); } } }