UNPKG

21.1 kBJavaScriptView Raw
1import { cubicSubdivide } from '../core/curve.js';
2import Path from '../graphic/Path.js';
3import { defaults, map } from '../core/util.js';
4import { lerp } from '../core/vector.js';
5import { clonePath } from './path.js';
6import Transformable from '../core/Transformable.js';
7import { split } from './dividePath.js';
8import { pathToBezierCurves } from './convertPath.js';
9function alignSubpath(subpath1, subpath2) {
10 var len1 = subpath1.length;
11 var len2 = subpath2.length;
12 if (len1 === len2) {
13 return [subpath1, subpath2];
14 }
15 var tmpSegX = [];
16 var tmpSegY = [];
17 var shorterPath = len1 < len2 ? subpath1 : subpath2;
18 var shorterLen = Math.min(len1, len2);
19 var diff = Math.abs(len2 - len1) / 6;
20 var shorterBezierCount = (shorterLen - 2) / 6;
21 var eachCurveSubDivCount = Math.ceil(diff / shorterBezierCount) + 1;
22 var newSubpath = [shorterPath[0], shorterPath[1]];
23 var remained = diff;
24 for (var i = 2; i < shorterLen;) {
25 var x0 = shorterPath[i - 2];
26 var y0 = shorterPath[i - 1];
27 var x1 = shorterPath[i++];
28 var y1 = shorterPath[i++];
29 var x2 = shorterPath[i++];
30 var y2 = shorterPath[i++];
31 var x3 = shorterPath[i++];
32 var y3 = shorterPath[i++];
33 if (remained <= 0) {
34 newSubpath.push(x1, y1, x2, y2, x3, y3);
35 continue;
36 }
37 var actualSubDivCount = Math.min(remained, eachCurveSubDivCount - 1) + 1;
38 for (var k = 1; k <= actualSubDivCount; k++) {
39 var p = k / actualSubDivCount;
40 cubicSubdivide(x0, x1, x2, x3, p, tmpSegX);
41 cubicSubdivide(y0, y1, y2, y3, p, tmpSegY);
42 x0 = tmpSegX[3];
43 y0 = tmpSegY[3];
44 newSubpath.push(tmpSegX[1], tmpSegY[1], tmpSegX[2], tmpSegY[2], x0, y0);
45 x1 = tmpSegX[5];
46 y1 = tmpSegY[5];
47 x2 = tmpSegX[6];
48 y2 = tmpSegY[6];
49 }
50 remained -= actualSubDivCount - 1;
51 }
52 return shorterPath === subpath1 ? [newSubpath, subpath2] : [subpath1, newSubpath];
53}
54function createSubpath(lastSubpathSubpath, otherSubpath) {
55 var len = lastSubpathSubpath.length;
56 var lastX = lastSubpathSubpath[len - 2];
57 var lastY = lastSubpathSubpath[len - 1];
58 var newSubpath = [];
59 for (var i = 0; i < otherSubpath.length;) {
60 newSubpath[i++] = lastX;
61 newSubpath[i++] = lastY;
62 }
63 return newSubpath;
64}
65export function alignBezierCurves(array1, array2) {
66 var _a;
67 var lastSubpath1;
68 var lastSubpath2;
69 var newArray1 = [];
70 var newArray2 = [];
71 for (var i = 0; i < Math.max(array1.length, array2.length); i++) {
72 var subpath1 = array1[i];
73 var subpath2 = array2[i];
74 var newSubpath1 = void 0;
75 var newSubpath2 = void 0;
76 if (!subpath1) {
77 newSubpath1 = createSubpath(lastSubpath1 || subpath2, subpath2);
78 newSubpath2 = subpath2;
79 }
80 else if (!subpath2) {
81 newSubpath2 = createSubpath(lastSubpath2 || subpath1, subpath1);
82 newSubpath1 = subpath1;
83 }
84 else {
85 _a = alignSubpath(subpath1, subpath2), newSubpath1 = _a[0], newSubpath2 = _a[1];
86 lastSubpath1 = newSubpath1;
87 lastSubpath2 = newSubpath2;
88 }
89 newArray1.push(newSubpath1);
90 newArray2.push(newSubpath2);
91 }
92 return [newArray1, newArray2];
93}
94export function centroid(array) {
95 var signedArea = 0;
96 var cx = 0;
97 var cy = 0;
98 var len = array.length;
99 for (var i = 0, j = len - 2; i < len; j = i, i += 2) {
100 var x0 = array[j];
101 var y0 = array[j + 1];
102 var x1 = array[i];
103 var y1 = array[i + 1];
104 var a = x0 * y1 - x1 * y0;
105 signedArea += a;
106 cx += (x0 + x1) * a;
107 cy += (y0 + y1) * a;
108 }
109 if (signedArea === 0) {
110 return [array[0] || 0, array[1] || 0];
111 }
112 return [cx / signedArea / 3, cy / signedArea / 3, signedArea];
113}
114function findBestRingOffset(fromSubBeziers, toSubBeziers, fromCp, toCp) {
115 var bezierCount = (fromSubBeziers.length - 2) / 6;
116 var bestScore = Infinity;
117 var bestOffset = 0;
118 var len = fromSubBeziers.length;
119 var len2 = len - 2;
120 for (var offset = 0; offset < bezierCount; offset++) {
121 var cursorOffset = offset * 6;
122 var score = 0;
123 for (var k = 0; k < len; k += 2) {
124 var idx = k === 0 ? cursorOffset : ((cursorOffset + k - 2) % len2 + 2);
125 var x0 = fromSubBeziers[idx] - fromCp[0];
126 var y0 = fromSubBeziers[idx + 1] - fromCp[1];
127 var x1 = toSubBeziers[k] - toCp[0];
128 var y1 = toSubBeziers[k + 1] - toCp[1];
129 var dx = x1 - x0;
130 var dy = y1 - y0;
131 score += dx * dx + dy * dy;
132 }
133 if (score < bestScore) {
134 bestScore = score;
135 bestOffset = offset;
136 }
137 }
138 return bestOffset;
139}
140function reverse(array) {
141 var newArr = [];
142 var len = array.length;
143 for (var i = 0; i < len; i += 2) {
144 newArr[i] = array[len - i - 2];
145 newArr[i + 1] = array[len - i - 1];
146 }
147 return newArr;
148}
149function findBestMorphingRotation(fromArr, toArr, searchAngleIteration, searchAngleRange) {
150 var result = [];
151 var fromNeedsReverse;
152 for (var i = 0; i < fromArr.length; i++) {
153 var fromSubpathBezier = fromArr[i];
154 var toSubpathBezier = toArr[i];
155 var fromCp = centroid(fromSubpathBezier);
156 var toCp = centroid(toSubpathBezier);
157 if (fromNeedsReverse == null) {
158 fromNeedsReverse = fromCp[2] < 0 !== toCp[2] < 0;
159 }
160 var newFromSubpathBezier = [];
161 var newToSubpathBezier = [];
162 var bestAngle = 0;
163 var bestScore = Infinity;
164 var tmpArr = [];
165 var len = fromSubpathBezier.length;
166 if (fromNeedsReverse) {
167 fromSubpathBezier = reverse(fromSubpathBezier);
168 }
169 var offset = findBestRingOffset(fromSubpathBezier, toSubpathBezier, fromCp, toCp) * 6;
170 var len2 = len - 2;
171 for (var k = 0; k < len2; k += 2) {
172 var idx = (offset + k) % len2 + 2;
173 newFromSubpathBezier[k + 2] = fromSubpathBezier[idx] - fromCp[0];
174 newFromSubpathBezier[k + 3] = fromSubpathBezier[idx + 1] - fromCp[1];
175 }
176 newFromSubpathBezier[0] = fromSubpathBezier[offset] - fromCp[0];
177 newFromSubpathBezier[1] = fromSubpathBezier[offset + 1] - fromCp[1];
178 if (searchAngleIteration > 0) {
179 var step = searchAngleRange / searchAngleIteration;
180 for (var angle = -searchAngleRange / 2; angle <= searchAngleRange / 2; angle += step) {
181 var sa = Math.sin(angle);
182 var ca = Math.cos(angle);
183 var score = 0;
184 for (var k = 0; k < fromSubpathBezier.length; k += 2) {
185 var x0 = newFromSubpathBezier[k];
186 var y0 = newFromSubpathBezier[k + 1];
187 var x1 = toSubpathBezier[k] - toCp[0];
188 var y1 = toSubpathBezier[k + 1] - toCp[1];
189 var newX1 = x1 * ca - y1 * sa;
190 var newY1 = x1 * sa + y1 * ca;
191 tmpArr[k] = newX1;
192 tmpArr[k + 1] = newY1;
193 var dx = newX1 - x0;
194 var dy = newY1 - y0;
195 score += dx * dx + dy * dy;
196 }
197 if (score < bestScore) {
198 bestScore = score;
199 bestAngle = angle;
200 for (var m = 0; m < tmpArr.length; m++) {
201 newToSubpathBezier[m] = tmpArr[m];
202 }
203 }
204 }
205 }
206 else {
207 for (var i_1 = 0; i_1 < len; i_1 += 2) {
208 newToSubpathBezier[i_1] = toSubpathBezier[i_1] - toCp[0];
209 newToSubpathBezier[i_1 + 1] = toSubpathBezier[i_1 + 1] - toCp[1];
210 }
211 }
212 result.push({
213 from: newFromSubpathBezier,
214 to: newToSubpathBezier,
215 fromCp: fromCp,
216 toCp: toCp,
217 rotation: -bestAngle
218 });
219 }
220 return result;
221}
222export function isCombineMorphing(path) {
223 return path.__isCombineMorphing;
224}
225export function isMorphing(el) {
226 return el.__morphT >= 0;
227}
228var SAVED_METHOD_PREFIX = '__mOriginal_';
229function saveAndModifyMethod(obj, methodName, modifiers) {
230 var savedMethodName = SAVED_METHOD_PREFIX + methodName;
231 var originalMethod = obj[savedMethodName] || obj[methodName];
232 if (!obj[savedMethodName]) {
233 obj[savedMethodName] = obj[methodName];
234 }
235 var replace = modifiers.replace;
236 var after = modifiers.after;
237 var before = modifiers.before;
238 obj[methodName] = function () {
239 var args = arguments;
240 var res;
241 before && before.apply(this, args);
242 if (replace) {
243 res = replace.apply(this, args);
244 }
245 else {
246 res = originalMethod.apply(this, args);
247 }
248 after && after.apply(this, args);
249 return res;
250 };
251}
252function restoreMethod(obj, methodName) {
253 var savedMethodName = SAVED_METHOD_PREFIX + methodName;
254 if (obj[savedMethodName]) {
255 obj[methodName] = obj[savedMethodName];
256 obj[savedMethodName] = null;
257 }
258}
259function applyTransformOnBeziers(bezierCurves, mm) {
260 for (var i = 0; i < bezierCurves.length; i++) {
261 var subBeziers = bezierCurves[i];
262 for (var k = 0; k < subBeziers.length;) {
263 var x = subBeziers[k];
264 var y = subBeziers[k + 1];
265 subBeziers[k++] = mm[0] * x + mm[2] * y + mm[4];
266 subBeziers[k++] = mm[1] * x + mm[3] * y + mm[5];
267 }
268 }
269}
270function prepareMorphPath(fromPath, toPath) {
271 var fromPathProxy = fromPath.getUpdatedPathProxy();
272 var toPathProxy = toPath.getUpdatedPathProxy();
273 var _a = alignBezierCurves(pathToBezierCurves(fromPathProxy), pathToBezierCurves(toPathProxy)), fromBezierCurves = _a[0], toBezierCurves = _a[1];
274 var fromPathTransform = fromPath.getComputedTransform();
275 var toPathTransform = toPath.getComputedTransform();
276 function updateIdentityTransform() {
277 this.transform = null;
278 }
279 fromPathTransform && applyTransformOnBeziers(fromBezierCurves, fromPathTransform);
280 toPathTransform && applyTransformOnBeziers(toBezierCurves, toPathTransform);
281 saveAndModifyMethod(toPath, 'updateTransform', { replace: updateIdentityTransform });
282 toPath.transform = null;
283 var morphingData = findBestMorphingRotation(fromBezierCurves, toBezierCurves, 10, Math.PI);
284 var tmpArr = [];
285 saveAndModifyMethod(toPath, 'buildPath', { replace: function (path) {
286 var t = toPath.__morphT;
287 var onet = 1 - t;
288 var newCp = [];
289 for (var i = 0; i < morphingData.length; i++) {
290 var item = morphingData[i];
291 var from = item.from;
292 var to = item.to;
293 var angle = item.rotation * t;
294 var fromCp = item.fromCp;
295 var toCp = item.toCp;
296 var sa = Math.sin(angle);
297 var ca = Math.cos(angle);
298 lerp(newCp, fromCp, toCp, t);
299 for (var m = 0; m < from.length; m += 2) {
300 var x0_1 = from[m];
301 var y0_1 = from[m + 1];
302 var x1 = to[m];
303 var y1 = to[m + 1];
304 var x = x0_1 * onet + x1 * t;
305 var y = y0_1 * onet + y1 * t;
306 tmpArr[m] = (x * ca - y * sa) + newCp[0];
307 tmpArr[m + 1] = (x * sa + y * ca) + newCp[1];
308 }
309 var x0 = tmpArr[0];
310 var y0 = tmpArr[1];
311 path.moveTo(x0, y0);
312 for (var m = 2; m < from.length;) {
313 var x1 = tmpArr[m++];
314 var y1 = tmpArr[m++];
315 var x2 = tmpArr[m++];
316 var y2 = tmpArr[m++];
317 var x3 = tmpArr[m++];
318 var y3 = tmpArr[m++];
319 if (x0 === x1 && y0 === y1 && x2 === x3 && y2 === y3) {
320 path.lineTo(x3, y3);
321 }
322 else {
323 path.bezierCurveTo(x1, y1, x2, y2, x3, y3);
324 }
325 x0 = x3;
326 y0 = y3;
327 }
328 }
329 } });
330}
331export function morphPath(fromPath, toPath, animationOpts) {
332 if (!fromPath || !toPath) {
333 return toPath;
334 }
335 var oldDone = animationOpts.done;
336 var oldDuring = animationOpts.during;
337 prepareMorphPath(fromPath, toPath);
338 toPath.__morphT = 0;
339 function restoreToPath() {
340 restoreMethod(toPath, 'buildPath');
341 restoreMethod(toPath, 'updateTransform');
342 toPath.__morphT = -1;
343 toPath.createPathProxy();
344 toPath.dirtyShape();
345 }
346 toPath.animateTo({
347 __morphT: 1
348 }, defaults({
349 during: function (p) {
350 toPath.dirtyShape();
351 oldDuring && oldDuring(p);
352 },
353 done: function () {
354 restoreToPath();
355 oldDone && oldDone();
356 }
357 }, animationOpts));
358 return toPath;
359}
360function hilbert(x, y, minX, minY, maxX, maxY) {
361 var bits = 16;
362 x = (maxX === minX) ? 0 : Math.round(32767 * (x - minX) / (maxX - minX));
363 y = (maxY === minY) ? 0 : Math.round(32767 * (y - minY) / (maxY - minY));
364 var d = 0;
365 var tmp;
366 for (var s = (1 << bits) / 2; s > 0; s /= 2) {
367 var rx = 0;
368 var ry = 0;
369 if ((x & s) > 0) {
370 rx = 1;
371 }
372 if ((y & s) > 0) {
373 ry = 1;
374 }
375 d += s * s * ((3 * rx) ^ ry);
376 if (ry === 0) {
377 if (rx === 1) {
378 x = s - 1 - x;
379 y = s - 1 - y;
380 }
381 tmp = x;
382 x = y;
383 y = tmp;
384 }
385 }
386 return d;
387}
388function sortPaths(pathList) {
389 var xMin = Infinity;
390 var yMin = Infinity;
391 var xMax = -Infinity;
392 var yMax = -Infinity;
393 var cps = map(pathList, function (path) {
394 var rect = path.getBoundingRect();
395 var m = path.getComputedTransform();
396 var x = rect.x + rect.width / 2 + (m ? m[4] : 0);
397 var y = rect.y + rect.height / 2 + (m ? m[5] : 0);
398 xMin = Math.min(x, xMin);
399 yMin = Math.min(y, yMin);
400 xMax = Math.max(x, xMax);
401 yMax = Math.max(y, yMax);
402 return [x, y];
403 });
404 var items = map(cps, function (cp, idx) {
405 return {
406 cp: cp,
407 z: hilbert(cp[0], cp[1], xMin, yMin, xMax, yMax),
408 path: pathList[idx]
409 };
410 });
411 return items.sort(function (a, b) { return a.z - b.z; }).map(function (item) { return item.path; });
412}
413;
414function defaultDividePath(param) {
415 return split(param.path, param.count);
416}
417function createEmptyReturn() {
418 return {
419 fromIndividuals: [],
420 toIndividuals: [],
421 count: 0
422 };
423}
424export function combineMorph(fromList, toPath, animationOpts) {
425 var fromPathList = [];
426 function addFromPath(fromList) {
427 for (var i = 0; i < fromList.length; i++) {
428 var from = fromList[i];
429 if (isCombineMorphing(from)) {
430 addFromPath(from.childrenRef());
431 }
432 else if (from instanceof Path) {
433 fromPathList.push(from);
434 }
435 }
436 }
437 addFromPath(fromList);
438 var separateCount = fromPathList.length;
439 if (!separateCount) {
440 return createEmptyReturn();
441 }
442 var dividePath = animationOpts.dividePath || defaultDividePath;
443 var toSubPathList = dividePath({
444 path: toPath, count: separateCount
445 });
446 if (toSubPathList.length !== separateCount) {
447 console.error('Invalid morphing: unmatched splitted path');
448 return createEmptyReturn();
449 }
450 fromPathList = sortPaths(fromPathList);
451 toSubPathList = sortPaths(toSubPathList);
452 var oldDone = animationOpts.done;
453 var oldDuring = animationOpts.during;
454 var individualDelay = animationOpts.individualDelay;
455 var identityTransform = new Transformable();
456 for (var i = 0; i < separateCount; i++) {
457 var from = fromPathList[i];
458 var to = toSubPathList[i];
459 to.parent = toPath;
460 to.copyTransform(identityTransform);
461 if (!individualDelay) {
462 prepareMorphPath(from, to);
463 }
464 }
465 toPath.__isCombineMorphing = true;
466 toPath.childrenRef = function () {
467 return toSubPathList;
468 };
469 function addToSubPathListToZr(zr) {
470 for (var i = 0; i < toSubPathList.length; i++) {
471 toSubPathList[i].addSelfToZr(zr);
472 }
473 }
474 saveAndModifyMethod(toPath, 'addSelfToZr', {
475 after: function (zr) {
476 addToSubPathListToZr(zr);
477 }
478 });
479 saveAndModifyMethod(toPath, 'removeSelfFromZr', {
480 after: function (zr) {
481 for (var i = 0; i < toSubPathList.length; i++) {
482 toSubPathList[i].removeSelfFromZr(zr);
483 }
484 }
485 });
486 function restoreToPath() {
487 toPath.__isCombineMorphing = false;
488 toPath.__morphT = -1;
489 toPath.childrenRef = null;
490 restoreMethod(toPath, 'addSelfToZr');
491 restoreMethod(toPath, 'removeSelfFromZr');
492 }
493 var toLen = toSubPathList.length;
494 if (individualDelay) {
495 var animating_1 = toLen;
496 var eachDone = function () {
497 animating_1--;
498 if (animating_1 === 0) {
499 restoreToPath();
500 oldDone && oldDone();
501 }
502 };
503 for (var i = 0; i < toLen; i++) {
504 var indivdualAnimationOpts = individualDelay ? defaults({
505 delay: (animationOpts.delay || 0) + individualDelay(i, toLen, fromPathList[i], toSubPathList[i]),
506 done: eachDone
507 }, animationOpts) : animationOpts;
508 morphPath(fromPathList[i], toSubPathList[i], indivdualAnimationOpts);
509 }
510 }
511 else {
512 toPath.__morphT = 0;
513 toPath.animateTo({
514 __morphT: 1
515 }, defaults({
516 during: function (p) {
517 for (var i = 0; i < toLen; i++) {
518 var child = toSubPathList[i];
519 child.__morphT = toPath.__morphT;
520 child.dirtyShape();
521 }
522 oldDuring && oldDuring(p);
523 },
524 done: function () {
525 restoreToPath();
526 for (var i = 0; i < fromList.length; i++) {
527 restoreMethod(fromList[i], 'updateTransform');
528 }
529 oldDone && oldDone();
530 }
531 }, animationOpts));
532 }
533 if (toPath.__zr) {
534 addToSubPathListToZr(toPath.__zr);
535 }
536 return {
537 fromIndividuals: fromPathList,
538 toIndividuals: toSubPathList,
539 count: toLen
540 };
541}
542export function separateMorph(fromPath, toPathList, animationOpts) {
543 var toLen = toPathList.length;
544 var fromPathList = [];
545 var dividePath = animationOpts.dividePath || defaultDividePath;
546 function addFromPath(fromList) {
547 for (var i = 0; i < fromList.length; i++) {
548 var from = fromList[i];
549 if (isCombineMorphing(from)) {
550 addFromPath(from.childrenRef());
551 }
552 else if (from instanceof Path) {
553 fromPathList.push(from);
554 }
555 }
556 }
557 if (isCombineMorphing(fromPath)) {
558 addFromPath(fromPath.childrenRef());
559 var fromLen = fromPathList.length;
560 if (fromLen < toLen) {
561 var k = 0;
562 for (var i = fromLen; i < toLen; i++) {
563 fromPathList.push(clonePath(fromPathList[k++ % fromLen]));
564 }
565 }
566 fromPathList.length = toLen;
567 }
568 else {
569 fromPathList = dividePath({ path: fromPath, count: toLen });
570 var fromPathTransform = fromPath.getComputedTransform();
571 for (var i = 0; i < fromPathList.length; i++) {
572 fromPathList[i].setLocalTransform(fromPathTransform);
573 }
574 if (fromPathList.length !== toLen) {
575 console.error('Invalid morphing: unmatched splitted path');
576 return createEmptyReturn();
577 }
578 }
579 fromPathList = sortPaths(fromPathList);
580 toPathList = sortPaths(toPathList);
581 var individualDelay = animationOpts.individualDelay;
582 for (var i = 0; i < toLen; i++) {
583 var indivdualAnimationOpts = individualDelay ? defaults({
584 delay: (animationOpts.delay || 0) + individualDelay(i, toLen, fromPathList[i], toPathList[i])
585 }, animationOpts) : animationOpts;
586 morphPath(fromPathList[i], toPathList[i], indivdualAnimationOpts);
587 }
588 return {
589 fromIndividuals: fromPathList,
590 toIndividuals: toPathList,
591 count: toPathList.length
592 };
593}
594export { split as defaultDividePath };