warp-grid
Version:
Create a complex grid, warped in 2D space and access data about its lines and cells
1,126 lines (1,125 loc) • 36.3 kB
JavaScript
import { interpolatePointOnCurveEvenlySpacedFactory, interpolatePointOnCurveLinearFactory, coonsPatch } from "coons-patch";
import BezierEasing from "bezier-easing";
import memoize from "fast-memoize";
import matrix from "matrix-js";
import { Bezier } from "bezier-js";
var InterpolationStrategy = /* @__PURE__ */ ((InterpolationStrategy2) => {
InterpolationStrategy2["LINEAR"] = `linear`;
InterpolationStrategy2["EVEN"] = `even`;
return InterpolationStrategy2;
})(InterpolationStrategy || {});
var LineStrategy = /* @__PURE__ */ ((LineStrategy2) => {
LineStrategy2["STRAIGHT_LINES"] = `straightLines`;
LineStrategy2["CURVES"] = `curves`;
return LineStrategy2;
})(LineStrategy || {});
var CellBoundsOrder = /* @__PURE__ */ ((CellBoundsOrder2) => {
CellBoundsOrder2["TTB_LTR"] = `topToBottom-leftToRight`;
CellBoundsOrder2["TTB_RTL"] = `topToBottom-rightToLeft`;
CellBoundsOrder2["BTT_LTR"] = `bottomToTop-leftToRight`;
CellBoundsOrder2["BTT_RTL"] = `bottomToTop-rightToLeft`;
CellBoundsOrder2["LTR_TTB"] = `leftToRight-topToBottom`;
CellBoundsOrder2["LTR_BTT"] = `leftToRight-bottomToTop`;
CellBoundsOrder2["RTL_TTB"] = `rightToLeft-topToBottom`;
CellBoundsOrder2["RTL_BTT"] = `rightToLeft-bottomToTop`;
return CellBoundsOrder2;
})(CellBoundsOrder || {});
const wrapInterpolatePointOnCurveWithEasing = (interpolatePointOnCurve) => (config) => {
const interpolatePointOnCurveWrappedWithEasing = (t, curve) => {
const ease = BezierEasing(...config.bezierEasing);
const tEased = ease(t);
return interpolatePointOnCurve(config)(tEased, curve);
};
return interpolatePointOnCurveWrappedWithEasing;
};
const interpolatePointOnCurveEvenlySpacedEasedFactory = wrapInterpolatePointOnCurveWithEasing(
interpolatePointOnCurveEvenlySpacedFactory
);
const interpolatePointOnCurveLinearEasedFactory = wrapInterpolatePointOnCurveWithEasing(interpolatePointOnCurveLinearFactory);
class ValidationError extends Error {
constructor(message = `A validation error occurred`) {
super(message);
this.message = message;
}
}
const times = (f, n) => {
const result = [];
for (let i = 0; i < n; i++) {
result.push(f(i));
}
return result;
};
const mapObj = (f, o) => {
return Object.keys(o).reduce((acc, key, idx) => {
const value = o[key];
return {
...acc,
[key]: f(value, key, idx)
};
}, {});
};
const VALID_GUTTER_REGEXP = /^\d*\.?\d+px$/;
const isType = (type, value) => typeof value === type;
const isBoolean = (value) => isType(`boolean`, value);
const isArray = (value) => Array.isArray(value);
const isInt = (value) => Number.isInteger(value);
const isNumber = (value) => !isNaN(value) && isType(`number`, value);
const isUndefined = (value) => isType(`undefined`, value);
const isNull = (value) => value === null;
const isNil = (value) => isUndefined(value) || isNull(value);
const isString = (value) => isType(`string`, value) || value instanceof String;
const isPlainObj = (value) => !isNull(value) && isType(`object`, value) && value.constructor === Object;
const isFunction = (value) => isType(`function`, value);
const isPixelNumberString = (value) => {
return isString(value) && VALID_GUTTER_REGEXP.test(value);
};
const getProcessedStepValue = (step) => {
if (step === `0px`) {
return 0;
}
return step;
};
const ensureArray = (unprocessedSteps) => isInt(unprocessedSteps) ? times(() => 1, unprocessedSteps) : unprocessedSteps;
const ensureObjectsAndProcess = (steps) => steps.map((step) => {
if (isPlainObj(step)) {
return {
...step,
value: getProcessedStepValue(step.value)
};
} else {
return {
value: getProcessedStepValue(step)
};
}
});
const insertGutters = (steps, gutter) => {
const lastStepIndex = steps.length - 1;
const hasGutter = isGutterNonZero(gutter);
return steps.reduce((acc, step, idx) => {
const isLastStep = idx === lastStepIndex;
const nextStep = steps[idx + 1];
const hasNextStep = !isNil(nextStep);
const nextStepIsGutter = hasNextStep && nextStep.isGutter;
const shouldAddGutter = hasGutter && !isLastStep && !step.isGutter && !nextStepIsGutter;
return shouldAddGutter ? [...acc, step, { value: gutter, isGutter: true }] : [...acc, step];
}, []);
};
const addStepRatioValues = (steps) => steps.reduce((total, step) => {
const { value } = step;
return isNumber(value) ? total + value : total;
}, 0);
const addStepAbsoluteValues = (steps) => steps.reduce((acc, step) => {
const { value } = step;
return isPixelNumberString(value) ? acc + getPixelStringNumericComponent(value) : acc;
}, 0);
const getCounts = (columns, rows) => ({
columnsTotalCount: columns.length,
rowsTotalCount: rows.length
});
const getTotalRatioValues = (columns, rows) => ({
columnsTotalRatioValue: addStepRatioValues(columns),
rowsTotalRatioValue: addStepRatioValues(rows)
});
const getAbsoluteRatio = (value, totalLength) => {
return getPixelStringNumericComponent(value) / totalLength;
};
const getNonAbsoluteSpaceRatios = (columns, rows, curveLengths) => {
const totalAbsoluteSizeU = addStepAbsoluteValues(columns);
const totalAbsoluteSizeV = addStepAbsoluteValues(rows);
const totalAbsoluteRatioTop = totalAbsoluteSizeU / curveLengths.top;
const totalAbsoluteRatioBottom = totalAbsoluteSizeU / curveLengths.bottom;
const totalAbsoluteRatioLeft = totalAbsoluteSizeV / curveLengths.left;
const totalAbsoluteRatioRight = totalAbsoluteSizeV / curveLengths.right;
const nonAbsoluteSpaceTopRatio = Math.max(1 - totalAbsoluteRatioTop, 0);
const nonAbsoluteSpaceBottomRatio = Math.max(1 - totalAbsoluteRatioBottom, 0);
const nonAbsoluteSpaceLeftRatio = Math.max(1 - totalAbsoluteRatioLeft, 0);
const nonAbsoluteSpaceRightRatio = Math.max(1 - totalAbsoluteRatioRight, 0);
return {
nonAbsoluteSpaceTopRatio,
nonAbsoluteSpaceBottomRatio,
nonAbsoluteSpaceLeftRatio,
nonAbsoluteSpaceRightRatio
};
};
const getSize = (step, totalNonAbsoluteValue, sideData) => {
if (isPixelNumberString(step.value)) {
return getAbsoluteRatio(step.value, sideData.curveLength);
}
const stepRatio = step.value / totalNonAbsoluteValue;
return stepRatio * sideData.nonAbsoluteRatio;
};
function getPixelStringNumericComponent(value) {
return Number(value.split(`px`)[0]);
}
const isGutterNonZero = (gutter) => isNumber(gutter) && gutter > 0 || isPixelNumberString(gutter);
const getEndValues = (step, totalRatioValue, sideData, sideDataOpposite) => {
const size = getSize(step, totalRatioValue, sideData);
const uOppositeSize = getSize(step, totalRatioValue, sideDataOpposite);
const end = sideData.start + size;
const oppositeEnd = sideDataOpposite.start + uOppositeSize;
return [end, oppositeEnd];
};
const processSteps = ({
steps,
gutter
}) => {
const stepsArray = ensureArray(steps);
const stepsArrayOfObjs = ensureObjectsAndProcess(stepsArray);
return insertGutters(stepsArrayOfObjs, gutter);
};
const getStepData = (columns, rows, curveLengths) => {
const processedColumns = columns;
const processedRows = rows;
return {
...getCounts(columns, rows),
...getTotalRatioValues(columns, rows),
...getNonAbsoluteSpaceRatios(columns, rows, curveLengths),
processedColumns,
processedRows
};
};
const getNonGutterSteps = (steps) => steps.filter((step) => !step.isGutter);
const BINOMIAL_COEFFICIENTS = [[1], [1, 1]];
const roundToN = (n, value) => Number(value.toFixed(n));
const roundTo5 = (value) => roundToN(5, value);
const binomial = (n, k) => {
if (n === 0) {
return 1;
}
const lut = BINOMIAL_COEFFICIENTS;
while (n >= lut.length) {
const lutLength = lut.length;
const nextRow = [1];
for (let i = 1, prev = lutLength - 1; i < lutLength; i++) {
nextRow[i] = lut[prev][i - 1] + lut[prev][i];
}
nextRow[lutLength] = 1;
lut.push(nextRow);
}
return lut[n][k];
};
const CURVE_NAMES = [`top`, `right`, `bottom`, `left`];
const getPointsAreSame = (point1, point2) => {
const roundedPoint1 = mapObj(roundTo5, point1);
const roundedPoint2 = mapObj(roundTo5, point2);
return roundedPoint1.x === roundedPoint2.x && roundedPoint1.y === roundedPoint2.y;
};
const isValidPoint = (point) => isPlainObj(point) && isNumber(point.x) && isNumber(point.y);
const validateCurve = (curve, name) => {
if (!isPlainObj(curve)) {
throw new Error(`Curve '${name}' must be an object`);
}
};
const validateFunction = (func, name) => {
if (!isFunction(func)) {
throw new ValidationError(`${name} must be a function`);
}
};
const validateStepNumber = (value) => {
if (value < 0) {
throw new ValidationError(
`If step is a number, it must be a positive integer, but was '${value}'`
);
}
};
const validatePixelString = (value) => {
const numericPortion = getPixelStringNumericComponent(value);
validateStepNumber(numericPortion);
};
const validateStep = (value) => {
if (isNumber(value)) {
validateStepNumber(value);
} else if (isPixelNumberString(value)) {
validatePixelString(value);
} else if (isPlainObj(value)) {
validateStepObj(value);
} else {
throw new ValidationError(
`A step value must be a non-negative number or a pixel string, but it was '${value}'`
);
}
};
const validateStepObj = (step) => {
validateStep(step.value);
};
const validateColumnsAndRows = (columns, rows) => {
if (isInt(columns)) {
validateStepNumber(columns);
} else if (isArray(columns)) {
columns.map((column) => {
validateStep(column);
}, columns);
} else {
throw new ValidationError(
`columns must be an integer or an array, but it was '${columns}'`
);
}
if (isInt(rows)) {
validateStepNumber(rows);
} else if (isArray(rows)) {
rows.map((row) => {
validateStep(row);
}, rows);
} else {
throw new ValidationError(
`rows must be an integer or an array, but it was '${columns}'`
);
}
};
const validateStartAndEndPoints = ({ startPoint, endPoint }, name) => {
if (!isValidPoint(startPoint)) {
throw new Error(`Bounding curve '${name}' startPoint must be a valid point`);
}
if (!isValidPoint(endPoint)) {
throw new Error(`Bounding curve '${name}' endPoint must be a valid point`);
}
};
const validateCurves = (boundingCurves) => {
CURVE_NAMES.map((name) => {
const curve = boundingCurves[name];
validateCurve(curve, name);
validateStartAndEndPoints(curve, name);
});
};
const validateCornerPoints = (boundingCurves) => {
if (!getPointsAreSame(
boundingCurves.top.startPoint,
boundingCurves.left.startPoint
)) {
throw new ValidationError(
`top curve startPoint and left curve startPoint must have same coordinates`
);
}
if (!getPointsAreSame(
boundingCurves.bottom.startPoint,
boundingCurves.left.endPoint
)) {
throw new ValidationError(
`bottom curve startPoint and left curve endPoint must have the same coordinates`
);
}
if (!getPointsAreSame(
boundingCurves.top.endPoint,
boundingCurves.right.startPoint
)) {
throw new ValidationError(
`top curve endPoint and right curve startPoint must have the same coordinates`
);
}
if (!getPointsAreSame(
boundingCurves.bottom.endPoint,
boundingCurves.right.endPoint
)) {
throw new ValidationError(
`bottom curve endPoint and right curve endPoint must have the same coordinates`
);
}
};
const validateBoundingCurves = (boundingCurves) => {
if (isNil(boundingCurves)) {
throw new ValidationError(`You must supply boundingCurves(Object)`);
}
if (!isPlainObj(boundingCurves)) {
throw new ValidationError(`boundingCurves must be an object`);
}
validateCurves(boundingCurves);
validateCornerPoints(boundingCurves);
};
const validateGutterNumber = (gutter) => {
if (gutter < 0) {
throw new ValidationError(
`Gutter must be a positive number, but was '${gutter}'`
);
}
};
const validateGutter = (gutter) => {
if (isNumber(gutter)) {
validateGutterNumber(gutter);
} else if (isPixelNumberString(gutter)) {
const numericPortion = getPixelStringNumericComponent(gutter);
validateGutterNumber(numericPortion);
} else {
throw new ValidationError(
`Gutter must be a number, or a pixel string, but was '${gutter}'`
);
}
};
const validateGridDefinition = (gridDefinition) => {
const {
rows,
columns,
gutter,
interpolationStrategy,
lineStrategy,
precision
} = gridDefinition;
if (isNil(columns)) {
throw new ValidationError(`You must supply grid.columns(Array or Int)`);
}
if (!isArray(columns) && !isInt(columns)) {
throw new ValidationError(
`grid.columns must be an Int, an Array of Ints and/or pixel strings, or an Array of objects`
);
}
if (isNil(rows)) {
throw new ValidationError(`You must supply grid.rows(Array or Int)`);
}
if (!isArray(rows) && !isInt(rows)) {
throw new ValidationError(
`grid.rows must be an Int, an Array of Ints and/or pixel strings, or an Array of objects`
);
}
validateColumnsAndRows(columns, rows);
if (!isNil(gutter)) {
if (isArray(gutter)) {
if (gutter.length > 2) {
throw new ValidationError(
`if grid.gutters is an Array it must have a length of 1 or 2`
);
}
gutter.map(validateGutter);
} else {
if (!isNumber(gutter) && !isPixelNumberString(gutter)) {
throw new ValidationError(
`grid.gutters must be an Int, a pixel-string, or an Array of Ints and/or pixel-strings`
);
}
validateGutter(gutter);
}
}
if (!isUndefined(interpolationStrategy)) {
if (isFunction(interpolationStrategy)) {
validateFunction(interpolationStrategy, `interpolationStrategy`);
} else if (isArray(interpolationStrategy)) {
validateFunction(interpolationStrategy[0], `interpolationStrategyU`);
validateFunction(interpolationStrategy[1], `interpolationStrategyV`);
} else {
const possibleValues = Object.values(InterpolationStrategy);
if (!possibleValues.includes(interpolationStrategy)) {
throw new ValidationError(
`Interpolation strategy '${interpolationStrategy}' is not recognised. Must be one of '${possibleValues}'`
);
}
}
}
if (!isUndefined(lineStrategy)) {
if (isArray(lineStrategy)) {
validateFunction(lineStrategy[0], `lineStrategyU`);
validateFunction(lineStrategy[1], `lineStrategyV`);
} else {
const possibleValues = Object.values(LineStrategy);
if (!possibleValues.includes(lineStrategy)) {
throw new ValidationError(
`Line strategy '${lineStrategy}' is not recognised. Must be one of '${possibleValues}'`
);
}
}
}
if (!isUndefined(precision)) {
if (!isInt(precision) || precision < 1) {
throw new ValidationError(
`Precision must be a positive integer greater than 0, but was '${precision}'`
);
}
}
};
const validateGetPointArguments = (params) => {
mapObj((value, name) => {
if (value < 0 || value > 1) {
throw new ValidationError(
`${name} must be between 0 and 1, but was '${value}'`
);
}
}, params);
};
const validateGetGridSquareArguments = (x, y, columns, rows) => {
const columnCount = columns.length;
const rowCount = rows.length;
if (x < 0 || y < 0) {
throw new ValidationError(
`Coordinates must not be negative. You supplied x:'${x}' x y:'${y}'`
);
}
if (x >= columnCount) {
throw new ValidationError(
`X is zero-based and cannot be greater than number of columns - 1. Grid is '${columnCount}' columns wide and you passed x:'${x}'`
);
}
if (y >= rowCount) {
throw new ValidationError(
`Y is zero-based and cannot be greater than number of rows - 1. Grid is '${rowCount}' rows wide and you passed y:'${y}'`
);
}
};
const validateGetAllCellBoundsArguments = (makeBoundsCurvesSequential, cellBoundsOrder) => {
if (!isBoolean(makeBoundsCurvesSequential)) {
throw new ValidationError(`makeBoundsCurvesSequential must be a boolean`);
}
if (!Object.values(CellBoundsOrder).includes(cellBoundsOrder)) {
throw new ValidationError(
`cellBoundsOrder must be one of ${Object.values(CellBoundsOrder)}`
);
}
};
const getRatioMatrix = (ratios) => {
const ratioLength = ratios.length;
const data = times((i) => ratios.map((v) => v ** i), ratioLength);
const tMatrix = matrix(data);
const tMatrixTransposed = matrix(tMatrix.trans());
return { tMatrix, tMatrixTransposed };
};
const getBasisMatrix = (numberOfPoints) => {
let basisMatrix = matrix(matrix.gen(0).size(numberOfPoints, numberOfPoints));
for (let i = 0; i < numberOfPoints; i++) {
const value = binomial(numberOfPoints - 1, i);
const ret = basisMatrix.set(i, i).to(value);
basisMatrix = matrix(ret);
}
for (let c = 0, r; c < numberOfPoints; c++) {
for (r = c + 1; r < numberOfPoints; r++) {
const sign = (r + c) % 2 === 0 ? 1 : -1;
const value = binomial(r, c) * basisMatrix(r, r);
basisMatrix = matrix(basisMatrix.set(r, c).to(sign * value));
}
}
return basisMatrix;
};
const getBezierFromCurve = ({
startPoint,
endPoint,
controlPoint1,
controlPoint2
}) => new Bezier(
startPoint.x,
startPoint.y,
controlPoint1.x,
controlPoint1.y,
controlPoint2.x,
controlPoint2.y,
endPoint.x,
endPoint.y
);
const pointsToCurve = (points) => ({
startPoint: points[0],
controlPoint1: points[1],
controlPoint2: points[2],
endPoint: points[3]
});
const fitCubicBezierToPoints = (points, ratios) => {
const numberOfPoints = points.length;
const { tMatrix, tMatrixTransposed } = getRatioMatrix(ratios);
const basisMatrix = getBasisMatrix(numberOfPoints);
const basisMatrixInverted = matrix(basisMatrix.inv());
const ratioMultipliedMatrix = matrix(tMatrix.prod(tMatrixTransposed));
const invertedRatioMultipliedMatrix = matrix(ratioMultipliedMatrix.inv());
const step1 = matrix(invertedRatioMultipliedMatrix.prod(tMatrix));
const step2 = matrix(basisMatrixInverted.prod(step1));
const X = matrix(points.map((v) => [v.x]));
const x = step2.prod(X);
const Y = matrix(points.map((v) => [v.y]));
const y = step2.prod(Y);
const result = x.map((r, i) => ({ x: r[0], y: y[i][0] }));
return pointsToCurve(result);
};
function getBezierCurveLength(curve) {
return getBezierFromCurve(curve).length();
}
const getCurveReversed = (curve) => {
return {
startPoint: curve.endPoint,
endPoint: curve.startPoint,
controlPoint1: curve.controlPoint2,
controlPoint2: curve.controlPoint1
};
};
const getAreStepsVerticalFirst = (cellBoundsOrder) => [
CellBoundsOrder.TTB_LTR,
CellBoundsOrder.TTB_RTL,
CellBoundsOrder.BTT_LTR,
CellBoundsOrder.BTT_RTL
].includes(cellBoundsOrder);
const getIsOuterReversed = (cellBoundsOrder, isVerticalFirst) => {
const reversedEntries = isVerticalFirst ? [CellBoundsOrder.BTT_LTR, CellBoundsOrder.BTT_RTL] : [CellBoundsOrder.RTL_TTB, CellBoundsOrder.RTL_BTT];
return reversedEntries.includes(cellBoundsOrder);
};
const getIsInnerReversed = (cellBoundsOrder, isVerticalFirst) => {
const reversedEntries = isVerticalFirst ? [CellBoundsOrder.TTB_RTL, CellBoundsOrder.BTT_RTL] : [CellBoundsOrder.RTL_BTT, CellBoundsOrder.LTR_BTT];
return reversedEntries.includes(cellBoundsOrder);
};
const clampT = (t) => Math.min(Math.max(t, 0), 1);
const mapClampT = (o) => mapObj(clampT, o);
const previousWasAlsoGutter = (steps, stepIdx) => !!(stepIdx > 0 && steps[stepIdx - 1].isGutter && steps[stepIdx] && steps[stepIdx].isGutter);
const getStepIdxIncludingGutters = (stepIdx, steps) => steps.reduce(
(acc, step, idx) => {
if (acc.isComplete) {
return acc;
}
if (step.isGutter) {
return acc;
} else {
if (acc.value === stepIdx) {
return { value: idx, isComplete: true };
}
return {
value: acc.value + 1
};
}
},
{ value: 0 }
).value;
const getAllCellCornerPoints = (curves) => curves.reduce((acc, items) => {
const subItems = items.reduce(
(acc2, curve) => [
...acc2,
curve.startPoint,
curve.endPoint
],
[]
);
return [...acc, ...subItems];
}, []);
const iterateOverSteps = (rows, columns, cellBoundsOrder, getBoundingCurves) => {
const nonGutterRows = getNonGutterSteps(rows);
const nonGutterColumns = getNonGutterSteps(columns);
const isVerticalFirst = getAreStepsVerticalFirst(cellBoundsOrder);
const outerSteps = isVerticalFirst ? nonGutterRows : nonGutterColumns;
const innerSteps = isVerticalFirst ? nonGutterColumns : nonGutterRows;
const outerStepsLength = outerSteps.length;
const innerStepsLength = innerSteps.length;
const isOuterReversed = getIsOuterReversed(cellBoundsOrder, isVerticalFirst);
const isInnerReversed = getIsInnerReversed(cellBoundsOrder, isVerticalFirst);
const items = [];
for (let i = 0; i < outerStepsLength; i++) {
const outerIdxResolved = isOuterReversed ? outerStepsLength - i - 1 : i;
for (let j = 0; j < innerStepsLength; j++) {
const innerIdxResolved = isInnerReversed ? innerStepsLength - j - 1 : j;
const rowIdx = isVerticalFirst ? innerIdxResolved : outerIdxResolved;
const columnIdx = isVerticalFirst ? outerIdxResolved : innerIdxResolved;
items.push(getBoundingCurves(rowIdx, columnIdx));
}
}
return items;
};
const getApi = (boundingCurves, columns, rows, {
interpolatePointOnCurveU,
interpolatePointOnCurveV,
interpolateLineU,
interpolateLineV
}) => {
const curveLengths = {
top: getBezierCurveLength(boundingCurves.top),
left: getBezierCurveLength(boundingCurves.left),
bottom: getBezierCurveLength(boundingCurves.bottom),
right: getBezierCurveLength(boundingCurves.right)
};
const {
processedColumns,
processedRows,
columnsTotalCount,
rowsTotalCount,
columnsTotalRatioValue,
rowsTotalRatioValue,
nonAbsoluteSpaceLeftRatio,
nonAbsoluteSpaceRightRatio,
nonAbsoluteSpaceTopRatio,
nonAbsoluteSpaceBottomRatio
} = getStepData(columns, rows, curveLengths);
const getPoint = memoize((params) => {
validateGetPointArguments(params);
return coonsPatch(boundingCurves, params, {
interpolatePointOnCurveU,
interpolatePointOnCurveV
});
});
const getLinesXAxis = memoize(() => {
const curves = [];
let vStart = 0;
let vOppositeStart = 0;
if (columnsTotalCount === 1 && rowsTotalCount === 1) {
return [[boundingCurves.top], [boundingCurves.bottom]];
}
for (let rowIdx = 0; rowIdx <= rowsTotalCount; rowIdx++) {
const lineSections = [];
let uStart = 0;
let uOppositeStart = 0;
const row = processedRows[rowIdx];
processedColumns.map((column) => {
const [uEnd, uOppositeEnd] = getEndValues(
column,
columnsTotalRatioValue,
{
start: uStart,
curveLength: curveLengths.top,
nonAbsoluteRatio: nonAbsoluteSpaceTopRatio
},
{
start: uOppositeStart,
curveLength: curveLengths.bottom,
nonAbsoluteRatio: nonAbsoluteSpaceBottomRatio
}
);
const isFirstRowAndRowIsGutter = rowIdx === 0 && row.isGutter;
if (!column.isGutter && !isFirstRowAndRowIsGutter && !previousWasAlsoGutter(rows, rowIdx)) {
const paramsClamped = mapClampT({
uStart,
uEnd,
vStart,
uOppositeStart,
uOppositeEnd,
vOppositeStart
});
const curve = interpolateLineU(
boundingCurves,
paramsClamped,
interpolatePointOnCurveU,
interpolatePointOnCurveV
);
lineSections.push(curve);
}
uStart = uEnd;
uOppositeStart = uOppositeEnd;
});
curves.push(lineSections);
if (rowIdx < rowsTotalCount) {
const row2 = processedRows[rowIdx];
const [vEnd, vOppositeEnd] = getEndValues(
row2,
rowsTotalRatioValue,
{
start: vStart,
curveLength: curveLengths.left,
nonAbsoluteRatio: nonAbsoluteSpaceLeftRatio
},
{
start: vOppositeStart,
curveLength: curveLengths.right,
nonAbsoluteRatio: nonAbsoluteSpaceRightRatio
}
);
vStart = vEnd;
vOppositeStart = vOppositeEnd;
}
}
return curves;
});
const getLinesYAxis = memoize(() => {
const curves = [];
let uStart = 0;
let uOppositeStart = 0;
if (columnsTotalCount === 1 && rowsTotalCount === 1) {
return [[boundingCurves.left], [boundingCurves.right]];
}
for (let columnIdx = 0; columnIdx <= columnsTotalCount; columnIdx++) {
const lineSections = [];
let vStart = 0;
let vOppositeStart = 0;
const column = processedColumns[columnIdx];
processedRows.map((row) => {
const [vEnd, vOppositeEnd] = getEndValues(
row,
rowsTotalRatioValue,
{
start: vStart,
curveLength: curveLengths.left,
nonAbsoluteRatio: nonAbsoluteSpaceLeftRatio
},
{
start: vOppositeStart,
curveLength: curveLengths.right,
nonAbsoluteRatio: nonAbsoluteSpaceRightRatio
}
);
const isFirstColumnAndColumnIsGutter = columnIdx === 0 && column.isGutter;
if (!row.isGutter && !isFirstColumnAndColumnIsGutter && !previousWasAlsoGutter(columns, columnIdx)) {
const paramsClamped = mapClampT({
vStart,
vEnd,
uStart,
vOppositeStart,
vOppositeEnd,
uOppositeStart
});
const curve = interpolateLineV(
boundingCurves,
paramsClamped,
interpolatePointOnCurveU,
interpolatePointOnCurveV
);
lineSections.push(curve);
}
vStart = vEnd;
vOppositeStart = vOppositeEnd;
});
curves.push(lineSections);
if (columnIdx < columnsTotalCount) {
const column2 = processedColumns[columnIdx];
const [uEnd, uOppositeEnd] = getEndValues(
column2,
columnsTotalRatioValue,
{
start: uStart,
curveLength: curveLengths.top,
nonAbsoluteRatio: nonAbsoluteSpaceTopRatio
},
{
start: uOppositeStart,
curveLength: curveLengths.bottom,
nonAbsoluteRatio: nonAbsoluteSpaceBottomRatio
}
);
uStart = uEnd;
uOppositeStart = uOppositeEnd;
}
}
return curves;
});
const getLines = memoize(() => {
return {
xAxis: getLinesXAxis(),
yAxis: getLinesYAxis()
};
});
const getIntersections = memoize(() => {
const { xAxis } = getLines();
const intersections = getAllCellCornerPoints(xAxis);
return intersections;
});
const getCellBounds = memoize(
(columnIdx, rowIdx, { makeBoundsCurvesSequential = false } = {}) => {
const nonGutterColumns = getNonGutterSteps(columns);
const nonGutterRows = getNonGutterSteps(rows);
validateGetGridSquareArguments(
columnIdx,
rowIdx,
nonGutterColumns,
nonGutterRows
);
const rowIdxIncludingGutters = getStepIdxIncludingGutters(
rowIdx,
processedRows
);
const columnIdxIncludingGutters = getStepIdxIncludingGutters(
columnIdx,
processedColumns
);
const { xAxis, yAxis } = getLines();
const top = xAxis[rowIdxIncludingGutters][columnIdx];
const bottom = xAxis[rowIdxIncludingGutters + 1][columnIdx];
const left = yAxis[columnIdxIncludingGutters][rowIdx];
const right = yAxis[columnIdxIncludingGutters + 1][rowIdx];
return {
meta: {
row: rowIdx,
column: columnIdx
},
top,
bottom: makeBoundsCurvesSequential ? getCurveReversed(bottom) : bottom,
left: makeBoundsCurvesSequential ? getCurveReversed(left) : left,
right
};
}
);
const getAllCellBounds = memoize(
({
makeBoundsCurvesSequential = false,
cellBoundsOrder = CellBoundsOrder.TTB_LTR
} = {}) => {
validateGetAllCellBoundsArguments(
makeBoundsCurvesSequential,
cellBoundsOrder
);
return iterateOverSteps(
rows,
columns,
cellBoundsOrder,
(rowIdx, columnIdx) => getCellBounds(rowIdx, columnIdx, {
makeBoundsCurvesSequential
})
);
}
);
return {
getPoint,
getLinesXAxis,
getLinesYAxis,
getLines,
getIntersections,
getCellBounds,
getAllCellBounds
};
};
const interpolateStraightLineU = (boundingCurves, {
uStart,
uEnd,
uOppositeStart,
uOppositeEnd,
vStart,
vOppositeStart
}, interpolatePointOnCurveU, interpolatePointOnCurveV) => {
const startPoint = coonsPatch(
boundingCurves,
{
u: uStart,
v: vStart,
uOpposite: uOppositeStart,
vOpposite: vOppositeStart
},
{ interpolatePointOnCurveU, interpolatePointOnCurveV }
);
const endPoint = coonsPatch(
boundingCurves,
{ u: uEnd, v: vStart, uOpposite: uOppositeEnd, vOpposite: vOppositeStart },
{ interpolatePointOnCurveU, interpolatePointOnCurveV }
);
return {
startPoint,
endPoint,
controlPoint1: startPoint,
controlPoint2: endPoint
};
};
const interpolateStraightLineV = (boundingCurves, {
vStart,
vEnd,
vOppositeStart,
vOppositeEnd,
uStart,
uOppositeStart
}, interpolatePointOnCurveU, interpolatePointOnCurveV) => {
const startPoint = coonsPatch(
boundingCurves,
{
u: uStart,
v: vStart,
uOpposite: uOppositeStart,
vOpposite: vOppositeStart
},
{ interpolatePointOnCurveU, interpolatePointOnCurveV }
);
const endPoint = coonsPatch(
boundingCurves,
{ u: uStart, v: vEnd, uOpposite: uOppositeStart, vOpposite: vOppositeEnd },
{ interpolatePointOnCurveU, interpolatePointOnCurveV }
);
return {
startPoint,
endPoint,
controlPoint1: startPoint,
controlPoint2: endPoint
};
};
const T_MIDPOINT_1 = 0.25;
const T_MIDPOINT_2 = 0.75;
const interpolateCurveU = (boundingCurves, {
uStart,
uEnd,
vStart,
vOppositeStart,
uOppositeEnd,
uOppositeStart
}, interpolatePointOnCurveU, interpolatePointOnCurveV) => {
const uSize = uEnd - uStart;
const uOppositeSize = uOppositeEnd - uOppositeStart;
const { startPoint, endPoint } = interpolateStraightLineU(
boundingCurves,
{
uStart,
uEnd,
vStart,
vOppositeStart,
uOppositeEnd,
uOppositeStart
},
interpolatePointOnCurveU,
interpolatePointOnCurveV
);
const midPoint1 = coonsPatch(
boundingCurves,
{
u: uStart + uSize * T_MIDPOINT_1,
v: vStart,
uOpposite: uOppositeStart + uOppositeSize * T_MIDPOINT_1,
vOpposite: vOppositeStart
},
{ interpolatePointOnCurveU, interpolatePointOnCurveV }
);
const midPoint2 = coonsPatch(
boundingCurves,
{
u: uStart + uSize * T_MIDPOINT_2,
v: vStart,
uOpposite: uOppositeStart + uOppositeSize * T_MIDPOINT_2,
vOpposite: vOppositeStart
},
{ interpolatePointOnCurveU, interpolatePointOnCurveV }
);
return fitCubicBezierToPoints(
[startPoint, midPoint1, midPoint2, endPoint],
[0, T_MIDPOINT_1, T_MIDPOINT_2, 1]
);
};
const interpolateCurveV = (boundingCurves, {
vStart,
vEnd,
uStart,
uOppositeStart,
vOppositeEnd,
vOppositeStart
}, interpolatePointOnCurveU, interpolatePointOnCurveV) => {
const vSize = vEnd - vStart;
const vOppositeSize = vOppositeEnd - vOppositeStart;
const { startPoint, endPoint } = interpolateStraightLineV(
boundingCurves,
{
vStart,
vEnd,
uStart,
uOppositeStart,
vOppositeEnd,
vOppositeStart
},
interpolatePointOnCurveU,
interpolatePointOnCurveV
);
const midPoint1 = coonsPatch(
boundingCurves,
{
u: uStart,
v: vStart + vSize * T_MIDPOINT_1,
uOpposite: uOppositeStart,
vOpposite: vOppositeStart + vOppositeSize * T_MIDPOINT_1
},
{ interpolatePointOnCurveU, interpolatePointOnCurveV }
);
const midPoint2 = coonsPatch(
boundingCurves,
{
u: uStart,
v: vStart + vSize * T_MIDPOINT_2,
uOpposite: uOppositeStart,
vOpposite: vOppositeStart + vOppositeSize * T_MIDPOINT_2
},
{ interpolatePointOnCurveU, interpolatePointOnCurveV }
);
return fitCubicBezierToPoints(
[startPoint, midPoint1, midPoint2, endPoint],
[0, T_MIDPOINT_1, T_MIDPOINT_2, 1]
);
};
const interpolatePointOnCurveFactory = ([interpolatePointOnCurveUFactory, interpolatePointOnCurveVFactory], bezierEasing, precision) => {
return [
interpolatePointOnCurveUFactory({
precision,
bezierEasing: bezierEasing.xAxis
}),
interpolatePointOnCurveVFactory({
precision,
bezierEasing: bezierEasing.yAxis
})
];
};
const getInterpolationStrategy = ({
interpolationStrategy = InterpolationStrategy.EVEN,
precision,
bezierEasing
}) => {
if (isFunction(interpolationStrategy)) {
return interpolatePointOnCurveFactory(
[interpolationStrategy, interpolationStrategy],
bezierEasing,
precision
);
}
if (isArray(interpolationStrategy)) {
return interpolatePointOnCurveFactory(
interpolationStrategy,
bezierEasing,
precision
);
}
if (interpolationStrategy === InterpolationStrategy.EVEN) {
return interpolatePointOnCurveFactory(
[
interpolatePointOnCurveEvenlySpacedEasedFactory,
interpolatePointOnCurveEvenlySpacedEasedFactory
],
bezierEasing,
precision
);
}
if (interpolationStrategy === InterpolationStrategy.LINEAR) {
return interpolatePointOnCurveFactory(
[
interpolatePointOnCurveLinearEasedFactory,
interpolatePointOnCurveLinearEasedFactory
],
bezierEasing,
precision
);
}
throw new ValidationError(
`Unknown interpolation strategy: '${interpolationStrategy}'`
);
};
const getLineStrategy = ({
lineStrategy
}) => {
const interpolateLineU = lineStrategy === LineStrategy.CURVES ? interpolateCurveU : interpolateStraightLineU;
const interpolateLineV = lineStrategy === LineStrategy.CURVES ? interpolateCurveV : interpolateStraightLineV;
return [interpolateLineU, interpolateLineV];
};
const mergeWithDefaults = (definition) => {
return {
...{
gutter: 0,
interpolationStrategy: InterpolationStrategy.EVEN,
precision: 20,
lineStrategy: LineStrategy.STRAIGHT_LINES,
bezierEasing: {
xAxis: [0, 0, 1, 1],
yAxis: [0, 0, 1, 1]
}
},
...definition
};
};
const warpGrid = (boundingCurves, gridDefinition) => {
const gridDefinitionWithDefaults = mergeWithDefaults(gridDefinition);
validateBoundingCurves(boundingCurves);
validateGridDefinition(gridDefinitionWithDefaults);
const { gutter } = gridDefinitionWithDefaults;
const gutterArray = isArray(gutter) ? gutter : [gutter, gutter];
const [columns, rows] = [
{ steps: gridDefinitionWithDefaults.columns, gutter: gutterArray[0] },
{ steps: gridDefinitionWithDefaults.rows, gutter: gutterArray[1] }
].map(processSteps);
const [interpolatePointOnCurveU, interpolatePointOnCurveV] = getInterpolationStrategy(gridDefinitionWithDefaults);
const [interpolateLineU, interpolateLineV] = getLineStrategy(
gridDefinitionWithDefaults
);
const api = getApi(boundingCurves, columns, rows, {
interpolatePointOnCurveU,
interpolatePointOnCurveV,
interpolateLineU,
interpolateLineV
});
return {
model: {
boundingCurves,
columns,
rows,
columnsNonGutter: getNonGutterSteps(columns),
rowsNonGutter: getNonGutterSteps(rows)
},
...api
};
};
export {
CellBoundsOrder,
InterpolationStrategy,
LineStrategy,
interpolatePointOnCurveEvenlySpacedEasedFactory as interpolatePointOnCurveEvenlySpacedFactory,
interpolatePointOnCurveLinearEasedFactory as interpolatePointOnCurveLinearFactory,
warpGrid
};
//# sourceMappingURL=index.js.map