echarts可以将离散点绘制成光滑曲线,那么是否能够根据该曲线拟合出任意x位置的y值呢?
首先我们看这个拟合值是否有意义——光滑曲线往往比折线更接近离散点的实际分布,比如以sin函数正态分布的点,echarts最终绘制出来的曲线和该sin函数的拟合度非常高,因此我们可以一定程度上参考该曲线拟合值。
假设能找到该echarts曲线的函数,那么就可以求解出任意拟合值了。要找到这个函数,首先要理解echarts是如何绘制曲线的。
echarts是如何绘制曲线的
ecahrts曲线绘制方法在源码src/chart/line/poly.ts中,
function drawSegment(
ctx: PathProxy,
points: ArrayLike<number>,
start: number,
segLen: number,
allLen: number,
dir: number,
smooth: number,
smoothMonotone: "x" | "y" | "none",
connectNulls: boolean
) {
...
ctx[dir > 0 ? "moveTo" : "lineTo"](x, y);
...
ctx.bezierCurveTo(cpx0, cpy0, cpx1, cpy1, x, y);
...
ctx.lineTo(x, y);
}
return k;
}
显然它调用了canvas的贝塞尔曲线绘制。
canvas 绘制曲线
bezierCurveTo方法用于将三次贝赛尔曲线添加到当前子路径中。该方法需要三个点:前两个点是控制点,第三个点是结束点。起始点是当前路径的最后一个点。
bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)
由此我们知道了echarts的光滑曲线是三次贝塞尔曲线,也即获得了该曲线的函数:
那么接下来的任务就是如何获取x位置的控制点了。
如何获取目标x位置所在曲线的控制点
观察drawSegment,可以看到该函数通过传入的ctx参数绘制曲线,那么我们只要将ctx换成一个收集对象,再模拟曲线绘制,就能得到所有的曲线路径了。
收集对象关键代码如下:
const pathData: PathCMD = [];
const ctx = {
moveTo: (x: number, y: number) => {
pathData.push([CMD.M, [x, y]]);
},
lineTo: (x: number, y: number) => {
pathData.push([CMD.L, [x, y]]);
},
bezierCurveTo: (cp1x: number, cp1y: number, cp2x: number, cp2y: number, x: number, y: number) => {
pathData.push([CMD.C, [cp1x, cp1y], [cp2x, cp2y], [x, y]]);
},
} as unknown as CanvasRenderingContext2D;
之后只要遍历pathData,就能找到x所在的线段的绘制命令以及控制点了。
到此我们就只剩下一个问题没有解决了——如何求解三次贝塞尔方程。
如何求解三次贝塞尔方程
针对三次贝塞尔方程:
已知P0,P1,P2,P3四个控制点的x和y值,以及目标B(t)的x值,求B(t)的y值。要求解这个函数,首先需要求解出t值。
我们将x带入,得到一个一元三次方程:
如何求解t值
由于t值的取值范围为0~1,因此我们可以在某个精度下多次迭代推测t的值。比如使用二分法和牛顿迭代法。
牛顿迭代法简介
牛顿迭代法的优点是收敛快,不过在实测中求解速度并不如二分法快。
不理解牛顿迭代法也没有关系,因为我也没有采用这个方法~
回到方程本身,那么一元三次方程有没有类似一元二次方程的通用求根公式呢?有的。
一元三次方程的通用解法
一元三次方程的标准形式为:
通过变量代换 ,可消去二次项,将方程化为 缺省型三次方程: 其中 。
针对该弱三次方程,16实际卡尔达诺就找到了通用解法,即卡尔达诺公式。但相比该公式,我们可以使用更简洁的盛金公式求解t值。
盛金公式介绍
盛金公式通过计算 判别式 A、B、C 和 总判别式 来判断根的情况:
-
判别式定义:
-
根的分类与对应公式:
- 情况 1: (有一个实根和两个共轭虚根)
- 情况 2: (有三个实根,其中至少两个相等)
- 情况 3: (有三个互不相等的实根)
判断规则如下:
1. 当 时
- 实根公式: 其中:
- 虚根可通过共轭复数形式表示,需注意 和 取实数根。
2. 当 时
- 若 (三重实根):
- 若 (有一个二重实根和一个单实根):
3. 当 时(三个实根,需用三角函数表示)
- 令 ,其中 (此处 ),则:
最后
至此,我们得到了echarts曲线任意x位置的y值的拟合方法。
在找求解方法的过程中还有很多非常有趣的点,比如
- echarts是如何保证曲线光滑的(光滑曲线的数学证明)
- 贝塞尔曲线的特性和公式的推导过程
- 牛顿迭代法的原理和缺陷
- 卡尔达诺本人以及卡尔达诺公式的证明
上学时老师讲的数学之美,在我毕业10年后终于能够理解到了一些。