UNPKG

@amcharts/amcharts4

Version:
446 lines 18.5 kB
/** * Morpher module contains functionality that allows morphing one polygon to * another. */ import { __extends } from "tslib"; /** * ============================================================================ * IMPORTS * ============================================================================ * @hidden */ import { BaseObject } from "../Base"; import { Animation, AnimationDisposer } from "../utils/Animation"; import * as $math from "../utils/Math"; import * as $ease from "../utils/Ease"; import * as $type from "../utils/Type"; /** * Morpher can be used to morph one polygon to some other polygon. */ var Morpher = /** @class */ (function (_super) { __extends(Morpher, _super); /** * Constructor. * * @param morphable An object to morph */ function Morpher(morphable) { var _this = _super.call(this) || this; /** * A storage for measurements. */ _this._bboxes = []; /** * Duration of the morphing animation in milliseconds. */ _this.morphDuration = 800; /** * An easing function to use for morphing animation. * * @see {@link Ease} */ _this.morphEasing = $ease.cubicOut; /** * If set to `true`, all separate parts of the multi-part polygon will * morph into a single circle or polygon when using built-in methods * `morphToCircle()` or `morphToPolygon()`. * * Otherwise each separate part of polygon will morph to individual target * circle or polgyon. */ _this.morphToSingle = true; /** * A ratio to scale morphed object in relation to the source object. */ _this.scaleRatio = 1; _this.className = "Morpher"; _this.morphable = morphable; _this.applyTheme(); return _this; } /** * Morphs a polygon to another polygon. * * @param toPoints Corner points of the target shape * @param duration Duration in milliseconds * @param easing Easing function * @return Animation */ Morpher.prototype.morphToPolygon = function (toPoints, duration, easing) { var points = this.morphable.currentPoints; if (points && toPoints) { this.sortPoints(points); this.sortPoints(toPoints); this._morphFromPointsReal = []; this._morphToPointsReal = []; if (!$type.hasValue(duration)) { duration = this.morphDuration; } if (!$type.hasValue(easing)) { easing = this.morphEasing; } this._morphFromPointsReal = this.normalizePoints(toPoints, points); this._morphToPointsReal = this.normalizePoints(points, toPoints); this.morphable.currentPoints = this._morphFromPointsReal; var animation = new Animation(this, { property: "morphProgress", from: 0, to: 1 }, duration, easing); this._disposers.push(animation); animation.start(); return animation; } }; /** * [normalizePoints description] * * @ignore Exclude from docs * @todo Description * @param pointsA Point A * @param pointsB Point B * @return Normalized points */ Morpher.prototype.normalizePoints = function (pointsA, pointsB) { for (var i = 0, len = pointsA.length; i < len; i++) { var surfaceA = pointsA[i][0]; var holeA = pointsA[i][1]; var bboxA = $type.getValue($math.getBBox(surfaceA)); var middleX = bboxA.x + bboxA.width; var middleY = bboxA.y + bboxA.height; // check if we have the same in PointsB if (!pointsB[i]) { pointsB[i] = []; } // check if we have surface in pointsB if (surfaceA && !pointsB[i][0]) { pointsB[i][0] = [{ x: middleX, y: middleY }, { x: middleX, y: middleY }]; } if (pointsB[i][0]) { pointsB[i][0] = this.addPoints(pointsB[i][0], surfaceA.length); var distance = Infinity; var splitAt = 0; for (var a = 0; a < pointsB[i][0].length; a++) { var newDistance = $math.getDistance(pointsB[i][0][a], surfaceA[0]); if (newDistance < distance) { splitAt = a; distance = newDistance; } } var partA = pointsB[i][0].slice(0, splitAt); var partB = pointsB[i][0].slice(splitAt); pointsB[i][0] = partB.concat(partA); } if (holeA) { if (!pointsB[i][1]) { pointsB[i][1] = [{ x: middleX, y: middleY }, { x: middleX, y: middleY }]; } pointsB[i][1] = this.addPoints(pointsB[i][1], holeA.length); } } return pointsB; }; /** * [sortPoints description] * * @ignore Exclude from doc * @todo Description * @param points [description] * @return common bbox of points */ Morpher.prototype.sortPoints = function (points) { points.sort(function (a, b) { var bbox1 = $type.getValue($math.getBBox(a[0])); var bbox2 = $type.getValue($math.getBBox(b[0])); if (bbox1.width * bbox1.height > bbox2.width * bbox2.height) { return -1; } else { return 1; } }); var bboxes = []; for (var i = 0, len = points.length; i < len; i++) { var surface = points[i][0]; if (surface) { bboxes.push($type.getValue($math.getBBox(surface))); } } return $math.getCommonRectangle(bboxes); }; /** * Morphs polygon to a circle (it is actually a polygon which makes a circle). * * @param radius Target circle radius (px) * @param duration Duration (ms) * @param easing Easing function * @return Animation */ Morpher.prototype.morphToCircle = function (radius, duration, easing) { var points = this.morphable.points; var commonBBox = this.sortPoints(points); this._morphFromPointsReal = []; this._morphToPointsReal = []; if (!$type.hasValue(duration)) { duration = this.morphDuration; } if (!$type.hasValue(easing)) { easing = this.morphEasing; } // surface for (var i = 0, len = points.length; i < len; i++) { var surface = points[i][0]; var hole = points[i][1]; this._morphFromPointsReal[i] = []; this._morphToPointsReal[i] = []; if (surface) { var toPoints = surface; var fromPoints = surface; var bbox = $type.getValue($math.getBBox(fromPoints)); // this._bboxes[i]; if (this.morphToSingle) { bbox = $type.getValue(commonBBox); } var middleX = bbox.x + bbox.width / 2; var middleY = bbox.y + bbox.height / 2; var realRadius = radius; if (!$type.isNumber(realRadius)) { realRadius = Math.min(bbox.width / 2, bbox.height / 2); } toPoints = []; // find angle for the first point var startAngle = $math.getAngle({ x: middleX, y: middleY }, surface[0]); var count = 100; if (surface.length > count) { count = surface.length; } fromPoints = this.addPoints(surface, count); count = fromPoints.length; // add Points might increase number a bit var angle = 360 / (count - 1); for (var a = 0; a < count; a++) { var realAngle = angle * a + startAngle; var pointOnCircle = { x: middleX + realRadius * $math.cos(realAngle), y: middleY + realRadius * $math.sin(realAngle) }; toPoints[a] = pointOnCircle; } if (hole && hole.length > 0) { for (var i_1 = 0, hlen = hole.length; i_1 < hlen; i_1++) { toPoints.push({ x: middleX, y: middleY }); } } this._morphFromPointsReal[i][0] = fromPoints; this._morphToPointsReal[i][0] = toPoints; } } this.morphable.currentPoints = this._morphFromPointsReal; var animation = new Animation(this, { property: "morphProgress", from: 0, to: 1 }, duration, easing); this._disposers.push(animation); animation.start(); return animation; }; /** * [addPoints description] * * @ignore Exclude from doc * @todo Description * @param points [description] * @param mustHaveCount [description] * @return [description] */ Morpher.prototype.addPoints = function (points, mustHaveCount) { var addToSegmentCount = Math.round(mustHaveCount / points.length); var newPoints = []; for (var i = 0, len = points.length; i < len; i++) { var point0 = points[i]; var point1 = void 0; if (i == points.length - 1) { point1 = points[0]; } else { point1 = points[i + 1]; } newPoints.push(point0); for (var p = 1; p < addToSegmentCount; p++) { var percent = p / addToSegmentCount; var extraPoint = { x: point0.x + (point1.x - point0.x) * percent, y: point0.y + (point1.y - point0.y) * percent }; newPoints.push(extraPoint); } // stop adding in case we already added more than left in original if (newPoints.length + points.length - i == mustHaveCount) { addToSegmentCount = 0; } } if (newPoints.length < mustHaveCount && points.length > 0) { var lastPoint = points[points.length - 1]; for (var p = newPoints.length; p < mustHaveCount; p++) { // add same as last newPoints.push({ x: lastPoint.x, y: lastPoint.y }); } } return newPoints; }; /** * Morphs polygon into a rectangular polygon. * * @param width Width of the target rectangle (px) * @param height Height of the target rectangle (px) * @param duration Duration (ms) * @param easing Easing function * @return Animation */ Morpher.prototype.morphToRectangle = function (width, height, duration, easing) { var points = this.morphable.points; this.sortPoints(points); this._morphFromPointsReal = []; this._morphToPointsReal = []; if (!$type.hasValue(duration)) { duration = this.morphDuration; } if (!$type.hasValue(easing)) { easing = this.morphEasing; } // let biggestBBox: IRectangle = this._bboxes[this._biggestIndex]; // surface for (var i = 0, len = points.length; i < len; i++) { var surface = points[i][0]; var hole = points[i][1]; this._morphFromPointsReal[i] = []; this._morphToPointsReal[i] = []; if (surface) { var toPoints = surface; var fromPoints = surface; var bbox = this._bboxes[i]; // we only work with first area. TODO: maybe we should find the biggest one? if (this.morphToSingle) { //if (i != this._biggestIndex) { // bbox = { x: biggestBBox.x + biggestBBox.width / 2, y: biggestBBox.y + biggestBBox.height / 2, width: 0, height: 0 }; //} } var x = bbox.x; var y = bbox.y; var realWidth = width; var realHeight = height; if (!$type.isNumber(realWidth)) { realWidth = bbox.width; } if (!$type.isNumber(realHeight)) { realHeight = bbox.height; } toPoints = [{ x: x, y: y }, { x: x + realWidth, y: y }, { x: x + realWidth, y: y + realHeight }, { x: x, y: y + realHeight }]; toPoints = this.addPoints(toPoints, surface.length); // if polygon has less points then count, add if (surface.length < 4) { for (var i_2 = surface.length; i_2 < 4; i_2++) { toPoints.push({ x: surface[i_2].x, y: surface[i_2].y }); } } if (hole && hole.length > 0) { var middleX = bbox.x + bbox.width / 2; var middleY = bbox.y + bbox.height / 2; for (var i_3 = 0, hlen = hole.length; i_3 < hlen; i_3++) { toPoints.push({ x: middleX, y: middleY }); } } this._morphFromPointsReal[i][0] = fromPoints; this._morphToPointsReal[i][0] = toPoints; } } this.morphable.currentPoints = this._morphFromPointsReal; var animation = new Animation(this, { property: "morphProgress", from: 0, to: 1 }, duration, easing); this._disposers.push(animation); animation.start(); return animation; }; Object.defineProperty(Morpher.prototype, "morphProgress", { /** * Returns the progress of morph transition. * * @return Progress (0-1) */ get: function () { return this._morphProgress; }, /** * Progress of the morph transition. * * Setting this will also trigger actual transformation. * * @param value Progress (0-1) */ set: function (value) { this._morphProgress = value; var currentPoints = []; if (value != null) { var fromPoints = this._morphFromPointsReal; var toPoints = this._morphToPointsReal; if (fromPoints != null && toPoints != null) { for (var i = 0, len = fromPoints.length; i < len; i++) { var currentArea = []; currentPoints.push(currentArea); var surfaceFrom = fromPoints[i][0]; var holeFrom = fromPoints[i][1]; var surfaceTo = toPoints[i][0]; var holeTo = toPoints[i][1]; if (surfaceFrom && surfaceFrom.length > 0 && surfaceTo && surfaceTo.length > 0) { var currentSurface = []; for (var i_4 = 0, slen = surfaceFrom.length; i_4 < slen; i_4++) { var point0 = surfaceFrom[i_4]; var point1 = surfaceTo[i_4]; var currentPoint = { x: point0.x + (point1.x * this.scaleRatio - point0.x) * value, y: point0.y + (point1.y * this.scaleRatio - point0.y) * value }; currentSurface.push(currentPoint); } currentArea[0] = currentSurface; } if (holeFrom && holeFrom.length > 0 && holeTo && holeTo.length > 0) { var currentHole = []; for (var i_5 = 0, hlen = holeFrom.length; i_5 < hlen; i_5++) { var point0 = holeFrom[i_5]; var point1 = holeTo[i_5]; var currentPoint = { x: point0.x + (point1.x * this.scaleRatio - point0.x) * value, y: point0.y + (point1.y * this.scaleRatio - point0.y) * value }; currentHole.push(currentPoint); } currentArea[1] = currentHole; } } } } this.morphable.currentPoints = currentPoints; }, enumerable: true, configurable: true }); /** * Restores the polygon to its original appearance. * * @param duration Duration (ms) * @param easing Easing function * @return Animation */ Morpher.prototype.morphBack = function (duration, easing) { this._morphToPointsReal = this._morphFromPointsReal; this._morphFromPointsReal = this.morphable.currentPoints; if (!$type.hasValue(duration)) { duration = this.morphDuration; } if (!$type.hasValue(easing)) { easing = this.morphEasing; } var animation = new Animation(this, { property: "morphProgress", from: 0, to: 1 }, duration, easing); this._disposers.push(animation); animation.start(); return animation; }; Object.defineProperty(Morpher.prototype, "animations", { /** * Returns a list of morph animations currently being played. * * @return List of animations */ get: function () { if (!this._animations) { this._animations = []; this._disposers.push(new AnimationDisposer(this._animations)); } return this._animations; }, enumerable: true, configurable: true }); return Morpher; }(BaseObject)); export { Morpher }; //# sourceMappingURL=Morpher.js.map