ControllableLabel.js 14 KB

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