vislite
Version:
灵活、快速、简单的数据可视化交互式跨端前端库
111 lines (90 loc) • 4.15 kB
text/typescript
/**
* Cardinal三次插值
* ----------------------------
* Hermite拟合的计算是,确定两个点和两个点的斜率
* 用一个y=ax(3)+bx(2)+cx+d的三次多项式来求解
* 而Cardinal是建立在此基础上
* 给定需要拟合的两个点和第一个点的前一个点+最后一个点的后一个点
* 第一个点的斜率由第一个点的前一个点和第二个点的斜率确定
* 第二个点的斜率由第一个点和第二个点的后一个点的斜率确定
*/
import type CardinalType from '../../types/Cardinal'
import Hermite from './Hermite'
class Cardinal implements CardinalType {
readonly name: string = 'Cardinal'
// 该参数用于调整曲线走势,默认数值t=0,分水岭t=-1,|t-(-1)|的值越大,曲线走势调整的越严重
private __t: number
private __HS: {
x: number[],
h: Hermite[]
}
private __i: number
constructor(t: number = 0) {
this.__t = t
}
// 设置点的位置
// 参数格式:[[x1, y1], [x2, y2], ... ]
// 至少两个点
setP(points: number[][]) {
this.__HS = {
"x": [],
"h": []
};
let flag: number,
slope = (points[1][1] - points[0][1]) / (points[1][0] - points[0][0]),
temp: number
this.__HS.x[0] = points[0][0]
for (flag = 1; flag < points.length; flag++) {
if (points[flag][0] <= points[flag - 1][0]) throw new Error('The point position should be increamented!')
this.__HS.x[flag] = points[flag][0]
// 求点斜率
// temp = flag < points.length - 1 ?
// (points[flag + 1][1] - points[flag - 1][1]) / (points[flag + 1][0] - points[flag - 1][0]) :
// (points[flag][1] - points[flag - 1][1]) / (points[flag][0] - points[flag - 1][0])
// 修复超过界限的值问题
// 2023年6月25日 于南京
if (flag < points.length - 1) {
if (
(points[flag + 1][1] > points[flag][1] && points[flag - 1][1] > points[flag][1]) ||
(points[flag + 1][1] < points[flag][1] && points[flag - 1][1] < points[flag][1]) ||
points[flag + 1][1] == points[flag][1] ||
points[flag - 1][1] == points[flag][1]
) {
temp = 0
} else {
temp = (points[flag + 1][1] - points[flag - 1][1]) / (points[flag + 1][0] - points[flag - 1][0])
}
} else {
temp = (points[flag][1] - points[flag - 1][1]) / (points[flag][0] - points[flag - 1][0])
}
// 求解两个点直接的插值方程
// 第一个点的前一个点直接取第一个点
// 最后一个点的后一个点直接取最后一个点
this.__HS.h[flag - 1] = new Hermite((1 - this.__t) * 0.5).setP(points[flag - 1][0], points[flag - 1][1], points[flag][0], points[flag][1], slope, temp)
slope = temp
}
return this
}
// 根据x值返回y值
use(x: number) {
if (this.__HS) {
this.__i = -1
// 寻找记录x所在位置的区间
// 这里就是寻找对应的拟合函数
while (this.__i + 1 < this.__HS.x.length && (x > this.__HS.x[this.__i + 1] || (this.__i == -1 && x >= this.__HS.x[this.__i + 1]))) {
this.__i += 1
}
// 由于js浮点运算不准确,我们对于越界的情况进行兼容
if (this.__i < 0) {
return this.__HS.h[0].use(this.__HS.x[0])
}
else if (this.__i >= this.__HS.h.length) {
return this.__HS.h[this.__HS.h.length - 1].use(this.__HS.x[this.__HS.x.length - 1])
}
return this.__HS.h[this.__i].use(x)
} else {
throw new Error('You shoud first set the position!')
}
}
}
export default Cardinal