Files

525 lines
20 KiB
C#
Raw Permalink Normal View History

2026-02-07 10:07:45 +08:00
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"));
}
}
}
}