morphing.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.Morphing = void 0;
  4. const g_1 = require("@antv/g");
  5. const helper_1 = require("../utils/helper");
  6. const utils_1 = require("./utils");
  7. function localBBoxOf(shape) {
  8. const { min, max } = shape.getLocalBounds();
  9. const [x0, y0] = min;
  10. const [x1, y1] = max;
  11. const height = y1 - y0;
  12. const width = x1 - x0;
  13. return [x0, y0, width, height];
  14. }
  15. function d(bbox) {
  16. const [x, y, width, height] = bbox;
  17. return `
  18. M ${x} ${y}
  19. L ${x + width} ${y}
  20. L ${x + width} ${y + height}
  21. L ${x} ${y + height}
  22. Z
  23. `;
  24. }
  25. function pack(shape, count) {
  26. const [x0, y0, width, height] = localBBoxOf(shape);
  27. const aspect = height / width;
  28. const col = Math.ceil(Math.sqrt(count / aspect));
  29. const row = Math.ceil(count / col);
  30. const B = [];
  31. const h = height / row;
  32. let j = 0;
  33. let n = count;
  34. while (n > 0) {
  35. const c = Math.min(n, col);
  36. const w = width / c;
  37. for (let i = 0; i < c; i++) {
  38. const x = x0 + i * w;
  39. const y = y0 + j * h;
  40. B.push(d([x, y, w, h]));
  41. }
  42. n -= c;
  43. j += 1;
  44. }
  45. return B;
  46. }
  47. function normalizeSplit(split = 'pack') {
  48. if (typeof split == 'function')
  49. return split;
  50. return pack;
  51. }
  52. /**
  53. * Translate and scale.
  54. */
  55. function shapeToShape(from, to, timeEffect) {
  56. const [x0, y0, w0, h0] = localBBoxOf(from);
  57. const { transform: fromTransform } = from.style;
  58. const { transform: toTransform } = to.style;
  59. // Replace first to get right bbox after mounting.
  60. replaceChild(to, from);
  61. // Apply translate and scale transform.
  62. const [x1, y1, w1, h1] = localBBoxOf(to);
  63. const dx = x0 - x1;
  64. const dy = y0 - y1;
  65. const sx = w0 / w1;
  66. const sy = h0 / h1;
  67. const keyframes = [
  68. Object.assign({ transform: `${fromTransform ? fromTransform + ' ' : ''}translate(${dx}, ${dy}) scale(${sx}, ${sy})` }, (0, utils_1.attributeOf)(from, utils_1.attributeKeys)),
  69. Object.assign({ transform: `${toTransform ? toTransform + ' ' : ''}translate(0, 0) scale(1, 1)` }, (0, utils_1.attributeOf)(to, utils_1.attributeKeys)),
  70. ];
  71. const animation = to.animate(keyframes, timeEffect);
  72. return animation;
  73. }
  74. /**
  75. * Replace object and copy className and __data__
  76. */
  77. function replaceChild(newChild, oldChild) {
  78. newChild['__data__'] = oldChild['__data__'];
  79. newChild.className = oldChild.className;
  80. oldChild.parentNode.replaceChild(newChild, oldChild);
  81. }
  82. /**
  83. * Replace element with a path shape.
  84. */
  85. function maybePath(node, d) {
  86. const { nodeName } = node;
  87. if (nodeName === 'path')
  88. return node;
  89. const path = new g_1.Path({
  90. style: Object.assign(Object.assign({}, (0, utils_1.attributeOf)(node, utils_1.attributeKeys)), { d }),
  91. });
  92. replaceChild(path, node);
  93. return path;
  94. }
  95. function hasUniqueString(search, pattern) {
  96. const first = search.indexOf(pattern);
  97. const last = search.lastIndexOf(pattern);
  98. return first === last;
  99. }
  100. // Path definition with multiple m and M command has sub path.
  101. // eg. 'M10,10...M20,20', 'm10,10...m20,20'
  102. function hasSubPath(path) {
  103. return !hasUniqueString(path, 'm') || !hasUniqueString(path, 'M');
  104. }
  105. function shape2path(shape) {
  106. const path = (0, g_1.convertToPath)(shape);
  107. if (!path)
  108. return;
  109. // Path definition with sub path can't do path morphing animation,
  110. // so skip this kind of path.
  111. if (hasSubPath(path))
  112. return;
  113. return path;
  114. }
  115. function oneToOne(shape, from, to, timeEffect) {
  116. // If the nodeTypes of from and to are equal,
  117. // or non of them can convert to path,
  118. // the apply shape to shape animation.
  119. const { nodeName: fromName } = from;
  120. const { nodeName: toName } = to;
  121. const fromPath = shape2path(from);
  122. const toPath = shape2path(to);
  123. const isSameNodes = fromName === toName && fromName !== 'path';
  124. const hasNonPathNode = fromPath === undefined || toPath === undefined;
  125. if (isSameNodes || hasNonPathNode)
  126. return shapeToShape(from, to, timeEffect);
  127. const pathShape = maybePath(shape, fromPath);
  128. // Convert Path will take transform, anchor, etc into account,
  129. // so there is no need to specify these attributes in keyframes.
  130. const keyframes = [
  131. Object.assign({ path: fromPath }, (0, utils_1.attributeOf)(from, utils_1.attributeKeys)),
  132. Object.assign({ path: toPath }, (0, utils_1.attributeOf)(to, utils_1.attributeKeys)),
  133. ];
  134. const animation = pathShape.animate(keyframes, timeEffect);
  135. animation.onfinish = () => {
  136. (0, helper_1.copyAttributes)(pathShape, to);
  137. };
  138. // Remove transform because it already applied in path
  139. // converted by convertToPath.
  140. // @todo Remove this scale(1, 1)
  141. pathShape.style.transform = 'scale(1, 1)';
  142. pathShape.style.transform = 'none';
  143. return animation;
  144. }
  145. function oneToMultiple(from, to, timeEffect, split) {
  146. // Hide the shape to be split before being removing.
  147. from.style.visibility = 'hidden';
  148. const D = split(from, to.length);
  149. return to.map((shape, i) => {
  150. const path = new g_1.Path({
  151. style: Object.assign({ path: D[i] }, (0, utils_1.attributeOf)(from, utils_1.attributeKeys)),
  152. });
  153. return oneToOne(shape, path, shape, timeEffect);
  154. });
  155. }
  156. function multipleToOne(from, to, timeEffect, split) {
  157. const D = split(to, from.length);
  158. const { fillOpacity = 1, strokeOpacity = 1, opacity = 1 } = to.style;
  159. const keyframes = [
  160. { fillOpacity: 0, strokeOpacity: 0, opacity: 0 },
  161. { fillOpacity: 0, strokeOpacity: 0, opacity: 0, offset: 0.99 },
  162. {
  163. fillOpacity,
  164. strokeOpacity,
  165. opacity,
  166. },
  167. ];
  168. const animation = to.animate(keyframes, timeEffect);
  169. const animations = from.map((shape, i) => {
  170. const path = new g_1.Path({
  171. style: {
  172. path: D[i],
  173. fill: to.style.fill,
  174. },
  175. });
  176. return oneToOne(shape, shape, path, timeEffect);
  177. });
  178. return [...animations, animation];
  179. }
  180. /**
  181. * Morphing animations.
  182. * @todo Support more split function.
  183. */
  184. const Morphing = (options) => {
  185. return (from, to, value, coordinate, defaults) => {
  186. const split = normalizeSplit(options.split);
  187. const timeEffect = (0, utils_1.effectTiming)(defaults, value, options);
  188. const { length: fl } = from;
  189. const { length: tl } = to;
  190. if ((fl === 1 && tl === 1) || (fl > 1 && tl > 1)) {
  191. const [f] = from;
  192. const [t] = to;
  193. return oneToOne(f, f, t, timeEffect);
  194. }
  195. if (fl === 1 && tl > 1) {
  196. const [f] = from;
  197. return oneToMultiple(f, to, timeEffect, split);
  198. }
  199. if (fl > 1 && tl === 1) {
  200. const [t] = to;
  201. return multipleToOne(from, t, timeEffect, split);
  202. }
  203. return null;
  204. };
  205. };
  206. exports.Morphing = Morphing;
  207. exports.Morphing.props = {};
  208. //# sourceMappingURL=morphing.js.map