UNPKG

highcharts

Version:
363 lines (362 loc) 12.1 kB
/* * * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ 'use strict'; import Controllable from './Controllable.js'; import F from '../../../Core/Templating.js'; const { format } = F; import MockPoint from '../MockPoint.js'; import U from '../../../Core/Utilities.js'; const { extend, getAlignFactor, isNumber, pick } = U; /* * * * Functions * * */ /** * General symbol definition for labels with connector * @private */ function symbolConnector(x, y, w, h, options) { const anchorX = options && options.anchorX, anchorY = options && options.anchorY; let 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 || []; } /* * * * Class * * */ /** * 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. */ class ControllableLabel extends Controllable { /* * * * Static Functions * * */ /** * 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. */ static alignedPosition(alignOptions, box) { return { x: Math.round((box.x || 0) + (alignOptions.x || 0) + (box.width - (alignOptions.width || 0)) * getAlignFactor(alignOptions.align)), y: Math.round((box.y || 0) + (alignOptions.y || 0) + (box.height - (alignOptions.height || 0)) * getAlignFactor(alignOptions.verticalAlign)) }; } static compose(SVGRendererClass) { const symbols = SVGRendererClass.prototype.symbols; symbols.connector = symbolConnector; } /** * 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. */ static justifiedOptions(chart, label, alignOptions, alignAttr) { const align = alignOptions.align, verticalAlign = alignOptions.verticalAlign, padding = label.box ? 0 : (label.padding || 0), bBox = label.getBBox(), // options = { align: align, verticalAlign: verticalAlign, x: alignOptions.x, y: alignOptions.y, width: label.width, height: label.height }, // x = (alignAttr.x || 0) - chart.plotLeft, y = (alignAttr.y || 0) - chart.plotTop; let off; // Off left off = x + padding; if (off < 0) { if (align === 'right') { options.align = 'left'; } else { options.x = (options.x || 0) - off; } } // Off right off = x + bBox.width - padding; if (off > chart.plotWidth) { if (align === 'left') { options.align = 'right'; } else { options.x = (options.x || 0) + chart.plotWidth - off; } } // Off top off = y + padding; if (off < 0) { if (verticalAlign === 'bottom') { options.verticalAlign = 'top'; } else { options.y = (options.y || 0) - off; } } // Off bottom off = y + bBox.height - padding; if (off > chart.plotHeight) { if (verticalAlign === 'top') { options.verticalAlign = 'bottom'; } else { options.y = (options.y || 0) + chart.plotHeight - off; } } return options; } /* * * * Constructors * * */ constructor(annotation, options, index) { super(annotation, options, index, 'label'); } /* * * * Functions * * */ /** * 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(dx, dy) { super.translatePoint(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(dx, dy) { const 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]; if (chart.inverted) { const 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(parent) { const 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); } this.graphic.labelrank = options.labelrank; super.render(); } redraw(animation) { const options = this.options, text = this.text || options.format || options.text, label = this.graphic, point = this.points[0]; if (!label) { this.redraw(animation); return; } label.attr({ text: text ? format(String(text), point, this.annotation.chart) : options.formatter.call(point, this) }); const anchor = this.anchor(point); const attrs = this.position(anchor); if (attrs) { 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 = !!attrs; super.redraw(animation); } /** * All basic shapes don't support alignTo() method except label. * For a controllable label, we need to subtract translation from * options. */ anchor( // eslint-disable-next-line @typescript-eslint/no-unused-vars _point) { const anchor = super.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. */ position(anchor) { const item = this.graphic, chart = this.annotation.chart, tooltip = chart.tooltip, point = this.points[0], itemOptions = this.options, anchorAbsolutePosition = anchor.absolutePosition, anchorRelativePosition = anchor.relativePosition; let itemPosition, alignTo, itemPosRelativeX, itemPosRelativeY, showItem = point.series.visible && MockPoint.prototype.isInsidePlot.call(point); if (item && showItem) { const { width = 0, height = 0 } = item; if (itemOptions.distance && tooltip) { itemPosition = tooltip.getPosition.call({ chart, distance: pick(itemOptions.distance, 16), getPlayingField: tooltip.getPlayingField, pointer: tooltip.pointer }, width, 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, 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 + width, itemPosRelativeY + height); } } return showItem ? itemPosition : null; } } /* * * * Static Properties * * */ /** * A map object which allows to map options attributes to element attributes * * @type {Highcharts.Dictionary<string>} */ ControllableLabel.attrsMap = { backgroundColor: 'fill', borderColor: 'stroke', borderWidth: 'stroke-width', zIndex: 'zIndex', borderRadius: 'r', padding: 'padding' }; /** * Shapes which do not have background - the object is used for proper * setting of the contrast color. * * @type {Array<string>} */ ControllableLabel.shapesWithoutBackground = ['connector']; /* * * * Default Export * * */ export default ControllableLabel;