编辑
2025-02-14
C# 应用
00
请注意,本文编写于 81 天前,最后修改于 81 天前,其中某些信息可能已经过时。

目录

概述
完整代码实现
三次样条插值核心类
主窗体实现
3. 关键功能说明
随机数据生成
动画实现
使用说明
总结

概述

‌**三次样条插值(Cubic Spline Interpolation)**‌是一种通过一系列形值点构造一条光滑曲线的数学方法。它使用分段三次多项式来逼近原始数据点,从而在保持数据点的同时,使得整个曲线在各段内光滑连续。

本文将实现一个动态的三次样条插值可视化程序,包含以下功能:

  • 随机生成折线图数据
  • 点击按钮后动画显示平滑的三次样条曲线
  • 完整的GDI+绘图实现
  • 动画效果的实现

完整代码实现

image.png

三次样条插值核心类

C#
public class CubicSpline { private readonly double[] _x; private readonly double[] _y; private readonly double[] _a; private readonly double[] _b; private readonly double[] _c; private readonly double[] _d; public CubicSpline(double[] x, double[] y) { if (x == null || y == null || x.Length != y.Length || x.Length < 2) throw new ArgumentException("Invalid input data"); _x = x; _y = y; int n = x.Length; _a = new double[n]; _b = new double[n - 1]; _c = new double[n]; _d = new double[n - 1]; CalculateCoefficients(); } private void CalculateCoefficients() { int n = _x.Length; double[] h = new double[n - 1]; double[] alpha = new double[n - 1]; for (int i = 0; i < n - 1; i++) { h[i] = _x[i + 1] - _x[i]; _a[i] = _y[i]; } _a[n - 1] = _y[n - 1]; for (int i = 1; i < n - 1; i++) { alpha[i] = 3 / h[i] * (_y[i + 1] - _y[i]) - 3 / h[i - 1] * (_y[i] - _y[i - 1]); } double[] l = new double[n]; double[] mu = new double[n]; double[] z = new double[n]; l[0] = 1; mu[0] = 0; z[0] = 0; for (int i = 1; i < n - 1; i++) { l[i] = 2 * (_x[i + 1] - _x[i - 1]) - h[i - 1] * mu[i - 1]; mu[i] = h[i] / l[i]; z[i] = (alpha[i] - h[i - 1] * z[i - 1]) / l[i]; } l[n - 1] = 1; z[n - 1] = 0; _c[n - 1] = 0; for (int j = n - 2; j >= 0; j--) { _c[j] = z[j] - mu[j] * _c[j + 1]; _b[j] = (_y[j + 1] - _y[j]) / h[j] - h[j] * (_c[j + 1] + 2 * _c[j]) / 3; _d[j] = (_c[j + 1] - _c[j]) / (3 * h[j]); } } public double Interpolate(double x) { int i = 0; while (i < _x.Length - 2 && x > _x[i + 1]) i++; double dx = x - _x[i]; return _a[i] + _b[i] * dx + _c[i] * dx * dx + _d[i] * dx * dx * dx; } }

主窗体实现

C#
public partial class Form2 : Form { private List<Point> _dataPoints; private List<Point> _splinePoints; private float _animationProgress; private Timer _animationTimer; private bool _isAnimating; private CubicSpline _spline; public Form2() { InitializeComponent(); this.DoubleBuffered = true; GenerateRandomData(); CalculateSplinePoints(); _animationTimer = new Timer { Interval = 60 }; _animationTimer.Tick += AnimationTimer_Tick; this.Paint += Form1_Paint; } private void AnimationTimer_Tick(object sender, EventArgs e) { _animationProgress += 0.02f; if (_animationProgress >= 1) { _animationProgress = 1; _animationTimer.Stop(); _isAnimating = false; } this.Invalidate(); } private void GenerateRandomData() { Random rand = new Random(); _dataPoints = new List<Point>(); // 生成20个随机点 int pointCount = 20; int margin = 50; int stepX = (this.ClientSize.Width - 2 * margin) / (pointCount - 1); for (int i = 0; i < pointCount; i++) { int x = margin + i * stepX; int y = rand.Next(margin, this.ClientSize.Height - margin); _dataPoints.Add(new Point(x, y)); } } private void CalculateSplinePoints() { double[] x = _dataPoints.Select(p => (double)p.X).ToArray(); double[] y = _dataPoints.Select(p => (double)p.Y).ToArray(); _spline = new CubicSpline(x, y); _splinePoints = new List<Point>(); int steps = (_dataPoints[_dataPoints.Count - 1].X - _dataPoints[0].X); for (int i = 0; i <= steps; i++) { double xVal = _dataPoints[0].X + i; double yVal = _spline.Interpolate(xVal); _splinePoints.Add(new Point((int)xVal, (int)yVal)); } } private void Form1_Paint(object sender, PaintEventArgs e) { Graphics g = e.Graphics; g.SmoothingMode = SmoothingMode.AntiAlias; // 绘制网格 DrawGrid(g); // 绘制原始折线 using (Pen linePen = new Pen(Color.Gray, 2)) { g.DrawLines(linePen, _dataPoints.ToArray()); } // 绘制数据点 using (Brush pointBrush = new SolidBrush(Color.Blue)) { foreach (var point in _dataPoints) { g.FillEllipse(pointBrush, point.X - 4, point.Y - 4, 8, 8); } } // 绘制样条曲线(动画) if (_isAnimating || _animationProgress > 0) { using (Pen splinePen = new Pen(Color.Red, 2)) { int pointCount = (int)(_splinePoints.Count * _animationProgress); if (pointCount > 1) { g.DrawLines(splinePen, _splinePoints.Take(pointCount).ToArray()); } } } } private void DrawGrid(Graphics g) { using (Pen gridPen = new Pen(Color.LightGray, 1)) { // 绘制垂直线 for (int x = 0; x < this.ClientSize.Width; x += 50) { g.DrawLine(gridPen, x, 0, x, this.ClientSize.Height); } // 绘制水平线 for (int y = 0; y < this.ClientSize.Height; y += 50) { g.DrawLine(gridPen, 0, y, this.ClientSize.Width, y); } } } private void btnStart_Click(object sender, EventArgs e) { if (!_isAnimating) { _isAnimating = true; _animationProgress = 0; _animationTimer.Start(); } } }

3. 关键功能说明

随机数据生成

C#
private void GenerateRandomData() { Random rand = new Random(); int margin = 50; int stepX = (this.ClientSize.Width - 2 * margin) / (pointCount - 1); for (int i = 0; i < pointCount; i++) { int x = margin + i * stepX; int y = rand.Next(margin, this.ClientSize.Height - margin); _dataPoints.Add(new Point(x, y)); } }

动画实现

C#
private void AnimationTimer_Tick(object sender, EventArgs e) { _animationProgress += 0.02f; if (_animationProgress >= 1) { _animationProgress = 1; _animationTimer.Stop(); _isAnimating = false; _animateButton.Enabled = true; } this.Invalidate(); }

使用说明

  1. 运行程序后,会显示随机生成的折线图
  2. 点击"显示平滑曲线"按钮,将会动画显示三次样条插值的平滑曲线
  3. 动画完成后可以再次点击按钮重新显示

总结

这个示例展示了如何在C#中实现动态的三次样条插值可视化。通过合理使用GDI+和定时器,我们实现了平滑的动画效果。代码结构清晰,易于扩展和维护。您可以根据实际需求进行修改和优化。

本文作者:rick

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!