1
0
قرینه از https://github.com/ialley-workshop-open/uni-halo.git synced 2026-06-12 13:19:31 +08:00

v1.0.0-beta 源码正式开源

This commit is contained in:
小莫唐尼
2022-12-06 15:08:29 +08:00
کامیت 636ae7b169
461فایلهای تغییر یافته به همراه116817 افزوده شده و 0 حذف شده
@@ -0,0 +1,310 @@
const { sqrt, pow, ceil, abs } = Math
// Initialize the number of points per curve
const defaultSegmentPointsNum = 50
/**
* @example data structure of bezierCurve
* bezierCurve = [
* // Starting point of the curve
* [10, 10],
* // BezierCurve segment data (controlPoint1, controlPoint2, endPoint)
* [
* [20, 20], [40, 20], [50, 10]
* ],
* ...
* ]
*/
/**
* @description Abstract the curve as a polyline consisting of N points
* @param {Array} bezierCurve bezierCurve data
* @param {Number} precision calculation accuracy. Recommended for 1-20. Default = 5
* @return {Object} Calculation results and related data
* @return {Array} Option.segmentPoints Point data that constitutes a polyline after calculation
* @return {Number} Option.cycles Number of iterations
* @return {Number} Option.rounds The number of recursions for the last iteration
*/
function abstractBezierCurveToPolyline (bezierCurve, precision = 5) {
const segmentsNum = bezierCurve.length - 1
const startPoint = bezierCurve[0]
const endPoint = bezierCurve[segmentsNum][2]
const segments = bezierCurve.slice(1)
const getSegmentTPointFuns = segments.map((seg, i) => {
let beginPoint = (i === 0) ? startPoint : segments[i - 1][2]
return createGetBezierCurveTPointFun(beginPoint, ...seg)
})
// Initialize the curve to a polyline
let segmentPointsNum = new Array(segmentsNum).fill(defaultSegmentPointsNum)
let segmentPoints = getSegmentPointsByNum(getSegmentTPointFuns, segmentPointsNum)
// Calculate uniformly distributed points by iteratively
const result = calcUniformPointsByIteration(segmentPoints, getSegmentTPointFuns, segments, precision)
result.segmentPoints.push(endPoint)
return result
}
/**
* @description Generate a method for obtaining corresponding point by t according to curve data
* @param {Array} beginPoint BezierCurve begin point. [x, y]
* @param {Array} controlPoint1 BezierCurve controlPoint1. [x, y]
* @param {Array} controlPoint2 BezierCurve controlPoint2. [x, y]
* @param {Array} endPoint BezierCurve end point. [x, y]
* @return {Function} Expected function
*/
function createGetBezierCurveTPointFun (beginPoint, controlPoint1, controlPoint2, endPoint) {
return function (t) {
const tSubed1 = 1 - t
const tSubed1Pow3 = pow(tSubed1, 3)
const tSubed1Pow2 = pow(tSubed1, 2)
const tPow3 = pow(t, 3)
const tPow2 = pow(t, 2)
return [
beginPoint[0] * tSubed1Pow3 + 3 * controlPoint1[0] * t * tSubed1Pow2 + 3 * controlPoint2[0] * tPow2 * tSubed1 + endPoint[0] * tPow3,
beginPoint[1] * tSubed1Pow3 + 3 * controlPoint1[1] * t * tSubed1Pow2 + 3 * controlPoint2[1] * tPow2 * tSubed1 + endPoint[1] * tPow3
]
}
}
/**
* @description Get the distance between two points
* @param {Array} point1 BezierCurve begin point. [x, y]
* @param {Array} point2 BezierCurve controlPoint1. [x, y]
* @return {Number} Expected distance
*/
function getTwoPointDistance ([ax, ay], [bx, by]) {
return sqrt(pow(ax - bx, 2) + pow(ay - by, 2))
}
/**
* @description Get the sum of the array of numbers
* @param {Array} nums An array of numbers
* @return {Number} Expected sum
*/
function getNumsSum (nums) {
return nums.reduce((sum, num) => sum + num, 0)
}
/**
* @description Get the distance of multiple sets of points
* @param {Array} segmentPoints Multiple sets of point data
* @return {Array} Distance of multiple sets of point data
*/
function getSegmentPointsDistance (segmentPoints) {
return segmentPoints.map((points, i) => {
return new Array(points.length - 1)
.fill(0)
.map((temp, j) => getTwoPointDistance(points[j], points[j + 1]))
})
}
/**
* @description Get the distance of multiple sets of points
* @param {Array} segmentPoints Multiple sets of point data
* @return {Array} Distance of multiple sets of point data
*/
function getSegmentPointsByNum (getSegmentTPointFuns, segmentPointsNum) {
return getSegmentTPointFuns.map((getSegmentTPointFun, i) => {
const tGap = 1 / segmentPointsNum[i]
return new Array(segmentPointsNum[i])
.fill('')
.map((foo, j) => getSegmentTPointFun(j * tGap))
})
}
/**
* @description Get the sum of deviations between line segment and the average length
* @param {Array} segmentPointsDistance Segment length of polyline
* @param {Number} avgLength Average length of the line segment
* @return {Number} Deviations
*/
function getAllDeviations (segmentPointsDistance, avgLength) {
return segmentPointsDistance
.map(seg => seg.map(s => abs(s - avgLength)))
.map(seg => getNumsSum(seg))
.reduce((total, v) => total + v, 0)
}
/**
* @description Calculate uniformly distributed points by iteratively
* @param {Array} segmentPoints Multiple setd of points that make up a polyline
* @param {Array} getSegmentTPointFuns Functions of get a point on the curve with t
* @param {Array} segments BezierCurve data
* @param {Number} precision Calculation accuracy
* @return {Object} Calculation results and related data
* @return {Array} Option.segmentPoints Point data that constitutes a polyline after calculation
* @return {Number} Option.cycles Number of iterations
* @return {Number} Option.rounds The number of recursions for the last iteration
*/
function calcUniformPointsByIteration (segmentPoints, getSegmentTPointFuns, segments, precision) {
// The number of loops for the current iteration
let rounds = 4
// Number of iterations
let cycles = 1
do {
// Recalculate the number of points per curve based on the last iteration data
let totalPointsNum = segmentPoints.reduce((total, seg) => total + seg.length, 0)
// Add last points of segment to calc exact segment length
segmentPoints.forEach((seg, i) => seg.push(segments[i][2]))
let segmentPointsDistance = getSegmentPointsDistance(segmentPoints)
let lineSegmentNum = segmentPointsDistance.reduce((total, seg) => total + seg.length, 0)
let segmentlength = segmentPointsDistance.map(seg => getNumsSum(seg))
let totalLength = getNumsSum(segmentlength)
let avgLength = totalLength / lineSegmentNum
// Check if precision is reached
let allDeviations = getAllDeviations(segmentPointsDistance, avgLength)
if (allDeviations <= precision) break
totalPointsNum = ceil(avgLength / precision * totalPointsNum * 1.1)
const segmentPointsNum = segmentlength.map(length => ceil(length / totalLength * totalPointsNum))
// Calculate the points after redistribution
segmentPoints = getSegmentPointsByNum(getSegmentTPointFuns, segmentPointsNum)
totalPointsNum = segmentPoints.reduce((total, seg) => total + seg.length, 0)
let segmentPointsForLength = JSON.parse(JSON.stringify(segmentPoints))
segmentPointsForLength.forEach((seg, i) => seg.push(segments[i][2]))
segmentPointsDistance = getSegmentPointsDistance(segmentPointsForLength)
lineSegmentNum = segmentPointsDistance.reduce((total, seg) => total + seg.length, 0)
segmentlength = segmentPointsDistance.map(seg => getNumsSum(seg))
totalLength = getNumsSum(segmentlength)
avgLength = totalLength / lineSegmentNum
const stepSize = 1 / totalPointsNum / 10
// Recursively for each segment of the polyline
getSegmentTPointFuns.forEach((getSegmentTPointFun, i) => {
const currentSegmentPointsNum = segmentPointsNum[i]
const t = new Array(currentSegmentPointsNum).fill('').map((foo, j) => j / segmentPointsNum[i])
// Repeated recursive offset
for (let r = 0; r < rounds; r++) {
let distance = getSegmentPointsDistance([segmentPoints[i]])[0]
const deviations = distance.map(d => d - avgLength)
let offset = 0
for (let j = 0; j < currentSegmentPointsNum; j++) {
if (j === 0) return
offset += deviations[j - 1]
t[j] -= stepSize * offset
if (t[j] > 1) t[j] = 1
if (t[j] < 0) t[j] = 0
segmentPoints[i][j] = getSegmentTPointFun(t[j])
}
}
})
rounds *= 4
cycles++
} while (rounds <= 1025)
segmentPoints = segmentPoints.reduce((all, seg) => all.concat(seg), [])
return {
segmentPoints,
cycles,
rounds
}
}
/**
* @description Get the polyline corresponding to the Bezier curve
* @param {Array} bezierCurve BezierCurve data
* @param {Number} precision Calculation accuracy. Recommended for 1-20. Default = 5
* @return {Array|Boolean} Point data that constitutes a polyline after calculation (Invalid input will return false)
*/
export function bezierCurveToPolyline (bezierCurve, precision = 5) {
if (!bezierCurve) {
console.error('bezierCurveToPolyline: Missing parameters!')
return false
}
if (!(bezierCurve instanceof Array)) {
console.error('bezierCurveToPolyline: Parameter bezierCurve must be an array!')
return false
}
if (typeof precision !== 'number') {
console.error('bezierCurveToPolyline: Parameter precision must be a number!')
return false
}
const { segmentPoints } = abstractBezierCurveToPolyline(bezierCurve, precision)
return segmentPoints
}
/**
* @description Get the bezier curve length
* @param {Array} bezierCurve bezierCurve data
* @param {Number} precision calculation accuracy. Recommended for 5-10. Default = 5
* @return {Number|Boolean} BezierCurve length (Invalid input will return false)
*/
export function getBezierCurveLength (bezierCurve, precision = 5) {
if (!bezierCurve) {
console.error('getBezierCurveLength: Missing parameters!')
return false
}
if (!(bezierCurve instanceof Array)) {
console.error('getBezierCurveLength: Parameter bezierCurve must be an array!')
return false
}
if (typeof precision !== 'number') {
console.error('getBezierCurveLength: Parameter precision must be a number!')
return false
}
const { segmentPoints } = abstractBezierCurveToPolyline(bezierCurve, precision)
// Calculate the total length of the points that make up the polyline
const pointsDistance = getSegmentPointsDistance([segmentPoints])[0]
const length = getNumsSum(pointsDistance)
return length
}
export default bezierCurveToPolyline
@@ -0,0 +1,465 @@
/**
* 点方法。
* 此库来自konva
* 移植:https://jx2d.cn
* tmzdy
*/
export function parsePathData(data) {
if (!data) {
return [];
}
var cs = data;
var cc = [
'm',
'M',
'l',
'L',
'v',
'V',
'h',
'H',
'z',
'Z',
'c',
'C',
'q',
'Q',
't',
'T',
's',
'S',
'a',
'A',
];
cs = cs.replace(new RegExp(' ', 'g'), ',');
for (var n = 0; n < cc.length; n++) {
cs = cs.replace(new RegExp(cc[n], 'g'), '|' + cc[n]);
}
var arr = cs.split('|');
var ca = [];
var coords = [];
var cpx = 0;
var cpy = 0;
var re = /([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/gi;
var match;
for (n = 1; n < arr.length; n++) {
var str = arr[n];
var c = str.charAt(0);
str = str.slice(1);
coords.length = 0;
while ((match = re.exec(str))) {
coords.push(match[0]);
}
var p = [];
for (var j = 0, jlen = coords.length; j < jlen; j++) {
if (coords[j] === '00') {
p.push(0, 0);
continue;
}
var parsed = parseFloat(coords[j]);
if (!isNaN(parsed)) {
p.push(parsed);
} else {
p.push(0);
}
}
while (p.length > 0) {
if (isNaN(p[0])) {
break;
}
var cmd = null;
var points = [];
var startX = cpx,
startY = cpy;
var prevCmd, ctlPtx, ctlPty;
var rx, ry, psi, fa, fs, x1, y1;
switch (c) {
case 'l':
cpx += p.shift();
cpy += p.shift();
cmd = 'L';
points.push(cpx, cpy);
break;
case 'L':
cpx = p.shift();
cpy = p.shift();
points.push(cpx, cpy);
break;
case 'm':
var dx = p.shift();
var dy = p.shift();
cpx += dx;
cpy += dy;
cmd = 'M';
if (ca.length > 2 && ca[ca.length - 1].command === 'z') {
for (var idx = ca.length - 2; idx >= 0; idx--) {
if (ca[idx].command === 'M') {
cpx = ca[idx].points[0] + dx;
cpy = ca[idx].points[1] + dy;
break;
}
}
}
points.push(cpx, cpy);
c = 'l';
break;
case 'M':
cpx = p.shift();
cpy = p.shift();
cmd = 'M';
points.push(cpx, cpy);
c = 'L';
break;
case 'h':
cpx += p.shift();
cmd = 'L';
points.push(cpx, cpy);
break;
case 'H':
cpx = p.shift();
cmd = 'L';
points.push(cpx, cpy);
break;
case 'v':
cpy += p.shift();
cmd = 'L';
points.push(cpx, cpy);
break;
case 'V':
cpy = p.shift();
cmd = 'L';
points.push(cpx, cpy);
break;
case 'C':
points.push(p.shift(), p.shift(), p.shift(), p.shift());
cpx = p.shift();
cpy = p.shift();
points.push(cpx, cpy);
break;
case 'c':
points.push(cpx + p.shift(), cpy + p.shift(), cpx + p.shift(), cpy + p.shift());
cpx += p.shift();
cpy += p.shift();
cmd = 'C';
points.push(cpx, cpy);
break;
case 'S':
ctlPtx = cpx;
ctlPty = cpy;
prevCmd = ca[ca.length - 1];
if (prevCmd.command === 'C') {
ctlPtx = cpx + (cpx - prevCmd.points[2]);
ctlPty = cpy + (cpy - prevCmd.points[3]);
}
points.push(ctlPtx, ctlPty, p.shift(), p.shift());
cpx = p.shift();
cpy = p.shift();
cmd = 'C';
points.push(cpx, cpy);
break;
case 's':
ctlPtx = cpx;
ctlPty = cpy;
prevCmd = ca[ca.length - 1];
if (prevCmd.command === 'C') {
ctlPtx = cpx + (cpx - prevCmd.points[2]);
ctlPty = cpy + (cpy - prevCmd.points[3]);
}
points.push(ctlPtx, ctlPty, cpx + p.shift(), cpy + p.shift());
cpx += p.shift();
cpy += p.shift();
cmd = 'C';
points.push(cpx, cpy);
break;
case 'Q':
points.push(p.shift(), p.shift());
cpx = p.shift();
cpy = p.shift();
points.push(cpx, cpy);
break;
case 'q':
points.push(cpx + p.shift(), cpy + p.shift());
cpx += p.shift();
cpy += p.shift();
cmd = 'Q';
points.push(cpx, cpy);
break;
case 'T':
ctlPtx = cpx;
ctlPty = cpy;
prevCmd = ca[ca.length - 1];
if (prevCmd.command === 'Q') {
ctlPtx = cpx + (cpx - prevCmd.points[0]);
ctlPty = cpy + (cpy - prevCmd.points[1]);
}
cpx = p.shift();
cpy = p.shift();
cmd = 'Q';
points.push(ctlPtx, ctlPty, cpx, cpy);
break;
case 't':
ctlPtx = cpx;
ctlPty = cpy;
prevCmd = ca[ca.length - 1];
if (prevCmd.command === 'Q') {
ctlPtx = cpx + (cpx - prevCmd.points[0]);
ctlPty = cpy + (cpy - prevCmd.points[1]);
}
cpx += p.shift();
cpy += p.shift();
cmd = 'Q';
points.push(ctlPtx, ctlPty, cpx, cpy);
break;
case 'A':
rx = p.shift();
ry = p.shift();
psi = p.shift();
fa = p.shift();
fs = p.shift();
x1 = cpx;
y1 = cpy;
cpx = p.shift();
cpy = p.shift();
cmd = 'A';
points = convertEndpointToCenterParameterization(x1, y1, cpx, cpy, fa, fs, rx, ry, psi);
break;
case 'a':
rx = p.shift();
ry = p.shift();
psi = p.shift();
fa = p.shift();
fs = p.shift();
x1 = cpx;
y1 = cpy;
cpx += p.shift();
cpy += p.shift();
cmd = 'A';
points = convertEndpointToCenterParameterization(x1, y1, cpx, cpy, fa, fs, rx, ry, psi);
break;
}
ca.push({
command: cmd || c,
points: points,
start: {
x: startX,
y: startY,
},
pathLength: calcLength(startX, startY, cmd || c, points),
});
}
if (c === 'z' || c === 'Z') {
ca.push({
command: 'z',
points: [],
start: undefined,
pathLength: 0,
});
}
}
return ca;
}
export function calcLength(x, y, cmd, points) {
var len, p1, p2, t;
switch (cmd) {
case 'L':
return getLineLength(x, y, points[0], points[1]);
case 'C':
len = 0.0;
p1 = getPointOnCubicBezier(0, x, y, points[0], points[1], points[2], points[3], points[4], points[5]);
for (t = 0.01; t <= 1; t += 0.01) {
p2 = getPointOnCubicBezier(t, x, y, points[0], points[1], points[2], points[3], points[4], points[
5]);
len += getLineLength(p1.x, p1.y, p2.x, p2.y);
p1 = p2;
}
return len;
case 'Q':
len = 0.0;
p1 = getPointOnQuadraticBezier(0, x, y, points[0], points[1], points[2], points[3]);
for (t = 0.01; t <= 1; t += 0.01) {
p2 = getPointOnQuadraticBezier(t, x, y, points[0], points[1], points[2], points[3]);
len += getLineLength(p1.x, p1.y, p2.x, p2.y);
p1 = p2;
}
return len;
case 'A':
len = 0.0;
var start = points[4];
var dTheta = points[5];
var end = points[4] + dTheta;
var inc = Math.PI / 180.0;
if (Math.abs(start - end) < inc) {
inc = Math.abs(start - end);
}
p1 = getPointOnEllipticalArc(points[0], points[1], points[2], points[3], start, 0);
if (dTheta < 0) {
for (t = start - inc; t > end; t -= inc) {
p2 = getPointOnEllipticalArc(points[0], points[1], points[2], points[3], t, 0);
len += getLineLength(p1.x, p1.y, p2.x, p2.y);
p1 = p2;
}
} else {
for (t = start + inc; t < end; t += inc) {
p2 = getPointOnEllipticalArc(points[0], points[1], points[2], points[3], t, 0);
len += getLineLength(p1.x, p1.y, p2.x, p2.y);
p1 = p2;
}
}
p2 = getPointOnEllipticalArc(points[0], points[1], points[2], points[3], end, 0);
len += getLineLength(p1.x, p1.y, p2.x, p2.y);
return len;
}
return 0;
}
export function convertEndpointToCenterParameterization(x1, y1, x2, y2, fa, fs, rx, ry, psiDeg) {
var psi = psiDeg * (Math.PI / 180.0);
var xp = (Math.cos(psi) * (x1 - x2)) / 2.0 + (Math.sin(psi) * (y1 - y2)) / 2.0;
var yp = (-1 * Math.sin(psi) * (x1 - x2)) / 2.0 +
(Math.cos(psi) * (y1 - y2)) / 2.0;
var lambda = (xp * xp) / (rx * rx) + (yp * yp) / (ry * ry);
if (lambda > 1) {
rx *= Math.sqrt(lambda);
ry *= Math.sqrt(lambda);
}
var f = Math.sqrt((rx * rx * (ry * ry) - rx * rx * (yp * yp) - ry * ry * (xp * xp)) /
(rx * rx * (yp * yp) + ry * ry * (xp * xp)));
if (fa === fs) {
f *= -1;
}
if (isNaN(f)) {
f = 0;
}
var cxp = (f * rx * yp) / ry;
var cyp = (f * -ry * xp) / rx;
var cx = (x1 + x2) / 2.0 + Math.cos(psi) * cxp - Math.sin(psi) * cyp;
var cy = (y1 + y2) / 2.0 + Math.sin(psi) * cxp + Math.cos(psi) * cyp;
var vMag = function(v) {
return Math.sqrt(v[0] * v[0] + v[1] * v[1]);
};
var vRatio = function(u, v) {
return (u[0] * v[0] + u[1] * v[1]) / (vMag(u) * vMag(v));
};
var vAngle = function(u, v) {
return (u[0] * v[1] < u[1] * v[0] ? -1 : 1) * Math.acos(vRatio(u, v));
};
var theta = vAngle([1, 0], [(xp - cxp) / rx, (yp - cyp) / ry]);
var u = [(xp - cxp) / rx, (yp - cyp) / ry];
var v = [(-1 * xp - cxp) / rx, (-1 * yp - cyp) / ry];
var dTheta = vAngle(u, v);
if (vRatio(u, v) <= -1) {
dTheta = Math.PI;
}
if (vRatio(u, v) >= 1) {
dTheta = 0;
}
if (fs === 0 && dTheta > 0) {
dTheta = dTheta - 2 * Math.PI;
}
if (fs === 1 && dTheta < 0) {
dTheta = dTheta + 2 * Math.PI;
}
return [cx, cy, rx, ry, theta, dTheta, psi, fs];
}
export function getSelfRect() {
var points = [];
this.dataArray.forEach(function(data) {
if (data.command === 'A') {
var start = data.points[4];
var dTheta = data.points[5];
var end = data.points[4] + dTheta;
var inc = Math.PI / 180.0;
if (Math.abs(start - end) < inc) {
inc = Math.abs(start - end);
}
if (dTheta < 0) {
for (let t = start - inc; t > end; t -= inc) {
const point = Path.getPointOnEllipticalArc(data.points[0], data.points[1], data.points[2],
data.points[3], t, 0);
points.push(point.x, point.y);
}
} else {
for (let t = start + inc; t < end; t += inc) {
const point = Path.getPointOnEllipticalArc(data.points[0], data.points[1], data.points[2],
data.points[3], t, 0);
points.push(point.x, point.y);
}
}
} else if (data.command === 'C') {
for (let t = 0.0; t <= 1; t += 0.01) {
const point = Path.getPointOnCubicBezier(t, data.start.x, data.start.y, data.points[0], data
.points[1], data.points[2], data.points[3], data.points[4], data.points[5]);
points.push(point.x, point.y);
}
} else {
points = points.concat(data.points);
}
});
var minX = points[0];
var maxX = points[0];
var minY = points[1];
var maxY = points[1];
var x, y;
for (var i = 0; i < points.length / 2; i++) {
x = points[i * 2];
y = points[i * 2 + 1];
if (!isNaN(x)) {
minX = Math.min(minX, x);
maxX = Math.max(maxX, x);
}
if (!isNaN(y)) {
minY = Math.min(minY, y);
maxY = Math.max(maxY, y);
}
}
return {
x: Math.round(minX),
y: Math.round(minY),
width: Math.round(maxX - minX),
height: Math.round(maxY - minY),
};
}
export function expandPoints(p, tension) {
var len = p.length, allPoints = [], n, cp;
for (n = 2; n < len - 2; n += 2) {
cp = getControlPoints(p[n - 2], p[n - 1], p[n], p[n + 1], p[n + 2], p[n + 3], tension);
if (isNaN(cp[0])) {
continue;
}
allPoints.push(cp[0]);
allPoints.push(cp[1]);
allPoints.push(p[n]);
allPoints.push(p[n + 1]);
allPoints.push(cp[2]);
allPoints.push(cp[3]);
}
return allPoints;
}
export function getControlPoints(x0, y0, x1, y1, x2, y2, t) {
var d01 = Math.sqrt(Math.pow(x1 - x0, 2) + Math.pow(y1 - y0, 2)), d12 = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)), fa = (t * d01) / (d01 + d12), fb = (t * d12) / (d01 + d12), p1x = x1 - fa * (x2 - x0), p1y = y1 - fa * (y2 - y0), p2x = x1 + fb * (x2 - x0), p2y = y1 + fb * (y2 - y0);
return [p1x, p1y, p2x, p2y];
}
export function getTensionPointsClosed(points,tension) {
var p = points ,len = p.length, firstControlPoints = getControlPoints(p[len - 2], p[len - 1], p[0], p[1], p[2], p[3], tension), lastControlPoints = getControlPoints(p[len - 4], p[len - 3], p[len - 2], p[len - 1], p[0], p[1], tension), middle = expandPoints(p, tension), tp = [firstControlPoints[2], firstControlPoints[3]]
.concat(middle)
.concat([
lastControlPoints[0],
lastControlPoints[1],
p[len - 2],
p[len - 1],
lastControlPoints[2],
lastControlPoints[3],
firstControlPoints[0],
firstControlPoints[1],
p[0],
p[1],
]);
return tp;
}
@@ -0,0 +1,111 @@
/**
* @description Abstract the polyline formed by N points into a set of bezier curve
* @param {Array} polyline A set of points that make up a polyline
* @param {Boolean} close Closed curve
* @param {Number} offsetA Smoothness
* @param {Number} offsetB Smoothness
* @return {Array|Boolean} A set of bezier curve (Invalid input will return false)
*/
function polylineToBezierCurve (polyline, close = false, offsetA = 0.25, offsetB = 0.25) {
if (!(polyline instanceof Array)) {
console.error('polylineToBezierCurve: Parameter polyline must be an array!')
return false
}
if (polyline.length <= 2) {
console.error('polylineToBezierCurve: Converting to a curve requires at least 3 points!')
return false
}
const startPoint = polyline[0]
const bezierCurveLineNum = polyline.length - 1
const bezierCurvePoints = new Array(bezierCurveLineNum).fill(0).map((foo, i) =>
[...getBezierCurveLineControlPoints(polyline, i, close, offsetA, offsetB), polyline[i + 1]])
if (close) closeBezierCurve(bezierCurvePoints, startPoint)
bezierCurvePoints.unshift(polyline[0])
return bezierCurvePoints
}
/**
* @description Get the control points of the Bezier curve
* @param {Array} polyline A set of points that make up a polyline
* @param {Number} index The index of which get controls points's point in polyline
* @param {Boolean} close Closed curve
* @param {Number} offsetA Smoothness
* @param {Number} offsetB Smoothness
* @return {Array} Control points
*/
function getBezierCurveLineControlPoints (polyline, index, close = false, offsetA = 0.25, offsetB = 0.25) {
const pointNum = polyline.length
if (pointNum < 3 || index >= pointNum) return
let beforePointIndex = index - 1
if (beforePointIndex < 0) beforePointIndex = (close ? pointNum + beforePointIndex : 0)
let afterPointIndex = index + 1
if (afterPointIndex >= pointNum) afterPointIndex = (close ? afterPointIndex - pointNum : pointNum - 1)
let afterNextPointIndex = index + 2
if (afterNextPointIndex >= pointNum) afterNextPointIndex = (close ? afterNextPointIndex - pointNum : pointNum - 1)
const pointBefore = polyline[beforePointIndex]
const pointMiddle = polyline[index]
const pointAfter = polyline[afterPointIndex]
const pointAfterNext = polyline[afterNextPointIndex]
return [
[
pointMiddle[0] + offsetA * (pointAfter[0] - pointBefore[0]),
pointMiddle[1] + offsetA * (pointAfter[1] - pointBefore[1])
],
[
pointAfter[0] - offsetB * (pointAfterNext[0] - pointMiddle[0]),
pointAfter[1] - offsetB * (pointAfterNext[1] - pointMiddle[1])
]
]
}
/**
* @description Get the last curve of the closure
* @param {Array} bezierCurve A set of sub-curve
* @param {Array} startPoint Start point
* @return {Array} The last curve for closure
*/
function closeBezierCurve (bezierCurve, startPoint) {
const firstSubCurve = bezierCurve[0]
const lastSubCurve = bezierCurve.slice(-1)[0]
bezierCurve.push([
getSymmetryPoint(lastSubCurve[1], lastSubCurve[2]),
getSymmetryPoint(firstSubCurve[0], startPoint),
startPoint
])
return bezierCurve
}
/**
* @description Get the symmetry point
* @param {Array} point Symmetric point
* @param {Array} centerPoint Symmetric center
* @return {Array} Symmetric point
*/
function getSymmetryPoint (point, centerPoint) {
const [px, py] = point
const [cx, cy] = centerPoint
const minusX = cx - px
const minusY = cy - py
return [cx + minusX, cy + minusY]
}
export default polylineToBezierCurve
@@ -0,0 +1,11 @@
import { bezierCurveToPolyline, getBezierCurveLength } from './core/bezierCurveToPolyline'
import polylineToBezierCurve from './core/polylineToBezierCurve'
export { bezierCurveToPolyline, getBezierCurveLength } from './core/bezierCurveToPolyline'
export { default as polylineToBezierCurve } from './core/polylineToBezierCurve'
export default {
bezierCurveToPolyline,
getBezierCurveLength,
polylineToBezierCurve
}
تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است Diff را بارگزاری کن
@@ -0,0 +1,377 @@
import Style from './style.class'
import transition from '../transition'
import {
deepClone,
getRotatePointPos,
getScalePointPos,
getTranslatePointPos,
checkPointIsInRect
} from '../plugin/util'
/**
* @description Class Graph
* @param {Object} graph Graph default configuration
* @param {Object} config Graph config
* @return {Graph} Instance of Graph
*/
export default class Graph {
constructor (graph, config) {
config = deepClone(config, true)
config = {animationFrame:24,...config};
const defaultConfig = {
/**
* @description Weather to render graph
* @type {Boolean}
* @default visible = true
*/
visible: true,
/**
* @description Whether to enable drag
* @type {Boolean}
* @default drag = false
*/
drag: false,
/**
* @description Whether to enable hover
* @type {Boolean}
* @default hover = false
*/
hover: false,
/**
* @description Graph rendering index
* Give priority to index high graph in rendering
* @type {Number}
* @example index = 1
*/
index: 1,
/**
* @description Animation delay time(ms)
* @type {Number}
* @default animationDelay = 0
*/
animationDelay: 0,
/**
* @description Number of animation frames
* @type {Number}
* @default animationFrame = 30
*/
animationFrame: 30,
/**
* @description Animation dynamic curve (Supported by transition)
* @type {String}
* @default animationCurve = 'linear'
* @link https://github.com/jiaming743/Transition
*/
animationCurve: 'linear',
/**
* @description Weather to pause graph animation
* @type {Boolean}
* @default animationPause = false
*/
animationPause: false,
/**
* @description Rectangular hover detection zone
* Use this method for hover detection first
* @type {Null|Array}
* @default hoverRect = null
* @example hoverRect = [0, 0, 100, 100] // [Rect start x, y, Rect width, height]
*/
hoverRect: null,
/**
* @description Mouse enter event handler
* @type {Function|Null}
* @default mouseEnter = null
*/
mouseEnter: null,
/**
* @description Mouse outer event handler
* @type {Function|Null}
* @default mouseOuter = null
*/
mouseOuter: null,
/**
* @description Mouse click event handler
* @type {Function|Null}
* @default click = null
*/
click: null
}
const configAbleNot = {
status: 'static',
animationRoot: [],
animationKeys: [],
animationFrameState: [],
cache: {}
}
if (!config.shape) config.shape = {}
if (!config.style) config.style = {}
const shape = Object.assign({}, graph.shape, config.shape)
Object.assign(defaultConfig, config, configAbleNot)
Object.assign(this, graph, defaultConfig)
this.shape = shape
this.style = new Style(config.style)
this.addedProcessor()
}
}
/**
* @description Processor of added
* @return {Undefined} Void
*/
Graph.prototype.addedProcessor = function () {
if (typeof this.setGraphCenter === 'function') this.setGraphCenter(null, this)
// The life cycle 'added"
if (typeof this.added === 'function') this.added(this)
}
/**
* @description Processor of draw
* @param {CRender} render Instance of CRender
* @param {Graph} graph Instance of Graph
* @return {Undefined} Void
*/
Graph.prototype.drawProcessor = function (render, graph) {
const { ctx } = render
const { shape } = graph
graph.style.initStyle(ctx,shape)
if (typeof this.beforeDraw === 'function') this.beforeDraw(this, render)
graph.draw(render, graph)
// ctx.draw(true)
if (typeof this.drawed === 'function') this.drawed(this, render)
graph.style.restoreTransform(ctx)
}
/**
* @description Processor of hover check
* @param {Array} position Mouse Position
* @param {Graph} graph Instance of Graph
* @return {Boolean} Result of hover check
*/
Graph.prototype.hoverCheckProcessor = function (position, { hoverRect, style, hoverCheck }) {
const { graphCenter, rotate, scale, translate } = style
if (graphCenter) {
if (rotate) position = getRotatePointPos(-rotate, position, graphCenter)
if (scale) position = getScalePointPos(scale.map(s => 1 / s), position, graphCenter)
if (translate) position = getTranslatePointPos(translate.map(v => v * -1), position)
}
if (hoverRect) return checkPointIsInRect(position, ...hoverRect)
return hoverCheck(position, this)
}
/**
* @description Processor of move
* @param {Event} e Mouse movement event
* @return {Undefined} Void
*/
Graph.prototype.moveProcessor = function (e) {
this.move(e, this)
if (typeof this.beforeMove === 'function') this.beforeMove(e, this)
if (typeof this.setGraphCenter === 'function') this.setGraphCenter(e, this)
if (typeof this.moved === 'function') this.moved(e, this)
}
/**
* @description Update graph state
* @param {String} attrName Updated attribute name
* @param {Any} change Updated value
* @return {Undefined} Void
*/
Graph.prototype.attr = function (attrName, change = undefined) {
if (!attrName || change === undefined) return false
const isObject = typeof this[attrName] === 'object'
if (isObject) change = deepClone(change, true)
const { render } = this
if (attrName === 'style') {
this.style.update(change)
} else if (isObject) {
Object.assign(this[attrName], change)
} else {
this[attrName] = change
}
if (attrName === 'index') render.sortGraphsByIndex()
render.drawAllGraph()
}
/**
* @description Update graphics state (with animation)
* Only shape and style attributes are supported
* @param {String} attrName Updated attribute name
* @param {Any} change Updated value
* @param {Boolean} wait Whether to store the animation waiting
* for the next animation request
* @return {Promise} Animation Promise
*/
Graph.prototype.animation = async function (attrName, change, wait = false) {
if (attrName !== 'shape' && attrName !== 'style') {
console.error('Only supported shape and style animation!')
return
}
change = deepClone(change, true)
if (attrName === 'style') this.style.colorProcessor(change)
const changeRoot = this[attrName]
const changeKeys = Object.keys(change)
const beforeState = {}
changeKeys.forEach(key => (beforeState[key] = changeRoot[key]))
const { animationFrame, animationCurve, animationDelay } = this
const animationFrameState = transition(animationCurve, beforeState, change, animationFrame, true)
this.animationRoot.push(changeRoot)
this.animationKeys.push(changeKeys)
this.animationFrameState.push(animationFrameState)
if (wait) return
if (animationDelay > 0) await delay(animationDelay)
const { render } = this
return new Promise(async resolve => {
await render.launchAnimation()
resolve()
})
}
/**
* @description Extract the next frame of data from the animation queue
* and update the graph state
* @return {Undefined} Void
*/
Graph.prototype.turnNextAnimationFrame = function (timeStamp) {
const { animationDelay, animationRoot, animationKeys, animationFrameState, animationPause } = this
if (animationPause) return
if (Date.now() - timeStamp < animationDelay) return
animationRoot.forEach((root, i) => {
animationKeys[i].forEach(key => {
root[key] = animationFrameState[i][0][key]
})
})
animationFrameState.forEach((stateItem, i) => {
stateItem.shift()
const noFrame = stateItem.length === 0
if (noFrame) animationRoot[i] = null
if (noFrame) animationKeys[i] = null
})
this.animationFrameState = animationFrameState.filter(state => state.length)
this.animationRoot = animationRoot.filter(root => root)
this.animationKeys = animationKeys.filter(keys => keys)
}
/**
* @description Skip to the last frame of animation
* @return {Undefined} Void
*/
Graph.prototype.animationEnd = function () {
const { animationFrameState, animationKeys, animationRoot, render } = this
animationRoot.forEach((root, i) => {
const currentKeys = animationKeys[i]
const lastState = animationFrameState[i].pop()
currentKeys.forEach(key => (root[key] = lastState[key]))
})
this.animationFrameState = []
this.animationKeys = []
this.animationRoot = []
return render.drawAllGraph()
}
/**
* @description Pause animation behavior
* @return {Undefined} Void
*/
Graph.prototype.pauseAnimation = function () {
this.attr('animationPause', true)
}
/**
* @description Try animation behavior
* @return {Undefined} Void
*/
Graph.prototype.playAnimation = function () {
const { render } = this
this.attr('animationPause', false)
return new Promise(async resolve => {
await render.launchAnimation()
resolve()
})
}
/**
* @description Processor of delete
* @param {CRender} render Instance of CRender
* @return {Undefined} Void
*/
Graph.prototype.delProcessor = function (render) {
const { graphs } = render
const index = graphs.findIndex(graph => graph === this)
if (index === -1) return
if (typeof this.beforeDelete === 'function') this.beforeDelete(this)
graphs.splice(index, 1, null)
if (typeof this.deleted === 'function') this.deleted(this)
}
/**
* @description Return a timed release Promise
* @param {Number} time Release time
* @return {Promise} A timed release Promise
*/
function delay (time) {
return new Promise(resolve => {
setTimeout(resolve, time)
})
}
@@ -0,0 +1,467 @@
import { getRgbaValue, getColorFromRgbValue } from '../color'
import { deepClone } from '../plugin/util'
/**
* @description Class Style
* @param {Object} style Style configuration
* @return {Style} Instance of Style
*/
export default class Style {
constructor (style) {
this.colorProcessor(style)
const defaultStyle = {
/**
* @description Rgba value of graph fill color
* @type {Array}
* @default fill = [0, 0, 0, 1]
*/
fill: [0, 0, 0, 1],
/**
* @description Rgba value of graph stroke color
* @type {Array}
* @default stroke = [0, 0, 0, 1]
*/
stroke: [0, 0, 0, 0],
/**
* @description Opacity of graph
* @type {Number}
* @default opacity = 1
*/
opacity: 1,
/**
* @description LineCap of Ctx
* @type {String}
* @default lineCap = null
* @example lineCap = 'butt'|'round'|'square'
*/
lineCap: null,
/**
* @description Linejoin of Ctx
* @type {String}
* @default lineJoin = null
* @example lineJoin = 'round'|'bevel'|'miter'
*/
lineJoin: null,
/**
* @description LineDash of Ctx
* @type {Array}
* @default lineDash = null
* @example lineDash = [10, 10]
*/
lineDash: null,
/**
* @description LineDashOffset of Ctx
* @type {Number}
* @default lineDashOffset = null
* @example lineDashOffset = 10
*/
lineDashOffset: null,
/**
* @description ShadowBlur of Ctx
* @type {Number}
* @default shadowBlur = 0
*/
shadowBlur: 0,
/**
* @description Rgba value of graph shadow color
* @type {Array}
* @default shadowColor = [0, 0, 0, 0]
*/
shadowColor: [0, 0, 0, 0],
/**
* @description ShadowOffsetX of Ctx
* @type {Number}
* @default shadowOffsetX = 0
*/
shadowOffsetX: 0,
/**
* @description ShadowOffsetY of Ctx
* @type {Number}
* @default shadowOffsetY = 0
*/
shadowOffsetY: 0,
/**
* @description LineWidth of Ctx
* @type {Number}
* @default lineWidth = 0
*/
lineWidth: 0,
/**
* @description Center point of the graph
* @type {Array}
* @default graphCenter = null
* @example graphCenter = [10, 10]
*/
graphCenter: null,
/**
* @description Graph scale
* @type {Array}
* @default scale = null
* @example scale = [1.5, 1.5]
*/
scale: null,
/**
* @description Graph rotation degree
* @type {Number}
* @default rotate = null
* @example rotate = 10
*/
rotate: null,
/**
* @description Graph translate distance
* @type {Array}
* @default translate = null
* @example translate = [10, 10]
*/
translate: null,
/**
* @description Cursor status when hover
* @type {String}
* @default hoverCursor = 'pointer'
* @example hoverCursor = 'default'|'pointer'|'auto'|'crosshair'|'move'|'wait'|...
*/
hoverCursor: 'pointer',
/**
* @description Font style of Ctx
* @type {String}
* @default fontStyle = 'normal'
* @example fontStyle = 'normal'|'italic'|'oblique'
*/
fontStyle: 'normal',
/**
* @description Font varient of Ctx
* @type {String}
* @default fontVarient = 'normal'
* @example fontVarient = 'normal'|'small-caps'
*/
fontVarient: 'normal',
/**
* @description Font weight of Ctx
* @type {String|Number}
* @default fontWeight = 'normal'
* @example fontWeight = 'normal'|'bold'|'bolder'|'lighter'|Number
*/
fontWeight: 'normal',
/**
* @description Font size of Ctx
* @type {Number}
* @default fontSize = 10
*/
fontSize: 10,
/**
* @description Font family of Ctx
* @type {String}
* @default fontFamily = 'Arial'
*/
padding:0,
lineHeight:1,//行高
wrap:true,//是否自动断行。
ellipsis:false,//一行,超过省略号。
letterSpacing:0,
fontFamily: 'Arial',
textDecoration:'none',
/**
* @description TextAlign of Ctx
* @type {String}
* @default textAlign = 'center'
* @example textAlign = 'start'|'end'|'left'|'right'|'center'
*/
textAlign: 'left',
/**
* @description TextBaseline of Ctx
* @type {String}
* @default textBaseline = 'middle'
* @example textBaseline = 'top'|'bottom'|'middle'|'alphabetic'|'hanging'
*/
textBaseline: 'middle',
/**
* @description The color used to create the gradient
* @type {Array}
* @default gradientColor = null
* @example gradientColor = ['#000', '#111', '#222']
*/
gradientColor: null,
/**
* @description Gradient type
* @type {String}
* @default gradientType = 'linear'
* @example gradientType = 'linear' | 'radial'
*/
gradientType: 'linear',
/**
* @description Gradient params
* @type {Array}
* @default gradientParams = null
* @example gradientParams = [x0, y0, x1, y1] (Linear Gradient)
* @example gradientParams = [x0, y0, r0, x1, y1, r1] (Radial Gradient)
*/
gradientParams: null,
/**
* @description When to use gradients
* @type {String}
* @default gradientWith = 'stroke'
* @example gradientWith = 'stroke' | 'fill'
*/
gradientWith: 'stroke',
/**
* @description Gradient color stops
* @type {String}
* @default gradientStops = 'auto'
* @example gradientStops = 'auto' | [0, .2, .3, 1]
*/
gradientStops: 'auto',
/**
* @description Extended color that supports animation transition
* @type {Array|Object}
* @default colors = null
* @example colors = ['#000', '#111', '#222', 'red' ]
* @example colors = { a: '#000', b: '#111' }
*/
colors: null
}
Object.assign(this, defaultStyle, style)
}
}
/**
* @description Set colors to rgba value
* @param {Object} style style config
* @param {Boolean} reverse Whether to perform reverse operation
* @return {Undefined} Void
*/
Style.prototype.colorProcessor = function (style, reverse = false) {
const processor = reverse ? getColorFromRgbValue : getRgbaValue
const colorProcessorKeys = ['fill', 'stroke', 'shadowColor']
const allKeys = Object.keys(style)
const colorKeys = allKeys.filter(key => colorProcessorKeys.find(k => k === key))
colorKeys.forEach(key => (style[key] = processor(style[key])))
const { gradientColor, colors } = style
if (gradientColor) style.gradientColor = gradientColor.map(c => processor(c))
if (colors) {
const colorsKeys = Object.keys(colors)
colorsKeys.forEach(key => (colors[key] = processor(colors[key])))
}
}
/**
* @description Init graph style
* @param {Object} ctx Context of canvas
* @return {Undefined} Void
*/
Style.prototype.initStyle = function (ctx,shape) {
initTransform(ctx, this)
initGraphStyle(ctx, this)
initGradient(ctx, this,shape)
}
/**
* @description Init canvas transform
* @param {Object} ctx Context of canvas
* @param {Style} style Instance of Style
* @return {Undefined} Void
*/
function initTransform (ctx, style) {
ctx.save()
const { graphCenter, rotate, scale, translate } = style
if (!(graphCenter instanceof Array)) return
if(graphCenter.length>0) ctx.translate(...graphCenter);
if (rotate) ctx.rotate(rotate * Math.PI / 180)
if (scale instanceof Array) ctx.scale(...scale)
if (translate) ctx.translate(...translate)
ctx.translate(-graphCenter[0], -graphCenter[1])
}
const autoSetStyleKeys = [
'lineCap', 'lineJoin', 'lineDashOffset',
'shadowOffsetX', 'shadowOffsetY', 'lineWidth',
'textAlign', 'textBaseline'
]
/**
* @description Set the style of canvas ctx
* @param {Object} ctx Context of canvas
* @param {Style} style Instance of Style
* @return {Undefined} Void
*/
function initGraphStyle (ctx, style) {
let { fill, stroke, shadowColor, opacity } = style
autoSetStyleKeys.forEach(key => {
if (key || typeof key === 'number') ctx[key] = style[key]
})
fill = [...fill]
stroke = [...stroke]
shadowColor = [...shadowColor]
fill[3] *= opacity
stroke[3] *= opacity
shadowColor[3] *= opacity
ctx.fillStyle = getColorFromRgbValue(fill)
ctx.strokeStyle = getColorFromRgbValue(stroke)
ctx.shadowColor = getColorFromRgbValue(shadowColor)
let { lineDash, shadowBlur } = style
if (lineDash) {
lineDash = lineDash.map(v => v >= 0 ? v : 0)
ctx.setLineDash(lineDash)
}
if (typeof shadowBlur === 'number') ctx.shadowBlur = shadowBlur > 0 ? shadowBlur : 0.001
const { fontStyle, fontVarient, fontWeight, fontSize, fontFamily } = style
ctx.font = fontStyle + ' ' + fontVarient + ' ' + fontWeight + ' ' + fontSize + 'px' + ' ' + fontFamily
}
/**
* @description Set the gradient color of canvas ctx
* @param {Object} ctx Context of canvas
* @param {Style} style Instance of Style
* @return {Undefined} Void
*/
function initGradient (ctx, style,shape) {
if (!gradientValidator(style)) return
let { gradientColor, gradientParams, gradientType, gradientWith, gradientStops, opacity } = style
gradientColor = gradientColor.map(color => {
let colorOpacity = color[3] * opacity
let clonedColor = [...color]
clonedColor[3] = colorOpacity
return clonedColor
})
gradientColor = gradientColor.map(c => getColorFromRgbValue(c))
if (gradientStops === 'auto') gradientStops = getAutoColorStops(gradientColor)
let gra = [...gradientParams];
if(gradientType =='linear'){
gra[0] =shape.x + gradientParams[0]
gra[1] =shape.y + gradientParams[1]
gra[2] =shape.x +gradientParams[2]
gra[3] =shape.y +gradientParams[3]
}
const gradient = ctx[`create${gradientType.slice(0, 1).toUpperCase() + gradientType.slice(1)}Gradient`](...gra)
gradientStops.forEach((stop, i) => gradient.addColorStop(stop, gradientColor[i]))
ctx[`${gradientWith}Style`] = gradient
}
/**
* @description Check if the gradient configuration is legal
* @param {Style} style Instance of Style
* @return {Boolean} Check Result
*/
function gradientValidator (style) {
const { gradientColor, gradientParams, gradientType, gradientWith, gradientStops } = style
if (!gradientColor || !gradientParams) return false
if (gradientColor.length === 1) {
console.warn('The gradient needs to provide at least two colors')
return false
}
if (gradientType !== 'linear' && gradientType !== 'radial') {
console.warn('GradientType only supports linear or radial, current value is ' + gradientType)
return false
}
const gradientParamsLength = gradientParams.length
if (
(gradientType === 'linear' && gradientParamsLength !== 4) ||
(gradientType === 'radial' && gradientParamsLength !== 6)
) {
console.warn('The expected length of gradientParams is ' + (gradientType === 'linear' ? '4' : '6'))
return false
}
if (gradientWith !== 'fill' && gradientWith !== 'stroke') {
console.warn('GradientWith only supports fill or stroke, current value is ' + gradientWith)
return false
}
if (gradientStops !== 'auto' && !(gradientStops instanceof Array)) {
console.warn(`gradientStops only supports 'auto' or Number Array ([0, .5, 1]), current value is ` + gradientStops)
return false
}
return true
}
/**
* @description Get a uniform gradient color stop
* @param {Array} color Gradient color
* @return {Array} Gradient color stop
*/
function getAutoColorStops (color) {
const stopGap = 1 / (color.length - 1)
return color.map((foo, i) => stopGap * i)
}
/**
* @description Restore canvas ctx transform
* @param {Object} ctx Context of canvas
* @return {Undefined} Void
*/
Style.prototype.restoreTransform = function (ctx) {
ctx.restore()
}
/**
* @description Update style data
* @param {Object} change Changed data
* @return {Undefined} Void
*/
Style.prototype.update = function (change) {
this.colorProcessor(change)
Object.assign(this, change)
}
/**
* @description Get the current style configuration
* @return {Object} Style configuration
*/
Style.prototype.getStyle = function () {
const clonedStyle = deepClone(this, true)
this.colorProcessor(clonedStyle, true)
return clonedStyle
}
@@ -0,0 +1,150 @@
export default new Map([
['transparent', 'rgba(0,0,0,0)'],
['black', '#000000'],
['silver', '#C0C0C0'],
['gray', '#808080'],
['white', '#FFFFFF'],
['maroon', '#800000'],
['red', '#FF0000'],
['purple', '#800080'],
['fuchsia', '#FF00FF'],
['green', '#008000'],
['lime', '#00FF00'],
['olive', '#808000'],
['yellow', '#FFFF00'],
['navy', '#000080'],
['blue', '#0000FF'],
['teal', '#008080'],
['aqua', '#00FFFF'],
['aliceblue', '#f0f8ff'],
['antiquewhite', '#faebd7'],
['aquamarine', '#7fffd4'],
['azure', '#f0ffff'],
['beige', '#f5f5dc'],
['bisque', '#ffe4c4'],
['blanchedalmond', '#ffebcd'],
['blueviolet', '#8a2be2'],
['brown', '#a52a2a'],
['burlywood', '#deb887'],
['cadetblue', '#5f9ea0'],
['chartreuse', '#7fff00'],
['chocolate', '#d2691e'],
['coral', '#ff7f50'],
['cornflowerblue', '#6495ed'],
['cornsilk', '#fff8dc'],
['crimson', '#dc143c'],
['cyan', '#00ffff'],
['darkblue', '#00008b'],
['darkcyan', '#008b8b'],
['darkgoldenrod', '#b8860b'],
['darkgray', '#a9a9a9'],
['darkgreen', '#006400'],
['darkgrey', '#a9a9a9'],
['darkkhaki', '#bdb76b'],
['darkmagenta', '#8b008b'],
['darkolivegreen', '#556b2f'],
['darkorange', '#ff8c00'],
['darkorchid', '#9932cc'],
['darkred', '#8b0000'],
['darksalmon', '#e9967a'],
['darkseagreen', '#8fbc8f'],
['darkslateblue', '#483d8b'],
['darkslategray', '#2f4f4f'],
['darkslategrey', '#2f4f4f'],
['darkturquoise', '#00ced1'],
['darkviolet', '#9400d3'],
['deeppink', '#ff1493'],
['deepskyblue', '#00bfff'],
['dimgray', '#696969'],
['dimgrey', '#696969'],
['dodgerblue', '#1e90ff'],
['firebrick', '#b22222'],
['floralwhite', '#fffaf0'],
['forestgreen', '#228b22'],
['gainsboro', '#dcdcdc'],
['ghostwhite', '#f8f8ff'],
['gold', '#ffd700'],
['goldenrod', '#daa520'],
['greenyellow', '#adff2f'],
['grey', '#808080'],
['honeydew', '#f0fff0'],
['hotpink', '#ff69b4'],
['indianred', '#cd5c5c'],
['indigo', '#4b0082'],
['ivory', '#fffff0'],
['khaki', '#f0e68c'],
['lavender', '#e6e6fa'],
['lavenderblush', '#fff0f5'],
['lawngreen', '#7cfc00'],
['lemonchiffon', '#fffacd'],
['lightblue', '#add8e6'],
['lightcoral', '#f08080'],
['lightcyan', '#e0ffff'],
['lightgoldenrodyellow', '#fafad2'],
['lightgray', '#d3d3d3'],
['lightgreen', '#90ee90'],
['lightgrey', '#d3d3d3'],
['lightpink', '#ffb6c1'],
['lightsalmon', '#ffa07a'],
['lightseagreen', '#20b2aa'],
['lightskyblue', '#87cefa'],
['lightslategray', '#778899'],
['lightslategrey', '#778899'],
['lightsteelblue', '#b0c4de'],
['lightyellow', '#ffffe0'],
['limegreen', '#32cd32'],
['linen', '#faf0e6'],
['magenta', '#ff00ff'],
['mediumaquamarine', '#66cdaa'],
['mediumblue', '#0000cd'],
['mediumorchid', '#ba55d3'],
['mediumpurple', '#9370db'],
['mediumseagreen', '#3cb371'],
['mediumslateblue', '#7b68ee'],
['mediumspringgreen', '#00fa9a'],
['mediumturquoise', '#48d1cc'],
['mediumvioletred', '#c71585'],
['midnightblue', '#191970'],
['mintcream', '#f5fffa'],
['mistyrose', '#ffe4e1'],
['moccasin', '#ffe4b5'],
['navajowhite', '#ffdead'],
['oldlace', '#fdf5e6'],
['olivedrab', '#6b8e23'],
['orange', '#ffa500'],
['orangered', '#ff4500'],
['orchid', '#da70d6'],
['palegoldenrod', '#eee8aa'],
['palegreen', '#98fb98'],
['paleturquoise', '#afeeee'],
['palevioletred', '#db7093'],
['papayawhip', '#ffefd5'],
['peachpuff', '#ffdab9'],
['peru', '#cd853f'],
['pink', '#ffc0cb'],
['plum', '#dda0dd'],
['powderblue', '#b0e0e6'],
['rosybrown', '#bc8f8f'],
['royalblue', '#4169e1'],
['saddlebrown', '#8b4513'],
['salmon', '#fa8072'],
['sandybrown', '#f4a460'],
['seagreen', '#2e8b57'],
['seashell', '#fff5ee'],
['sienna', '#a0522d'],
['skyblue', '#87ceeb'],
['slateblue', '#6a5acd'],
['slategray', '#708090'],
['slategrey', '#708090'],
['snow', '#fffafa'],
['springgreen', '#00ff7f'],
['steelblue', '#4682b4'],
['tan', '#d2b48c'],
['thistle', '#d8bfd8'],
['tomato', '#ff6347'],
['turquoise', '#40e0d0'],
['violet', '#ee82ee'],
['wheat', '#f5deb3'],
['whitesmoke', '#f5f5f5'],
['yellowgreen', '#9acd32']
])
@@ -0,0 +1,299 @@
import colorKeywords from './config/keywords'
const hexReg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/
const rgbReg = /^(rgb|rgba|RGB|RGBA)/
const rgbaReg = /^(rgba|RGBA)/
/**
* @description Color validator
* @param {String} color Hex|Rgb|Rgba color or color keyword
* @return {String|Boolean} Valid color Or false
*/
function validator (color) {
let isHex = hexReg.test(color)
let isRgb = rgbReg.test(color)
if (isHex || isRgb) return color
color = getColorByKeyword(color)
if (!color) {
console.error('Color: Invalid color!')
return false
}
return color
}
/**
* @description Get color by keyword
* @param {String} keyword Color keyword like red, green and etc.
* @return {String|Boolean} Hex or rgba color (Invalid keyword will return false)
*/
function getColorByKeyword (keyword) {
if (!keyword) {
console.error('getColorByKeywords: Missing parameters!')
return false
}
if (!colorKeywords.has(keyword)) return false
return colorKeywords.get(keyword)
}
/**
* @description Get the Rgb value of the color
* @param {String} color Hex|Rgb|Rgba color or color keyword
* @return {Array<Number>|Boolean} Rgb value of the color (Invalid input will return false)
*/
export function getRgbValue (color) {
if (!color) {
console.error('getRgbValue: Missing parameters!')
return false
}
color = validator(color)
if (!color) return false
const isHex = hexReg.test(color)
const isRgb = rgbReg.test(color)
const lowerColor = color.toLowerCase()
if (isHex) return getRgbValueFromHex(lowerColor)
if (isRgb) return getRgbValueFromRgb(lowerColor)
}
/**
* @description Get the rgb value of the hex color
* @param {String} color Hex color
* @return {Array<Number>} Rgb value of the color
*/
function getRgbValueFromHex (color) {
color = color.replace('#', '')
if (color.length === 3) color = Array.from(color).map(hexNum => hexNum + hexNum).join('')
color = color.split('')
return new Array(3).fill(0).map((t, i) => parseInt(`0x${color[i * 2]}${color[i * 2 + 1]}`))
}
/**
* @description Get the rgb value of the rgb/rgba color
* @param {String} color Hex color
* @return {Array} Rgb value of the color
*/
function getRgbValueFromRgb (color) {
return color
.replace(/rgb\(|rgba\(|\)/g, '')
.split(',')
.slice(0, 3)
.map(n => parseInt(n))
}
/**
* @description Get the Rgba value of the color
* @param {String} color Hex|Rgb|Rgba color or color keyword
* @return {Array<Number>|Boolean} Rgba value of the color (Invalid input will return false)
*/
export function getRgbaValue (color) {
if (!color) {
console.error('getRgbaValue: Missing parameters!')
return false
}
const colorValue = getRgbValue(color)
if (!colorValue) return false
colorValue.push(getOpacity(color))
return colorValue
}
/**
* @description Get the opacity of color
* @param {String} color Hex|Rgb|Rgba color or color keyword
* @return {Number|Boolean} Color opacity (Invalid input will return false)
*/
export function getOpacity (color) {
if (!color) {
console.error('getOpacity: Missing parameters!')
return false
}
color = validator(color)
if (!color) return false
const isRgba = rgbaReg.test(color)
if (!isRgba) return 1
color = color.toLowerCase()
return Number(color.split(',').slice(-1)[0].replace(/[)|\s]/g, ''))
}
/**
* @description Convert color to Rgb|Rgba color
* @param {String} color Hex|Rgb|Rgba color or color keyword
* @param {Number} opacity The opacity of color
* @return {String|Boolean} Rgb|Rgba color (Invalid input will return false)
*/
export function toRgb (color, opacity) {
if (!color) {
console.error('toRgb: Missing parameters!')
return false
}
const rgbValue = getRgbValue(color)
if (!rgbValue) return false
const addOpacity = typeof opacity === 'number'
if (addOpacity) return 'rgba(' + rgbValue.join(',') + `,${opacity})`
return 'rgb(' + rgbValue.join(',') + ')'
}
/**
* @description Convert color to Hex color
* @param {String} color Hex|Rgb|Rgba color or color keyword
* @return {String|Boolean} Hex color (Invalid input will return false)
*/
export function toHex (color) {
if (!color) {
console.error('toHex: Missing parameters!')
return false
}
if (hexReg.test(color)) return color
color = getRgbValue(color)
if (!color) return false
return '#' + color
.map(n => Number(n).toString(16))
.map(n => n === '0' ? '00' : n)
.join('')
}
/**
* @description Get Color from Rgb|Rgba value
* @param {Array<Number>} value Rgb|Rgba color value
* @return {String|Boolean} Rgb|Rgba color (Invalid input will return false)
*/
export function getColorFromRgbValue (value) {
if (!value) {
console.error('getColorFromRgbValue: Missing parameters!')
return false
}
const valueLength = value.length
if (valueLength !== 3 && valueLength !== 4) {
console.error('getColorFromRgbValue: Value is illegal!')
return false
}
let color = (valueLength === 3 ? 'rgb(' : 'rgba(')
color += value.join(',') + ')'
return color
}
/**
* @description Deepen color
* @param {String} color Hex|Rgb|Rgba color or color keyword
* @return {Number} Percent of Deepen (1-100)
* @return {String|Boolean} Rgba color (Invalid input will return false)
*/
export function darken (color, percent = 0) {
if (!color) {
console.error('darken: Missing parameters!')
return false
}
let rgbaValue = getRgbaValue(color)
if (!rgbaValue) return false
rgbaValue = rgbaValue
.map((v, i) => (i === 3 ? v : v - Math.ceil(2.55 * percent)))
.map(v => (v < 0 ? 0 : v))
return getColorFromRgbValue(rgbaValue)
}
/**
* @description Brighten color
* @param {String} color Hex|Rgb|Rgba color or color keyword
* @return {Number} Percent of brighten (1-100)
* @return {String|Boolean} Rgba color (Invalid input will return false)
*/
export function lighten (color, percent = 0) {
if (!color) {
console.error('lighten: Missing parameters!')
return false
}
let rgbaValue = getRgbaValue(color)
if (!rgbaValue) return false
rgbaValue = rgbaValue
.map((v, i) => (i === 3 ? v : v + Math.ceil(2.55 * percent)))
.map(v => (v > 255 ? 255 : v))
return getColorFromRgbValue(rgbaValue)
}
/**
* @description Adjust color opacity
* @param {String} color Hex|Rgb|Rgba color or color keyword
* @param {Number} Percent of opacity
* @return {String|Boolean} Rgba color (Invalid input will return false)
*/
export function fade (color, percent = 100) {
if (!color) {
console.error('fade: Missing parameters!')
return false
}
const rgbValue = getRgbValue(color)
if (!rgbValue) return false
const rgbaValue = [...rgbValue, percent / 100]
return getColorFromRgbValue(rgbaValue)
}
export default {
fade,
toHex,
toRgb,
darken,
lighten,
getOpacity,
getRgbValue,
getRgbaValue,
getColorFromRgbValue
}
تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است Diff را بارگزاری کن
تفاوت فایل به دلیل طولانی بودن یک یا چند خط حذف شد
@@ -0,0 +1,679 @@
/**
* svg路径绘制。
* 作者:tmzdy
* url:https://jx2d.cn
*/
function parsePathData(data) {
if (!data) {
return [];
}
var cs = data;
var cc = [
'm',
'M',
'l',
'L',
'v',
'V',
'h',
'H',
'z',
'Z',
'c',
'C',
'q',
'Q',
't',
'T',
's',
'S',
'a',
'A',
];
cs = cs.replace(new RegExp(' ', 'g'), ',');
for (var n = 0; n < cc.length; n++) {
cs = cs.replace(new RegExp(cc[n], 'g'), '|' + cc[n]);
}
var arr = cs.split('|');
var ca = [];
var coords = [];
var cpx = 0;
var cpy = 0;
var re = /([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/gi;
var match;
for (n = 1; n < arr.length; n++) {
var str = arr[n];
var c = str.charAt(0);
str = str.slice(1);
coords.length = 0;
while ((match = re.exec(str))) {
coords.push(match[0]);
}
var p = [];
for (var j = 0, jlen = coords.length; j < jlen; j++) {
if (coords[j] === '00') {
p.push(0, 0);
continue;
}
var parsed = parseFloat(coords[j]);
if (!isNaN(parsed)) {
p.push(parsed);
} else {
p.push(0);
}
}
while (p.length > 0) {
if (isNaN(p[0])) {
break;
}
var cmd = null;
var points = [];
var startX = cpx,
startY = cpy;
var prevCmd, ctlPtx, ctlPty;
var rx, ry, psi, fa, fs, x1, y1;
switch (c) {
case 'l':
cpx += p.shift();
cpy += p.shift();
cmd = 'L';
points.push(cpx, cpy);
break;
case 'L':
cpx = p.shift();
cpy = p.shift();
points.push(cpx, cpy);
break;
case 'm':
var dx = p.shift();
var dy = p.shift();
cpx += dx;
cpy += dy;
cmd = 'M';
if (ca.length > 2 && ca[ca.length - 1].command === 'z') {
for (var idx = ca.length - 2; idx >= 0; idx--) {
if (ca[idx].command === 'M') {
cpx = ca[idx].points[0] + dx;
cpy = ca[idx].points[1] + dy;
break;
}
}
}
points.push(cpx, cpy);
c = 'l';
break;
case 'M':
cpx = p.shift();
cpy = p.shift();
cmd = 'M';
points.push(cpx, cpy);
c = 'L';
break;
case 'h':
cpx += p.shift();
cmd = 'L';
points.push(cpx, cpy);
break;
case 'H':
cpx = p.shift();
cmd = 'L';
points.push(cpx, cpy);
break;
case 'v':
cpy += p.shift();
cmd = 'L';
points.push(cpx, cpy);
break;
case 'V':
cpy = p.shift();
cmd = 'L';
points.push(cpx, cpy);
break;
case 'C':
points.push(p.shift(), p.shift(), p.shift(), p.shift());
cpx = p.shift();
cpy = p.shift();
points.push(cpx, cpy);
break;
case 'c':
points.push(cpx + p.shift(), cpy + p.shift(), cpx + p.shift(), cpy + p.shift());
cpx += p.shift();
cpy += p.shift();
cmd = 'C';
points.push(cpx, cpy);
break;
case 'S':
ctlPtx = cpx;
ctlPty = cpy;
prevCmd = ca[ca.length - 1];
if (prevCmd.command === 'C') {
ctlPtx = cpx + (cpx - prevCmd.points[2]);
ctlPty = cpy + (cpy - prevCmd.points[3]);
}
points.push(ctlPtx, ctlPty, p.shift(), p.shift());
cpx = p.shift();
cpy = p.shift();
cmd = 'C';
points.push(cpx, cpy);
break;
case 's':
ctlPtx = cpx;
ctlPty = cpy;
prevCmd = ca[ca.length - 1];
if (prevCmd.command === 'C') {
ctlPtx = cpx + (cpx - prevCmd.points[2]);
ctlPty = cpy + (cpy - prevCmd.points[3]);
}
points.push(ctlPtx, ctlPty, cpx + p.shift(), cpy + p.shift());
cpx += p.shift();
cpy += p.shift();
cmd = 'C';
points.push(cpx, cpy);
break;
case 'Q':
points.push(p.shift(), p.shift());
cpx = p.shift();
cpy = p.shift();
points.push(cpx, cpy);
break;
case 'q':
points.push(cpx + p.shift(), cpy + p.shift());
cpx += p.shift();
cpy += p.shift();
cmd = 'Q';
points.push(cpx, cpy);
break;
case 'T':
ctlPtx = cpx;
ctlPty = cpy;
prevCmd = ca[ca.length - 1];
if (prevCmd.command === 'Q') {
ctlPtx = cpx + (cpx - prevCmd.points[0]);
ctlPty = cpy + (cpy - prevCmd.points[1]);
}
cpx = p.shift();
cpy = p.shift();
cmd = 'Q';
points.push(ctlPtx, ctlPty, cpx, cpy);
break;
case 't':
ctlPtx = cpx;
ctlPty = cpy;
prevCmd = ca[ca.length - 1];
if (prevCmd.command === 'Q') {
ctlPtx = cpx + (cpx - prevCmd.points[0]);
ctlPty = cpy + (cpy - prevCmd.points[1]);
}
cpx += p.shift();
cpy += p.shift();
cmd = 'Q';
points.push(ctlPtx, ctlPty, cpx, cpy);
break;
case 'A':
rx = p.shift();
ry = p.shift();
psi = p.shift();
fa = p.shift();
fs = p.shift();
x1 = cpx;
y1 = cpy;
cpx = p.shift();
cpy = p.shift();
cmd = 'A';
points = convertEndpointToCenterParameterization(x1, y1, cpx, cpy, fa, fs, rx, ry, psi);
break;
case 'a':
rx = p.shift();
ry = p.shift();
psi = p.shift();
fa = p.shift();
fs = p.shift();
x1 = cpx;
y1 = cpy;
cpx += p.shift();
cpy += p.shift();
cmd = 'A';
points = convertEndpointToCenterParameterization(x1, y1, cpx, cpy, fa, fs, rx, ry, psi);
break;
}
ca.push({
command: cmd || c,
points: points,
start: {
x: startX,
y: startY,
},
pathLength: calcLength(startX, startY, cmd || c, points),
});
}
if (c === 'z' || c === 'Z') {
ca.push({
command: 'z',
points: [],
start: undefined,
pathLength: 0,
});
}
}
return ca;
}
function calcLength(x, y, cmd, points) {
var len, p1, p2, t;
switch (cmd) {
case 'L':
return getLineLength(x, y, points[0], points[1]);
case 'C':
len = 0.0;
p1 = getPointOnCubicBezier(0, x, y, points[0], points[1], points[2], points[3], points[4], points[5]);
for (t = 0.01; t <= 1; t += 0.01) {
p2 = getPointOnCubicBezier(t, x, y, points[0], points[1], points[2], points[3], points[4], points[
5]);
len += getLineLength(p1.x, p1.y, p2.x, p2.y);
p1 = p2;
}
return len;
case 'Q':
len = 0.0;
p1 = getPointOnQuadraticBezier(0, x, y, points[0], points[1], points[2], points[3]);
for (t = 0.01; t <= 1; t += 0.01) {
p2 = getPointOnQuadraticBezier(t, x, y, points[0], points[1], points[2], points[3]);
len += getLineLength(p1.x, p1.y, p2.x, p2.y);
p1 = p2;
}
return len;
case 'A':
len = 0.0;
var start = points[4];
var dTheta = points[5];
var end = points[4] + dTheta;
var inc = Math.PI / 180.0;
if (Math.abs(start - end) < inc) {
inc = Math.abs(start - end);
}
p1 = getPointOnEllipticalArc(points[0], points[1], points[2], points[3], start, 0);
if (dTheta < 0) {
for (t = start - inc; t > end; t -= inc) {
p2 = getPointOnEllipticalArc(points[0], points[1], points[2], points[3], t, 0);
len += getLineLength(p1.x, p1.y, p2.x, p2.y);
p1 = p2;
}
} else {
for (t = start + inc; t < end; t += inc) {
p2 = getPointOnEllipticalArc(points[0], points[1], points[2], points[3], t, 0);
len += getLineLength(p1.x, p1.y, p2.x, p2.y);
p1 = p2;
}
}
p2 = getPointOnEllipticalArc(points[0], points[1], points[2], points[3], end, 0);
len += getLineLength(p1.x, p1.y, p2.x, p2.y);
return len;
}
return 0;
}
function convertEndpointToCenterParameterization(x1, y1, x2, y2, fa, fs, rx, ry, psiDeg) {
var psi = psiDeg * (Math.PI / 180.0);
var xp = (Math.cos(psi) * (x1 - x2)) / 2.0 + (Math.sin(psi) * (y1 - y2)) / 2.0;
var yp = (-1 * Math.sin(psi) * (x1 - x2)) / 2.0 +
(Math.cos(psi) * (y1 - y2)) / 2.0;
var lambda = (xp * xp) / (rx * rx) + (yp * yp) / (ry * ry);
if (lambda > 1) {
rx *= Math.sqrt(lambda);
ry *= Math.sqrt(lambda);
}
var f = Math.sqrt((rx * rx * (ry * ry) - rx * rx * (yp * yp) - ry * ry * (xp * xp)) /
(rx * rx * (yp * yp) + ry * ry * (xp * xp)));
if (fa === fs) {
f *= -1;
}
if (isNaN(f)) {
f = 0;
}
var cxp = (f * rx * yp) / ry;
var cyp = (f * -ry * xp) / rx;
var cx = (x1 + x2) / 2.0 + Math.cos(psi) * cxp - Math.sin(psi) * cyp;
var cy = (y1 + y2) / 2.0 + Math.sin(psi) * cxp + Math.cos(psi) * cyp;
var vMag = function(v) {
return Math.sqrt(v[0] * v[0] + v[1] * v[1]);
};
var vRatio = function(u, v) {
return (u[0] * v[0] + u[1] * v[1]) / (vMag(u) * vMag(v));
};
var vAngle = function(u, v) {
return (u[0] * v[1] < u[1] * v[0] ? -1 : 1) * Math.acos(vRatio(u, v));
};
var theta = vAngle([1, 0], [(xp - cxp) / rx, (yp - cyp) / ry]);
var u = [(xp - cxp) / rx, (yp - cyp) / ry];
var v = [(-1 * xp - cxp) / rx, (-1 * yp - cyp) / ry];
var dTheta = vAngle(u, v);
if (vRatio(u, v) <= -1) {
dTheta = Math.PI;
}
if (vRatio(u, v) >= 1) {
dTheta = 0;
}
if (fs === 0 && dTheta > 0) {
dTheta = dTheta - 2 * Math.PI;
}
if (fs === 1 && dTheta < 0) {
dTheta = dTheta + 2 * Math.PI;
}
return [cx, cy, rx, ry, theta, dTheta, psi, fs];
}
function getSelfRect() {
var points = [];
this.dataArray.forEach(function(data) {
if (data.command === 'A') {
var start = data.points[4];
var dTheta = data.points[5];
var end = data.points[4] + dTheta;
var inc = Math.PI / 180.0;
if (Math.abs(start - end) < inc) {
inc = Math.abs(start - end);
}
if (dTheta < 0) {
for (let t = start - inc; t > end; t -= inc) {
const point = Path.getPointOnEllipticalArc(data.points[0], data.points[1], data.points[2],
data.points[3], t, 0);
points.push(point.x, point.y);
}
} else {
for (let t = start + inc; t < end; t += inc) {
const point = Path.getPointOnEllipticalArc(data.points[0], data.points[1], data.points[2],
data.points[3], t, 0);
points.push(point.x, point.y);
}
}
} else if (data.command === 'C') {
for (let t = 0.0; t <= 1; t += 0.01) {
const point = Path.getPointOnCubicBezier(t, data.start.x, data.start.y, data.points[0], data
.points[1], data.points[2], data.points[3], data.points[4], data.points[5]);
points.push(point.x, point.y);
}
} else {
points = points.concat(data.points);
}
});
var minX = points[0];
var maxX = points[0];
var minY = points[1];
var maxY = points[1];
var x, y;
for (var i = 0; i < points.length / 2; i++) {
x = points[i * 2];
y = points[i * 2 + 1];
if (!isNaN(x)) {
minX = Math.min(minX, x);
maxX = Math.max(maxX, x);
}
if (!isNaN(y)) {
minY = Math.min(minY, y);
maxY = Math.max(maxY, y);
}
}
return {
x: Math.round(minX),
y: Math.round(minY),
width: Math.round(maxX - minX),
height: Math.round(maxY - minY),
};
}
function getPointAtLength(length) {
var point, i = 0,
ii = this.dataArray.length;
if (!ii) {
return null;
}
while (i < ii && length > this.dataArray[i].pathLength) {
length -= this.dataArray[i].pathLength;
++i;
}
if (i === ii) {
point = this.dataArray[i - 1].points.slice(-2);
return {
x: point[0],
y: point[1],
};
}
if (length < 0.01) {
point = this.dataArray[i].points.slice(0, 2);
return {
x: point[0],
y: point[1],
};
}
var cp = this.dataArray[i];
var p = cp.points;
switch (cp.command) {
case 'L':
return Path.getPointOnLine(length, cp.start.x, cp.start.y, p[0], p[1]);
case 'C':
return Path.getPointOnCubicBezier(length / cp.pathLength, cp.start.x, cp.start.y, p[0], p[1], p[2], p[3], p[
4], p[5]);
case 'Q':
return Path.getPointOnQuadraticBezier(length / cp.pathLength, cp.start.x, cp.start.y, p[0], p[1], p[2], p[
3]);
case 'A':
var cx = p[0],
cy = p[1],
rx = p[2],
ry = p[3],
theta = p[4],
dTheta = p[5],
psi = p[6];
theta += (dTheta * length) / cp.pathLength;
return Path.getPointOnEllipticalArc(cx, cy, rx, ry, theta, psi);
}
return null;
}
function getLineLength(x1, y1, x2, y2) {
return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
}
function getPointOnLine(dist, P1x, P1y, P2x, P2y, fromX, fromY) {
if (fromX === undefined) {
fromX = P1x;
}
if (fromY === undefined) {
fromY = P1y;
}
var m = (P2y - P1y) / (P2x - P1x + 0.00000001);
var run = Math.sqrt((dist * dist) / (1 + m * m));
if (P2x < P1x) {
run *= -1;
}
var rise = m * run;
var pt;
if (P2x === P1x) {
pt = {
x: fromX,
y: fromY + rise,
};
} else if ((fromY - P1y) / (fromX - P1x + 0.00000001) === m) {
pt = {
x: fromX + run,
y: fromY + rise,
};
} else {
var ix, iy;
var len = this.getLineLength(P1x, P1y, P2x, P2y);
var u = (fromX - P1x) * (P2x - P1x) + (fromY - P1y) * (P2y - P1y);
u = u / (len * len);
ix = P1x + u * (P2x - P1x);
iy = P1y + u * (P2y - P1y);
var pRise = this.getLineLength(fromX, fromY, ix, iy);
var pRun = Math.sqrt(dist * dist - pRise * pRise);
run = Math.sqrt((pRun * pRun) / (1 + m * m));
if (P2x < P1x) {
run *= -1;
}
rise = m * run;
pt = {
x: ix + run,
y: iy + rise,
};
}
return pt;
}
function getPointOnCubicBezier(pct, P1x, P1y, P2x, P2y, P3x, P3y, P4x, P4y) {
function CB1(t) {
return t * t * t;
}
function CB2(t) {
return 3 * t * t * (1 - t);
}
function CB3(t) {
return 3 * t * (1 - t) * (1 - t);
}
function CB4(t) {
return (1 - t) * (1 - t) * (1 - t);
}
var x = P4x * CB1(pct) + P3x * CB2(pct) + P2x * CB3(pct) + P1x * CB4(pct);
var y = P4y * CB1(pct) + P3y * CB2(pct) + P2y * CB3(pct) + P1y * CB4(pct);
return {
x: x,
y: y,
};
}
function getPointOnQuadraticBezier(pct, P1x, P1y, P2x, P2y, P3x, P3y) {
function QB1(t) {
return t * t;
}
function QB2(t) {
return 2 * t * (1 - t);
}
function QB3(t) {
return (1 - t) * (1 - t);
}
var x = P3x * QB1(pct) + P2x * QB2(pct) + P1x * QB3(pct);
var y = P3y * QB1(pct) + P2y * QB2(pct) + P1y * QB3(pct);
return {
x: x,
y: y,
};
}
function getPointOnEllipticalArc(cx, cy, rx, ry, theta, psi) {
var cosPsi = Math.cos(psi),
sinPsi = Math.sin(psi);
var pt = {
x: rx * Math.cos(theta),
y: ry * Math.sin(theta),
};
return {
x: cx + (pt.x * cosPsi - pt.y * sinPsi),
y: cy + (pt.x * sinPsi + pt.y * cosPsi),
};
}
let path2d = function(render, config = {}) {
const [w, h] = render.area;
let cfg = {
name: 'path',
animationCurve: 'easeOutBack',
hover: true,
drag: true,
shape: {
path: '',
close:true,
points:[]
},
style: {
stroke: '#000',
fill:'#000',
lineWidth: 1,
hoverCursor: 'pointer',
},
...config,
draw({ ctx }, { shape, style: { lineWidth } }){
let { points, close,x,y ,path} = shape
var ca=[]
if(this.shape['points'].length>0&&this.shape['points']){
ca = this.shape['svg']
}else{
ca = parsePathData(path);
// ca = ca.map(el=>{
// if(el.points.length){
// return {...el,points:[el.points[0]+x,el.points[1]+y]}
// }
// return el
// })
let ar = ca.map(el=> el.points)
this.shape['points'] = ar.filter((el)=>el.length==2)
this.shape['svg'] = ca;
}
const context=ctx
context.beginPath();
for (var n = 0; n < ca.length; n++) {
var c = ca[n].command;
var p = ca[n].points;
switch (c) {
case 'L':
context.lineTo(p[0], p[1]);
break;
case 'M':
context.moveTo(p[0], p[1]);
break;
case 'C':
context.bezierCurveTo(p[0], p[1], p[2], p[3], p[4], p[5]);
break;
case 'Q':
context.quadraticCurveTo(p[0], p[1], p[2], p[3]);
break;
case 'A':
var cx = p[0], cy = p[1], rx = p[2], ry = p[3], theta = p[4], dTheta = p[5], psi = p[6], fs = p[7];
var r = rx > ry ? rx : ry;
var scaleX = rx > ry ? 1 : rx / ry;
var scaleY = rx > ry ? ry / rx : 1;
context.translate(cx, cy);
context.rotate(psi);
context.scale(scaleX, scaleY);
context.arc(0, 0, r, theta, theta + dTheta, 1 - fs);
context.scale(1 / scaleX, 1 / scaleY);
context.rotate(-psi);
context.translate(-cx, -cy);
break;
case 'z':
close = true;
context.closePath();
break;
}
}
if (close) {
ctx.closePath()
ctx.fill()
ctx.stroke()
} else {
ctx.stroke()
}
},
};
return cfg;
}
export default path2d;
@@ -0,0 +1,136 @@
/**
* 环形图。
* 作者:tmzdy
* url:https://jx2d.cn
*/
let ring = function (render, config={}) {
let defauletcfg = {
tooltip: {
trigger: 'item'
},
color:["#E91E63","#2196F3","#311B92","#FDD835","#BCAAA4"],
legend: {
top: '5%',
left: 'center'
},
series: [{
name: 'Access From',
type: 'pie',
radius: ['20%', '35%'],
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: '40',
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: [{
value: 1048,
name: 'Search Engine'
},
{
value: 735,
name: 'Direct'
},
{
value: 580,
name: 'Email'
},
{
value: 484,
name: 'Union Ads'
},
{
value: 300,
name: 'Video Ads'
}
]
}]
}
defauletcfg = {...defauletcfg,...config}
const [w, h] = render.area;
//最大圆环厚度。等于圆。
let lineMaxHeight = w / 2;
let lineStoreWidth = (parseFloat(defauletcfg.series[0].radius[1]) - parseFloat(defauletcfg.series[0].radius[0]))/100;
lineStoreWidth =Math.floor( lineStoreWidth*lineMaxHeight);
let r = parseFloat(defauletcfg.series[0].radius[1])/100 * lineMaxHeight;
let data = defauletcfg.series[0].data;
let maxValue = data.map(el => parseInt(el.value));
//总数量。
maxValue = maxValue.reduce((p,c)=>p+c);
//计算各自的比例数量。保留两位小数点。
let rang = Math.PI *2;
//每一个元素的圆弧的长度。
let rangArray = data.map(el=>{
let num = parseInt(el.value)/maxValue;
return rang*num
})
let dataBlv = data.map((el,index) => {
let st = index;
let num = parseFloat(el.value)/maxValue;
let blv = parseFloat(num.toFixed(2));
let startAngle = 0;
let endAngle = 0;
if(st==0){
startAngle = 0;
endAngle = rangArray[st]
}else{
let s = rangArray.slice(0,st)
startAngle = s.reduce((p,c)=>p+c);
endAngle = startAngle+rangArray[st]
}
return {...el,
blv:blv,
startAngle:startAngle,
endAngle:endAngle
};
});
let xf = [];
dataBlv.forEach((el,index)=>{
let cfg = {
name: 'arc',
animationCurve: 'liner',
hover: false,
drag: false,
shape: {
rx: w/2,
ry: h/2,
r: r,
startAngle: el.startAngle,
endAngle: el.endAngle,
},
style: {
stroke: defauletcfg.color[index],
lineWidth: lineStoreWidth,
shadowBlur: 0,
rotate: 0,
shadowColor: '#66eece',
hoverCursor: 'pointer',
},
};
xf.push(cfg)
})
return xf;
}
export default ring;
@@ -0,0 +1,11 @@
import CRender from './class/crender.class'
import {
extendNewGraph
} from './config/graphs'
export default {
CRender,
extendNewGraph
}
@@ -0,0 +1,50 @@
/**
* @description Draw a polyline path
* @param {Object} ctx Canvas 2d context
* @param {Array} points The points that makes up a polyline
* @param {Boolean} beginPath Whether to execute beginPath
* @param {Boolean} closePath Whether to execute closePath
* @return {Undefined} Void
*/
export function drawPolylinePath (ctx, points, beginPath = false, closePath = false) {
if (!ctx || points.length < 2) return false
if (beginPath) ctx.beginPath()
points.forEach((point, i) =>
point && (i === 0 ? ctx.moveTo(...point) : ctx.lineTo(...point)))
if (closePath) {
ctx.closePath()
ctx.draw()
}
}
/**
* @description Draw a bezier curve path
* @param {Object} ctx Canvas 2d context
* @param {Array} points The points that makes up a bezier curve
* @param {Array} moveTo The point need to excute moveTo
* @param {Boolean} beginPath Whether to execute beginPath
* @param {Boolean} closePath Whether to execute closePath
* @return {Undefined} Void
*/
export function drawBezierCurvePath (ctx, points, moveTo = false, beginPath = false, closePath = false) {
if (!ctx || !points) return false
if (beginPath) ctx.beginPath()
if (moveTo) ctx.moveTo(...moveTo)
points.forEach(item => (item && ctx.bezierCurveTo(...item[0], ...item[1], ...item[2])))
if (closePath) {
ctx.closePath()
ctx.draw()
}
}
export default {
drawPolylinePath,
drawBezierCurvePath
}
@@ -0,0 +1,332 @@
const { abs, sqrt, sin, cos, max, min, PI } = Math
/**
* @description Clone an object or array
* @param {Object|Array} object Cloned object
* @param {Boolean} recursion Whether to use recursive cloning
* @return {Object|Array} Clone object
*/
export function deepClone (object, recursion = false) {
if (!object) return object
const { parse, stringify } = JSON
if (!recursion) return parse(stringify(object))
const clonedObj = object instanceof Array ? [] : {}
if (object && typeof object === 'object') {
for (let key in object) {
if (object.hasOwnProperty(key)) {
if (object[key] && typeof object[key] === 'object') {
clonedObj[key] = deepClone(object[key], true)
} else {
clonedObj[key] = object[key]
}
}
}
}
return clonedObj
}
/**
* @description Eliminate line blur due to 1px line width
* @param {Array} points Line points
* @return {Array} Line points after processed
*/
export function eliminateBlur (points) {
return points.map(([x, y]) => [parseInt(x) + 0.5, parseInt(y) + 0.5])
}
/**
* @description Check if the point is inside the circle
* @param {Array} point Postion of point
* @param {Number} rx Circle x coordinate
* @param {Number} ry Circle y coordinate
* @param {Number} r Circle radius
* @return {Boolean} Result of check
*/
export function checkPointIsInCircle (point, rx, ry, r) {
return getTwoPointDistance(point, [rx, ry]) <= r
}
/**
* @description Get the distance between two points
* @param {Array} point1 point1
* @param {Array} point2 point2
* @return {Number} Distance between two points
*/
export function getTwoPointDistance ([xa, ya], [xb, yb]) {
const minusX = abs(xa - xb)
const minusY = abs(ya - yb)
return sqrt(minusX * minusX + minusY * minusY)
}
/**
* @description Check if the point is inside the polygon
* @param {Array} point Postion of point
* @param {Array} points The points that makes up a polyline
* @return {Boolean} Result of check
*/
export function checkPointIsInPolygon (point, polygon) {
let counter = 0
const [x, y] = point
const pointNum = polygon.length
for (let i = 1, p1 = polygon[0]; i <= pointNum; i++) {
const p2 = polygon[i % pointNum]
if (x > min(p1[0], p2[0]) && x <= max(p1[0], p2[0])) {
if (y <= max(p1[1], p2[1])) {
if (p1[0] !== p2[0]) {
const xinters = (x - p1[0]) * (p2[1] - p1[1]) / (p2[0] - p1[0]) + p1[1]
if (p1[1] === p2[1] || y <= xinters) {
counter++
}
}
}
}
p1 = p2
}
return counter % 2 === 1
}
/**
* @description Check if the point is inside the sector
* @param {Array} point Postion of point
* @param {Number} rx Sector x coordinate
* @param {Number} ry Sector y coordinate
* @param {Number} r Sector radius
* @param {Number} startAngle Sector start angle
* @param {Number} endAngle Sector end angle
* @param {Boolean} clockWise Whether the sector angle is clockwise
* @return {Boolean} Result of check
*/
export function checkPointIsInSector (point, rx, ry, r, startAngle, endAngle, clockWise) {
if (!point) return false
if (getTwoPointDistance(point, [rx, ry]) > r) return false
if (!clockWise) [startAngle, endAngle] = deepClone([endAngle, startAngle])
const reverseBE = startAngle > endAngle
if (reverseBE) [startAngle, endAngle] = [endAngle, startAngle]
const minus = endAngle - startAngle
if (minus >= PI * 2) return true
const [x, y] = point
const [bx, by] = getCircleRadianPoint(rx, ry, r, startAngle)
const [ex, ey] = getCircleRadianPoint(rx, ry, r, endAngle)
const vPoint = [x - rx, y - ry]
let vBArm = [bx - rx, by - ry]
let vEArm = [ex - rx, ey - ry]
const reverse = minus > PI
if (reverse) [vBArm, vEArm] = deepClone([vEArm, vBArm])
let inSector = isClockWise(vBArm, vPoint) && !isClockWise(vEArm, vPoint)
if (reverse) inSector = !inSector
if (reverseBE) inSector = !inSector
return inSector
}
/**
* @description Determine if the point is in the clockwise direction of the vector
* @param {Array} vArm Vector
* @param {Array} vPoint Point
* @return {Boolean} Result of check
*/
function isClockWise (vArm, vPoint) {
const [ax, ay] = vArm
const [px, py] = vPoint
return -ay * px + ax * py > 0
}
/**
* @description Check if the point is inside the polyline
* @param {Array} point Postion of point
* @param {Array} polyline The points that makes up a polyline
* @param {Number} lineWidth Polyline linewidth
* @return {Boolean} Result of check
*/
export function checkPointIsNearPolyline (point, polyline, lineWidth) {
const halfLineWidth = lineWidth / 2
const moveUpPolyline = polyline.map(([x, y]) => [x, y - halfLineWidth])
const moveDownPolyline = polyline.map(([x, y]) => [x, y + halfLineWidth])
const polygon = [...moveUpPolyline, ...moveDownPolyline.reverse()]
return checkPointIsInPolygon(point, polygon)
}
/**
* @description Check if the point is inside the rect
* @param {Array} point Postion of point
* @param {Number} x Rect start x coordinate
* @param {Number} y Rect start y coordinate
* @param {Number} width Rect width
* @param {Number} height Rect height
* @return {Boolean} Result of check
*/
export function checkPointIsInRect ([px, py], x, y, width, height) {
if (px < x) return false
if (py < y) return false
if (px > x + width) return false
if (py > y + height) return false
return true
}
/**
* @description Get the coordinates of the rotated point
* @param {Number} rotate Degree of rotation
* @param {Array} point Postion of point
* @param {Array} origin Rotation center
* @param {Array} origin Rotation center
* @return {Number} Coordinates after rotation
*/
export function getRotatePointPos (rotate = 0, point, origin = [0, 0]) {
if (!point) return false
if (rotate % 360 === 0) return point
const [x, y] = point
const [ox, oy] = origin
rotate *= PI / 180
return [
(x - ox) * cos(rotate) - (y - oy) * sin(rotate) + ox,
(x - ox) * sin(rotate) + (y - oy) * cos(rotate) + oy
]
}
/**
* @description Get the coordinates of the scaled point
* @param {Array} scale Scale factor
* @param {Array} point Postion of point
* @param {Array} origin Scale center
* @return {Number} Coordinates after scale
*/
export function getScalePointPos (scale = [1, 1], point, origin = [0, 0]) {
if (!point) return false
if (scale === 1) return point
const [x, y] = point
const [ox, oy] = origin
const [xs, ys] = scale
const relativePosX = x - ox
const relativePosY = y - oy
return [
relativePosX * xs + ox,
relativePosY * ys + oy
]
}
/**
* @description Get the coordinates of the scaled point
* @param {Array} translate Translation distance
* @param {Array} point Postion of point
* @return {Number} Coordinates after translation
*/
export function getTranslatePointPos (translate, point) {
if (!translate || !point) return false
const [x, y] = point
const [tx, ty] = translate
return [x + tx, y + ty]
}
/**
* @description Get the distance from the point to the line
* @param {Array} point Postion of point
* @param {Array} lineBegin Line start position
* @param {Array} lineEnd Line end position
* @return {Number} Distance between point and line
*/
export function getDistanceBetweenPointAndLine (point, lineBegin, lineEnd) {
if (!point || !lineBegin || !lineEnd) return false
const [x, y] = point
const [x1, y1] = lineBegin
const [x2, y2] = lineEnd
const a = y2 - y1
const b = x1 - x2
const c = y1 * (x2 - x1) - x1 * (y2 - y1)
const molecule = abs(a * x + b * y + c)
const denominator = sqrt(a * a + b * b)
return molecule / denominator
}
/**
* @description Get the coordinates of the specified radian on the circle
* @param {Number} x Circle x coordinate
* @param {Number} y Circle y coordinate
* @param {Number} radius Circle radius
* @param {Number} radian Specfied radian
* @return {Array} Postion of point
*/
export function getCircleRadianPoint (x, y, radius, radian) {
return [x + cos(radian) * radius, y + sin(radian) * radius]
}
/**
* @description Get the points that make up a regular polygon
* @param {Number} x X coordinate of the polygon inscribed circle
* @param {Number} y Y coordinate of the polygon inscribed circle
* @param {Number} r Radius of the polygon inscribed circle
* @param {Number} side Side number
* @param {Number} minus Radian offset
* @return {Array} Points that make up a regular polygon
*/
export function getRegularPolygonPoints (rx, ry, r, side, minus = PI * -0.5) {
const radianGap = PI * 2 / side
const radians = new Array(side).fill('').map((t, i) => i * radianGap + minus)
return radians.map(radian => getCircleRadianPoint(rx, ry, r, radian))
}
export default {
deepClone,
eliminateBlur,
checkPointIsInCircle,
checkPointIsInPolygon,
checkPointIsInSector,
checkPointIsNearPolyline,
getTwoPointDistance,
getRotatePointPos,
getScalePointPos,
getTranslatePointPos,
getCircleRadianPoint,
getRegularPolygonPoints,
getDistanceBetweenPointAndLine
}
@@ -0,0 +1,109 @@
export const linear = [[[0, 1], '', [0.33, 0.67]], [[1, 0], [0.67, 0.33]]]
/**
* @description Sine
*/
export const easeInSine = [[[0, 1]], [[0.538, 0.564], [0.169, 0.912], [0.880, 0.196]], [[1, 0]]]
export const easeOutSine = [[[0, 1]], [[0.444, 0.448], [0.169, 0.736], [0.718, 0.16]], [[1, 0]]]
export const easeInOutSine = [[[0, 1]], [[0.5, 0.5], [0.2, 1], [0.8, 0]], [[1, 0]]]
/**
* @description Quad
*/
export const easeInQuad = [[[0, 1]], [[0.550, 0.584], [0.231, 0.904], [0.868, 0.264]], [[1, 0]]]
export const easeOutQuad = [[[0, 1]], [[0.413, 0.428], [0.065, 0.816], [0.760, 0.04]], [[1, 0]]]
export const easeInOutQuad = [[[0, 1]], [[0.5, 0.5], [0.3, 0.9], [0.7, 0.1]], [[1, 0]]]
/**
* @description Cubic
*/
export const easeInCubic = [[[0, 1]], [[0.679, 0.688], [0.366, 0.992], [0.992, 0.384]], [[1, 0]]]
export const easeOutCubic = [[[0, 1]], [[0.321, 0.312], [0.008, 0.616], [0.634, 0.008]], [[1, 0]]]
export const easeInOutCubic = [[[0, 1]], [[0.5, 0.5], [0.3, 1], [0.7, 0]], [[1, 0]]]
/**
* @description Quart
*/
export const easeInQuart = [[[0, 1]], [[0.812, 0.74], [0.611, 0.988], [1.013, 0.492]], [[1, 0]]]
export const easeOutQuart = [[[0, 1]], [[0.152, 0.244], [0.001, 0.448], [0.285, -0.02]], [[1, 0]]]
export const easeInOutQuart = [[[0, 1]], [[0.5, 0.5], [0.4, 1], [0.6, 0]], [[1, 0]]]
/**
* @description Quint
*/
export const easeInQuint = [[[0, 1]], [[0.857, 0.856], [0.714, 1], [1, 0.712]], [[1, 0]]]
export const easeOutQuint = [[[0, 1]], [[0.108, 0.2], [0.001, 0.4], [0.214, -0.012]], [[1, 0]]]
export const easeInOutQuint = [[[0, 1]], [[0.5, 0.5], [0.5, 1], [0.5, 0]], [[1, 0]]]
/**
* @description Back
*/
export const easeInBack = [[[0, 1]], [[0.667, 0.896], [0.380, 1.184], [0.955, 0.616]], [[1, 0]]]
export const easeOutBack = [[[0, 1]], [[0.335, 0.028], [0.061, 0.22], [0.631, -0.18]], [[1, 0]]]
export const easeInOutBack = [[[0, 1]], [[0.5, 0.5], [0.4, 1.4], [0.6, -0.4]], [[1, 0]]]
/**
* @description Elastic
*/
export const easeInElastic = [[[0, 1]], [[0.474, 0.964], [0.382, 0.988], [0.557, 0.952]], [[0.619, 1.076], [0.565, 1.088], [0.669, 1.08]], [[0.770, 0.916], [0.712, 0.924], [0.847, 0.904]], [[0.911, 1.304], [0.872, 1.316], [0.961, 1.34]], [[1, 0]]]
export const easeOutElastic = [[[0, 1]], [[0.073, -0.32], [0.034, -0.328], [0.104, -0.344]], [[0.191, 0.092], [0.110, 0.06], [0.256, 0.08]], [[0.310, -0.076], [0.260, -0.068], [0.357, -0.076]], [[0.432, 0.032], [0.362, 0.028], [0.683, -0.004]], [[1, 0]]]
export const easeInOutElastic = [[[0, 1]], [[0.210, 0.94], [0.167, 0.884], [0.252, 0.98]], [[0.299, 1.104], [0.256, 1.092], [0.347, 1.108]], [[0.5, 0.496], [0.451, 0.672], [0.548, 0.324]], [[0.696, -0.108], [0.652, -0.112], [0.741, -0.124]], [[0.805, 0.064], [0.756, 0.012], [0.866, 0.096]], [[1, 0]]]
/**
* @description Bounce
*/
export const easeInBounce = [[[0, 1]], [[0.148, 1], [0.075, 0.868], [0.193, 0.848]], [[0.326, 1], [0.276, 0.836], [0.405, 0.712]], [[0.600, 1], [0.511, 0.708], [0.671, 0.348]], [[1, 0]]]
export const easeOutBounce = [[[0, 1]], [[0.357, 0.004], [0.270, 0.592], [0.376, 0.252]], [[0.604, -0.004], [0.548, 0.312], [0.669, 0.184]], [[0.820, 0], [0.749, 0.184], [0.905, 0.132]], [[1, 0]]]
export const easeInOutBounce = [[[0, 1]], [[0.102, 1], [0.050, 0.864], [0.117, 0.86]], [[0.216, 0.996], [0.208, 0.844], [0.227, 0.808]], [[0.347, 0.996], [0.343, 0.8], [0.480, 0.292]], [[0.635, 0.004], [0.511, 0.676], [0.656, 0.208]], [[0.787, 0], [0.760, 0.2], [0.795, 0.144]], [[0.905, -0.004], [0.899, 0.164], [0.944, 0.144]], [[1, 0]]]
export default new Map([
['linear', linear],
['easeInSine', easeInSine],
['easeOutSine', easeOutSine],
['easeInOutSine', easeInOutSine],
['easeInQuad', easeInQuad],
['easeOutQuad', easeOutQuad],
['easeInOutQuad', easeInOutQuad],
['easeInCubic', easeInCubic],
['easeOutCubic', easeOutCubic],
['easeInOutCubic', easeInOutCubic],
['easeInQuart', easeInQuart],
['easeOutQuart', easeOutQuart],
['easeInOutQuart', easeInOutQuart],
['easeInQuint', easeInQuint],
['easeOutQuint', easeOutQuint],
['easeInOutQuint', easeInOutQuint],
['easeInBack', easeInBack],
['easeOutBack', easeOutBack],
['easeInOutBack', easeInOutBack],
['easeInElastic', easeInElastic],
['easeOutElastic', easeOutElastic],
['easeInOutElastic', easeInOutElastic],
['easeInBounce', easeInBounce],
['easeOutBounce', easeOutBounce],
['easeInOutBounce', easeInOutBounce]
])
@@ -0,0 +1,310 @@
import curves from './config/curves'
const defaultTransitionBC = 'linear'
/**
* @description Get the N-frame animation state by the start and end state
* of the animation and the easing curve
* @param {String|Array} tBC Easing curve name or data
* @param {Number|Array|Object} startState Animation start state
* @param {Number|Array|Object} endState Animation end state
* @param {Number} frameNum Number of Animation frames
* @param {Boolean} deep Whether to use recursive mode
* @return {Array|Boolean} State of each frame of the animation (Invalid input will return false)
*/
export function transition (tBC, startState = null, endState = null, frameNum = 30, deep = false) {
if (!checkParams(...arguments)) return false
try {
// Get the transition bezier curve
const bezierCurve = getBezierCurve(tBC)
// Get the progress of each frame state
const frameStateProgress = getFrameStateProgress(bezierCurve, frameNum)
// If the recursion mode is not enabled or the state type is Number, the shallow state calculation is performed directly.
if (!deep || typeof endState === 'number') return getTransitionState(startState, endState, frameStateProgress)
return recursionTransitionState(startState, endState, frameStateProgress)
} catch(e) {
console.warn('Transition parameter may be abnormal!')
return [endState]
}
}
/**
* @description Check if the parameters are legal
* @param {String} tBC Name of transition bezier curve
* @param {Any} startState Transition start state
* @param {Any} endState Transition end state
* @param {Number} frameNum Number of transition frames
* @return {Boolean} Is the parameter legal
*/
function checkParams (tBC, startState = false, endState = false, frameNum = 30) {
if (!tBC || startState === false || endState === false || !frameNum) {
console.error('transition: Missing Parameters!')
return false
}
if (typeof startState !== typeof endState) {
console.error('transition: Inconsistent Status Types!')
return false
}
const stateType = typeof endState
if (stateType === 'string' || stateType === 'boolean' || !tBC.length) {
console.error('transition: Unsupported Data Type of State!')
return false
}
if (!curves.has(tBC) && !(tBC instanceof Array)) {
// console.warn('transition: Transition curve not found, default curve will be used!')
}
return true
}
/**
* @description Get the transition bezier curve
* @param {String} tBC Name of transition bezier curve
* @return {Array} Bezier curve data
*/
function getBezierCurve (tBC) {
let bezierCurve = ''
if (curves.has(tBC)) {
bezierCurve = curves.get(tBC)
} else if (tBC instanceof Array) {
bezierCurve = tBC
} else {
bezierCurve = curves.get(defaultTransitionBC)
}
return bezierCurve
}
/**
* @description Get the progress of each frame state
* @param {Array} bezierCurve Transition bezier curve
* @param {Number} frameNum Number of transition frames
* @return {Array} Progress of each frame state
*/
function getFrameStateProgress (bezierCurve, frameNum) {
const tMinus = 1 / (frameNum - 1)
const tState = new Array(frameNum).fill(0).map((t, i) => i * tMinus)
const frameState = tState.map(t => getFrameStateFromT(bezierCurve, t))
return frameState
}
/**
* @description Get the progress of the corresponding frame according to t
* @param {Array} bezierCurve Transition bezier curve
* @param {Number} t Current frame t
* @return {Number} Progress of current frame
*/
function getFrameStateFromT (bezierCurve, t) {
const tBezierCurvePoint = getBezierCurvePointFromT(bezierCurve, t)
const bezierCurvePointT = getBezierCurvePointTFromReT(tBezierCurvePoint, t)
return getBezierCurveTState(tBezierCurvePoint, bezierCurvePointT)
}
/**
* @description Get the corresponding sub-curve according to t
* @param {Array} bezierCurve Transition bezier curve
* @param {Number} t Current frame t
* @return {Array} Sub-curve of t
*/
function getBezierCurvePointFromT (bezierCurve, t) {
const lastIndex = bezierCurve.length - 1
let [begin, end] = ['', '']
bezierCurve.findIndex((item, i) => {
if (i === lastIndex) return
begin = item
end = bezierCurve[i + 1]
const currentMainPointX = begin[0][0]
const nextMainPointX = end[0][0]
return t >= currentMainPointX && t < nextMainPointX
})
const p0 = begin[0]
const p1 = begin[2] || begin[0]
const p2 = end[1] || end[0]
const p3 = end[0]
return [p0, p1, p2, p3]
}
/**
* @description Get local t based on t and sub-curve
* @param {Array} bezierCurve Sub-curve
* @param {Number} t Current frame t
* @return {Number} local t of sub-curve
*/
function getBezierCurvePointTFromReT (bezierCurve, t) {
const reBeginX = bezierCurve[0][0]
const reEndX = bezierCurve[3][0]
const xMinus = reEndX - reBeginX
const tMinus = t - reBeginX
return tMinus / xMinus
}
/**
* @description Get the curve progress of t
* @param {Array} bezierCurve Sub-curve
* @param {Number} t Current frame t
* @return {Number} Progress of current frame
*/
function getBezierCurveTState ([[, p0], [, p1], [, p2], [, p3]], t) {
const { pow } = Math
const tMinus = 1 - t
const result1 = p0 * pow(tMinus, 3)
const result2 = 3 * p1 * t * pow(tMinus, 2)
const result3 = 3 * p2 * pow(t, 2) * tMinus
const result4 = p3 * pow(t, 3)
return 1 - (result1 + result2 + result3 + result4)
}
/**
* @description Get transition state according to frame progress
* @param {Any} startState Transition start state
* @param {Any} endState Transition end state
* @param {Array} frameState Frame state progress
* @return {Array} Transition frame state
*/
function getTransitionState (begin, end, frameState) {
let stateType = 'object'
if (typeof begin === 'number') stateType = 'number'
if (begin instanceof Array) stateType = 'array'
if (stateType === 'number') return getNumberTransitionState(begin, end, frameState)
if (stateType === 'array') return getArrayTransitionState(begin, end, frameState)
if (stateType === 'object') return getObjectTransitionState(begin, end, frameState)
return frameState.map(t => end)
}
/**
* @description Get the transition data of the number type
* @param {Number} startState Transition start state
* @param {Number} endState Transition end state
* @param {Array} frameState Frame state progress
* @return {Array} Transition frame state
*/
function getNumberTransitionState (begin, end, frameState) {
const minus = end - begin
return frameState.map(s => begin + minus * s)
}
/**
* @description Get the transition data of the array type
* @param {Array} startState Transition start state
* @param {Array} endState Transition end state
* @param {Array} frameState Frame state progress
* @return {Array} Transition frame state
*/
function getArrayTransitionState (begin, end, frameState) {
const minus = end.map((v, i) => {
if (typeof v !== 'number') return false
return v - begin[i]
})
return frameState.map(s =>
minus.map((v, i) => {
if (v === false) return end[i]
return begin[i] + v * s
}))
}
/**
* @description Get the transition data of the object type
* @param {Object} startState Transition start state
* @param {Object} endState Transition end state
* @param {Array} frameState Frame state progress
* @return {Array} Transition frame state
*/
function getObjectTransitionState (begin, end, frameState) {
const keys = Object.keys(end)
const beginValue = keys.map(k => begin[k])
const endValue = keys.map(k => end[k])
const arrayState = getArrayTransitionState(beginValue, endValue, frameState)
return arrayState.map(item => {
const frameData = {}
item.forEach((v, i) => (frameData[keys[i]] = v))
return frameData
})
}
/**
* @description Get the transition state data by recursion
* @param {Array|Object} startState Transition start state
* @param {Array|Object} endState Transition end state
* @param {Array} frameState Frame state progress
* @return {Array} Transition frame state
*/
function recursionTransitionState (begin, end, frameState) {
const state = getTransitionState(begin, end, frameState)
for (let key in end) {
const bTemp = begin[key]
const eTemp = end[key]
if (typeof eTemp !== 'object') continue
const data = recursionTransitionState(bTemp, eTemp, frameState)
state.forEach((fs, i) => (fs[key] = data[i]))
}
return state
}
/**
* @description Inject new curve into curves as config
* @param {Any} key The key of curve
* @param {Array} curve Bezier curve data
* @return {Undefined} No return
*/
export function injectNewCurve (key, curve) {
if (!key || !curve) {
console.error('InjectNewCurve Missing Parameters!')
return
}
curves.set(key, curve)
}
export default transition