using System;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using OxyPlot;
using OxyPlot.Series;
using OxyPlot.WindowsForms;
using OxyPlot.Axes;
using MathNet.Numerics;
using MathNet.Numerics.Interpolation;
using Microsoft.VisualBasic.FileIO;
using PdfSharp.Pdf;
using PdfSharp.Drawing;
namespace TestPro
{
public partial class Form1 : Form
{
private List<double> depthData = new List<double>();
private List<double> gammaData = new List<double>();
private PlotModel plotModel;
private PlotView plotView;
private Panel plotPanel;
private ComboBox interpolationComboBox;
private Button exportPdfButton;
private Label statusLabel;
private double scaleRatio = 200; // 默认比例尺1:200
private double minVisibleDepth = 0;
private double maxVisibleDepth = 100;
private const double VisibleDepthRange = 100; // 初始可见深度范围
private double pixelsPerMeter = 20; // 增加默认每米像素数
private double visibleDepthRange = 100; // 可见深度范围(米)
private double minDepth = 0;
private double maxDepth = 0;
private VScrollBar depthScrollBar; // 垂直滚动条
private double totalDepthRange = 0; // 总深度范围
private bool isScrollBarDragging = false; // 标记是否正在拖动滚动条
private double dpi = 96; // 默认DPI值
public Form1()
{
InitializeComponent();
InitializeCustomComponents();
LoadData();
SetupPlotModel();
SetupScrollBar();
}
private void InitializeCustomComponents()
{
this.Text = "自然伽马测井曲线可视化工具";
this.Size = new Size(1200, 800);
this.StartPosition = FormStartPosition.CenterScreen;
// 获取系统DPI
using (Graphics g = this.CreateGraphics())
{
dpi = g.DpiY;
}
// 创建主布局面板
var mainPanel = new Panel { Dock = DockStyle.Fill };
this.Controls.Add(mainPanel);
// 创建顶部控制面板
var controlPanel = new Panel
{
Dock = DockStyle.Top,
Height = 50,
BackColor = Color.LightGray
};
mainPanel.Controls.Add(controlPanel);
// 创建插值方法选择下拉框
interpolationComboBox = new ComboBox
{
Location = new Point(20, 15),
Width = 150,
DropDownStyle = ComboBoxStyle.DropDownList
};
interpolationComboBox.Items.AddRange(new object[]
{
"线性插值",
"三次样条",
"PCHIP",
"Akima"
});
interpolationComboBox.SelectedIndex = 2; // 默认选择PCHIP
interpolationComboBox.SelectedIndexChanged += InterpolationComboBox_SelectedIndexChanged;
controlPanel.Controls.Add(interpolationComboBox);
// 添加比例尺选择
var scaleLabel = new Label
{
Text = "比例尺:",
Location = new Point(190, 18),
AutoSize = true
};
controlPanel.Controls.Add(scaleLabel);
var scaleComboBox = new ComboBox
{
Location = new Point(240, 15),
Width = 80,
DropDownStyle = ComboBoxStyle.DropDownList
};
scaleComboBox.Items.AddRange(new object[] { "1:100", "1:200", "1:500" });
scaleComboBox.SelectedIndex = 1; // 默认1:200
// 初始计算像素/米 - 使用优化的公式
pixelsPerMeter = CalculatePixelsPerMeter(200, dpi);
scaleComboBox.SelectedIndexChanged += (s, e) =>
{
string selectedScale = scaleComboBox.SelectedItem.ToString();
switch (selectedScale)
{
case "1:100":
scaleRatio = 100;
pixelsPerMeter = CalculatePixelsPerMeter(100, dpi);
break;
case "1:200":
scaleRatio = 200;
pixelsPerMeter = CalculatePixelsPerMeter(200, dpi);
break;
case "1:500":
scaleRatio = 500;
pixelsPerMeter = CalculatePixelsPerMeter(500, dpi);
break;
}
// 重新计算可见深度范围
CalculateVisibleDepthRange();
// 更新图表
UpdatePlot();
// 更新滚动条
UpdateScrollBar();
};
controlPanel.Controls.Add(scaleComboBox);
// 比例因子调整滑块 - 修改为仅影响深度范围
var scaleFactorTrackBar = new TrackBar
{
Location = new Point(520, 15),
Width = 150,
Minimum = 50,
Maximum = 200,
Value = 100,
TickFrequency = 10
};
scaleFactorTrackBar.ValueChanged += (s, e) =>
{
double factor = scaleFactorTrackBar.Value / 100.0;
double basePixels = CalculatePixelsPerMeter(scaleRatio, dpi);
pixelsPerMeter = basePixels * factor;
// 仅更新可见范围和图表
CalculateVisibleDepthRange();
UpdatePlot();
UpdateScrollBar();
};
controlPanel.Controls.Add(scaleFactorTrackBar);
// 创建PDF导出按钮
exportPdfButton = new Button
{
Text = "导出PDF",
Location = new Point(690, 15),
Size = new Size(80, 25)
};
exportPdfButton.Click += ExportPdfButton_Click;
controlPanel.Controls.Add(exportPdfButton);
// 创建状态标签
statusLabel = new Label
{
Dock = DockStyle.Bottom,
Height = 20,
Text = "就绪",
TextAlign = ContentAlignment.MiddleLeft,
BorderStyle = BorderStyle.FixedSingle
};
mainPanel.Controls.Add(statusLabel);
// 创建绘图面板容器
var plotContainer = new Panel
{
Dock = DockStyle.Fill,
BackColor = Color.White
};
mainPanel.Controls.Add(plotContainer);
// 创建绘图面板(带滚动条)
plotPanel = new Panel
{
Dock = DockStyle.Left,
//Location = new Point(0, 0),
Width = 365,
//Height = plotContainer.Height,
BackColor = Color.White
};
plotContainer.Controls.Add(plotPanel);
// 创建垂直滚动条
depthScrollBar = new VScrollBar
{
Dock = DockStyle.Right,
Width = 20
};
depthScrollBar.Scroll += DepthScrollBar_Scroll;
depthScrollBar.MouseDown += (s, e) => isScrollBarDragging = true;
depthScrollBar.MouseUp += (s, e) => isScrollBarDragging = false;
plotContainer.Controls.Add(depthScrollBar);
// 创建PlotView
plotView = new PlotView
{
Dock = DockStyle.Fill,
BackColor = Color.White
};
// 创建自定义控制器并禁用所有交互
var customController = new PlotController();
customController.UnbindAll(); // 解绑所有默认操作
plotView.Controller = customController;
// 使用标准的MouseWheel事件处理
plotView.MouseWheel += PlotView_MouseWheel;
plotPanel.Controls.Add(plotView);
// 添加Resize事件处理
this.Resize += Form1_Resize;
}
// 优化的每米像素计算
private double CalculatePixelsPerMeter(double scaleRatio, double dpi)
{
// 比例尺1:100表示1厘米代表1米
// 公式: pixelsPerMeter = (100 / scaleRatio) * (dpi / 2.54)
return (100.0 / scaleRatio) * (dpi / 2.54);
}
private void SetupScrollBar()
{
if (depthData.Count == 0) return;
// 计算总深度范围
totalDepthRange = maxDepth - minDepth;
// 设置滚动条范围
depthScrollBar.Minimum = 0;
depthScrollBar.Maximum = (int)Math.Ceiling(totalDepthRange - visibleDepthRange);
depthScrollBar.SmallChange = 1; // 滚动步长为1米
depthScrollBar.LargeChange = (int)visibleDepthRange; // 一页的大小为当前可见深度范围
// 设置滚动条当前位置
depthScrollBar.Value = (int)(minVisibleDepth - minDepth);
}
private void UpdateScrollBar()
{
if (depthData.Count == 0) return;
// 更新滚动条范围
depthScrollBar.Maximum = (int)Math.Ceiling(totalDepthRange - visibleDepthRange);
depthScrollBar.LargeChange = (int)visibleDepthRange;
// 更新滚动条位置
depthScrollBar.Value = Math.Max(0, Math.Min(depthScrollBar.Maximum, (int)(minVisibleDepth - minDepth)));
}
private void DepthScrollBar_Scroll(object sender, ScrollEventArgs e)
{
// 实时更新,不再检查ScrollEventType
double newMinDepth = minDepth + e.NewValue;
double newMaxDepth = newMinDepth + visibleDepthRange;
// 只有当深度范围确实变化时才更新
if (Math.Abs(newMinDepth - minVisibleDepth) > 0.01 ||
Math.Abs(newMaxDepth - maxVisibleDepth) > 0.01)
{
minVisibleDepth = newMinDepth;
maxVisibleDepth = newMaxDepth;
// 确保不超出数据范围
if (maxVisibleDepth > maxDepth)
{
maxVisibleDepth = maxDepth;
minVisibleDepth = maxDepth - visibleDepthRange;
}
if (minVisibleDepth < minDepth)
{
minVisibleDepth = minDepth;
maxVisibleDepth = minDepth + visibleDepthRange;
}
// 立即更新图表
UpdatePlot();
// 如果正在拖动,强制重绘滚动条
if (isScrollBarDragging)
{
depthScrollBar.Refresh();
}
}
}
private void Form1_Resize(object sender, EventArgs e)
{
// 仅重新计算可见深度范围(不改变每米像素数)
CalculateVisibleDepthRange();
UpdateScrollBar();
UpdatePlot();
}
private void CalculateVisibleDepthRange()
{
// 根据屏幕高度和固定的pixelsPerMeter计算可见深度范围
if (plotView != null && plotView.Height > 0)
{
// 获取绘图区域高度(像素)
int plotAreaHeight = plotView.Height;
// 使用固定的pixelsPerMeter计算可见深度范围(米)
visibleDepthRange = plotAreaHeight / pixelsPerMeter;
// 确保可见范围至少为10米
if (visibleDepthRange < 10) visibleDepthRange = 10;
// 确保不超过总深度范围
if (visibleDepthRange > totalDepthRange)
{
visibleDepthRange = totalDepthRange;
}
// 更新当前可见范围
maxVisibleDepth = minVisibleDepth + visibleDepthRange;
// 确保不超过最大深度
if (maxVisibleDepth > maxDepth)
{
maxVisibleDepth = maxDepth;
minVisibleDepth = maxDepth - visibleDepthRange;
if (minVisibleDepth < minDepth) minVisibleDepth = minDepth;
}
}
}
private void LoadData()
{
try
{
// 使用OpenFileDialog选择CSV文件
using (var openFileDialog = new OpenFileDialog())
{
openFileDialog.Filter = "CSV文件|*.csv|所有文件|*.*";
openFileDialog.Title = "选择测井数据文件";
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
statusLabel.Text = "正在加载数据...";
Application.DoEvents();
// 使用TextFieldParser读取CSV
using (var parser = new TextFieldParser(openFileDialog.FileName))
{
parser.TextFieldType = FieldType.Delimited;
parser.SetDelimiters(",");
parser.HasFieldsEnclosedInQuotes = true;
// 跳过前3行头信息
for (int i = 0; i < 4; i++)
{
parser.ReadLine();
}
// 读取数据
while (!parser.EndOfData)
{
string[] fields = parser.ReadFields();
if (fields.Length >= 2)
{
// 修复:先声明变量再在TryParse中使用
double depth;
double gamma;
// 假设第一列是深度,第二列是伽马值
if (double.TryParse(fields[2], out depth) &&
double.TryParse(fields[6], out gamma))
{
depthData.Add(depth);
gammaData.Add(gamma);
}
}
}
}
statusLabel.Text = $"已加载 {depthData.Count} 个数据点";
}
else
{
statusLabel.Text = "未选择文件,使用示例数据";
GenerateSampleData();
}
}
if (depthData.Count > 0)
{
minDepth = depthData.Min();
maxDepth = depthData.Max();
totalDepthRange = maxDepth - minDepth;
// 设置初始可见深度范围
CalculateVisibleDepthRange();
minVisibleDepth = minDepth;
maxVisibleDepth = minDepth + visibleDepthRange;
// 确保不超过最大深度
if (maxVisibleDepth > maxDepth)
{
maxVisibleDepth = maxDepth;
minVisibleDepth = maxDepth - visibleDepthRange;
if (minVisibleDepth < minDepth) minVisibleDepth = minDepth;
}
}
}
catch (Exception ex)
{
statusLabel.Text = $"错误: {ex.Message}";
GenerateSampleData();
}
}
private void GenerateSampleData()
{
// 生成示例数据
depthData.Clear();
gammaData.Clear();
double depth = 1000;
Random rand = new Random();
for (int i = 0; i < 500; i++)
{
depth += 0.5;
double gamma = 50 + 30 * Math.Sin(depth / 50) + rand.NextDouble() * 10;
depthData.Add(depth);
gammaData.Add(gamma);
}
// 设置示例数据的深度范围
minDepth = depthData.Min();
maxDepth = depthData.Max();
totalDepthRange = maxDepth - minDepth;
// 设置初始可见深度范围
CalculateVisibleDepthRange();
minVisibleDepth = minDepth;
maxVisibleDepth = minDepth + visibleDepthRange;
}
private void SetupPlotModel()
{
plotModel = new PlotModel
{
Title = "自然伽马测井曲线",
TitleFontSize = 14,
TitleFontWeight = OxyPlot.FontWeights.Bold,
PlotMargins = new OxyThickness(60, 40, 60, 60)
};
// 设置Y轴(深度)在右侧 - 固定网格间距
var depthAxis = new LinearAxis
{
Position = AxisPosition.Right,
Title = "深度 (m)",
Key = "Depth",
StartPosition = 1,
EndPosition = 0, // 反转Y轴
MajorGridlineStyle = LineStyle.Solid,
MinorGridlineStyle = LineStyle.Dot,
MajorGridlineColor = OxyColor.FromRgb(180, 180, 180),
MinorGridlineColor = OxyColor.FromRgb(220, 220, 220),
Minimum = minVisibleDepth,
Maximum = maxVisibleDepth,
// 固定网格间距:每5米主网格,每1米次网格
MajorStep = 5, // 固定值,不随窗体变化
MinorStep = 1, // 固定值
// 添加以下属性确保步长绝对不变
IsPanEnabled = false,
IsZoomEnabled = false,
AbsoluteMaximum = double.MaxValue,
AbsoluteMinimum = double.MinValue,
AxislineThickness = 2,
ExtraGridlineThickness = 2
};
plotModel.Axes.Add(depthAxis);
// 设置X轴(伽马值)在顶部
var gammaAxis = new LinearAxis
{
Position = AxisPosition.Top,
Title = "伽马值 (API)",
Key = "Gamma",
Minimum = 0,
Maximum = 200,
MajorStep = 20, // 10个刻度 (200/20=10)
MinorStep = 5,
MajorGridlineStyle = LineStyle.Solid,
//MinorGridlineStyle = LineStyle.Dot,
MajorGridlineColor = OxyColor.FromRgb(180, 180, 180),
//MinorGridlineColor = OxyColor.FromRgb(220, 220, 220),
AxislineThickness = 2
};
plotModel.Axes.Add(gammaAxis);
// 添加原始数据点
//var scatterSeries = new ScatterSeries
//{
// Title = "原始数据点",
// MarkerType = MarkerType.Circle,
// MarkerSize = 2,
// MarkerFill = OxyColor.FromRgb(100, 100, 100)
//};
//for (int i = 0; i < depthData.Count; i++)
//{
// scatterSeries.Points.Add(new ScatterPoint(gammaData[i], depthData[i]));
//}
//plotModel.Series.Add(scatterSeries);
plotView.Model = plotModel;
}
private void PlotView_MouseWheel(object sender, MouseEventArgs e)
{
// 计算滚轮方向
double direction = e.Delta > 0 ? -1 : 1;
double scrollAmount = direction * 1; // 每次滚动1米
// 更新可见深度范围
minVisibleDepth += scrollAmount;
maxVisibleDepth += scrollAmount;
// 确保不超出数据范围
if (minVisibleDepth < minDepth)
{
minVisibleDepth = minDepth;
maxVisibleDepth = minDepth + visibleDepthRange;
}
if (maxVisibleDepth > maxDepth)
{
maxVisibleDepth = maxDepth;
minVisibleDepth = maxDepth - visibleDepthRange;
}
// 更新滚动条位置
depthScrollBar.Value = (int)(minVisibleDepth - minDepth);
// 更新图表
UpdatePlot();
}
private void InterpolationComboBox_SelectedIndexChanged(object sender, EventArgs e)
{
UpdatePlot();
}
private void UpdatePlot()
{
if (depthData.Count == 0) return;
// 更新深度轴范围
var depthAxis = plotModel.Axes.FirstOrDefault(a => a.Key == "Depth") as LinearAxis;
if (depthAxis != null)
{
depthAxis.Minimum = minVisibleDepth;
depthAxis.Maximum = maxVisibleDepth;
}
// 移除之前的插值曲线
var seriesToRemove = plotModel.Series.Where(s => s is LineSeries).ToList();
foreach (var series in seriesToRemove)
{
plotModel.Series.Remove(series);
}
// 获取选择的插值方法
string method = interpolationComboBox.SelectedItem.ToString();
// 执行插值
IInterpolation interpolationFunc = CreateInterpolationFunction(depthData, gammaData, method);
// 创建插值曲线 - 使用正确的平滑设置
var lineSeries = new LineSeries
{
Title = $"{method}插值",
Color = OxyColor.FromRgb(0, 100, 200),
StrokeThickness = 1.5
};
// 根据插值方法选择合适的平滑算法
if (method == "线性插值")
{
// 线性插值不需要额外平滑
}
else
{
// 使用Catmull-Rom样条插值算法
lineSeries.InterpolationAlgorithm = InterpolationAlgorithms.CatmullRomSpline;
// 使用CanonicalSpline并设置张力参数(如果可用)
// lineSeries.InterpolationAlgorithm = InterpolationAlgorithms.CanonicalSpline;
// lineSeries.CanonicalSplineTension = 0.5;
}
// 添加当前可见范围内的点
double step = Math.Max(0.1, visibleDepthRange / 500);
for (double d = minVisibleDepth; d <= maxVisibleDepth; d += step)
{
double gamma = interpolationFunc.Interpolate(d);
lineSeries.Points.Add(new DataPoint(gamma, d));
}
plotModel.Series.Add(lineSeries);
plotModel.InvalidatePlot(true);
// 计算5米网格线对应的像素距离
double gridSpacingPixels = 5 * pixelsPerMeter;
statusLabel.Text = $"显示深度: {minVisibleDepth:F1}m - {maxVisibleDepth:F1}m ({visibleDepthRange:F1}m) | 总深度: {minDepth:F1}m - {maxDepth:F1}m | 插值: {method} | 比例: 1:{scaleRatio} | 网格间距: {gridSpacingPixels:F1}像素";
}
private Tuple<List<double>, List<double>> SmoothCurve(List<double> depth, List<double> gamma, string method)
{
// 去除重复深度值
var uniqueDepth = new List<double>();
var uniqueGamma = new List<double>();
var seen = new HashSet<double>();
for (int i = 0; i < depth.Count; i++)
{
if (seen.Contains(depth[i])) continue;
seen.Add(depth[i]);
uniqueDepth.Add(depth[i]);
uniqueGamma.Add(gamma[i]);
}
// 排序
var sorted = uniqueDepth.Select((d, i) => new { Depth = d, Gamma = uniqueGamma[i] })
.OrderBy(x => x.Depth)
.ToList();
var sortedDepth = sorted.Select(x => x.Depth).ToArray();
var sortedGamma = sorted.Select(x => x.Gamma).ToArray();
// 生成新的深度点(用于插值)
double minDepth = sortedDepth.Min();
double maxDepth = sortedDepth.Max();
int numPoints = 1000; // 插值点数
var xnew = Generate.LinearSpaced(numPoints, minDepth, maxDepth).ToList();
// 根据选择的插值方法进行插值
List<double> ynew;
IInterpolation interpolation = null;
switch (method)
{
case "线性插值":
interpolation = Interpolate.Linear(sortedDepth, sortedGamma);
break;
case "三次样条":
interpolation = Interpolate.CubicSpline(sortedDepth, sortedGamma);
break;
case "PCHIP":
interpolation = CubicSpline.InterpolatePchipSorted(sortedDepth, sortedGamma);
break;
case "Akima":
interpolation = CubicSpline.InterpolateAkima(sortedDepth, sortedGamma);
break;
default:
interpolation = Interpolate.Linear(sortedDepth, sortedGamma);
break;
}
ynew = xnew.Select(d => interpolation.Interpolate(d)).ToList();
return Tuple.Create(xnew, ynew);
}
private void ExportPdfButton_Click(object sender, EventArgs e)
{
if (depthData.Count == 0) return;
using (var saveFileDialog = new SaveFileDialog())
{
saveFileDialog.Filter = "PDF文件|*.pdf";
saveFileDialog.Title = "导出PDF";
saveFileDialog.FileName = "GammaLogCurve.pdf";
if (saveFileDialog.ShowDialog() == DialogResult.OK)
{
statusLabel.Text = "正在生成PDF...";
Application.DoEvents();
try
{
GeneratePdfReport(saveFileDialog.FileName);
statusLabel.Text = $"PDF已成功导出到: {saveFileDialog.FileName}";
}
catch (Exception ex)
{
statusLabel.Text = $"PDF导出错误: {ex.Message}";
}
}
}
}
private void GeneratePdfReport(string filePath)
{
try
{
statusLabel.Text = "正在准备PDF导出...";
Application.DoEvents();
// 获取plotPanel的宽度(365像素)
double panelWidth = plotPanel.Width;
// 计算每米对应的点(point)数(1点=1/72英寸)
double dotsPerMeter = pixelsPerMeter * (72.0 / dpi);
double minDepth = depthData.Min();
double maxDepth = depthData.Max();
double depthRange = maxDepth - minDepth;
// 设置横轴宽度为plotPanel的宽度(转换为点)
double gammaAxisWidth = panelWidth * (72.0 / dpi);
// 保持页面宽度不变(与之前相同)
double pageWidth = 500 + 100; // 伽马轴宽度500点 + 左右边距100点
// 计算图形在页面中的起始位置(居左显示)
double graphStartX = 40; // 左边距40点
// 创建PDF文档
var document = new PdfDocument();
document.Info.Title = "自然伽马测井曲线报告";
document.Info.Author = "测井曲线可视化工具";
// 分页参数
const double maxPageHeight = 14400; // 200英寸 * 72点/英寸
double currentStartDepth = minDepth;
int pageIndex = 0;
int maxPages = 1000; // 安全限制,防止无限循环
bool isLastPage = false;
// 获取插值方法
string method = interpolationComboBox.SelectedItem.ToString();
// 创建整个深度范围的插值函数
IInterpolation interpolationFunc = CreateInterpolationFunction(depthData, gammaData, method);
// 计算总页数(用于页码显示)
int totalPages = (int)Math.Ceiling(depthRange / (maxPageHeight / dotsPerMeter));
while (pageIndex < maxPages && !isLastPage)
{
statusLabel.Text = $"正在生成第 {pageIndex + 1} 页...";
Application.DoEvents();
// 计算当前页的深度范围
double currentPageDepthRange = maxPageHeight / dotsPerMeter;
double currentEndDepth = currentStartDepth + currentPageDepthRange;
// 检查是否是最后一页
if (currentEndDepth >= maxDepth - 0.001) // 使用容差避免浮点误差
{
currentEndDepth = maxDepth;
isLastPage = true;
}
// 计算当前页实际高度
double pageHeight = (currentEndDepth - currentStartDepth) * dotsPerMeter;
// 创建页面(动态高度)
var page = document.AddPage();
page.Width = pageWidth;
page.Height = pageHeight;
page.Orientation = PdfSharp.PageOrientation.Portrait;
using (var xgraphics = XGraphics.FromPdfPage(page))
{
// 设置边距
double margin = 40;
double topMargin = 0; // 上边距为0
double bottomMargin = 0; // 下边距为0
double usableHeight = page.Height - topMargin - bottomMargin;
// 计算比例因子
double gammaRange = 200; // 伽马值范围0-200
double gammaScale = gammaAxisWidth / gammaRange;
double depthScale = usableHeight / (currentEndDepth - currentStartDepth);
// 仅在第一页绘制标题
if (pageIndex == 0)
{
var titleFont = new XFont("Arial", 16, XFontStyle.Bold);
xgraphics.DrawString("自然伽马测井曲线", titleFont, XBrushes.Navy,
new XPoint(page.Width / 2, 10),
new XStringFormat { Alignment = XStringAlignment.Center });
}
// 绘制坐标轴框架 - 居左显示
var axisPen = new XPen(XColors.Black, 1.5);
double leftAxisX = graphStartX + 30; // 左边距+标签空间
double rightAxisX = leftAxisX + gammaAxisWidth;
// Y轴(深度)从顶部到底部边缘
xgraphics.DrawLine(axisPen,
leftAxisX, topMargin,
leftAxisX, topMargin + usableHeight);
// X轴(伽马值)- 只在第一页绘制顶部轴
if (pageIndex == 0)
{
xgraphics.DrawLine(axisPen,
leftAxisX, topMargin,
rightAxisX, topMargin);
}
// 绘制网格线 - 深度(Y轴)延伸到页面底部
var gridPen = new XPen(XColors.LightGray, 0.3);
var majorGridPen = new XPen(XColors.Gray, 0.6);
// 计算起始网格深度
double startGridDepth = Math.Floor(currentStartDepth);
double endGridDepth = Math.Ceiling(currentEndDepth);
for (double i = startGridDepth; i <= endGridDepth; i += 1)
{
double y = topMargin + (i - currentStartDepth) * depthScale;
// 确保y在页面范围内
if (y < topMargin || y > topMargin + usableHeight) continue;
// 每5米主网格
if (i % 5 == 0)
{
xgraphics.DrawLine(majorGridPen,
leftAxisX, y,
rightAxisX, y);
// 主刻度标签
var labelFont = new XFont("Arial", 8, XFontStyle.Bold);
xgraphics.DrawString($"{i:F1}", labelFont, XBrushes.Black,
new XPoint(leftAxisX - 5, y),
new XStringFormat
{
Alignment = XStringAlignment.Far,
LineAlignment = XLineAlignment.Center
});
}
else // 1米次网格
{
xgraphics.DrawLine(gridPen,
leftAxisX, y,
rightAxisX, y);
}
}
// 绘制网格线 - 伽马值(X轴)每20单位,延伸到页面底部
for (int i = 0; i <= 10; i++)
{
double gammaValue = i * 20;
double x = leftAxisX + gammaValue * gammaScale;
xgraphics.DrawLine(majorGridPen,
x, topMargin,
x, topMargin + usableHeight);
// 刻度标签(顶部)- 只在第一页绘制
if (pageIndex == 0)
{
var labelFont = new XFont("Arial", 8);
xgraphics.DrawString($"{gammaValue}", labelFont, XBrushes.Black,
new XPoint(x, topMargin - 10),
new XStringFormat
{
Alignment = XStringAlignment.Center,
LineAlignment = XLineAlignment.Far
});
}
}
// 绘制原始数据点(仅当前页范围内的点)
var pointPen = new XPen(XColors.Gray, 0.5);
for (int i = 0; i < depthData.Count; i++)
{
double depth = depthData[i];
double gamma = gammaData[i];
if (depth >= currentStartDepth && depth <= currentEndDepth)
{
double x = leftAxisX + gamma * gammaScale;
double y = topMargin + (depth - currentStartDepth) * depthScale;
// 只绘制在有效伽马值范围内的点
if (gamma >= 0 && gamma <= 200)
{
xgraphics.DrawEllipse(pointPen, x - 0.5, y - 0.5, 1, 1);
}
}
}
// ===== 平滑曲线绘制部分 =====
var curvePen = new XPen(XColor.FromArgb(0, 100, 200), 1.5);
// 收集当前页所有有效点
var validPoints = new List<XPoint>();
// 按0.1米步长生成当前页深度范围内的点
double step = 0.1;
double d = currentStartDepth;
while (d <= currentEndDepth)
{
double gamma = interpolationFunc.Interpolate(d);
if (gamma >= 0 && gamma <= 200) // 只处理有效范围内的点
{
double x = leftAxisX + gamma * gammaScale;
double y = topMargin + (d - currentStartDepth) * depthScale;
validPoints.Add(new XPoint(x, y));
}
d += step;
}
// 使用直线连接密集的点(点足够密时看起来是平滑的)
if (validPoints.Count >= 2)
{
xgraphics.DrawLines(curvePen, validPoints.ToArray());
}
// ===== 结束平滑曲线绘制部分 =====
// 添加坐标轴标签
var axisFont = new XFont("Arial", 10, XFontStyle.Bold);
// 深度标签(垂直文本)
var depthLabelState = xgraphics.Save();
xgraphics.TranslateTransform(graphStartX, topMargin + usableHeight / 2);
xgraphics.RotateTransform(-90);
xgraphics.DrawString("深度 (m)", axisFont, XBrushes.Black,
0, 0,
new XStringFormat
{
Alignment = XStringAlignment.Center,
LineAlignment = XLineAlignment.Center
});
xgraphics.Restore(depthLabelState);
// 伽马值标签 - 只在第一页绘制
if (pageIndex == 0)
{
xgraphics.DrawString("伽马值 (API)", axisFont, XBrushes.Black,
new XPoint(leftAxisX + gammaAxisWidth / 2, topMargin - 20),
new XStringFormat { Alignment = XStringAlignment.Center });
}
}
// 更新下一页的起始深度
currentStartDepth = currentEndDepth;
pageIndex++;
// 终止条件检查
if (currentStartDepth >= maxDepth - 0.001 || pageIndex >= maxPages)
{
isLastPage = true;
}
}
// 保存PDF
document.Save(filePath);
statusLabel.Text = $"PDF导出完成,共 {pageIndex} 页";
}
catch (Exception ex)
{
statusLabel.Text = $"PDF导出错误: {ex.Message}";
MessageBox.Show($"导出PDF时发生错误:\n{ex.Message}", "错误",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
// 创建插值函数的方法
private IInterpolation CreateInterpolationFunction(List<double> depth, List<double> gamma, string method)
{
// 去除重复深度值
var uniqueDepth = new List<double>();
var uniqueGamma = new List<double>();
var seen = new HashSet<double>();
for (int i = 0; i < depth.Count; i++)
{
if (seen.Contains(depth[i])) continue;
seen.Add(depth[i]);
uniqueDepth.Add(depth[i]);
uniqueGamma.Add(gamma[i]);
}
// 排序
var sorted = uniqueDepth.Select((d, i) => new { Depth = d, Gamma = uniqueGamma[i] })
.OrderBy(x => x.Depth)
.ToList();
var sortedDepth = sorted.Select(x => x.Depth).ToArray();
var sortedGamma = sorted.Select(x => x.Gamma).ToArray();
// 根据选择的插值方法进行插值
IInterpolation interpolation = null;
switch (method)
{
case "线性插值":
interpolation = Interpolate.Linear(sortedDepth, sortedGamma);
break;
case "三次样条":
interpolation = Interpolate.CubicSpline(sortedDepth, sortedGamma);
break;
case "PCHIP":
interpolation = CubicSpline.InterpolatePchipSorted(sortedDepth, sortedGamma);
break;
case "Akima":
try
{
// 尝试使用Akima插值(如果可用)
interpolation = CubicSpline.InterpolateAkima(sortedDepth, sortedGamma);
}
catch
{
// 如果Akima不可用,回退到PCHIP
interpolation = CubicSpline.InterpolatePchipSorted(sortedDepth, sortedGamma);
}
break;
default:
interpolation = Interpolate.Linear(sortedDepth, sortedGamma);
break;
}
return interpolation;
}
}
}上述代码中,当改变窗体高度时,Y轴的最大值发生了变化,显示的网格数也发生了变化,这是正确的,但是在电脑屏幕上看到的剩余网格线的间距也发生了变化,这是不希望发生的。我们希望发生的是当改变窗体高度时,Y轴的最大值发生了变化,显示的网格数也发生变化,但是在电脑屏幕上看到的剩余网格线的间距不发生任何的改变,即剩余显示的所有参数的相对位置都不发生改变。
最新发布