SOURCE

console 命令行工具 X clear

                    
>
console
function pchip(xs, ys, mono, extra) {
    /**
         * @brief 返回一个三次多项式,在 0 处有指定的值和导数;在 t 处有指定的值和导数。
              */
    if (xs.length == 0) {
        return function nan() { return NaN; };
    }
    if (xs.length == 1) {
        if (!extra && x < xs[0]) return ys[0];
        if (!extra && x > xs[xs.length - 1]) return ys[xs.length - 1];

        return function constance() { return ys[0]; };
    }
    if (xs.length == 2) {
        var k = (ys[1] - ys[0]) / (xs[1] - xs[0]);
        var b = ys[0] - k * xs[0];
        return function linear_fn(x) {
            return b + k * x;
        };
    }
    function piece(t, y0, y1, dy0, dy1) {
        /*
                    a: (dy0*t + dy1*t + 2*y0 - 2*y1)/t**3,
                                b: (-2*dy0*t - dy1*t - 3*y0 + 3*y1)/t**2,
                                            c: dy0,
                                                        d: y0
                                                                    y = axxx + bxx + cx + d
                                                                            */

        var a = (dy0 + dy1 + (2 * y0 - 2 * y1) / t) / t / t;
        var b = (-2 * dy0 - dy1 - (3 * y0 - 3 * y1) / t) / t;
        var c = dy0;
        var d = y0;
        return function (x) { return d + x * (c + x * (b + x * a)) }
    }
    function abs(x) {
        if (x < 0) return -x;
        return x;
    }
    var delta = [];
    var di = [];    // 各点导数
    var i, l = ys.length;
    // 计算分段倾斜
    for (i = 0; i < l - 1; i++) {
        delta[i] = (ys[i + 1] - ys[i]) / (xs[i + 1] - xs[i]);
    }
    delta[-1] = 2 * delta[0] - delta[1];
    delta[-2] = 2 * delta[-1] - delta[0];
    delta[l - 1] = 2 * delta[l - 2] - delta[l - 3];
    delta[l] = 2 * delta[l - 1] - delta[l - 2];
    // 计算中间点导数
    var w1, w2, w, s1, s2, h1, h2, a;
    if (mono) {
        for (i = 0; i < l; i++) {
            s1 = delta[i - 1];
            s2 = delta[i];
            h1 = xs[i] - xs[i - 1];
            h2 = xs[i + 1] - xs[i];
            if (isNaN(h1)) h1 = h2;
            if (isNaN(h2)) h2 = h1;
            a = (h1 + 2 * h2) / 3 / (h1 + h2);
            if (s1 * s2 > 0) {
                di[i] = s1 * s2 / (a * s2 + (1 - a) * s1);
            } else {
                di[i] = 0;
            }
        }
    } else {
        for (i = 0; i < l; i++) {
            w1 = abs(delta[i + 1] - delta[i]) + abs(delta[i + 1] + delta[i]) / 2;
            w2 = abs(delta[i - 1] - delta[i]) + abs(delta[i - 1] + delta[i]) / 2;
            if (w1 == 0 && w2 == 0) w = 0.5;
            else w = w1 / (w1 + w2);
            di[i] = (delta[i - 1] - delta[i]) * w + delta[i];
        }
    }
    // 制作函数
    var fnList = [];
    for (i = 1; i < l; i++) {
        fnList[i - 1] = piece(xs[i] - xs[i - 1], ys[i - 1], ys[i], di[i - 1], di[i]);
    }
    // alert(fnList);
    l -= 1;
    return function piecewize(x) {
        if (x < xs[0]) {
            if (extra) {
                return ys[0] + di[0] * (x - xs[0]);
            } else {
                return ys[0];
            }
        }
        if (x > xs[l]) {
            if (extra) {
                return ys[l] + di[l] * (x - xs[l]);
            } else {
                return ys[l];
            }
        }

        var i;
        for (i = 1; i < l; i++) {
            if (xs[i] > x) break;
        }
        return fnList[i - 1](x - xs[i - 1]);
    }
}

cv.width = window.innerWidth - 30;
cv.height = cv.width * 0.75;

pts = [];
cv.onclick = function (e) {
    var rect = cv.getBoundingClientRect()
    var x = e.clientX - rect.left;
    var y = e.clientY - rect.top;

    pts.push({ x: x, y: y });

    pts.sort(function (a, b) {
        return a.x - b.x;
    });
    var xs = pts.map(function (p) { return p.x });
    var ys = pts.map(function (p) { return p.y });
    var fn = pchip(xs, ys, false, true);
    var fn2 = pchip(xs, ys, true, true)
    var i;
    var ctx = cv.getContext("2d");
    ctx.clearRect(0, 0, 9999, 9999)
    ctx.fillStyle = "red";
    for (i = pts.length - 1; i >= 0; i--) {
        ctx.fillRect(pts[i].x - 1, pts[i].y - 1, 3, 3);
    }
    ctx.fillStyle = isDark() ? "white" : "black";
    for (i = 0; i < cv.width; i++) {
        ctx.fillRect(i, fn(i), 1, 1);
    }
    ctx.fillStyle = isDark() ? "yellow" : "green";
    for (i = 0; i < cv.width; i++) {
        ctx.fillRect(i, fn2(i), 1, 1);
    }


}
cv.oncontextmenu = function (event) {
    pts.length = 0;
    cv.getContext("2d").clearRect(0, 0, 9999, 9999);
    event.preventDefault();
}

function isDark() {
    return !!window.matchMedia('(prefers-color-scheme: dark)').matches;;
}

window.onresize = function() {
    var ele = document.querySelector("details div");
    ele.style.height = "auto";
    ele.style.setProperty("--ch", getComputedStyle(ele).height);
    ele.style.height = null;
}                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
window.onresize();
<h1>分段三次多项式插值</h1>
 <p>
    使用 MATLAB 中的 “<a href="https://ww2.mathworks.cn/help/matlab/ref/makima.html">makima</a>” 和 “<a href="https://ww2.mathworks.cn/help/matlab/ref/pchip.html">phicp</a>” 插值方案。
</p>
<details> 
<summary>如果你需要些背景……</summary>
<div>

<p>
    插值解决的问题是:已知某函数,给定一些点,让函数过这个点。
    最简单的方案是每两个点之间使用直线连接,但在所给定点处,曲线不光滑。
    另外的方法是使用一个多项式进行插值。“拉格朗日插值”和“牛顿插值”干的就是这件事。
    使用多项式插值,在点少的时候可以取得不错的效果,在各个方面用于数据的精细估计。
    然而,当给定的点多了之后,就会有“龙塔效应”,插值出来的曲线会有剧烈的抖动。
</p>
<p>
    分段三次多项式插值是另外的常见方案。每两个点之间采用三次多项式。相比于简单的直线连接,
    曲线更加光滑;相比于单多项式插值,阶数少,不会有剧烈的“龙塔效应”。因此,分段三次多项式插值
    在各种场景下得到应用。
</p>
<p>
    最常见的插值方案是“三次样条线“,保证曲线二阶导数连续。但在实际使用的时候仍有有可能产生抖动。而这里的两个插值方案
    设计为“好看”。“makima” 具有适当的抖动和平滑性,而 “phicp” 保证 “不会超调” 。这些插值方法只保证一阶导数连续,
    因此适合数据输入以及数据可视化,而不适合用于求精度的插值。
</p>

</div>
</details>

<p>在下面的方框点(至少三个)点查看效果。右键清除选择.</p>
<p>绿/黄色点为 phicp 插值方案,黑/白色点为 makima 插值方案。</p>
<canvas id="cv"></canvas>
canvas {
    outline: cornflowerblue solid 1px;
}


/* 默认背景色为白色 */

canvas {
    background-color: white;
}


/* 当浏览器设置为暗色模式时,背景色变为黑色 */

@media (prefers-color-scheme: dark) {
    canvas {
        background-color: black;
    }
}

/* 样式表寄了,浏览器自己的排版好难看... */

body{
    font-family: sans-serif;
    background: white;
    max-width: 800px;
    margin: auto;
    padding: 0 1em;
}

h1 {
    text-align: center;
    border-bottom: 1px solid red;
}

p {
    margin-top: 0px;
    margin-bottom: 1.2em;
    font-weight: 300;
    text-indent: 2em;
}

details {
    padding: 0em 1em;
    transition: all 0.3s;
    box-sizing: border-box;
    border: solid 1px;
    border-color: white;
    border-bottom: 2px solid currentColor;
    
}

details div {
    height: 0;
    overflow: hidden;
    transition: all 0.3s;
    margin-bottom: -1em;
}

summary {
    color: cornflowerblue;
    text-decoration: underline;
    cursor:pointer;
    margin-left: -1em;
    transition: margin-left 0.3s;
    user-select: none;
    padding-bottom: 0em;
}

[open] summary {
    margin-left: 0;
    user-select: text;
    padding-bottom: 1em;
}

details[open] div {
    height: var(--ch);
    
}


@media (prefers-color-scheme: dark) {
    body {
        background-color: black;
    }
    details {
        border-color:black; 
    }
}
details[open] {
    padding: 1em 1em;
    border-color: gray;
    border-width: 1px;
}