/* * * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ 'use strict'; import H from './../../parts/Globals.js'; import U from './../../parts/Utilities.js'; var extend = U.extend, format = U.format, isNumber = U.isNumber, merge = U.merge, pick = U.pick; import './../../parts/SvgRenderer.js'; import controllableMixin from './controllableMixin.js'; import MockPoint from './../MockPoint.js'; import Tooltip from '../../parts/Tooltip.js'; /* eslint-disable no-invalid-this, valid-jsdoc */ /** * A controllable label class. * * @requires modules/annotations * * @private * @class * @name Highcharts.AnnotationControllableLabel * * @param {Highcharts.Annotation} annotation * An annotation instance. * @param {Highcharts.AnnotationsLabelOptions} options * A label's options. * @param {number} index * Index of the label. */ var ControllableLabel = function (annotation, options, index) { this.init(annotation, options, index); this.collection = 'labels'; }; /** * Shapes which do not have background - the object is used for proper * setting of the contrast color. * * @type {Array} */ ControllableLabel.shapesWithoutBackground = ['connector']; /** * Returns new aligned position based alignment options and box to align to. * It is almost a one-to-one copy from SVGElement.prototype.align * except it does not use and mutate an element * * @param {Highcharts.AnnotationAlignObject} alignOptions * * @param {Highcharts.BBoxObject} box * * @return {Highcharts.PositionObject} * Aligned position. */ ControllableLabel.alignedPosition = function (alignOptions, box) { var align = alignOptions.align, vAlign = alignOptions.verticalAlign, x = (box.x || 0) + (alignOptions.x || 0), y = (box.y || 0) + (alignOptions.y || 0), alignFactor, vAlignFactor; if (align === 'right') { alignFactor = 1; } else if (align === 'center') { alignFactor = 2; } if (alignFactor) { x += (box.width - (alignOptions.width || 0)) / alignFactor; } if (vAlign === 'bottom') { vAlignFactor = 1; } else if (vAlign === 'middle') { vAlignFactor = 2; } if (vAlignFactor) { y += (box.height - (alignOptions.height || 0)) / vAlignFactor; } return { x: Math.round(x), y: Math.round(y) }; }; /** * Returns new alignment options for a label if the label is outside the * plot area. It is almost a one-to-one copy from * Series.prototype.justifyDataLabel except it does not mutate the label and * it works with absolute instead of relative position. */ ControllableLabel.justifiedOptions = function (chart, label, alignOptions, alignAttr) { var align = alignOptions.align, verticalAlign = alignOptions.verticalAlign, padding = label.box ? 0 : (label.padding || 0), bBox = label.getBBox(), off, // options = { align: align, verticalAlign: verticalAlign, x: alignOptions.x, y: alignOptions.y, width: label.width, height: label.height }, // x = alignAttr.x - chart.plotLeft, y = alignAttr.y - chart.plotTop; // Off left off = x + padding; if (off < 0) { if (align === 'right') { options.align = 'left'; } else { options.x = -off; } } // Off right off = x + bBox.width - padding; if (off > chart.plotWidth) { if (align === 'left') { options.align = 'right'; } else { options.x = chart.plotWidth - off; } } // Off top off = y + padding; if (off < 0) { if (verticalAlign === 'bottom') { options.verticalAlign = 'top'; } else { options.y = -off; } } // Off bottom off = y + bBox.height - padding; if (off > chart.plotHeight) { if (verticalAlign === 'top') { options.verticalAlign = 'bottom'; } else { options.y = chart.plotHeight - off; } } return options; }; /** * A map object which allows to map options attributes to element attributes * * @type {Highcharts.Dictionary} */ ControllableLabel.attrsMap = { backgroundColor: 'fill', borderColor: 'stroke', borderWidth: 'stroke-width', zIndex: 'zIndex', borderRadius: 'r', padding: 'padding' }; merge(true, ControllableLabel.prototype, controllableMixin, /** @lends Annotation.ControllableLabel# */ { /** * Translate the point of the label by deltaX and deltaY translations. * The point is the label's anchor. * * @param {number} dx translation for x coordinate * @param {number} dy translation for y coordinate **/ translatePoint: function (dx, dy) { controllableMixin.translatePoint.call(this, dx, dy, 0); }, /** * Translate x and y position relative to the label's anchor. * * @param {number} dx translation for x coordinate * @param {number} dy translation for y coordinate **/ translate: function (dx, dy) { var chart = this.annotation.chart, // Annotation.options labelOptions = this.annotation.userOptions, // Chart.options.annotations annotationIndex = chart.annotations.indexOf(this.annotation), chartAnnotations = chart.options.annotations, chartOptions = chartAnnotations[annotationIndex], temp; if (chart.inverted) { temp = dx; dx = dy; dy = temp; } // Local options: this.options.x += dx; this.options.y += dy; // Options stored in chart: chartOptions[this.collection][this.index].x = this.options.x; chartOptions[this.collection][this.index].y = this.options.y; labelOptions[this.collection][this.index].x = this.options.x; labelOptions[this.collection][this.index].y = this.options.y; }, render: function (parent) { var options = this.options, attrs = this.attrsFromOptions(options), style = options.style; this.graphic = this.annotation.chart.renderer .label('', 0, -9999, // #10055 options.shape, null, null, options.useHTML, null, 'annotation-label') .attr(attrs) .add(parent); if (!this.annotation.chart.styledMode) { if (style.color === 'contrast') { style.color = this.annotation.chart.renderer.getContrast(ControllableLabel.shapesWithoutBackground.indexOf(options.shape) > -1 ? '#FFFFFF' : options.backgroundColor); } this.graphic .css(options.style) .shadow(options.shadow); } if (options.className) { this.graphic.addClass(options.className); } this.graphic.labelrank = options.labelrank; controllableMixin.render.call(this); }, redraw: function (animation) { var options = this.options, text = this.text || options.format || options.text, label = this.graphic, point = this.points[0], show = false, anchor, attrs; label.attr({ text: text ? format(text, point.getLabelConfig(), this.annotation.chart) : options.formatter.call(point, this) }); anchor = this.anchor(point); attrs = this.position(anchor); show = attrs; if (show) { label.alignAttr = attrs; attrs.anchorX = anchor.absolutePosition.x; attrs.anchorY = anchor.absolutePosition.y; label[animation ? 'animate' : 'attr'](attrs); } else { label.attr({ x: 0, y: -9999 // #10055 }); } label.placed = Boolean(show); controllableMixin.redraw.call(this, animation); }, /** * All basic shapes don't support alignTo() method except label. * For a controllable label, we need to subtract translation from * options. */ anchor: function () { var anchor = controllableMixin.anchor.apply(this, arguments), x = this.options.x || 0, y = this.options.y || 0; anchor.absolutePosition.x -= x; anchor.absolutePosition.y -= y; anchor.relativePosition.x -= x; anchor.relativePosition.y -= y; return anchor; }, /** * Returns the label position relative to its anchor. * * @param {Highcharts.AnnotationAnchorObject} anchor * * @return {Highcharts.PositionObject|null} */ position: function (anchor) { var item = this.graphic, chart = this.annotation.chart, point = this.points[0], itemOptions = this.options, anchorAbsolutePosition = anchor.absolutePosition, anchorRelativePosition = anchor.relativePosition, itemPosition, alignTo, itemPosRelativeX, itemPosRelativeY, showItem = point.series.visible && MockPoint.prototype.isInsidePlot.call(point); if (showItem) { if (itemOptions.distance) { itemPosition = Tooltip.prototype.getPosition.call({ chart: chart, distance: pick(itemOptions.distance, 16) }, item.width, item.height, { plotX: anchorRelativePosition.x, plotY: anchorRelativePosition.y, negative: point.negative, ttBelow: point.ttBelow, h: (anchorRelativePosition.height || anchorRelativePosition.width) }); } else if (itemOptions.positioner) { itemPosition = itemOptions.positioner.call(this); } else { alignTo = { x: anchorAbsolutePosition.x, y: anchorAbsolutePosition.y, width: 0, height: 0 }; itemPosition = ControllableLabel.alignedPosition(extend(itemOptions, { width: item.width, height: item.height }), alignTo); if (this.options.overflow === 'justify') { itemPosition = ControllableLabel.alignedPosition(ControllableLabel.justifiedOptions(chart, item, itemOptions, itemPosition), alignTo); } } if (itemOptions.crop) { itemPosRelativeX = itemPosition.x - chart.plotLeft; itemPosRelativeY = itemPosition.y - chart.plotTop; showItem = chart.isInsidePlot(itemPosRelativeX, itemPosRelativeY) && chart.isInsidePlot(itemPosRelativeX + item.width, itemPosRelativeY + item.height); } } return showItem ? itemPosition : null; } }); /* ********************************************************************** */ /** * General symbol definition for labels with connector * @private */ H.SVGRenderer.prototype.symbols.connector = function (x, y, w, h, options) { var anchorX = options && options.anchorX, anchorY = options && options.anchorY, path, yOffset, lateral = w / 2; if (isNumber(anchorX) && isNumber(anchorY)) { path = [['M', anchorX, anchorY]]; // Prefer 45 deg connectors yOffset = y - anchorY; if (yOffset < 0) { yOffset = -h - yOffset; } if (yOffset < w) { lateral = anchorX < x + (w / 2) ? yOffset : w - yOffset; } // Anchor below label if (anchorY > y + h) { path.push(['L', x + lateral, y + h]); // Anchor above label } else if (anchorY < y) { path.push(['L', x + lateral, y]); // Anchor left of label } else if (anchorX < x) { path.push(['L', x, y + h / 2]); // Anchor right of label } else if (anchorX > x + w) { path.push(['L', x + w, y + h / 2]); } } return path || []; }; export default ControllableLabel;