525 lines
20 KiB
C#
525 lines
20 KiB
C#
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<lineData> Data { get; set; }
|
||
|
||
public void AddChart(lineData data)
|
||
{
|
||
Data.Add(data);
|
||
CreateBasicLineChart();
|
||
|
||
|
||
}
|
||
|
||
|
||
public TrendChart(List<lineData> 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<lineData>();
|
||
}
|
||
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"));
|
||
}
|
||
}
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
}
|