123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452 |
- 'use strict';
- import H from './../../parts/Globals.js';
- import './../../parts/Utilities.js';
- import './../../parts/SvgRenderer.js';
- import controllableMixin from './controllableMixin.js';
- import MockPoint from './../MockPoint.js';
- /**
- * A controllable label class.
- *
- * @class
- * @mixes Annotation.controllableMixin
- * @memberOf Annotation
- *
- * @param {Highcharts.Annotation} annotation an annotation instance
- * @param {Object} options a label's options
- * @param {number} index of the label
- **/
- function ControllableLabel(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<String>}
- */
- 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 {Object} alignOptions
- * @param {Object} box
- * @return {Annotation.controllableMixin.Position} 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.
- *
- * @param {Object} label
- * @param {Object} alignOptions
- * @param {Object} alignAttr
- * @return {Object} justified options
- **/
- 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;
- };
- /**
- * @typedef {Object} Annotation.ControllableLabel.AttrsMap
- * @property {string} backgroundColor=fill
- * @property {string} borderColor=stroke
- * @property {string} borderWidth=stroke-width
- * @property {string} zIndex=zIndex
- * @property {string} borderRadius=r
- * @property {string} padding=padding
- */
- /**
- * A map object which allows to map options attributes to element attributes
- *
- * @type {Annotation.ControllableLabel.AttrsMap}
- */
- ControllableLabel.attrsMap = {
- backgroundColor: 'fill',
- borderColor: 'stroke',
- borderWidth: 'stroke-width',
- zIndex: 'zIndex',
- borderRadius: 'r',
- padding: 'padding'
- };
- H.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 annotationOptions = this.annotation.userOptions,
- labelOptions = annotationOptions[this.collection][this.index];
- // Local options:
- this.options.x += dx;
- this.options.y += dy;
- // Options stored in chart:
- labelOptions.x = this.options.x;
- labelOptions.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,
- -9e9,
- 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 ?
- H.format(
- text,
- point.getLabelConfig(),
- this.annotation.chart.time
- ) :
- 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: -9e9
- });
- }
- 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 {Annotation.controllableMixin.Anchor} anchor
- * @return {Annotation.controllableMixin.Position|null} position
- */
- 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.isInsidePane.call(point);
- if (showItem) {
- if (itemOptions.distance) {
- itemPosition = H.Tooltip.prototype.getPosition.call(
- {
- chart: chart,
- distance: H.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(
- H.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
- */
- 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 (H.isNumber(anchorX) && H.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;
|