annotations-legacy.src.js 54 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942
  1. /* *
  2. * (c) 2009-2017 Highsoft, Black Label
  3. *
  4. * License: www.highcharts.com/license
  5. */
  6. /**
  7. * @interface Highcharts.AnnotationAnchorObject
  8. *//**
  9. * Relative to the plot area position
  10. * @name Highcharts.AnnotationAnchorObject#relativePosition
  11. * @type {Highcharts.AnnotationAnchorPositionObject}
  12. *//**
  13. * Absolute position
  14. * @name Highcharts.AnnotationAnchorObject#absolutePosition
  15. * @type {Highcharts.AnnotationAnchorPositionObject}
  16. */
  17. /**
  18. * An object which denotes an anchor position
  19. *
  20. * @interface Highcharts.AnnotationAnchorPositionObject
  21. *//**
  22. * @name Highcharts.AnnotationAnchorPositionObject#x
  23. * @property {number}
  24. *//**
  25. * @name Highcharts.AnnotationAnchorPositionObject#y
  26. * @property {number}
  27. *//**
  28. * @name Highcharts.AnnotationAnchorPositionObject#height
  29. * @property {number}
  30. *//**
  31. * @name Highcharts.AnnotationAnchorPositionObject#width
  32. * @property {number}
  33. */
  34. /**
  35. * A mock point label configuration.
  36. *
  37. * @private
  38. * @interface Highcharts.MockLabelOptionsObject
  39. *//**
  40. * X value translated to x axis scale
  41. * @name Highcharts.MockLabelOptionsObject#x
  42. * @type {number|undefined}
  43. *//**
  44. * Y value translated to y axis scale
  45. * @name Highcharts.MockLabelOptionsObject#y
  46. * @type {number|undefined}
  47. *//**
  48. * @name Highcharts.MockLabelOptionsObject#point
  49. * @type {Highcharts.Point}
  50. */
  51. /**
  52. * A mock point configuration.
  53. *
  54. * @todo Make official
  55. *
  56. * @private
  57. * @interface Highcharts.MockPointOptionsObject
  58. *//**
  59. * x value for the point in xAxis scale or pixels
  60. * @name Highcharts.MockPointOptionsObject#x
  61. * @type {number}
  62. *//**
  63. * y value for the point in yAxis scale or pixels
  64. * @name Highcharts.MockPointOptionsObject#y
  65. * @type {number}
  66. *//**
  67. * xAxis index or id
  68. * @name Highcharts.MockPointOptionsObject#xAxis
  69. * @type {number|string|undefined}
  70. *//**
  71. * yAxis index or id
  72. * @name Highcharts.MockPointOptionsObject#yAxis
  73. * @property {number|string|undefined}
  74. */
  75. 'use strict';
  76. import H from '../parts/Globals.js';
  77. import '../parts/Utilities.js';
  78. import '../parts/Chart.js';
  79. import '../parts/Series.js';
  80. import '../parts/Tooltip.js';
  81. var merge = H.merge,
  82. addEvent = H.addEvent,
  83. extend = H.extend,
  84. isString = H.isString,
  85. isNumber = H.isNumber,
  86. defined = H.defined,
  87. isObject = H.isObject,
  88. erase = H.erase,
  89. find = H.find,
  90. format = H.format,
  91. pick = H.pick,
  92. objectEach = H.objectEach,
  93. uniqueKey = H.uniqueKey,
  94. destroyObjectProperties = H.destroyObjectProperties,
  95. tooltipPrototype = H.Tooltip.prototype,
  96. seriesPrototype = H.Series.prototype,
  97. chartPrototype = H.Chart.prototype;
  98. /* ************************************************************************** *
  99. *
  100. * MARKER SECTION
  101. * Contains objects and functions for adding a marker element to a path
  102. * element
  103. *
  104. * ************************************************************************** */
  105. /**
  106. * Options for configuring markers for annotations.
  107. *
  108. * An example of the arrow marker:
  109. * ```
  110. * {
  111. * arrow: {
  112. * id: 'arrow',
  113. * tagName: 'marker',
  114. * refY: 5,
  115. * refX: 5,
  116. * markerWidth: 10,
  117. * markerHeight: 10,
  118. * children: [{
  119. * tagName: 'path',
  120. * attrs: {
  121. * d: 'M 0 0 L 10 5 L 0 10 Z',
  122. * strokeWidth: 0
  123. * }
  124. * }]
  125. * }
  126. * }
  127. * ```
  128. *
  129. * @sample highcharts/annotations/custom-markers/
  130. * Define a custom marker for annotations
  131. * @sample highcharts/css/annotations-markers/
  132. * Define markers in a styled mode
  133. *
  134. * @type {Highcharts.Dictionary<Highcharts.SVGAttributes>}
  135. * @since 6.0.0
  136. * @apioption defs
  137. */
  138. var defaultMarkers = {
  139. arrow: {
  140. tagName: 'marker',
  141. render: false,
  142. id: 'arrow',
  143. refY: 5,
  144. refX: 5,
  145. markerWidth: 10,
  146. markerHeight: 10,
  147. children: [{
  148. tagName: 'path',
  149. d: 'M 0 0 L 10 5 L 0 10 Z', // triangle (used as an arrow)
  150. strokeWidth: 0
  151. }]
  152. }
  153. };
  154. var MarkerMixin = {
  155. markerSetter: function (markerType) {
  156. return function (value) {
  157. this.attr(markerType, 'url(#' + value + ')');
  158. };
  159. }
  160. };
  161. extend(MarkerMixin, {
  162. markerEndSetter: MarkerMixin.markerSetter('marker-end'),
  163. markerStartSetter: MarkerMixin.markerSetter('marker-start')
  164. });
  165. H.SVGRenderer.prototype.addMarker = function (id, markerOptions) {
  166. var options = { id: id };
  167. if (!this.styledMode) {
  168. var attrs = {
  169. stroke: markerOptions.color || 'none',
  170. fill: markerOptions.color || 'rgba(0, 0, 0, 0.75)'
  171. };
  172. options.children = markerOptions.children.map(function (child) {
  173. return merge(attrs, child);
  174. });
  175. }
  176. var marker = this.definition(merge({
  177. markerWidth: 20,
  178. markerHeight: 20,
  179. refX: 0,
  180. refY: 0,
  181. orient: 'auto'
  182. }, markerOptions, options));
  183. marker.id = id;
  184. return marker;
  185. };
  186. /* ************************************************************************** *
  187. *
  188. * MOCK POINT
  189. *
  190. * ************************************************************************** */
  191. /**
  192. * A trimmed point object which imitates {@link Highchart.Point} class.
  193. * It is created when there is a need of pointing to some chart's position
  194. * using axis values or pixel values
  195. *
  196. * @private
  197. * @class
  198. * @name Highcharts.MockPoint
  199. *
  200. * @param {Highcharts.Chart} chart
  201. * The chart object
  202. *
  203. * @param {Highcharts.MockPointOptionsObject} options
  204. * The options object
  205. */
  206. var MockPoint = H.MockPoint = function (chart, options) {
  207. this.mock = true;
  208. this.series = {
  209. visible: true,
  210. chart: chart,
  211. getPlotBox: seriesPrototype.getPlotBox
  212. };
  213. // this.plotX
  214. // this.plotY
  215. /* Those might not exist if a specific axis was not found/defined */
  216. // this.x?
  217. // this.y?
  218. this.init(chart, options);
  219. };
  220. /**
  221. * A factory function for creating a mock point object
  222. *
  223. * @function Highcharts.MockPoint#mockPoint
  224. *
  225. * @param {Highcharts.MockPointOptionsObject} mockPointOptions
  226. *
  227. * @return {Highcharts.MockPoint}
  228. * A mock point
  229. */
  230. var mockPoint = H.mockPoint = function (chart, mockPointOptions) {
  231. return new MockPoint(chart, mockPointOptions);
  232. };
  233. /**
  234. * @private
  235. * @class
  236. * @name Highcharts.MockPoint
  237. */
  238. MockPoint.prototype = {
  239. /**
  240. * Initialisation of the mock point
  241. *
  242. * @function Highcharts.MockPoint#init
  243. *
  244. * @param {Highcharts.Chart} chart
  245. * A chart object to which the mock point is attached
  246. *
  247. * @param {Highcharts.MockPointOptionsObject} options
  248. * A config for the mock point
  249. */
  250. init: function (chart, options) {
  251. var xAxisId = options.xAxis,
  252. xAxis = defined(xAxisId) ?
  253. chart.xAxis[xAxisId] || chart.get(xAxisId) :
  254. null,
  255. yAxisId = options.yAxis,
  256. yAxis = defined(yAxisId) ?
  257. chart.yAxis[yAxisId] || chart.get(yAxisId) :
  258. null;
  259. if (xAxis) {
  260. this.x = options.x;
  261. this.series.xAxis = xAxis;
  262. } else {
  263. this.plotX = options.x;
  264. }
  265. if (yAxis) {
  266. this.y = options.y;
  267. this.series.yAxis = yAxis;
  268. } else {
  269. this.plotY = options.y;
  270. }
  271. },
  272. /**
  273. * Update of the point's coordinates (plotX/plotY)
  274. *
  275. * @function Highcharts.MockPoint#translate
  276. */
  277. translate: function () {
  278. var series = this.series,
  279. xAxis = series.xAxis,
  280. yAxis = series.yAxis;
  281. if (xAxis) {
  282. this.plotX = xAxis.toPixels(this.x, true);
  283. }
  284. if (yAxis) {
  285. this.plotY = yAxis.toPixels(this.y, true);
  286. }
  287. this.isInside = this.isInsidePane();
  288. },
  289. /**
  290. * Returns a box to which an item can be aligned to
  291. *
  292. * @function Highcharts.MockPoint#alignToBox
  293. *
  294. * @param {boolean} [forceTranslate=false]
  295. * Whether to update the point's coordinates
  296. *
  297. * @return {Array<number,number,number,number>}
  298. * A quadruple of numbers which denotes x, y, width and height of
  299. * the box
  300. **/
  301. alignToBox: function (forceTranslate) {
  302. if (forceTranslate) {
  303. this.translate();
  304. }
  305. var x = this.plotX,
  306. y = this.plotY,
  307. temp;
  308. if (this.series.chart.inverted) {
  309. temp = x;
  310. x = y;
  311. y = temp;
  312. }
  313. return [x, y, 0, 0];
  314. },
  315. /**
  316. * Returns a label config object -
  317. * the same as Highcharts.Point.prototype.getLabelConfig
  318. *
  319. * @function Highcharts.MockPoint#getLabelConfig
  320. *
  321. * @return {Highcharts.MockLabelOptionsObject}
  322. * Label config object
  323. */
  324. getLabelConfig: function () {
  325. return {
  326. x: this.x,
  327. y: this.y,
  328. point: this
  329. };
  330. },
  331. isInsidePane: function () {
  332. var plotX = this.plotX,
  333. plotY = this.plotY,
  334. xAxis = this.series.xAxis,
  335. yAxis = this.series.yAxis,
  336. isInside = true;
  337. if (xAxis) {
  338. isInside = defined(plotX) && plotX >= 0 && plotX <= xAxis.len;
  339. }
  340. if (yAxis) {
  341. isInside =
  342. isInside &&
  343. defined(plotY) &&
  344. plotY >= 0 && plotY <= yAxis.len;
  345. }
  346. return isInside;
  347. }
  348. };
  349. /* ************************************************************************** *
  350. *
  351. * ANNOTATION
  352. *
  353. * ************************************************************************** */
  354. H.defaultOptions.annotations = [];
  355. /**
  356. * An annotation class which serves as a container for items like labels or
  357. * shapes. Created items are positioned on the chart either by linking them to
  358. * existing points or created mock points
  359. *
  360. * @class
  361. * @name Highcharts.Annotation
  362. *
  363. * @param {Highcharts.Chart} chart
  364. * The chart object
  365. *
  366. * @param {Highcharts.AnnotationsOptions} userOptions
  367. * The options object
  368. */
  369. var Annotation = H.Annotation = function (chart, userOptions) {
  370. /**
  371. * The chart that the annotation belongs to.
  372. *
  373. * @name Highcharts.Annotation#chart
  374. * @type {Highcharts.Chart}
  375. */
  376. this.chart = chart;
  377. /**
  378. * The array of labels which belong to the annotation.
  379. *
  380. * @name Highcharts.Annotation#labels
  381. * @type {Array<Highcharts.SVGElement>}
  382. */
  383. this.labels = [];
  384. /**
  385. * The array of shapes which belong to the annotation.
  386. *
  387. * @name Highcharts.Annotation#shapes
  388. * @type {Array<Highcharts.SVGElement>}
  389. */
  390. this.shapes = [];
  391. /**
  392. * The user options for the annotations.
  393. *
  394. * @name Highcharts.Annotation#userOptions
  395. * @type {Highcharts.AnnotationsOptions}
  396. */
  397. this.userOptions = userOptions;
  398. /**
  399. * The options for the annotations. It contains user defined options
  400. * merged with the default options.
  401. *
  402. * @name Highcharts.Annotation#options
  403. * @type {Highcharts.AnnotationsOptions}
  404. */
  405. this.options = merge(this.defaultOptions, userOptions);
  406. /**
  407. * The callback that reports to the overlapping-labels module which
  408. * labels it should account for.
  409. *
  410. * @todo Needs more specific function interface
  411. *
  412. * @private
  413. * @name Highcharts.Annotation#labelCollector
  414. * @type {Function}
  415. */
  416. /**
  417. * The group element of the annotation.
  418. *
  419. * @private
  420. * @name Highcharts.Annotation#group
  421. * @type {Highcharts.SVGElement}
  422. */
  423. /**
  424. * The group element of the annotation's shapes.
  425. *
  426. * @private
  427. * @name Highcharts.Annotation#shapesGroup
  428. * @type {Highcharts.SVGElement}
  429. */
  430. /**
  431. * The group element of the annotation's labels.
  432. *
  433. * @private
  434. * @name Highcharts.Annotation#labelsGroup
  435. * @type {Highcharts.SVGElement}
  436. */
  437. this.init(chart, userOptions);
  438. };
  439. Annotation.prototype = {
  440. /**
  441. * Shapes which do not have background - the object is used for proper
  442. * setting of the contrast color
  443. *
  444. * @private
  445. * @name Highcharts.Annotations#shapesWithoutBackground
  446. * @type {Array<string>}
  447. */
  448. shapesWithoutBackground: ['connector'],
  449. /**
  450. * Returns a map object which allows to map options attributes to element
  451. * attributes.
  452. *
  453. * @private
  454. * @function Highcharts.Annotations#getAttrsMap
  455. *
  456. * @return {object}
  457. */
  458. getAttrsMap: function () {
  459. var attrsMap = {
  460. zIndex: 'zIndex',
  461. width: 'width',
  462. height: 'height',
  463. borderRadius: 'r',
  464. r: 'r',
  465. padding: 'padding'
  466. };
  467. if (!this.chart.styledMode) {
  468. extend(attrsMap, {
  469. backgroundColor: 'fill',
  470. borderColor: 'stroke',
  471. borderWidth: 'stroke-width',
  472. dashStyle: 'dashstyle',
  473. strokeWidth: 'stroke-width',
  474. stroke: 'stroke',
  475. fill: 'fill'
  476. });
  477. }
  478. return attrsMap;
  479. },
  480. /**
  481. * Options for configuring annotations, for example labels, arrows or
  482. * shapes. Annotations can be tied to points, axis coordinates or chart
  483. * pixel coordinates.
  484. *
  485. * @sample highcharts/annotations/basic/
  486. * Basic annotations
  487. * @sample highcharts/demo/annotations/
  488. * Advanced annotations
  489. * @sample highcharts/css/annotations
  490. * Styled mode
  491. * @sample {highstock} stock/annotations/fibonacci-retracements
  492. * Custom annotation, Fibonacci retracement
  493. *
  494. * @type {Array<*>}
  495. * @since 6.0.0
  496. * @optionparent annotations
  497. */
  498. defaultOptions: {
  499. /**
  500. * Sets an ID for an annotation. Can be user later when removing an
  501. * annotation in [Chart#removeAnnotation(id)](
  502. * /class-reference/Highcharts.Chart#removeAnnotation) method.
  503. *
  504. * @type {string}
  505. * @apioption annotations.id
  506. */
  507. /**
  508. * Whether the annotation is visible.
  509. *
  510. * @sample highcharts/annotations/visible/
  511. * Set annotation visibility
  512. */
  513. visible: true,
  514. /**
  515. * Options for annotation's labels. Each label inherits options
  516. * from the labelOptions object. An option from the labelOptions can be
  517. * overwritten by config for a specific label.
  518. */
  519. labelOptions: {
  520. /**
  521. * The alignment of the annotation's label. If right,
  522. * the right side of the label should be touching the point.
  523. *
  524. * @sample highcharts/annotations/label-position/
  525. * Set labels position
  526. *
  527. * @type {Highcharts.AlignType}
  528. */
  529. align: 'center',
  530. /**
  531. * Whether to allow the annotation's labels to overlap.
  532. * To make the labels less sensitive for overlapping,
  533. * the can be set to 0.
  534. *
  535. * @sample highcharts/annotations/tooltip-like/
  536. * Hide overlapping labels
  537. */
  538. allowOverlap: false,
  539. /**
  540. * The background color or gradient for the annotation's label.
  541. *
  542. * @sample highcharts/annotations/label-presentation/
  543. * Set labels graphic options
  544. *
  545. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  546. */
  547. backgroundColor: 'rgba(0, 0, 0, 0.75)',
  548. /**
  549. * The border color for the annotation's label.
  550. *
  551. * @sample highcharts/annotations/label-presentation/
  552. * Set labels graphic options
  553. *
  554. * @type {Highcharts.ColorString}
  555. */
  556. borderColor: 'black',
  557. /**
  558. * The border radius in pixels for the annotaiton's label.
  559. *
  560. * @sample highcharts/annotations/label-presentation/
  561. * Set labels graphic options
  562. */
  563. borderRadius: 3,
  564. /**
  565. * The border width in pixels for the annotation's label
  566. *
  567. * @sample highcharts/annotations/label-presentation/
  568. * Set labels graphic options
  569. */
  570. borderWidth: 1,
  571. /**
  572. * A class name for styling by CSS.
  573. *
  574. * @sample highcharts/css/annotations
  575. * Styled mode annotations
  576. *
  577. * @since 6.0.5
  578. */
  579. className: '',
  580. /**
  581. * Whether to hide the annotation's label that is outside the plot
  582. * area.
  583. *
  584. * @sample highcharts/annotations/label-crop-overflow/
  585. * Crop or justify labels
  586. */
  587. crop: false,
  588. /**
  589. * The label's pixel distance from the point.
  590. *
  591. * @sample highcharts/annotations/label-position/
  592. * Set labels position
  593. *
  594. * @type {number}
  595. * @apioption annotations.labelOptions.distance
  596. */
  597. /**
  598. * A
  599. * [format](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting)
  600. * string for the data label.
  601. *
  602. * @see [plotOptions.series.dataLabels.format](plotOptions.series.dataLabels.format.html)
  603. *
  604. * @sample highcharts/annotations/label-text/
  605. * Set labels text
  606. *
  607. * @type {string}
  608. * @apioption annotations.labelOptions.format
  609. */
  610. /**
  611. * Alias for the format option.
  612. *
  613. * @see [format](annotations.labelOptions.format.html)
  614. *
  615. * @sample highcharts/annotations/label-text/
  616. * Set labels text
  617. *
  618. * @type {string}
  619. * @apioption annotations.labelOptions.text
  620. */
  621. /**
  622. * Callback JavaScript function to format the annotation's label.
  623. * Note that if a `format` or `text` are defined, the format or text
  624. * take precedence and the formatter is ignored. `This` refers to a
  625. * point object.
  626. *
  627. * @sample highcharts/annotations/label-text/
  628. * Set labels text
  629. *
  630. * @type {Highcharts.FormatterCallbackFunction<Highcharts.Point>}
  631. * @default function () { return defined(this.y) ? this.y : 'Annotation label'; }
  632. */
  633. formatter: function () {
  634. return defined(this.y) ? this.y : 'Annotation label';
  635. },
  636. /**
  637. * How to handle the annotation's label that flow outside the plot
  638. * area. The justify option aligns the label inside the plot area.
  639. *
  640. * @sample highcharts/annotations/label-crop-overflow/
  641. * Crop or justify labels
  642. *
  643. * @validvalue ["allow", "justify"]
  644. */
  645. overflow: 'justify',
  646. /**
  647. * When either the borderWidth or the backgroundColor is set,
  648. * this is the padding within the box.
  649. *
  650. * @sample highcharts/annotations/label-presentation/
  651. * Set labels graphic options
  652. */
  653. padding: 5,
  654. /**
  655. * The shadow of the box. The shadow can be an object configuration
  656. * containing `color`, `offsetX`, `offsetY`, `opacity` and `width`.
  657. *
  658. * @sample highcharts/annotations/label-presentation/
  659. * Set labels graphic options
  660. *
  661. * @type {boolean|Highcharts.ShadowOptionsObject}
  662. */
  663. shadow: false,
  664. /**
  665. * The name of a symbol to use for the border around the label.
  666. * Symbols are predefined functions on the Renderer object.
  667. *
  668. * @sample highcharts/annotations/shapes/
  669. * Available shapes for labels
  670. *
  671. * @type {string}
  672. */
  673. shape: 'callout',
  674. /**
  675. * Styles for the annotation's label.
  676. *
  677. * @see [plotOptions.series.dataLabels.style](plotOptions.series.dataLabels.style.html)
  678. *
  679. * @sample highcharts/annotations/label-presentation/
  680. * Set labels graphic options
  681. *
  682. * @type {Highcharts.CSSObject}
  683. */
  684. style: {
  685. /** @ignore */
  686. fontSize: '11px',
  687. /** @ignore */
  688. fontWeight: 'normal',
  689. /** @ignore */
  690. color: 'contrast'
  691. },
  692. /**
  693. * Whether to [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html)
  694. * to render the annotation's label.
  695. */
  696. useHTML: false,
  697. /**
  698. * The vertical alignment of the annotation's label.
  699. *
  700. * @sample highcharts/annotations/label-position/
  701. * Set labels position
  702. *
  703. * @type {Highcharts.VerticalAlignType}
  704. */
  705. verticalAlign: 'bottom',
  706. /**
  707. * The x position offset of the label relative to the point.
  708. * Note that if a `distance` is defined, the distance takes
  709. * precedence over `x` and `y` options.
  710. *
  711. * @sample highcharts/annotations/label-position/
  712. * Set labels position
  713. */
  714. x: 0,
  715. /**
  716. * The y position offset of the label relative to the point.
  717. * Note that if a `distance` is defined, the distance takes
  718. * precedence over `x` and `y` options.
  719. *
  720. * @sample highcharts/annotations/label-position/
  721. * Set labels position
  722. */
  723. y: -16
  724. },
  725. /**
  726. * An array of labels for the annotation. For options that apply to
  727. * multiple labels, they can be added to the
  728. * [labelOptions](annotations.labelOptions.html).
  729. *
  730. * @type {Array<*>}
  731. * @extends annotations.labelOptions
  732. * @apioption annotations.labels
  733. */
  734. /**
  735. * This option defines the point to which the label will be connected.
  736. * It can be either the point which exists in the series - it is
  737. * referenced by the point's id - or a new point with defined x, y
  738. * properies and optionally axes.
  739. *
  740. * @sample highcharts/annotations/mock-point/
  741. * Attach annotation to a mock point
  742. *
  743. * @type {string|*}
  744. * @apioption annotations.labels.point
  745. */
  746. /**
  747. * The x position of the point. Units can be either in axis
  748. * or chart pixel coordinates.
  749. *
  750. * @type {number}
  751. * @apioption annotations.labels.point.x
  752. */
  753. /**
  754. * The y position of the point. Units can be either in axis
  755. * or chart pixel coordinates.
  756. *
  757. * @type {number}
  758. * @apioption annotations.labels.point.y
  759. */
  760. /**
  761. * This number defines which xAxis the point is connected to. It refers
  762. * to either the axis id or the index of the axis in the xAxis array.
  763. * If the option is not configured or the axis is not found the point's
  764. * x coordinate refers to the chart pixels.
  765. *
  766. * @type {number|string}
  767. * @apioption annotations.labels.point.xAxis
  768. */
  769. /**
  770. * This number defines which yAxis the point is connected to. It refers
  771. * to either the axis id or the index of the axis in the yAxis array.
  772. * If the option is not configured or the axis is not found the point's
  773. * y coordinate refers to the chart pixels.
  774. *
  775. * @type {number|string}
  776. * @apioption annotations.labels.point.yAxis
  777. */
  778. /**
  779. * An array of shapes for the annotation. For options that apply to
  780. * multiple shapes, then can be added to the
  781. * [shapeOptions](annotations.shapeOptions.html).
  782. *
  783. * @type {Array<*>}
  784. * @extends annotations.shapeOptions
  785. * @apioption annotations.shapes
  786. */
  787. /**
  788. * This option defines the point to which the shape will be connected.
  789. * It can be either the point which exists in the series - it is
  790. * referenced by the point's id - or a new point with defined x, y
  791. * properties and optionally axes.
  792. *
  793. * @extends annotations.labels.point
  794. * @apioption annotations.shapes.point
  795. */
  796. /**
  797. * An array of points for the shape. This option is available for shapes
  798. * which can use multiple points such as path. A point can be either
  799. * a point object or a point's id.
  800. *
  801. * @see [annotations.shapes.point](annotations.shapes.point.html)
  802. *
  803. * @type {Array<*>}
  804. * @extends annotations.labels.point
  805. * @apioption annotations.shapes.points
  806. */
  807. /**
  808. * Id of the marker which will be drawn at the final vertex of the path.
  809. * Custom markers can be defined in defs property.
  810. *
  811. * @see [defs.markers](defs.markers.html)
  812. *
  813. * @sample highcharts/annotations/custom-markers/
  814. * Define a custom marker for annotations
  815. *
  816. * @type {string}
  817. * @apioption annotations.shapes.markerEnd
  818. */
  819. /**
  820. * Id of the marker which will be drawn at the first vertex of the path.
  821. * Custom markers can be defined in defs property.
  822. *
  823. * @see [defs.markers](defs.markers.html)
  824. *
  825. * @sample {highcharts} highcharts/annotations/custom-markers/
  826. * Define a custom marker for annotations
  827. *
  828. * @type {string}
  829. * @apioption annotations.shapes.markerStart
  830. */
  831. /**
  832. * Options for annotation's shapes. Each shape inherits options
  833. * from the shapeOptions object. An option from the shapeOptions can be
  834. * overwritten by config for a specific shape.
  835. */
  836. shapeOptions: {
  837. /**
  838. * The width of the shape.
  839. *
  840. * @sample highcharts/annotations/shape/
  841. * Basic shape annotation
  842. *
  843. * @type {number}
  844. * @apioption annotations.shapeOptions.width
  845. **/
  846. /**
  847. * The height of the shape.
  848. *
  849. * @sample highcharts/annotations/shape/
  850. * Basic shape annotation
  851. *
  852. * @type {number}
  853. * @apioption annotations.shapeOptions.height
  854. */
  855. /**
  856. * The color of the shape's stroke.
  857. *
  858. * @sample highcharts/annotations/shape/
  859. * Basic shape annotation
  860. *
  861. * @type {Highcharts.ColorString}
  862. */
  863. stroke: 'rgba(0, 0, 0, 0.75)',
  864. /**
  865. * The pixel stroke width of the shape.
  866. *
  867. * @sample highcharts/annotations/shape/
  868. * Basic shape annotation
  869. */
  870. strokeWidth: 1,
  871. /**
  872. * The color of the shape's fill.
  873. *
  874. * @sample highcharts/annotations/shape/
  875. * Basic shape annotation
  876. *
  877. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  878. */
  879. fill: 'rgba(0, 0, 0, 0.75)',
  880. /**
  881. * The type of the shape, e.g. circle or rectangle.
  882. *
  883. * @sample highcharts/annotations/shape/
  884. * Basic shape annotation
  885. *
  886. * @type {string}
  887. * @default 'rect'
  888. * @apioption annotations.shapeOptions.type
  889. */
  890. /**
  891. * The radius of the shape.
  892. *
  893. * @sample highcharts/annotations/shape/
  894. * Basic shape annotation
  895. */
  896. r: 0
  897. },
  898. /**
  899. * The Z index of the annotation.
  900. */
  901. zIndex: 6
  902. },
  903. /**
  904. * Initialize the annotation.
  905. *
  906. * @function Highcharts.Annotation#init
  907. */
  908. init: function () {
  909. var anno = this;
  910. (this.options.labels || []).forEach(this.initLabel, this);
  911. (this.options.shapes || []).forEach(this.initShape, this);
  912. this.labelCollector = function () {
  913. return anno.labels.filter(function (label) {
  914. return !label.options.allowOverlap;
  915. });
  916. };
  917. this.chart.labelCollectors.push(this.labelCollector);
  918. },
  919. /**
  920. * Main method for drawing an annotation.
  921. *
  922. * @function Highcharts.Annotation#redraw
  923. */
  924. redraw: function () {
  925. if (!this.group) {
  926. this.render();
  927. }
  928. this.redrawItems(this.shapes);
  929. this.redrawItems(this.labels);
  930. },
  931. /**
  932. * @private
  933. * @function Highcharts.Annotation#redrawItems
  934. *
  935. * @param {Array<Highcharts.SVGElement>} items
  936. */
  937. redrawItems: function (items) {
  938. var i = items.length;
  939. // needs a backward loop
  940. // labels/shapes array might be modified due to destruction of the item
  941. while (i--) {
  942. this.redrawItem(items[i]);
  943. }
  944. },
  945. /**
  946. * Render the annotation.
  947. *
  948. * @function Highcharts.Annotation#render
  949. */
  950. render: function () {
  951. var renderer = this.chart.renderer;
  952. var group = this.group = renderer.g('annotation')
  953. .attr({
  954. zIndex: this.options.zIndex,
  955. visibility: this.options.visible ? 'visible' : 'hidden'
  956. })
  957. .add();
  958. this.shapesGroup = renderer.g('annotation-shapes').add(group);
  959. this.labelsGroup = renderer.g('annotation-labels').attr({
  960. // hideOverlappingLabels requires translation
  961. translateX: 0,
  962. translateY: 0
  963. }).add(group);
  964. this.shapesGroup.clip(this.chart.plotBoxClip);
  965. },
  966. /**
  967. * Set the annotation's visibility.
  968. *
  969. * @function Highcharts.Annotation#setVisible
  970. *
  971. * @param {boolean} [visibility]
  972. * Whether to show or hide an annotation. If the param is omitted,
  973. * the annotation's visibility is toggled.
  974. */
  975. setVisible: function (visibility) {
  976. var options = this.options,
  977. visible = pick(visibility, !options.visible);
  978. this.group.attr({
  979. visibility: visible ? 'visible' : 'hidden'
  980. });
  981. options.visible = visible;
  982. },
  983. /**
  984. * Destroy the annotation. This function does not touch the chart
  985. * that the annotation belongs to (all annotations are kept in
  986. * the chart.annotations array) - it is recommended to use
  987. * {@link Highcharts.Chart#removeAnnotation} instead.
  988. *
  989. * @function Highcharts.Annotation#destroy
  990. */
  991. destroy: function () {
  992. var chart = this.chart,
  993. destroyItem = function (item) {
  994. item.destroy();
  995. };
  996. erase(this.chart.labelCollectors, this.labelCollector);
  997. this.labels.forEach(destroyItem);
  998. this.shapes.forEach(destroyItem);
  999. destroyObjectProperties(this, chart);
  1000. },
  1001. /* ********************************************************************** *
  1002. *
  1003. * ITEM SECTION
  1004. * Contains methods for handling a single item in an annotation
  1005. *
  1006. * ********************************************************************** */
  1007. /**
  1008. * Initialisation of a single shape
  1009. *
  1010. * @private
  1011. * @function Highcharts.Annotation#initShape
  1012. *
  1013. * @param {Highcharts.AnnotationsShapesOptions} shapeOptions
  1014. * A confg object for a single shape
  1015. */
  1016. initShape: function (shapeOptions) {
  1017. var renderer = this.chart.renderer,
  1018. options = merge(this.options.shapeOptions, shapeOptions),
  1019. attr = this.attrsFromOptions(options),
  1020. type = renderer[options.type] ? options.type : 'rect',
  1021. shape = renderer[type](0, -9e9, 0, 0);
  1022. shape.points = [];
  1023. shape.type = type;
  1024. shape.options = options;
  1025. shape.itemType = 'shape';
  1026. if (type === 'path') {
  1027. extend(shape, {
  1028. markerStartSetter: MarkerMixin.markerStartSetter,
  1029. markerEndSetter: MarkerMixin.markerEndSetter,
  1030. markerStart: MarkerMixin.markerStart,
  1031. markerEnd: MarkerMixin.markerEnd
  1032. });
  1033. }
  1034. shape.attr(attr);
  1035. if (options.className) {
  1036. shape.addClass(options.className);
  1037. }
  1038. this.shapes.push(shape);
  1039. },
  1040. /**
  1041. * Initialisation of a single label
  1042. *
  1043. * @private
  1044. * @function Highcharts.Annotation#initLabel
  1045. *
  1046. * @param {Highcharts.AnnotationsLabelsOptions} labelOptions
  1047. */
  1048. initLabel: function (labelOptions) {
  1049. var options = merge(this.options.labelOptions, labelOptions),
  1050. attr = this.attrsFromOptions(options),
  1051. label = this.chart.renderer.label(
  1052. '',
  1053. 0, -9e9,
  1054. options.shape,
  1055. null,
  1056. null,
  1057. options.useHTML,
  1058. null,
  1059. 'annotation-label'
  1060. );
  1061. label.points = [];
  1062. label.options = options;
  1063. label.itemType = 'label';
  1064. // Labelrank required for hideOverlappingLabels()
  1065. label.labelrank = options.labelrank;
  1066. label.annotation = this;
  1067. label.attr(attr);
  1068. if (!this.chart.styledMode) {
  1069. var style = options.style;
  1070. if (style.color === 'contrast') {
  1071. style.color = this.chart.renderer.getContrast(
  1072. this.shapesWithoutBackground.indexOf(options.shape) > -1 ?
  1073. '#FFFFFF' :
  1074. options.backgroundColor
  1075. );
  1076. }
  1077. label.css(style).shadow(options.shadow);
  1078. }
  1079. if (options.className) {
  1080. label.addClass(options.className);
  1081. }
  1082. this.labels.push(label);
  1083. },
  1084. /**
  1085. * Redrawing a single item
  1086. *
  1087. * @private
  1088. * @function Highcharts.Annotation#redrawItem
  1089. *
  1090. * @param {Highcharts.SVGElement} item
  1091. */
  1092. redrawItem: function (item) {
  1093. var points = this.linkPoints(item),
  1094. itemOptions = item.options,
  1095. text,
  1096. time = this.chart.time,
  1097. hasVisiblePoints = false;
  1098. if (!points.length) {
  1099. this.destroyItem(item);
  1100. } else {
  1101. if (!item.parentGroup) {
  1102. this.renderItem(item);
  1103. }
  1104. if (item.itemType === 'label') {
  1105. text = itemOptions.format || itemOptions.text;
  1106. item.attr({
  1107. text: text ?
  1108. format(text, points[0].getLabelConfig(), time) :
  1109. itemOptions.formatter.call(points[0])
  1110. });
  1111. }
  1112. // Hide or show annotaiton attached to points (#9481)
  1113. points.forEach(function (point) {
  1114. if (
  1115. point.series.visible !== false &&
  1116. point.visible !== false
  1117. ) {
  1118. hasVisiblePoints = true;
  1119. }
  1120. });
  1121. if (!hasVisiblePoints) {
  1122. item.hide();
  1123. } else if (item.visibility === 'hidden') {
  1124. item.show();
  1125. }
  1126. if (item.type === 'path') {
  1127. this.redrawPath(item);
  1128. } else {
  1129. this.alignItem(item, !item.placed);
  1130. }
  1131. }
  1132. },
  1133. /**
  1134. * Destroing a single item
  1135. *
  1136. * @private
  1137. * @function Highcharts.Annotation#destroyItem
  1138. *
  1139. * @param {Highcharts.SVGElement} item
  1140. */
  1141. destroyItem: function (item) {
  1142. // erase from shapes or labels array
  1143. erase(this[item.itemType + 's'], item);
  1144. item.destroy();
  1145. },
  1146. /**
  1147. * Returns a point object
  1148. *
  1149. * @private
  1150. * @function Highcharts.Annotation#pointItem
  1151. *
  1152. * @param {object} pointOptions
  1153. *
  1154. * @param {Highcharts.MockPoint|Highcharts.Point} point
  1155. *
  1156. * @return {Highcharts.MockPoint|Highcharts.Point|null}
  1157. * If the point is found/exists returns this point, otherwise null
  1158. */
  1159. pointItem: function (pointOptions, point) {
  1160. if (!point || point.series === null) {
  1161. if (isObject(pointOptions)) {
  1162. point = mockPoint(this.chart, pointOptions);
  1163. } else if (isString(pointOptions)) {
  1164. point = this.chart.get(pointOptions) || null;
  1165. }
  1166. }
  1167. return point;
  1168. },
  1169. /**
  1170. * Linking item with the point or points and returning an array of linked
  1171. * points.
  1172. *
  1173. * @private
  1174. * @function Highcharts.Annotation#linkPoints
  1175. *
  1176. * @param {Highcharts.SVGElement} item
  1177. *
  1178. * @return {Highcharts.MockPoint|Highcharts.Point|Array<Highcharts.MockPoint|Highcharts.Point>}
  1179. */
  1180. linkPoints: function (item) {
  1181. var pointsOptions = (
  1182. item.options.points ||
  1183. (item.options.point && H.splat(item.options.point))
  1184. ),
  1185. points = item.points,
  1186. len = pointsOptions && pointsOptions.length,
  1187. i,
  1188. point;
  1189. for (i = 0; i < len; i++) {
  1190. point = this.pointItem(pointsOptions[i], points[i]);
  1191. if (!point) {
  1192. return (item.points = []);
  1193. }
  1194. points[i] = point;
  1195. }
  1196. return points;
  1197. },
  1198. /**
  1199. * Aligning the item and setting its anchor
  1200. *
  1201. * @private
  1202. * @function Highcharts.Annotation#alignItem
  1203. *
  1204. * @param {Highcharts.SVGElement} item
  1205. *
  1206. * @param {boolean} [isNew=false]
  1207. * If the label is re-positioned (is not new) it is animated
  1208. */
  1209. alignItem: function (item, isNew) {
  1210. var anchor = this.itemAnchor(item, item.points[0]),
  1211. attrs = this.itemPosition(item, anchor);
  1212. if (attrs) {
  1213. item.alignAttr = attrs;
  1214. item.placed = true;
  1215. attrs.anchorX = anchor.absolutePosition.x;
  1216. attrs.anchorY = anchor.absolutePosition.y;
  1217. item[isNew ? 'attr' : 'animate'](attrs);
  1218. } else {
  1219. item.placed = false;
  1220. item.attr({
  1221. x: 0,
  1222. y: -9e9
  1223. });
  1224. }
  1225. },
  1226. redrawPath: function (pathItem, isNew) {
  1227. var points = pathItem.points,
  1228. strokeWidth = pathItem['stroke-width'] || 1,
  1229. d = ['M'],
  1230. pointIndex = 0,
  1231. dIndex = 0,
  1232. len = points && points.length,
  1233. crispSegmentIndex,
  1234. anchor,
  1235. point,
  1236. showPath;
  1237. if (len) {
  1238. do {
  1239. point = points[pointIndex];
  1240. anchor = this.itemAnchor(pathItem, point).absolutePosition;
  1241. d[++dIndex] = anchor.x;
  1242. d[++dIndex] = anchor.y;
  1243. // Crisping line, it might be replaced with
  1244. // Renderer.prototype.crispLine but it requires creating many
  1245. // temporary arrays
  1246. crispSegmentIndex = dIndex % 5;
  1247. if (crispSegmentIndex === 0) {
  1248. if (d[crispSegmentIndex + 1] === d[crispSegmentIndex + 4]) {
  1249. d[crispSegmentIndex + 1] = d[crispSegmentIndex + 4] =
  1250. Math.round(d[crispSegmentIndex + 1]) -
  1251. (strokeWidth % 2 / 2);
  1252. }
  1253. if (d[crispSegmentIndex + 2] === d[crispSegmentIndex + 5]) {
  1254. d[crispSegmentIndex + 2] = d[crispSegmentIndex + 5] =
  1255. Math.round(d[crispSegmentIndex + 2]) +
  1256. (strokeWidth % 2 / 2);
  1257. }
  1258. }
  1259. if (pointIndex < len - 1) {
  1260. d[++dIndex] = 'L';
  1261. }
  1262. showPath = point.series.visible;
  1263. } while (++pointIndex < len && showPath);
  1264. }
  1265. if (showPath) {
  1266. pathItem[isNew ? 'attr' : 'animate']({
  1267. d: d
  1268. });
  1269. } else {
  1270. pathItem.attr({
  1271. d: 'M 0 ' + -9e9
  1272. });
  1273. }
  1274. pathItem.placed = showPath;
  1275. },
  1276. renderItem: function (item) {
  1277. item.add(
  1278. item.itemType === 'label' ?
  1279. this.labelsGroup :
  1280. this.shapesGroup
  1281. );
  1282. this.setItemMarkers(item);
  1283. },
  1284. setItemMarkers: function (item) {
  1285. var itemOptions = item.options,
  1286. chart = this.chart,
  1287. defs = chart.options.defs,
  1288. fill = itemOptions.fill,
  1289. color = defined(fill) && fill !== 'none' ?
  1290. fill :
  1291. itemOptions.stroke,
  1292. setMarker = function (markerType) {
  1293. var markerId = itemOptions[markerType],
  1294. def,
  1295. predefinedMarker,
  1296. key,
  1297. marker;
  1298. if (markerId) {
  1299. for (key in defs) {
  1300. def = defs[key];
  1301. if (markerId === def.id && def.tagName === 'marker') {
  1302. predefinedMarker = def;
  1303. break;
  1304. }
  1305. }
  1306. if (predefinedMarker) {
  1307. marker = item[markerType] = chart.renderer.addMarker(
  1308. (itemOptions.id || uniqueKey()) + '-' +
  1309. predefinedMarker.id,
  1310. merge(predefinedMarker, { color: color })
  1311. );
  1312. item.attr(markerType, marker.attr('id'));
  1313. }
  1314. }
  1315. };
  1316. ['markerStart', 'markerEnd'].forEach(setMarker);
  1317. },
  1318. /**
  1319. * Returns object which denotes anchor position - relative and absolute
  1320. *
  1321. * @private
  1322. * @function Highcharts.Annotation#itemAnchor
  1323. *
  1324. * @param {Highcharts.SVGElement} item
  1325. *
  1326. * @param {Highcharts.MockPoint|Highcharts.Point} point
  1327. *
  1328. * @return {Highcharts.AnnotationAnchorObject}
  1329. */
  1330. itemAnchor: function (item, point) {
  1331. var plotBox = point.series.getPlotBox(),
  1332. box = point.mock ?
  1333. point.alignToBox(true) :
  1334. tooltipPrototype.getAnchor.call({
  1335. chart: this.chart
  1336. }, point),
  1337. anchor = {
  1338. x: box[0],
  1339. y: box[1],
  1340. height: box[2] || 0,
  1341. width: box[3] || 0
  1342. };
  1343. return {
  1344. relativePosition: anchor,
  1345. absolutePosition: merge(anchor, {
  1346. x: anchor.x + plotBox.translateX,
  1347. y: anchor.y + plotBox.translateY
  1348. })
  1349. };
  1350. },
  1351. /**
  1352. * Returns the item position
  1353. *
  1354. * @private
  1355. * @function Highcharts.Annotation#itemPosition
  1356. *
  1357. * @param {Highcharts.SVGElement} item
  1358. *
  1359. * @param {Highcharts.AnnotationAnchorObject} anchor
  1360. *
  1361. * @return {Highcharts.AnnotationAnchorPositionObject}
  1362. */
  1363. itemPosition: function (item, anchor) {
  1364. var chart = this.chart,
  1365. point = item.points[0],
  1366. itemOptions = item.options,
  1367. anchorAbsolutePosition = anchor.absolutePosition,
  1368. anchorRelativePosition = anchor.relativePosition,
  1369. itemPosition,
  1370. alignTo,
  1371. itemPosRelativeX,
  1372. itemPosRelativeY,
  1373. showItem =
  1374. point.series.visible &&
  1375. MockPoint.prototype.isInsidePane.call(point);
  1376. if (showItem) {
  1377. if (defined(itemOptions.distance) || itemOptions.positioner) {
  1378. itemPosition = (
  1379. itemOptions.positioner ||
  1380. tooltipPrototype.getPosition
  1381. ).call(
  1382. {
  1383. chart: chart,
  1384. distance: pick(itemOptions.distance, 16)
  1385. },
  1386. item.width,
  1387. item.height,
  1388. {
  1389. plotX: anchorRelativePosition.x,
  1390. plotY: anchorRelativePosition.y,
  1391. negative: point.negative,
  1392. ttBelow: point.ttBelow,
  1393. h: anchorRelativePosition.height ||
  1394. anchorRelativePosition.width
  1395. }
  1396. );
  1397. } else {
  1398. alignTo = {
  1399. x: anchorAbsolutePosition.x,
  1400. y: anchorAbsolutePosition.y,
  1401. width: 0,
  1402. height: 0
  1403. };
  1404. itemPosition = this.alignedPosition(
  1405. extend(itemOptions, {
  1406. width: item.width,
  1407. height: item.height
  1408. }),
  1409. alignTo
  1410. );
  1411. if (item.options.overflow === 'justify') {
  1412. itemPosition = this.alignedPosition(
  1413. this.justifiedOptions(item, itemOptions, itemPosition),
  1414. alignTo
  1415. );
  1416. }
  1417. }
  1418. if (itemOptions.crop) {
  1419. itemPosRelativeX = itemPosition.x - chart.plotLeft;
  1420. itemPosRelativeY = itemPosition.y - chart.plotTop;
  1421. showItem =
  1422. chart.isInsidePlot(itemPosRelativeX, itemPosRelativeY) &&
  1423. chart.isInsidePlot(
  1424. itemPosRelativeX + item.width,
  1425. itemPosRelativeY + item.height
  1426. );
  1427. }
  1428. }
  1429. return showItem ? itemPosition : null;
  1430. },
  1431. /**
  1432. * Returns new aligned position based alignment options and box to align to.
  1433. * It is almost a one-to-one copy from SVGElement.prototype.align
  1434. * except it does not use and mutate an element
  1435. *
  1436. * @private
  1437. * @function Highcharts.Annotation#alignedPosition
  1438. *
  1439. * @param {Highcharts.AlignObject} alignOptions
  1440. *
  1441. * @param {Highcharts.BBoxObject} box
  1442. *
  1443. * @return {Highcharts.AlignObject}
  1444. */
  1445. alignedPosition: function (alignOptions, box) {
  1446. var align = alignOptions.align,
  1447. vAlign = alignOptions.verticalAlign,
  1448. x = (box.x || 0) + (alignOptions.x || 0),
  1449. y = (box.y || 0) + (alignOptions.y || 0),
  1450. alignFactor,
  1451. vAlignFactor;
  1452. if (align === 'right') {
  1453. alignFactor = 1;
  1454. } else if (align === 'center') {
  1455. alignFactor = 2;
  1456. }
  1457. if (alignFactor) {
  1458. x += (box.width - (alignOptions.width || 0)) / alignFactor;
  1459. }
  1460. if (vAlign === 'bottom') {
  1461. vAlignFactor = 1;
  1462. } else if (vAlign === 'middle') {
  1463. vAlignFactor = 2;
  1464. }
  1465. if (vAlignFactor) {
  1466. y += (box.height - (alignOptions.height || 0)) / vAlignFactor;
  1467. }
  1468. return {
  1469. x: Math.round(x),
  1470. y: Math.round(y)
  1471. };
  1472. },
  1473. /**
  1474. * Returns new alignment options for a label if the label is outside the
  1475. * plot area. It is almost a one-to-one copy from
  1476. * Series.prototype.justifyDataLabel except it does not mutate the label and
  1477. * it works with absolute instead of relative position.
  1478. *
  1479. * @private
  1480. * @function Highcharts.Annotation#justifiedOptions
  1481. *
  1482. * @param {Highcharts.SVGElement} label
  1483. *
  1484. * @param {Highcharts.AlignObject} alignOptions
  1485. *
  1486. * @param {Highcharts.SVGAttributes} alignAttr
  1487. *
  1488. * @return {Highcharts.AlignObject}
  1489. */
  1490. justifiedOptions: function (label, alignOptions, alignAttr) {
  1491. var chart = this.chart,
  1492. align = alignOptions.align,
  1493. verticalAlign = alignOptions.verticalAlign,
  1494. padding = label.box ? 0 : (label.padding || 0),
  1495. bBox = label.getBBox(),
  1496. off,
  1497. options = {
  1498. align: align,
  1499. verticalAlign: verticalAlign,
  1500. x: alignOptions.x,
  1501. y: alignOptions.y,
  1502. width: label.width,
  1503. height: label.height
  1504. },
  1505. x = alignAttr.x - chart.plotLeft,
  1506. y = alignAttr.y - chart.plotTop;
  1507. // Off left
  1508. off = x + padding;
  1509. if (off < 0) {
  1510. if (align === 'right') {
  1511. options.align = 'left';
  1512. } else {
  1513. options.x = -off;
  1514. }
  1515. }
  1516. // Off right
  1517. off = x + bBox.width - padding;
  1518. if (off > chart.plotWidth) {
  1519. if (align === 'left') {
  1520. options.align = 'right';
  1521. } else {
  1522. options.x = chart.plotWidth - off;
  1523. }
  1524. }
  1525. // Off top
  1526. off = y + padding;
  1527. if (off < 0) {
  1528. if (verticalAlign === 'bottom') {
  1529. options.verticalAlign = 'top';
  1530. } else {
  1531. options.y = -off;
  1532. }
  1533. }
  1534. // Off bottom
  1535. off = y + bBox.height - padding;
  1536. if (off > chart.plotHeight) {
  1537. if (verticalAlign === 'top') {
  1538. options.verticalAlign = 'bottom';
  1539. } else {
  1540. options.y = chart.plotHeight - off;
  1541. }
  1542. }
  1543. return options;
  1544. },
  1545. /**
  1546. * Utility function for mapping item's options to element's attribute
  1547. *
  1548. * @private
  1549. * @function Highcharts.Annotation#attrsFromOptions
  1550. *
  1551. * @param {object} options
  1552. *
  1553. * @return {object}
  1554. * Mapped options
  1555. */
  1556. attrsFromOptions: function (options) {
  1557. var map = this.getAttrsMap(),
  1558. attrs = {},
  1559. key,
  1560. mappedKey;
  1561. for (key in options) {
  1562. mappedKey = map[key];
  1563. if (mappedKey) {
  1564. attrs[mappedKey] = options[key];
  1565. }
  1566. }
  1567. return attrs;
  1568. }
  1569. };
  1570. /* ************************************************************************** *
  1571. *
  1572. * EXTENDING CHART PROTOTYPE
  1573. *
  1574. * ************************************************************************** */
  1575. H.extend(chartPrototype, {
  1576. /**
  1577. * Add an annotation to the chart after render time.
  1578. *
  1579. * @function Highcharts.Chart#addAnnotation
  1580. *
  1581. * @param {Highcharts.AnnotationsOptions} options
  1582. * The series options for the new, detailed series.
  1583. *
  1584. * @return {Highcharts.Annotation}
  1585. * The newly generated annotation.
  1586. */
  1587. addAnnotation: function (userOptions, redraw) {
  1588. var annotation = new Annotation(this, userOptions);
  1589. this.annotations.push(annotation);
  1590. this.options.annotations.push(userOptions);
  1591. if (pick(redraw, true)) {
  1592. annotation.redraw();
  1593. }
  1594. return annotation;
  1595. },
  1596. /**
  1597. * Remove an annotation from the chart.
  1598. *
  1599. * @function Highcharts.Chart#removeAnnotation
  1600. *
  1601. * @param {string} id
  1602. * The annotation's id.
  1603. */
  1604. removeAnnotation: function (id) {
  1605. var annotations = this.annotations,
  1606. annotation = find(annotations, function (annotation) {
  1607. return annotation.options.id === id;
  1608. });
  1609. if (annotation) {
  1610. erase(this.options.annotations, annotation.userOptions);
  1611. erase(annotations, annotation);
  1612. annotation.destroy();
  1613. }
  1614. },
  1615. drawAnnotations: function () {
  1616. var clip = this.plotBoxClip,
  1617. plotBox = this.plotBox;
  1618. if (clip) {
  1619. clip.attr(plotBox);
  1620. } else {
  1621. this.plotBoxClip = this.renderer.clipRect(plotBox);
  1622. }
  1623. this.annotations.forEach(function (annotation) {
  1624. annotation.redraw();
  1625. });
  1626. }
  1627. });
  1628. chartPrototype.callbacks.push(function (chart) {
  1629. chart.annotations = [];
  1630. chart.options.annotations.forEach(function (annotationOptions) {
  1631. chart.annotations.push(
  1632. new Annotation(chart, annotationOptions)
  1633. );
  1634. });
  1635. chart.drawAnnotations();
  1636. addEvent(chart, 'redraw', chart.drawAnnotations);
  1637. addEvent(chart, 'destroy', function () {
  1638. var plotBoxClip = chart.plotBoxClip;
  1639. if (plotBoxClip && plotBoxClip.destroy) {
  1640. plotBoxClip.destroy();
  1641. }
  1642. });
  1643. });
  1644. addEvent(H.Chart, 'afterGetContainer', function () {
  1645. this.options.defs = merge(defaultMarkers, this.options.defs || {});
  1646. if (!this.styledMode) {
  1647. objectEach(this.options.defs, function (def) {
  1648. if (def.tagName === 'marker' && def.render !== false) {
  1649. this.renderer.addMarker(def.id, def);
  1650. }
  1651. }, this);
  1652. }
  1653. });
  1654. /* ************************************************************************** */
  1655. // General symbol definition for labels with connector
  1656. H.SVGRenderer.prototype.symbols.connector = function (x, y, w, h, options) {
  1657. var anchorX = options && options.anchorX,
  1658. anchorY = options && options.anchorY,
  1659. path,
  1660. yOffset,
  1661. lateral = w / 2;
  1662. if (isNumber(anchorX) && isNumber(anchorY)) {
  1663. path = ['M', anchorX, anchorY];
  1664. // Prefer 45 deg connectors
  1665. yOffset = y - anchorY;
  1666. if (yOffset < 0) {
  1667. yOffset = -h - yOffset;
  1668. }
  1669. if (yOffset < w) {
  1670. lateral = anchorX < x + (w / 2) ? yOffset : w - yOffset;
  1671. }
  1672. // Anchor below label
  1673. if (anchorY > y + h) {
  1674. path.push('L', x + lateral, y + h);
  1675. // Anchor above label
  1676. } else if (anchorY < y) {
  1677. path.push('L', x + lateral, y);
  1678. // Anchor left of label
  1679. } else if (anchorX < x) {
  1680. path.push('L', x, y + h / 2);
  1681. // Anchor right of label
  1682. } else if (anchorX > x + w) {
  1683. path.push('L', x + w, y + h / 2);
  1684. }
  1685. }
  1686. return path || [];
  1687. };