**三次样条插值(Cubic Spline Interpolation)**是一种通过一系列形值点构造一条光滑曲线的数学方法。它使用分段三次多项式来逼近原始数据点,从而在保持数据点的同时,使得整个曲线在各段内光滑连续。
本文将实现一个动态的三次样条插值可视化程序,包含以下功能:
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();
}
}
}
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();
}
这个示例展示了如何在C#中实现动态的三次样条插值可视化。通过合理使用GDI+和定时器,我们实现了平滑的动画效果。代码结构清晰,易于扩展和维护。您可以根据实际需求进行修改和优化。
本文作者:rick
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!