1、如何通过给定的散点拟合出一条直线
2、用XChart把散点和拟合直线画出来
一、要实现的效果
有六个散点,如下所示:
6个散点坐标值:
var X = new List<double> { 5, 10, 20, 32, 15, 50 };
var Y = new List<double> { -20, -8, 6, 48, 9, 7 };
如何把上面的散点拟合出一条直线方程:
二、直线拟合的实现
- 1、用到的包
using MathNet.Numerics;
-2、用到的方法 Fit.Line(X,Y)
var s = Fit.Line(X, Y); //XY为相同长度的double数组
double b = s.Item1; //截距
double k = s.Item2; //斜率
则直线方程为:y = kx + b
测试代码:
#if UNITY_EDITOR
[ContextMenu("直线拟合操作")]
#endif
void FitLineTest()
{
Tuple<double, double> s = new Tuple<double, double>(0, 0);
double[] X = new double[] { 1, 2, 3, 4, 5 };
double[] Y = new double[] { 3, 2, 5, 4, 6 };
s = Fit.Line(X, Y);
double b = s.Item1; //截距
double k = s.Item2; //斜率
Debug.Log($"拟合的直线方程为 : y = {k} * x + {b}");
}
输出结果:
拟合的直线方程为 : y = 0.8 * x + 1.6
三、用XChart画散点和拟合直线的过程
四、代码清单
备注:右键菜单测试
using MathNet.Numerics;
using System;
using System.Collections.Generic;
using UnityEngine;
using XCharts.Runtime;
using System.Linq;
using Cysharp.Threading.Tasks;
using Unity.Mathematics;
/// <summary>
/// 散点拟合直线
/// </summary>
public class FitLine : MonoBehaviour
{
public GameObject charRoot;
#if UNITY_EDITOR
[ContextMenu("直线拟合操作")]
#endif
void FitLineTest()
{
Tuple<double, double> s = new Tuple<double, double>(0, 0);
double[] X = new double[] { 1, 2, 3, 4, 5 };
double[] Y = new double[] { 3, 2, 5, 4, 6 };
s = Fit.Line(X, Y);
double b = s.Item1; //截距
double k = s.Item2; //斜率
Debug.Log($"拟合的直线方程为 : y = {k} * x + {b}");
}
#if UNITY_EDITOR
[ContextMenu("画散点图并拟合一条直线")]
#endif
void DrawLine()
{
Flow();
}
private async UniTask Flow()
{
//【1】=============画散点图=============
var scatter = charRoot.AddComponent<ScatterChart>();
scatter.Init();//初始化极为重要,不然在running状态会报空
var title = scatter.GetOrAddChartComponent<Title>();
title.text = "用给定的散点拟合出一条直线"; //主标题
await UniTask.Delay(TimeSpan.FromSeconds(2.0f));
scatter.SetSize(1024, 768);
scatter.AddSerie<Serie>("数据分布图");
Debug.Log($"数据长度为:{scatter.series[0].data.Count}"); //默认自带10个数据,需要删除
scatter.series[0].data.Clear();
await UniTask.Delay(TimeSpan.FromSeconds(2.0f));
var X = new List<double> { 5, 10, 20, 32, 15, 50 };
var Y = new List<double> { -20, -8, 6, 48, 9, 7 };
var test = X.Zip(Y, (x, y) => new List<double> { x, y }).ToList();
X.Zip(Y, (x, y) => new List<double> { x, y }).ToList().ForEach(t =>
{
//Debug.Log($"添加数据:{t[0]} => {t[1]}");
scatter.AddData(0, t);
});
scatter.series[0].symbol.size = 9;//设置大小
//【2】=============直线拟合计算=============
await UniTask.Delay(TimeSpan.FromSeconds(2.0f));
var s = Fit.Line(X.ToArray(), Y.ToArray());
double b = s.Item1; //截距
double k = s.Item2; //斜率
//【3】=============画拟合出来的直线=============
//【11】画拟合的直线
var line1 = scatter.AddChartComponent<MarkLine>(); //添加一根直线
line1.data.Clear();//清空默认值,添加组的时候,会默认包含一个item
//增加一个端点数据
await UniTask.Delay(TimeSpan.FromSeconds(2.0f));
var p1 = new MarkLineData();
p1.type = MarkLineType.None;
p1.group = 1;
p1.xPosition = 0;
p1.yPosition = 0;
p1.xValue = X.Min();
p1.yValue = X.Min() * k + b;
p1.zeroPosition = false;
//标注【直线方程式】
await UniTask.Delay(TimeSpan.FromSeconds(2.0f));
var op = b > 0 ? "+" : "-";
var kStr = Math.Round(k,2, MidpointRounding.AwayFromZero).ToString();
var bStr = Math.Round(math.abs(b), 2, MidpointRounding.AwayFromZero).ToString();
p1.name = $"y = {kStr} * x {op}{bStr}";
p1.label.formatter = "{b}";
//直线的形状设置
p1.lineStyle.type = LineStyle.Type.Solid;
p1.startSymbol.type = SymbolType.None;
p1.endSymbol.type = SymbolType.None;
//添加第二个端点数据
var p2 = new MarkLineData();
p2.type = MarkLineType.None;
p2.group = 1;
p2.xPosition = 0;
p2.yPosition = 0;
p2.xValue = X.Max();
p2.yValue = X.Max() * k + b;
p2.zeroPosition = false;
p2.name = $"y = {kStr} * x {op}{bStr}"; ;//"y = 0 * x + 15";
p2.label.formatter = "{b}";
Debug.Log($"y = {kStr} * x {op}{bStr}");
//直线的形状设置
p2.lineStyle.type = LineStyle.Type.Solid;
p2.startSymbol.type = SymbolType.None;
p2.endSymbol.type = SymbolType.None;
//端点数据加入直线中
line1.data.Add(p1);
line1.data.Add(p2);
scatter.RefreshAllComponent();
}
}