using OxyPlot; using OxyPlot.Axes; using OxyPlot.Legends; using OxyPlot.Series; using OxyPlot.WindowsForms; using Sunny.UI; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using 薄膜气体透过率测试仪.Data; using OxyPlot.Pdf; using System.Drawing.Drawing2D; using System.Drawing.Imaging; namespace 薄膜气体透过率测试仪 { public partial class TrendChart : UIForm { private TestScreen _testScreen; Timer Timer; public List Data { get; set; } public void AddChart(lineData data) { Data.Add(data); CreateBasicLineChart(); } public TrendChart(List Data) { InitializeComponent(); Timer = new Timer(); Timer.Tick += Timer_Tick; Timer.Interval = 500; Timer.Start(); this.Data = Data; if (Data == null || Data.Count() == 0) { Data = new List(); } CreateBasicLineChart(); } private void Timer_Tick(object sender, EventArgs e) { uiLabel2.Text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); } private void CreateBasicLineChart() { // 创建模型 var plotModel = new PlotModel { //Title = "月度销售额统计", //TitleFontSize = 16, //TitleFontWeight = FontWeights.Bold }; // 设置坐标轴 plotModel.Axes.Add(new LinearAxis { Position = AxisPosition.Left, Title = "低压室压力(kPa)", //MajorStep = 0.05, // 可选:设置最小范围,确保坐标轴不会太小 MinimumRange = 0.1, // 最小显示范围 // 可选:设置绝对的边界 AbsoluteMinimum = 0, // 确保最小值不会低于0 AbsoluteMaximum = double.MaxValue, // 最大值无上限 // 自动调整的额外设置 MaximumPadding = 0.05, // 顶部留5%的空白 MinimumPadding = 0.05, // 底部留5%的空白 }); plotModel.Axes.Add(new CategoryAxis { Position = AxisPosition.Bottom, Title = "时间", MajorStep = 30, ItemsSource = Data.Select(s => s.X) }); // 创建折线系列 var lineSeries = new LineSeries { //Title = "2024年", Color = OxyColor.FromRgb(255, 0, 0), // 红色 //MarkerType = MarkerType.Circle, //MarkerSize = 5, //MarkerFill = OxyColors.Red, MarkerStroke = OxyColors.White, MarkerStrokeThickness = 1, StrokeThickness = 2 }; // 添加数据点 float[] salesData = Data.Select(x => x.Y).ToArray(); for (int i = 0; i < salesData.Length; i++) { lineSeries.Points.Add(new DataPoint(i, salesData[i])); } // 添加系列到模型 plotModel.Series.Add(lineSeries); // 设置背景色 plotModel.Background = OxyColors.White; plotModel.PlotAreaBorderColor = OxyColors.LightGray; // 应用到PlotView plotView1.Model = plotModel; } private void TrendChart_FormClosing(object sender, FormClosingEventArgs e) { Timer?.Stop(); Timer?.Dispose(); } //导出图表 private void uiButton1_Click(object sender, EventArgs e) { try { // 创建保存文件对话框 using (SaveFileDialog saveFileDialog = new SaveFileDialog()) { saveFileDialog.Filter = "PDF文件 (*.pdf)|*.pdf|PNG图片 (*.png)|*.png|所有文件 (*.*)|*.*"; saveFileDialog.FilterIndex = 1; saveFileDialog.RestoreDirectory = true; saveFileDialog.FileName = $"趋势图表_{DateTime.Now:yyyyMMdd_HHmmss}.pdf"; if (saveFileDialog.ShowDialog() == DialogResult.OK) { string filePath = saveFileDialog.FileName; // 根据选择的文件类型调用不同的导出方法 string extension = Path.GetExtension(filePath).ToLower(); if (extension == ".pdf") { ExportChartToPdf(filePath); } else if (extension == ".png") { ExportChartToImage(filePath); } else { // 如果没有扩展名或扩展名不对,默认使用PDF if (string.IsNullOrEmpty(extension)) { filePath += ".pdf"; ExportChartToPdf(filePath); } else { // 尝试导出为PDF ExportChartToPdf(filePath); } } MessageBox.Show($"图表已成功导出到:\n{filePath}", "导出成功", MessageBoxButtons.OK, MessageBoxIcon.Information); } } } catch (Exception ex) { MessageBox.Show($"导出图表时发生错误:\n{ex.Message}", "导出失败", MessageBoxButtons.OK, MessageBoxIcon.Error); } } private void ExportChartToPdf(string filePath) { try { // 1. 创建图表图片 Bitmap chartImage = CreateChartImage(800, 600); // 2. 使用System.Drawing.Printing创建PDF(简单方法) // 或者直接保存为高质量图片 string tempImagePath = Path.Combine(Path.GetTempPath(), $"chart_temp_{Guid.NewGuid()}.png"); // 保存为高质量PNG chartImage.Save(tempImagePath, System.Drawing.Imaging.ImageFormat.Png); // 3. 由于System.Drawing不直接支持PDF,我们使用iTextSharp(需要安装NuGet包) // 如果没有安装iTextSharp,我们直接保存为图片 ExportImageAsPdf(chartImage, filePath); // 4. 清理临时文件 if (File.Exists(tempImagePath)) { File.Delete(tempImagePath); } // 5. 释放资源 chartImage.Dispose(); } catch (Exception ex) { // 如果PDF导出失败,降级为图片导出 string imagePath = Path.ChangeExtension(filePath, ".png"); ExportChartToImage(imagePath); throw new Exception($"PDF导出失败,已保存为图片格式: {ex.Message}"); } } private void ExportChartToImage(string filePath) { Bitmap chartImage = null; try { // 创建高质量图表图片 chartImage = CreateChartImage(1600, 1200); // 高分辨率 // 保存图片 System.Drawing.Imaging.ImageFormat format = System.Drawing.Imaging.ImageFormat.Png; string extension = Path.GetExtension(filePath).ToLower(); if (extension == ".jpg" || extension == ".jpeg") format = System.Drawing.Imaging.ImageFormat.Jpeg; else if (extension == ".bmp") format = System.Drawing.Imaging.ImageFormat.Bmp; chartImage.Save(filePath, format); } finally { chartImage?.Dispose(); } } private Bitmap CreateChartImage(int width, int height) { Bitmap bitmap = new Bitmap(width, height); using (Graphics graphics = Graphics.FromImage(bitmap)) { // 设置高质量绘制 graphics.SmoothingMode = SmoothingMode.HighQuality; graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; graphics.CompositingQuality = CompositingQuality.HighQuality; // 填充白色背景 graphics.Clear(Color.White); // 设置边距 int marginLeft = 80; int marginRight = 40; int marginTop = 60; int marginBottom = 80; int chartWidth = width - marginLeft - marginRight; int chartHeight = height - marginTop - marginBottom; // 1. 绘制标题 using (Font titleFont = new Font("微软雅黑", 16, FontStyle.Bold)) using (Brush titleBrush = new SolidBrush(Color.Black)) { string title = "趋势图表"; SizeF titleSize = graphics.MeasureString(title, titleFont); graphics.DrawString(title, titleFont, titleBrush, (width - titleSize.Width) / 2, 20); } // 2. 绘制坐标轴 Pen axisPen = new Pen(Color.Black, 2); // Y轴 graphics.DrawLine(axisPen, marginLeft, marginTop, marginLeft, marginTop + chartHeight); // X轴 graphics.DrawLine(axisPen, marginLeft, marginTop + chartHeight, marginLeft + chartWidth, marginTop + chartHeight); // 3. 绘制坐标轴标题 using (Font axisFont = new Font("微软雅黑", 12)) using (Brush axisBrush = new SolidBrush(Color.Black)) { // Y轴标题 graphics.DrawString("低压室压力(kPa)", axisFont, axisBrush, marginLeft - 70, marginTop - 40); // X轴标题 graphics.DrawString("时间", axisFont, axisBrush, marginLeft + chartWidth / 2 - 20, marginTop + chartHeight + 40); } // 4. 计算数据范围 if (Data != null && Data.Count > 0) { float minY = Data.Min(d => d.Y); float maxY = Data.Max(d => d.Y); // 添加一些余量 float rangeY = maxY - minY; if (rangeY == 0) rangeY = 1; minY -= rangeY * 0.1f; maxY += rangeY * 0.1f; // 5. 自适应Y轴刻度 int yTicks = 5; // 固定5个刻度,清晰易读 // 计算合适的刻度步长 float roughStep = (maxY - minY) / (yTicks - 1); float magnitude = (float)Math.Pow(10, Math.Floor(Math.Log10(roughStep))); float stepSize = (float)Math.Ceiling(roughStep / magnitude) * magnitude; // 调整minY和maxY为步长的整数倍 minY = (float)(Math.Floor(minY / stepSize) * stepSize); maxY = (float)(Math.Ceiling(maxY / stepSize) * stepSize); // 重新计算刻度数量 yTicks = (int)Math.Ceiling((maxY - minY) / stepSize); float yTickStep = chartHeight / yTicks; float yValueStep = stepSize; using (Font tickFont = new Font("Arial", 9)) using (Brush tickBrush = new SolidBrush(Color.Black)) { for (int i = 0; i <= yTicks; i++) { float yPos = marginTop + chartHeight - (i * yTickStep); float yValue = minY + (i * yValueStep); // 绘制刻度线 graphics.DrawLine(Pens.Gray, marginLeft - 5, yPos, marginLeft, yPos); // 绘制刻度值(根据值的大小选择合适的小数位数) string tickLabel; if (Math.Abs(yValue) < 0.01) tickLabel = "0"; else if (stepSize < 0.1) tickLabel = yValue.ToString("F3"); else if (stepSize < 1) tickLabel = yValue.ToString("F2"); else if (stepSize < 10) tickLabel = yValue.ToString("F1"); else tickLabel = yValue.ToString("F0"); SizeF textSize = graphics.MeasureString(tickLabel, tickFont); graphics.DrawString(tickLabel, tickFont, tickBrush, marginLeft - textSize.Width - 8, yPos - textSize.Height / 2); } } // 6. 绘制X轴刻度(时间) int xTicks = Math.Min(10, Data.Count); using (Font tickFont = new Font("Arial", 9)) using (Brush tickBrush = new SolidBrush(Color.Black)) { for (int i = 0; i < xTicks; i++) { int dataIndex = i * (Data.Count - 1) / (xTicks - 1); if (dataIndex >= Data.Count) dataIndex = Data.Count - 1; float xPos = marginLeft + (i * chartWidth / (float)(xTicks - 1)); // 绘制刻度线 graphics.DrawLine(Pens.Gray, xPos, marginTop + chartHeight, xPos, marginTop + chartHeight + 5); // 绘制时间标签 if (dataIndex < Data.Count) { string timeLabel = Data[dataIndex].X; if (timeLabel.Length > 8) timeLabel = timeLabel.Substring(timeLabel.Length - 8); // 只取时间部分 SizeF textSize = graphics.MeasureString(timeLabel, tickFont); graphics.DrawString(timeLabel, tickFont, tickBrush, xPos - textSize.Width / 2, marginTop + chartHeight + 10); } } } // 7. 绘制数据曲线 if (Data.Count > 1) { PointF[] points = new PointF[Data.Count]; for (int i = 0; i < Data.Count; i++) { float xRatio = i / (float)(Data.Count - 1); float yRatio = (Data[i].Y - minY) / (maxY - minY); float xPos = marginLeft + (xRatio * chartWidth); float yPos = marginTop + chartHeight - (yRatio * chartHeight); points[i] = new PointF(xPos, yPos); } // 绘制曲线 using (Pen curvePen = new Pen(Color.Red, 2)) { curvePen.StartCap = System.Drawing.Drawing2D.LineCap.Round; curvePen.EndCap = System.Drawing.Drawing2D.LineCap.Round; // 绘制平滑曲线 if (points.Length > 1) { graphics.DrawCurve(curvePen, points, 0.5f); } // 绘制数据点 foreach (PointF point in points) { graphics.FillEllipse(Brushes.Red, point.X - 3, point.Y - 3, 6, 6); graphics.DrawEllipse(Pens.White, point.X - 3, point.Y - 3, 6, 6); } } } // 8. 绘制图例 using (Font legendFont = new Font("微软雅黑", 10)) { string legendText = "压力数据"; SizeF legendSize = graphics.MeasureString(legendText, legendFont); RectangleF legendRect = new RectangleF( width - marginRight - legendSize.Width - 30, marginTop, legendSize.Width + 20, legendSize.Height + 10); graphics.FillRectangle(Brushes.White, legendRect); graphics.DrawRectangle(Pens.Gray, legendRect.X, legendRect.Y, legendRect.Width, legendRect.Height); // 绘制图例颜色标记 graphics.FillRectangle(Brushes.Red, legendRect.X + 5, legendRect.Y + 5, 15, legendRect.Height - 10); graphics.DrawString(legendText, legendFont, Brushes.Black, legendRect.X + 25, legendRect.Y + 5); } } else { // 没有数据时显示提示 using (Font infoFont = new Font("微软雅黑", 14)) using (Brush infoBrush = new SolidBrush(Color.Gray)) { string infoText = "暂无数据"; SizeF textSize = graphics.MeasureString(infoText, infoFont); graphics.DrawString(infoText, infoFont, infoBrush, (width - textSize.Width) / 2, (height - textSize.Height) / 2); } } // 9. 绘制边框 graphics.DrawRectangle(Pens.LightGray, 0, 0, width - 1, height - 1); } return bitmap; } private void ExportImageAsPdf(Image image, string filePath) { try { // 方法1:如果有iTextSharp,使用它创建PDF // 这需要安装iTextSharp NuGet包 // 方法2:直接保存为图片 string imagePath = Path.ChangeExtension(filePath, ".png"); image.Save(imagePath, System.Drawing.Imaging.ImageFormat.Png); // 方法3:使用Windows自带的PDF打印功能(需要用户交互) // 这里我们使用方法2,因为最简单可靠 // 如果需要PDF,可以提示用户安装iTextSharp throw new NotImplementedException("PDF导出需要安装iTextSharp库,已保存为PNG图片"); } catch (NotImplementedException) { // 保存为图片 string imagePath = Path.ChangeExtension(filePath, ".png"); image.Save(imagePath, System.Drawing.Imaging.ImageFormat.Png); // 更新文件路径 File.Move(imagePath, filePath.Replace(".pdf", ".png")); } } } }