ControllableLabel.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. /* *
  2. *
  3. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  4. *
  5. * */
  6. 'use strict';
  7. import ControllableMixin from '../Mixins/ControllableMixin.js';
  8. import F from '../../../Core/FormatUtilities.js';
  9. var format = F.format;
  10. import MockPoint from '../MockPoint.js';
  11. import SVGRenderer from '../../../Core/Renderer/SVG/SVGRenderer.js';
  12. import Tooltip from '../../../Core/Tooltip.js';
  13. import U from '../../../Core/Utilities.js';
  14. var extend = U.extend, isNumber = U.isNumber, pick = U.pick;
  15. import '../../../Core/Renderer/SVG/SVGRenderer.js';
  16. /* eslint-disable no-invalid-this, valid-jsdoc */
  17. /**
  18. * A controllable label class.
  19. *
  20. * @requires modules/annotations
  21. *
  22. * @private
  23. * @class
  24. * @name Highcharts.AnnotationControllableLabel
  25. *
  26. * @param {Highcharts.Annotation} annotation
  27. * An annotation instance.
  28. * @param {Highcharts.AnnotationsLabelOptions} options
  29. * A label's options.
  30. * @param {number} index
  31. * Index of the label.
  32. */
  33. var ControllableLabel = /** @class */ (function () {
  34. /* *
  35. *
  36. * Constructors
  37. *
  38. * */
  39. function ControllableLabel(annotation, options, index) {
  40. /* *
  41. *
  42. * Properties
  43. *
  44. * */
  45. this.addControlPoints = ControllableMixin.addControlPoints;
  46. this.attr = ControllableMixin.attr;
  47. this.attrsFromOptions = ControllableMixin.attrsFromOptions;
  48. this.destroy = ControllableMixin.destroy;
  49. this.getPointsOptions = ControllableMixin.getPointsOptions;
  50. this.init = ControllableMixin.init;
  51. this.linkPoints = ControllableMixin.linkPoints;
  52. this.point = ControllableMixin.point;
  53. this.rotate = ControllableMixin.rotate;
  54. this.scale = ControllableMixin.scale;
  55. this.setControlPointsVisibility = ControllableMixin.setControlPointsVisibility;
  56. this.shouldBeDrawn = ControllableMixin.shouldBeDrawn;
  57. this.transform = ControllableMixin.transform;
  58. this.transformPoint = ControllableMixin.transformPoint;
  59. this.translateShape = ControllableMixin.translateShape;
  60. this.update = ControllableMixin.update;
  61. this.init(annotation, options, index);
  62. this.collection = 'labels';
  63. }
  64. /* *
  65. *
  66. * Static Functions
  67. *
  68. * */
  69. /**
  70. * Returns new aligned position based alignment options and box to align to.
  71. * It is almost a one-to-one copy from SVGElement.prototype.align
  72. * except it does not use and mutate an element
  73. *
  74. * @param {Highcharts.AnnotationAlignObject} alignOptions
  75. *
  76. * @param {Highcharts.BBoxObject} box
  77. *
  78. * @return {Highcharts.PositionObject}
  79. * Aligned position.
  80. */
  81. ControllableLabel.alignedPosition = function (alignOptions, box) {
  82. var align = alignOptions.align, vAlign = alignOptions.verticalAlign, x = (box.x || 0) + (alignOptions.x || 0), y = (box.y || 0) + (alignOptions.y || 0), alignFactor, vAlignFactor;
  83. if (align === 'right') {
  84. alignFactor = 1;
  85. }
  86. else if (align === 'center') {
  87. alignFactor = 2;
  88. }
  89. if (alignFactor) {
  90. x += (box.width - (alignOptions.width || 0)) / alignFactor;
  91. }
  92. if (vAlign === 'bottom') {
  93. vAlignFactor = 1;
  94. }
  95. else if (vAlign === 'middle') {
  96. vAlignFactor = 2;
  97. }
  98. if (vAlignFactor) {
  99. y += (box.height - (alignOptions.height || 0)) / vAlignFactor;
  100. }
  101. return {
  102. x: Math.round(x),
  103. y: Math.round(y)
  104. };
  105. };
  106. /**
  107. * Returns new alignment options for a label if the label is outside the
  108. * plot area. It is almost a one-to-one copy from
  109. * Series.prototype.justifyDataLabel except it does not mutate the label and
  110. * it works with absolute instead of relative position.
  111. */
  112. ControllableLabel.justifiedOptions = function (chart, label, alignOptions, alignAttr) {
  113. var align = alignOptions.align, verticalAlign = alignOptions.verticalAlign, padding = label.box ? 0 : (label.padding || 0), bBox = label.getBBox(), off,
  114. //
  115. options = {
  116. align: align,
  117. verticalAlign: verticalAlign,
  118. x: alignOptions.x,
  119. y: alignOptions.y,
  120. width: label.width,
  121. height: label.height
  122. },
  123. //
  124. x = (alignAttr.x || 0) - chart.plotLeft, y = (alignAttr.y || 0) - chart.plotTop;
  125. // Off left
  126. off = x + padding;
  127. if (off < 0) {
  128. if (align === 'right') {
  129. options.align = 'left';
  130. }
  131. else {
  132. options.x = (options.x || 0) - off;
  133. }
  134. }
  135. // Off right
  136. off = x + bBox.width - padding;
  137. if (off > chart.plotWidth) {
  138. if (align === 'left') {
  139. options.align = 'right';
  140. }
  141. else {
  142. options.x = (options.x || 0) + chart.plotWidth - off;
  143. }
  144. }
  145. // Off top
  146. off = y + padding;
  147. if (off < 0) {
  148. if (verticalAlign === 'bottom') {
  149. options.verticalAlign = 'top';
  150. }
  151. else {
  152. options.y = (options.y || 0) - off;
  153. }
  154. }
  155. // Off bottom
  156. off = y + bBox.height - padding;
  157. if (off > chart.plotHeight) {
  158. if (verticalAlign === 'top') {
  159. options.verticalAlign = 'bottom';
  160. }
  161. else {
  162. options.y = (options.y || 0) + chart.plotHeight - off;
  163. }
  164. }
  165. return options;
  166. };
  167. /* *
  168. *
  169. * Functions
  170. *
  171. * */
  172. /**
  173. * Translate the point of the label by deltaX and deltaY translations.
  174. * The point is the label's anchor.
  175. *
  176. * @param {number} dx translation for x coordinate
  177. * @param {number} dy translation for y coordinate
  178. */
  179. ControllableLabel.prototype.translatePoint = function (dx, dy) {
  180. ControllableMixin.translatePoint.call(this, dx, dy, 0);
  181. };
  182. /**
  183. * Translate x and y position relative to the label's anchor.
  184. *
  185. * @param {number} dx translation for x coordinate
  186. * @param {number} dy translation for y coordinate
  187. */
  188. ControllableLabel.prototype.translate = function (dx, dy) {
  189. var chart = this.annotation.chart,
  190. // Annotation.options
  191. labelOptions = this.annotation.userOptions,
  192. // Chart.options.annotations
  193. annotationIndex = chart.annotations.indexOf(this.annotation), chartAnnotations = chart.options.annotations, chartOptions = chartAnnotations[annotationIndex], temp;
  194. if (chart.inverted) {
  195. temp = dx;
  196. dx = dy;
  197. dy = temp;
  198. }
  199. // Local options:
  200. this.options.x += dx;
  201. this.options.y += dy;
  202. // Options stored in chart:
  203. chartOptions[this.collection][this.index].x = this.options.x;
  204. chartOptions[this.collection][this.index].y = this.options.y;
  205. labelOptions[this.collection][this.index].x = this.options.x;
  206. labelOptions[this.collection][this.index].y = this.options.y;
  207. };
  208. ControllableLabel.prototype.render = function (parent) {
  209. var options = this.options, attrs = this.attrsFromOptions(options), style = options.style;
  210. this.graphic = this.annotation.chart.renderer
  211. .label('', 0, -9999, // #10055
  212. options.shape, null, null, options.useHTML, null, 'annotation-label')
  213. .attr(attrs)
  214. .add(parent);
  215. if (!this.annotation.chart.styledMode) {
  216. if (style.color === 'contrast') {
  217. style.color = this.annotation.chart.renderer.getContrast(ControllableLabel.shapesWithoutBackground.indexOf(options.shape) > -1 ? '#FFFFFF' : options.backgroundColor);
  218. }
  219. this.graphic
  220. .css(options.style)
  221. .shadow(options.shadow);
  222. }
  223. if (options.className) {
  224. this.graphic.addClass(options.className);
  225. }
  226. this.graphic.labelrank = options.labelrank;
  227. ControllableMixin.render.call(this);
  228. };
  229. ControllableLabel.prototype.redraw = function (animation) {
  230. var options = this.options, text = this.text || options.format || options.text, label = this.graphic, point = this.points[0], anchor, attrs;
  231. label.attr({
  232. text: text ?
  233. format(text, point.getLabelConfig(), this.annotation.chart) :
  234. options.formatter.call(point, this)
  235. });
  236. anchor = this.anchor(point);
  237. attrs = this.position(anchor);
  238. if (attrs) {
  239. label.alignAttr = attrs;
  240. attrs.anchorX = anchor.absolutePosition.x;
  241. attrs.anchorY = anchor.absolutePosition.y;
  242. label[animation ? 'animate' : 'attr'](attrs);
  243. }
  244. else {
  245. label.attr({
  246. x: 0,
  247. y: -9999 // #10055
  248. });
  249. }
  250. label.placed = !!attrs;
  251. ControllableMixin.redraw.call(this, animation);
  252. };
  253. /**
  254. * All basic shapes don't support alignTo() method except label.
  255. * For a controllable label, we need to subtract translation from
  256. * options.
  257. */
  258. ControllableLabel.prototype.anchor = function (_point) {
  259. var anchor = ControllableMixin.anchor.apply(this, arguments), x = this.options.x || 0, y = this.options.y || 0;
  260. anchor.absolutePosition.x -= x;
  261. anchor.absolutePosition.y -= y;
  262. anchor.relativePosition.x -= x;
  263. anchor.relativePosition.y -= y;
  264. return anchor;
  265. };
  266. /**
  267. * Returns the label position relative to its anchor.
  268. *
  269. * @param {Highcharts.AnnotationAnchorObject} anchor
  270. *
  271. * @return {Highcharts.PositionObject|null}
  272. */
  273. ControllableLabel.prototype.position = function (anchor) {
  274. 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 &&
  275. MockPoint.prototype.isInsidePlot.call(point);
  276. var _a = item.width, width = _a === void 0 ? 0 : _a, _b = item.height, height = _b === void 0 ? 0 : _b;
  277. if (showItem) {
  278. if (itemOptions.distance) {
  279. itemPosition = Tooltip.prototype.getPosition.call({
  280. chart: chart,
  281. distance: pick(itemOptions.distance, 16)
  282. }, width, height, {
  283. plotX: anchorRelativePosition.x,
  284. plotY: anchorRelativePosition.y,
  285. negative: point.negative,
  286. ttBelow: point.ttBelow,
  287. h: (anchorRelativePosition.height || anchorRelativePosition.width)
  288. });
  289. }
  290. else if (itemOptions.positioner) {
  291. itemPosition = itemOptions.positioner.call(this);
  292. }
  293. else {
  294. alignTo = {
  295. x: anchorAbsolutePosition.x,
  296. y: anchorAbsolutePosition.y,
  297. width: 0,
  298. height: 0
  299. };
  300. itemPosition = ControllableLabel.alignedPosition(extend(itemOptions, {
  301. width: width,
  302. height: height
  303. }), alignTo);
  304. if (this.options.overflow === 'justify') {
  305. itemPosition = ControllableLabel.alignedPosition(ControllableLabel.justifiedOptions(chart, item, itemOptions, itemPosition), alignTo);
  306. }
  307. }
  308. if (itemOptions.crop) {
  309. itemPosRelativeX = itemPosition.x - chart.plotLeft;
  310. itemPosRelativeY = itemPosition.y - chart.plotTop;
  311. showItem =
  312. chart.isInsidePlot(itemPosRelativeX, itemPosRelativeY) &&
  313. chart.isInsidePlot(itemPosRelativeX + width, itemPosRelativeY + height);
  314. }
  315. }
  316. return showItem ? itemPosition : null;
  317. };
  318. /* *
  319. *
  320. * Static Properties
  321. *
  322. * */
  323. /**
  324. * A map object which allows to map options attributes to element attributes
  325. *
  326. * @type {Highcharts.Dictionary<string>}
  327. */
  328. ControllableLabel.attrsMap = {
  329. backgroundColor: 'fill',
  330. borderColor: 'stroke',
  331. borderWidth: 'stroke-width',
  332. zIndex: 'zIndex',
  333. borderRadius: 'r',
  334. padding: 'padding'
  335. };
  336. /**
  337. * Shapes which do not have background - the object is used for proper
  338. * setting of the contrast color.
  339. *
  340. * @type {Array<string>}
  341. */
  342. ControllableLabel.shapesWithoutBackground = ['connector'];
  343. return ControllableLabel;
  344. }());
  345. export default ControllableLabel;
  346. /* ********************************************************************** */
  347. /**
  348. * General symbol definition for labels with connector
  349. * @private
  350. */
  351. SVGRenderer.prototype.symbols.connector = function (x, y, w, h, options) {
  352. var anchorX = options && options.anchorX, anchorY = options && options.anchorY, path, yOffset, lateral = w / 2;
  353. if (isNumber(anchorX) && isNumber(anchorY)) {
  354. path = [['M', anchorX, anchorY]];
  355. // Prefer 45 deg connectors
  356. yOffset = y - anchorY;
  357. if (yOffset < 0) {
  358. yOffset = -h - yOffset;
  359. }
  360. if (yOffset < w) {
  361. lateral = anchorX < x + (w / 2) ? yOffset : w - yOffset;
  362. }
  363. // Anchor below label
  364. if (anchorY > y + h) {
  365. path.push(['L', x + lateral, y + h]);
  366. // Anchor above label
  367. }
  368. else if (anchorY < y) {
  369. path.push(['L', x + lateral, y]);
  370. // Anchor left of label
  371. }
  372. else if (anchorX < x) {
  373. path.push(['L', x, y + h / 2]);
  374. // Anchor right of label
  375. }
  376. else if (anchorX > x + w) {
  377. path.push(['L', x + w, y + h / 2]);
  378. }
  379. }
  380. return path || [];
  381. };