UNPKG

@antv/g2

Version:

the Grammar of Graphics in Javascript

224 lines 7.59 kB
import { convertToPath, Path, Shape, } from '@antv/g'; import { copyAttributes } from '../utils/helper'; import { attributeKeys, attributeOf, GEOMETRY_ATTRIBUTES } from './utils'; function localBBoxOf(shape) { const { min, max } = shape.getLocalBounds(); const [x0, y0] = min; const [x1, y1] = max; const height = y1 - y0; const width = x1 - x0; return [x0, y0, width, height]; } function d(bbox) { const [x, y, width, height] = bbox; return ` M ${x} ${y} L ${x + width} ${y} L ${x + width} ${y + height} L ${x} ${y + height} Z `; } function pack(shape, count) { const [x0, y0, width, height] = localBBoxOf(shape); const aspect = height / width; const col = Math.ceil(Math.sqrt(count / aspect)); const row = Math.ceil(count / col); const B = []; const h = height / row; let j = 0; let n = count; while (n > 0) { const c = Math.min(n, col); const w = width / c; for (let i = 0; i < c; i++) { const x = x0 + i * w; const y = y0 + j * h; B.push(d([x, y, w, h])); } n -= c; j += 1; } return B; } function normalizeSplit(split = 'pack') { if (typeof split == 'function') return split; return pack; } /** * Use attributes relative to geometry to do shape to shape animation. * * For example, the x, y, width, height of `Rect`, the cx, cy, r of `Circle`. * And for `Group`, it will use the bbox of the group. */ function shapeToShape(from, to, timeEffect) { let { transform: fromTransform } = from.style; const { transform: toTransform } = to.style; // Replace first to get right bbox after mounting. replaceChild(to, from); let keys = attributeKeys; if (from.nodeName === Shape.GROUP) { // Apply translate and scale transform. const [x0, y0, w0, h0] = localBBoxOf(from); const [x1, y1, w1, h1] = localBBoxOf(to); const dx = x0 - x1; const dy = y0 - y1; const sx = w0 / w1; const sy = h0 / h1; fromTransform = `translate(${dx}, ${dy}) scale(${sx}, ${sy})`; } else { keys = keys.concat(GEOMETRY_ATTRIBUTES[from.nodeName] || []); } const keyframes = [ Object.assign({ transform: fromTransform !== null && fromTransform !== void 0 ? fromTransform : 'none' }, attributeOf(from, keys, true)), Object.assign({ transform: toTransform !== null && toTransform !== void 0 ? toTransform : 'none' }, attributeOf(to, keys, true)), ]; const animation = to.animate(keyframes, timeEffect); return animation; } /** * Replace object and copy className and __data__ */ function replaceChild(newChild, oldChild) { newChild['__data__'] = oldChild['__data__']; newChild.className = oldChild.className; // @ts-ignore newChild.markType = oldChild.markType; oldChild.parentNode.replaceChild(newChild, oldChild); } /** * Replace element with a path shape. */ function maybePath(node, d) { const { nodeName } = node; if (nodeName === 'path') return node; const path = new Path({ style: Object.assign(Object.assign({}, attributeOf(node, attributeKeys)), { d }), }); replaceChild(path, node); return path; } function hasUniqueString(search, pattern) { const first = search.indexOf(pattern); const last = search.lastIndexOf(pattern); return first === last; } // Path definition with multiple m and M command has sub path. // eg. 'M10,10...M20,20', 'm10,10...m20,20' function hasSubPath(path) { return !hasUniqueString(path, 'm') || !hasUniqueString(path, 'M'); } function shape2path(shape) { const path = convertToPath(shape); if (!path) return; // Path definition with sub path can't do path morphing animation, // so skip this kind of path. if (hasSubPath(path)) return; return path; } function oneToOne(shape, from, to, timeEffect) { // If the nodeTypes of from and to are equal, // or non of them can convert to path, // the apply shape to shape animation. const { nodeName: fromName } = from; const { nodeName: toName } = to; const fromPath = shape2path(from); const toPath = shape2path(to); const isSameNodes = fromName === toName && fromName !== 'path'; const hasNonPathNode = fromPath === undefined || toPath === undefined; if (isSameNodes || hasNonPathNode) return shapeToShape(from, to, timeEffect); const pathShape = maybePath(shape, fromPath); // Convert Path will take transform, anchor, etc into account, // so there is no need to specify these attributes in keyframes. const keyframes = [ Object.assign({}, attributeOf(from, attributeKeys)), Object.assign({}, attributeOf(to, attributeKeys)), ]; if (fromPath !== toPath) { keyframes[0].d = fromPath; keyframes[1].d = toPath; const animation = pathShape.animate(keyframes, timeEffect); animation.onfinish = () => { // Should keep the original path definition. const d = pathShape.style.d; copyAttributes(pathShape, to); pathShape.style.d = d; pathShape.style.transform = 'none'; }; // Remove transform because it already applied in path // converted by convertToPath. pathShape.style.transform = 'none'; return animation; } // No need to apply animation since fromPath equals toPath. return null; } function oneToMultiple(from, to, timeEffect, split) { // Hide the shape to be split before being removing. from.style.visibility = 'hidden'; const D = split(from, to.length); return to.map((shape, i) => { const path = new Path({ style: Object.assign({ d: D[i] }, attributeOf(from, attributeKeys)), }); return oneToOne(shape, path, shape, timeEffect); }); } function multipleToOne(from, to, timeEffect, split) { const D = split(to, from.length); const { fillOpacity = 1, strokeOpacity = 1, opacity = 1 } = to.style; const keyframes = [ { fillOpacity: 0, strokeOpacity: 0, opacity: 0 }, { fillOpacity: 0, strokeOpacity: 0, opacity: 0, offset: 0.99 }, { fillOpacity, strokeOpacity, opacity, }, ]; const animation = to.animate(keyframes, timeEffect); const animations = from.map((shape, i) => { const path = new Path({ style: { d: D[i], fill: to.style.fill, }, }); return oneToOne(shape, shape, path, timeEffect); }); return [...animations, animation]; } /** * Morphing animations. * @todo Support more split function. */ export const Morphing = (options) => { return (from, to, defaults) => { const split = normalizeSplit(options.split); const timeEffect = Object.assign(Object.assign({}, defaults), options); const { length: fl } = from; const { length: tl } = to; if ((fl === 1 && tl === 1) || (fl > 1 && tl > 1)) { const [f] = from; const [t] = to; return oneToOne(f, f, t, timeEffect); } if (fl === 1 && tl > 1) { const [f] = from; return oneToMultiple(f, to, timeEffect, split); } if (fl > 1 && tl === 1) { const [t] = to; return multipleToOne(from, t, timeEffect, split); } return null; }; }; Morphing.props = {}; //# sourceMappingURL=morphing.js.map