Polynomial Curve Fitting and Rendering with MathNet.Numerics and SkiaSharp

// Perform polynomial fit using the given discrete points
var xCoords = _originalPoints.Select(p => (double)p.X).ToArray();
var yCoords = _originalPoints.Select(p => (double)p.Y).ToArray();
int maxOrder = Math.Min(5, xCoords.Length - 1);
double[] coefficients = Fit.Polynomial(xCoords, yCoords, maxOrder);

Polynomial evalutaion follows the form c0 + c1*x + c2*x^2 + ... + ck*x^k. A method can be written to compute this efficiently with out explicit iteratoin over powers:

static double EvaluatePolynomial(double[] coeffs, double x)
{
    double result = 0.0;
    double power = 1.0;
    foreach (double c in coeffs)
    {
        result += c * power;
        power *= x;
    }
    return result;
}

To generate points along the fitted curve over the drawing area width:

_fittedPoints.Clear();
int step = 5;
for (int px = 0; px <= width; px += step)
{
    double yVal = EvaluatePolynomial(coefficients, px);
    _fittedPoints.Add(new SKPoint(px, (float)yVal));
}

Calculate R² to quantify fit quality:

double[] predictedY = _originalPoints
    .Select(p => EvaluatePolynomial(coefficients, p.X))
    .ToArray();
double rSq = GoodnessOfFit.RSquared(yCoords, predictedY);

The complete Windows Forms example integrates SkiaSharp control and MathNet:

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using MathNet.Numerics;
using SkiaSharp;
using SkiaSharp.Views.Desktop;

public class CurveFitForm : Form
{
    private readonly Point[] _rawPoints =
    {
        new Point(16, 60),
        new Point(40, 20),
        new Point(52, 47),
        new Point(63, 85),
        new Point(87, 26)
    };

    private List<SKPoint> _originalPts = new();
    private List<SKPoint> _fittedPts = new();
    private SKControl _canvas;
    private Panel _container;
    private double _rSquared;
    private int _polyOrder;

    public CurveFitForm()
    {
        _container = new Panel();
        _canvas = new SKControl { Dock = DockStyle.Fill };
        _canvas.PaintSurface += OnPaintSurface;
        _container.Controls.Add(_canvas);
        Controls.Add(_container);
        Load += (_, _) => { FitAndDraw(); };
        SizeChanged += (_, _) => { LayoutCanvas(); FitAndDraw(); };
    }

    private void LayoutCanvas()
    {
        _container.Left = 10;
        _container.Top = 10;
        _container.Width = ClientSize.Width - 20;
        _container.Height = ClientSize.Height - 20;
    }

    private void FitAndDraw()
    {
        float w = _container.Width;
        float h = _container.Height;
        _originalPts = _rawPoints.Select(p =>
            new SKPoint(p.X / 100f * w, (100 - p.Y) / 100f * h)).ToList();

        double[] xs = _originalPts.Select(p => (double)p.X).ToArray();
        double[] ys = _originalPts.Select(p => (double)p.Y).ToArray();
        _polyOrder = Math.Min(5, xs.Length - 1);
        double[] coeffs = Fit.Polynomial(xs, ys, _polyOrder);

        _fittedPts.Clear();
        for (int x = 0; x <= w; x += 5)
        {
            double y = EvaluatePolynomial(coeffs, x);
            _fittedPts.Add(new SKPoint(x, (float)y));
        }

        double[] predicted = _originalPts
            .Select(p => EvaluatePolynomial(coeffs, p.X)).ToArray();
        _rSquared = GoodnessOfFit.RSquared(ys, predicted);

        _canvas.Refresh();
    }

    static double EvaluatePolynomial(double[] c, double x)
    {
        double sum = 0;
        double xpow = 1;
        foreach (double coeff in c)
        {
            sum += coeff * xpow;
            xpow *= x;
        }
        return sum;
    }

    private void OnPaintSurface(object sender, SKPaintSurfaceEventArgs e)
    {
        SKCanvas canvas = e.Surface.Canvas;
        canvas.Clear(new SKColor(211, 211, 211));
        DrawFittedCurve(canvas);
        DrawDataPoints(canvas);
        DrawStatsText(canvas);
    }

    private void DrawFittedCurve(SKCanvas canvas)
    {
        if (_fittedPts.Count == 0) return;
        using SKPaint paint = new()
        {
            Style = SKPaintStyle.Stroke,
            StrokeWidth = 3,
            IsAntialias = true,
            StrokeCap = SKStrokeCap.Round,
            Color = SKColors.Aqua
        };
        using SKPath path = new();
        path.MoveTo(_fittedPts[0]);
        for (int i = 1; i < _fittedPts.Count; i++)
            path.LineTo(_fittedPts[i]);
        canvas.DrawPath(path, paint);
    }

    private void DrawDataPoints(SKCanvas canvas)
    {
        using SKPaint paint = new()
        {
            Style = SKPaintStyle.Stroke,
            StrokeWidth = 8,
            IsAntialias = true,
            StrokeCap = SKStrokeCap.Round,
            Color = SKColors.Red
        };
        foreach (var pt in _originalPts)
            canvas.DrawPoint(pt, paint);
    }

    private void DrawStatsText(SKCanvas canvas)
    {
        using SKPaint paint = new()
        {
            Color = SKColors.Black,
            IsAntialias = true,
            TextSize = 16
        };
        string msg = $"{_polyOrder}‑order fit, R² = {_rSquared:F4}";
        canvas.DrawText(msg, 5, paint.TextSize + 5, paint);
    }
}

Tags: MathNet.Numerics SkiaSharp curve fitting Windows Forms polynomial

Posted on Tue, 19 May 2026 20:07:01 +0000 by jpmm76