Files
pressurediff/压差法气体渗透仪/TrendChart.cs
2026-02-07 10:07:45 +08:00

525 lines
20 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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"));
}
}
}
}