UNPKG

@amcharts/amcharts4

Version:
679 lines 24.4 kB
/** * Provides functionality used to creating and showing tooltips (balloons). */ import { __extends } from "tslib"; /** * ============================================================================ * IMPORTS * ============================================================================ * @hidden */ import { Container } from "../Container"; import { registry } from "../../core/Registry"; import { PointedRectangle } from "./PointedRectangle"; import { Label } from "../elements/Label"; import { Animation } from "../utils/Animation"; import { color } from "../utils/Color"; import { DropShadowFilter } from "../rendering/filters/DropShadowFilter"; import * as $math from "../utils/Math"; import * as $ease from "../utils/Ease"; import * as $utils from "../utils/Utils"; /** * ============================================================================ * MAIN CLASS * ============================================================================ * @hidden */ /** * Tooltip displays text and/or multimedia information in a balloon over chart * area. * @see {@link ITooltipEvents} for a list of available events * @see {@link ITooltipAdapters} for a list of available Adapters */ var Tooltip = /** @class */ (function (_super) { __extends(Tooltip, _super); /** * Constructor */ function Tooltip() { var _this = // Init _super.call(this) || this; /** * Holds numeric boundary values. Calculated from the `boundingContainer`. * @ignore */ _this._boundingRect = { x: -40000, y: -40000, width: 80000, height: 80000 }; /** * Coordinates tooltip's pointer (stem) should point to. */ _this._pointTo = { x: 0, y: 0 }; /** * If set to `true` the pointer/stem of the Tooltip will not go outside * Tooltip's width or height depending on pointer's orientation. * * @default false */ _this.fitPointerToBounds = false; /** * If `tooltipOrientation` is vertical, it can be drawn below or above point * We need to know this when solving overlapping. */ _this._verticalOrientation = "up"; /** * @ignore */ _this.fixDoc = true; _this.className = "Tooltip"; _this.isMeasured = false; _this.getFillFromObject = true; _this.margin(5, 5, 5, 5); _this.defaultState.transitionDuration = 1; _this.hiddenState.transitionDuration = 1; // Create chrome/background var background = _this.background; background.interactionsEnabled = false; background.fillOpacity = 0.9; background.strokeWidth = 1; background.strokeOpacity = 1; background.stroke = color("#ffffff"); background.cornerRadius = 3; background.pointerLength = 6; background.pointerBaseWidth = 10; var dropShadow = new DropShadowFilter(); dropShadow.dy = 1; dropShadow.dx = 1; dropShadow.opacity = 0.5; background.filters.push(dropShadow); _this.autoTextColor = true; // Create text element var label = _this.createChild(Label); label.shouldClone = false; _this.label = label; label.padding(7, 12, 4, 12); label.interactionsEnabled = false; label.horizontalCenter = "middle"; label.fill = color("#ffffff"); _this._disposers.push(label); _this.label.events.on("sizechanged", _this.drawBackground, _this); _this.label.zIndex = 1; // @todo remove this line when bg sorting is solved // Set defaults _this.pointerOrientation = "vertical"; _this.animationDuration = 0; _this.animationEasing = $ease.cubicOut; _this.setPropertyValue("showInViewport", false); // Set accessibility options _this.role = "tooltip"; _this.visible = false; _this.opacity = 0; _this.x = 0; _this.y = 0; _this.events.on("visibilitychanged", _this.handleVisibility, _this); // Apply theme _this.applyTheme(); return _this; } Tooltip.prototype.handleVisibility = function () { if (this.visible) { this.label.invalidate(); } }; Object.defineProperty(Tooltip.prototype, "getStrokeFromObject", { /** * Specifies if tooltip background should get stroke color from the sprite it is pointing to. * * @return {boolean} * @default false */ get: function () { return this.getPropertyValue("getStrokeFromObject"); }, /** * Specifies if tooltip background should get stroke color from the sprite it is pointing to. * * @param value boolean */ set: function (value) { this.setPropertyValue("getStrokeFromObject", value, true); }, enumerable: true, configurable: true }); Object.defineProperty(Tooltip.prototype, "autoTextColor", { /** * @return {boolean} */ get: function () { return this.getPropertyValue("autoTextColor"); }, /** * Specifies if text color should be chosen automatically for a better * readability. * * IMPORTANT: this feature is generally ignored, if `getFillFromObject = false`. * * If inheriting of `fill` color from object tooltip is displayed for is * disabled, this feature will not work. If you are explicitly setting a * color for tooltip background, you may set a color for its label as well * using `tooltip.label.fill` property. * * * @param value boolean */ set: function (value) { this.setPropertyValue("autoTextColor", value, true); }, enumerable: true, configurable: true }); Object.defineProperty(Tooltip.prototype, "keepTargetHover", { /** * @return Keep target hovered? */ get: function () { return this.getPropertyValue("keepTargetHover"); }, /** * If this tooltip is displayed on hover on some other object, keep that * element hovered if hovering on the tooltip. * * @default false * @since 4.1.13 * @param value Keep target hovered? */ set: function (value) { var _this = this; if (this.setPropertyValue("keepTargetHover", value, true)) { if (value) { this.hoverable = true; this.background.interactionsEnabled = true; this._disposers.push(this.events.on("over", function (ev) { if (_this.targetSprite && _this.targetSprite.hoverable) { _this.targetSprite.isHover = true; } })); this._disposers.push(this.events.on("out", function (ev) { if (_this.targetSprite && _this.targetSprite.hoverable) { //this.hideTooltip(); //this.targetSprite.handleOut(); _this.targetSprite.isHover = false; } })); } } }, enumerable: true, configurable: true }); Object.defineProperty(Tooltip.prototype, "showInViewport", { /** * @return Force showing tooltip? */ get: function () { return this.getPropertyValue("showInViewport"); }, /** * Normally, a tooltip will hide itself if it is pointing to a coordinate * that is outside viewport. * * Setting this setting to `true` will override that and make tooltip * appear next to the viewport edge closest to the target point. * * @default false * @since 4.5.7 * @param value Force showing tooltip? */ set: function (value) { this.setPropertyValue("showInViewport", value); }, enumerable: true, configurable: true }); Object.defineProperty(Tooltip.prototype, "getFillFromObject", { /** * Specifies if tooltip background should get fill color from the sprite it is pointing to. * * @return {boolean} * @default true */ get: function () { return this.getPropertyValue("getFillFromObject"); }, /** * @param value boolean */ set: function (value) { this.setPropertyValue("getFillFromObject", value, true); }, enumerable: true, configurable: true }); /** * Creates and returns a background element. * * @ignore Exclude from docs * @return Background */ Tooltip.prototype.createBackground = function () { return new PointedRectangle(); }; Object.defineProperty(Tooltip.prototype, "pointerOrientation", { /** * @return Orientation */ get: function () { return this.getPropertyValue("pointerOrientation"); }, /** * Pointer orientation: `"horizontal"`, `"vertical"`, `"up"`, `"down"`, * `"right"`, or `"left"`. * * Options`"horizontal"` or `"vertical"` are location-aware, meaning they * will change position of the Tooltip based on the target point's position * in relation to chart center. * * Options `"up"`, `"down"`, `"right"`, `"left"` are static and will point * in the specified direction regardless of the position, even if that means * going out of chart/screen bounds. * * IMPORTANT: in some situations, like having multiple tooltips stacked for * multiple series, the `"up"` and `"down"` values might be ignored in order * to make tooltip overlap algorithm work. * * @default "vertical" * @param value Orientation */ set: function (value) { this.setPropertyValue("pointerOrientation", value, true); }, enumerable: true, configurable: true }); Object.defineProperty(Tooltip.prototype, "animationDuration", { /** * @return Orientation */ get: function () { return this.getPropertyValue("animationDuration"); }, /** * Duration in milliseconds for the animation to take place when the tooltip * is moving from one place to another. * * @default 0 * @param value number */ set: function (value) { this.setPropertyValue("animationDuration", value); }, enumerable: true, configurable: true }); Object.defineProperty(Tooltip.prototype, "animationEasing", { /** * @return {Function} */ get: function () { return this.getPropertyValue("animationEasing"); }, /** * Tooltip animation (moving from one place to another) easing function. * * @default $ease.cubicOut * @param value (value: number) => number */ set: function (value) { this.setPropertyValue("animationEasing", value); }, enumerable: true, configurable: true }); Object.defineProperty(Tooltip.prototype, "html", { /** * @return HTML content */ get: function () { return this.label.html; }, /** * HTML content for the Tooltip. * * Provided value will be used as is, without applying any further * formatting to it. * * @param value HTML content */ set: function (value) { if (this.label.html != value) { this.label.html = value; this.invalidate(); } }, enumerable: true, configurable: true }); Object.defineProperty(Tooltip.prototype, "text", { /** * @return SVG text */ get: function () { return this.label.text; }, /** * SVG text content for the Tooltip. * * Text can have a number of formatting options supported by * [[TextFormatter]]. * * @param value SVG text */ set: function (value) { if (this.label.text != value) { this.label.text = value; this.invalidate(); } }, enumerable: true, configurable: true }); /** * Creates the Tooltip. * * @ignore Exclude from docs */ Tooltip.prototype.draw = function () { _super.prototype.draw.call(this); var label = this.label; if (label.invalid) { label.validate(); } var x = this._pointTo.x; var y = this._pointTo.y; var boundingRect = this._boundingRect; var textW = label.measuredWidth; var textH = label.measuredHeight; var pointerLength = this.background.pointerLength; var textX; var textY; if (this.ignoreBounds) { boundingRect = undefined; } // try to handle if text is wider than br if (boundingRect && this.fixDoc && textW > boundingRect.width) { // TODO maybe this isn't needed ? $utils.spritePointToDocument({ x: boundingRect.x, y: boundingRect.y }, this.parent); var p1 = $utils.spritePointToDocument({ x: boundingRect.x + boundingRect.width, y: boundingRect.y + boundingRect.height }, this.parent); var documentWidth = document.body.offsetWidth; // TODO maybe this isn't needed ? $utils.used(document.body.offsetHeight); if (p1.x > documentWidth / 2) { boundingRect.x = boundingRect.width - textW; } else { boundingRect.width = boundingRect.x + textW; } } var pointerOrientation = this.pointerOrientation; // horizontal if (pointerOrientation == "horizontal" || pointerOrientation == "left" || pointerOrientation == "right") { textY = -textH / 2; if (pointerOrientation == "horizontal") { if (boundingRect && x > boundingRect.x + boundingRect.width / 2) { textX = -textW / 2 - pointerLength; } else { textX = textW / 2 + pointerLength; } } else if (pointerOrientation == "left") { textX = textW / 2 + pointerLength; } else { textX = -textW / 2 - pointerLength; } } // vertical pointer else { if (boundingRect) { textX = $math.fitToRange(0, boundingRect.x - x + textW / 2, boundingRect.x - x + boundingRect.width - textW / 2); } if (pointerOrientation == "vertical") { if (boundingRect && y > boundingRect.y + textH + pointerLength) { textY = -textH - pointerLength; this._verticalOrientation = "up"; } else { textY = pointerLength; this._verticalOrientation = "down"; } } else if (pointerOrientation == "down") { textY = -textH - pointerLength; this._verticalOrientation = "up"; } else { textY = pointerLength; this._verticalOrientation = "down"; } } if (boundingRect) { textY = $math.fitToRange(textY, boundingRect.y - y, boundingRect.y + boundingRect.height - textH - y); } label.x = textX; label.y = textY; this.drawBackground(); }; /** * Overrides functionality from the superclass. * * @ignore Exclude from docs */ Tooltip.prototype.updateBackground = function () { this.group.addToBack(this.background.group); }; /** * Draws Tooltip background (chrome, background and pointer/stem). * * @ignore Exclude from docs */ Tooltip.prototype.drawBackground = function () { var label = this.label; var background = this.background; var textWidth = label.measuredWidth; var textHeight = label.measuredHeight; var boundingRect = this._boundingRect; var bgWidth = textWidth; var bgX = label.pixelX - textWidth / 2; var bgHeight = textHeight; var bgY = label.pixelY; var x = this._pointTo.x; var y = this._pointTo.y; var boundX1 = boundingRect.x - x; var boundX2 = boundX1 + boundingRect.width; var boundY1 = boundingRect.y - y; var boundY2 = boundY1 + boundingRect.height; background.x = bgX; background.y = bgY; background.width = bgWidth; background.height = bgHeight; if (this.fitPointerToBounds) { background.pointerX = $math.fitToRange(-background.x, boundX1 - background.x, boundX2 - background.x); background.pointerY = $math.fitToRange(-background.y, boundY1 - background.y, boundY2 - background.y); } else { background.pointerX = -background.x; background.pointerY = -background.y; } background.validate(); }; /** * */ Tooltip.prototype.delayedPointTo = function (point, instantly) { var _this = this; if (this._pointToDisposer) { this._pointToDisposer.dispose(); } this._pointToDisposer = registry.events.once("exitframe", function () { _this.pointTo(point, instantly); }); this.addDisposer(this._pointToDisposer); }; /** * Set nes tooltip's anchor point and moves whole tooltip. * * @param x X coordinate * @param y Y coordinate */ Tooltip.prototype.pointTo = function (point, instantly) { if (this._pointTo.x != point.x || this._pointTo.y != point.y) { this._pointTo = point; this.invalidate(); // this helps to avoid strange animation from nowhere on initial show or when balloon was hidden already if (!this.visible || instantly) { this.moveTo(this._pointTo); if (this._animation) { this._animation.kill(); } } else { // helps to avoid flicker on top/left corner if (this.pixelX == 0 && this.pixelY == 0) { this.moveTo(this._pointTo); } else { if (this._animation) { this._animation.kill(); } this._animation = new Animation(this, [{ property: "x", to: point.x, from: this.pixelX }, { property: "y", to: point.y, from: this.pixelY }], this.animationDuration, this.animationEasing).start(); } } } }; /** * Sets numeric boundaries Tooltip needs to obey (so it does not go outside * specific area). * * @ignore Exclude from docs * @param rectangle Boundary rectangle */ Tooltip.prototype.setBounds = function (rectangle) { var oldRect = this._boundingRect; if (oldRect.x != rectangle.x || oldRect.y != rectangle.y || oldRect.width != rectangle.width || oldRect.height != rectangle.height) { this._boundingRect = rectangle; this.invalidate(); } }; Object.defineProperty(Tooltip.prototype, "boundingContainer", { /** * Sets a [[Container]] instance to be used when calculating numeric * boundaries for the Tooltip. * * @ignore Exclude from docs * @param container Boundary container */ set: function (container) { this._boundingContainer = container; // TODO remove closures ? container.events.on("sizechanged", this.updateBounds, this); container.events.on("positionchanged", this.updateBounds, this); }, enumerable: true, configurable: true }); /** * Updates numeric boundaries for the Tooltip, based on the * `boundingCountrainer`. */ Tooltip.prototype.updateBounds = function () { var boundingContainer = this._boundingContainer; // to global var rect = $utils.spriteRectToSvg({ x: boundingContainer.pixelX, y: boundingContainer.pixelY, width: boundingContainer.maxWidth, height: boundingContainer.maxHeight }, boundingContainer); this.setBounds(rect); }; Object.defineProperty(Tooltip.prototype, "ignoreBounds", { /** * @return Ignore chart bounds? */ get: function () { return this.getPropertyValue("ignoreBounds"); }, /** * Normally, a tooltip's position will be adjusted so it always fits into * chart's coundaries. * * Setting this to `false` will disable such checks and will allow tooltip * to "bleed over" the edge of the chart. * * @default false * @since 4.10.8 * @param value Ignore chart bounds? */ set: function (value) { this.setPropertyValue("ignoreBounds", value); }, enumerable: true, configurable: true }); Object.defineProperty(Tooltip.prototype, "verticalOrientation", { /** * If tooltipOrientation is vertical, it can be drawn below or above point. * We need to know this when solving overlapping. * * @ignore Exclude from docs * @return "up" | "down" */ get: function () { return this._verticalOrientation; }, enumerable: true, configurable: true }); Object.defineProperty(Tooltip.prototype, "tooltip", { /** * To avoid stackoverflow * @ignore */ get: function () { return undefined; }, enumerable: true, configurable: true }); /** * Copies properties and other attributes. * * @param source Source */ Tooltip.prototype.copyFrom = function (source) { _super.prototype.copyFrom.call(this, source); this.label.copyFrom(source.label); if (source._boundingRect) { this._boundingRect = source._boundingRect; } }; /** * Adds easing functions to "function" fields. * * @param field Field name * @return Assign as function? */ Tooltip.prototype.asFunction = function (field) { return field == "animationEasing" || _super.prototype.asIs.call(this, field); }; return Tooltip; }(Container)); export { Tooltip }; /** * Register class in system, so that it can be instantiated using its name from * anywhere. * * @ignore */ registry.registeredClasses["Tooltip"] = Tooltip; //# sourceMappingURL=Tooltip.js.map