InterpolateExpression preview

by Michel Beloshitsky

HTML

<canvas id="chart"></canvas>

<p>
Expression:<br />
<textarea id="expression">[
    "interpolate",
    ["exponential", 1.99],
    ["zoom"],
    13,
    1.9,
    14,
    2,
    15,
    3.8,
    19,
    16
]</textarea><br />
</p>

CSS

body {
  padding: 20px;
  font-family: Helvetica;
}

#chart {
width: 200px;
height: 32px;
border: solid 1px #ccc;
}

#expression {
  width: 200px;
  height: 200px;
}

TypeScript

type Curve = Array<[number, number, number]>;

type Vec4 = [number, number, number, number];

function compileInterpolateExpr (expr: any): Curve {
    const [ tag, [curve, exp], [param], ...steps] = expr;
    if (tag !== 'interpolate') {
        return null;
    }
    const base = curve === 'linear' ? 1 : exp;
    if (param !== 'zoom') {
        return null;
    }
    const result = [];
    for (let i = 0; i < steps.length; i += 2) {
        const matchValue = steps[i],
            matchResult = steps[i + 1];
        result.push([matchValue, matchResult, base])
    }
    return result;
}

/**
 * Получает значение из переданной кривой для переданного зума
 */
function sample(curve: Curve, styleZoom: number): number {
    const segNumber = getCurveSegment(curve, styleZoom);

    if (segNumber === 0) {
        return curve[0][1];
    }

    if (segNumber === curve.length) {
        return sampleLastPoint(curve, styleZoom);
    }

    const ratio = getRatio(curve, styleZoom, segNumber);

    return (1 - ratio) * curve[segNumber - 1][1] + ratio * curve[segNumber][1];
}

/**
 * Экстраполирует значение кривой для случая, когда зум больше её последней точки
 */
function sampleLastPoint(curve: Curve, styleZoom: number): number {
    const lastPoint = curve[curve.length - 1];
    const base = lastPoint[2];

    if (base === 1) {
        return lastPoint[1];
    }

    return lastPoint[1] * Math.pow(base, styleZoom - lastPoint[0]);
}

/**
 * Возвращает индекс сегмента кривой, которому принадлежит переданный зум
 * Индекс сегмента = индексу его правой точки
 */
function getCurveSegment(curve: Curve, styleZoom: number): number {
    let i = 0;

    while (i < curve.length) {
        if (styleZoom < curve[i][0]) {
            break;
        } else {
            i++;
        }
    }

    return i;
}

/**
 * Возвращает соотношение для интерполяции между левой и правой точками сегмента
 */
function getRatio(curve: Curve, styleZoom: number, segNumber: number): number {
    const...