annotations.src.js 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231
  1. /**
  2. * (c) 2009-2017 Highsoft, Black Label
  3. *
  4. * License: www.highcharts.com/license
  5. */
  6. 'use strict';
  7. import H from '../parts/Globals.js';
  8. import '../parts/Utilities.js';
  9. import '../parts/Chart.js';
  10. import controllableMixin from './controllable/controllableMixin.js';
  11. import ControllableRect from './controllable/ControllableRect.js';
  12. import ControllableCircle from './controllable/ControllableCircle.js';
  13. import ControllablePath from './controllable/ControllablePath.js';
  14. import ControllableImage from './controllable/ControllableImage.js';
  15. import ControllableLabel from './controllable/ControllableLabel.js';
  16. import eventEmitterMixin from './eventEmitterMixin.js';
  17. import MockPoint from './MockPoint.js';
  18. import ControlPoint from './ControlPoint.js';
  19. var merge = H.merge,
  20. addEvent = H.addEvent,
  21. defined = H.defined,
  22. erase = H.erase,
  23. find = H.find,
  24. isString = H.isString,
  25. pick = H.pick,
  26. reduce = H.reduce,
  27. splat = H.splat,
  28. destroyObjectProperties = H.destroyObjectProperties;
  29. /* *********************************************************************
  30. *
  31. * ANNOTATION
  32. *
  33. ******************************************************************** */
  34. /**
  35. * @typedef {
  36. * Annotation.ControllableCircle|
  37. * Annotation.ControllableImage|
  38. * Annotation.ControllablePath|
  39. * Annotation.ControllableRect
  40. * }
  41. * Annotation.Shape
  42. */
  43. /**
  44. * @typedef {Annotation.ControllableLabel} Annotation.Label
  45. */
  46. /**
  47. * An annotation class which serves as a container for items like labels or
  48. * shapes. Created items are positioned on the chart either by linking them to
  49. * existing points or created mock points
  50. *
  51. * @class
  52. * @mixes Annotation.controllableMixin
  53. * @mixes Annotation.eventEmitterMixin
  54. *
  55. * @param {Highcharts.Chart} chart a chart instance
  56. * @param {AnnotationOptions} options the options object
  57. */
  58. var Annotation = H.Annotation = function (chart, options) {
  59. var labelsAndShapes;
  60. /**
  61. * The chart that the annotation belongs to.
  62. *
  63. * @type {Highcharts.Chart}
  64. */
  65. this.chart = chart;
  66. /**
  67. * The array of points which defines the annotation.
  68. *
  69. * @type {Array<Annotation.PointLike>}
  70. */
  71. this.points = [];
  72. /**
  73. * The array of control points.
  74. *
  75. * @type {Array<Annotation.ControlPoint>}
  76. */
  77. this.controlPoints = [];
  78. this.coll = 'annotations';
  79. /**
  80. * The array of labels which belong to the annotation.
  81. *
  82. * @type {Array<Annotation.Label>}
  83. */
  84. this.labels = [];
  85. /**
  86. * The array of shapes which belong to the annotation.
  87. *
  88. * @type {Array<Annotation.Shape>}
  89. */
  90. this.shapes = [];
  91. /**
  92. * The options for the annotations.
  93. *
  94. * @type {AnnotationOptions}
  95. */
  96. // this.options = merge(this.defaultOptions, userOptions);
  97. this.options = options;
  98. /**
  99. * The user options for the annotations.
  100. *
  101. * @type {AnnotationOptions}
  102. */
  103. this.userOptions = merge(true, {}, options);
  104. // Handle labels and shapes - those are arrays
  105. // Merging does not work with arrays (stores reference)
  106. labelsAndShapes = this.getLabelsAndShapesOptions(
  107. this.userOptions,
  108. options
  109. );
  110. this.userOptions.labels = labelsAndShapes.labels;
  111. this.userOptions.shapes = labelsAndShapes.shapes;
  112. /**
  113. * The callback that reports to the overlapping-labels module which
  114. * labels it should account for.
  115. *
  116. * @name labelCollector
  117. * @memberOf Annotation#
  118. * @type {Function}
  119. */
  120. /**
  121. * The group svg element.
  122. *
  123. * @name group
  124. * @memberOf Annotation#
  125. * @type {Highcharts.SVGElement}
  126. */
  127. /**
  128. * The group svg element of the annotation's shapes.
  129. *
  130. * @name shapesGroup
  131. * @memberOf Annotation#
  132. * @type {Highcharts.SVGElement}
  133. */
  134. /**
  135. * The group svg element of the annotation's labels.
  136. *
  137. * @name labelsGroup
  138. * @memberOf Annotation#
  139. * @type {Highcharts.SVGElement}
  140. */
  141. this.init(chart, options);
  142. };
  143. merge(
  144. true,
  145. Annotation.prototype,
  146. controllableMixin,
  147. eventEmitterMixin, /** @lends Annotation# */ {
  148. /**
  149. * A basic type of an annotation. It allows to add custom labels
  150. * or shapes. The items can be tied to points, axis coordinates
  151. * or chart pixel coordinates.
  152. *
  153. * @private
  154. * @type {Object}
  155. * @ignore-options base, annotations.crookedLine
  156. * @sample highcharts/annotations/basic/
  157. * Basic annotations
  158. * @sample highcharts/demo/annotations/
  159. * Advanced annotations
  160. * @sample highcharts/css/annotations
  161. * Styled mode
  162. * @sample highcharts/annotations-advanced/controllable
  163. * Controllable items
  164. * @sample {highstock} stock/annotations/fibonacci-retracements
  165. * Custom annotation, Fibonacci retracement
  166. * @since 6.0.0
  167. * @optionparent annotations.crookedLine
  168. */
  169. defaultOptions: {
  170. /**
  171. * Whether the annotation is visible.
  172. *
  173. * @sample highcharts/annotations/visible/
  174. * Set annotation visibility
  175. */
  176. visible: true,
  177. /**
  178. * Allow an annotation to be draggable by a user. Possible
  179. * values are `"x"`, `"xy"`, `"y"` and `""` (disabled).
  180. *
  181. * @type {string}
  182. * @validvalue ["x", "xy", "y", ""]
  183. */
  184. draggable: 'xy',
  185. /**
  186. * Options for annotation's labels. Each label inherits options
  187. * from the labelOptions object. An option from the labelOptions
  188. * can be overwritten by config for a specific label.
  189. */
  190. labelOptions: {
  191. /**
  192. * The alignment of the annotation's label. If right,
  193. * the right side of the label should be touching the point.
  194. *
  195. * @validvalue ["left", "center", "right"]
  196. * @sample highcharts/annotations/label-position/
  197. * Set labels position
  198. */
  199. align: 'center',
  200. /**
  201. * Whether to allow the annotation's labels to overlap.
  202. * To make the labels less sensitive for overlapping,
  203. * the can be set to 0.
  204. *
  205. * @sample highcharts/annotations/tooltip-like/
  206. * Hide overlapping labels
  207. */
  208. allowOverlap: false,
  209. /**
  210. * The background color or gradient for the annotation's label.
  211. *
  212. * @type {Color}
  213. * @sample highcharts/annotations/label-presentation/
  214. * Set labels graphic options
  215. */
  216. backgroundColor: 'rgba(0, 0, 0, 0.75)',
  217. /**
  218. * The border color for the annotation's label.
  219. *
  220. * @type {Color}
  221. * @sample highcharts/annotations/label-presentation/
  222. * Set labels graphic options
  223. */
  224. borderColor: 'black',
  225. /**
  226. * The border radius in pixels for the annotaiton's label.
  227. *
  228. * @sample highcharts/annotations/label-presentation/
  229. * Set labels graphic options
  230. */
  231. borderRadius: 3,
  232. /**
  233. * The border width in pixels for the annotation's label
  234. *
  235. * @sample highcharts/annotations/label-presentation/
  236. * Set labels graphic options
  237. */
  238. borderWidth: 1,
  239. /**
  240. * A class name for styling by CSS.
  241. *
  242. * @sample highcharts/css/annotations
  243. * Styled mode annotations
  244. * @since 6.0.5
  245. */
  246. className: '',
  247. /**
  248. * Whether to hide the annotation's label
  249. * that is outside the plot area.
  250. *
  251. * @sample highcharts/annotations/label-crop-overflow/
  252. * Crop or justify labels
  253. */
  254. crop: false,
  255. /**
  256. * The label's pixel distance from the point.
  257. *
  258. * @type {number}
  259. * @sample highcharts/annotations/label-position/
  260. * Set labels position
  261. * @default undefined
  262. * @apioption annotations.crookedLine.labelOptions.distance
  263. */
  264. /**
  265. * A [format](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting) string for the data label.
  266. *
  267. * @type {string}
  268. * @see [plotOptions.series.dataLabels.format](
  269. * plotOptions.series.dataLabels.format.html)
  270. * @sample highcharts/annotations/label-text/
  271. * Set labels text
  272. * @default undefined
  273. * @apioption annotations.crookedLine.labelOptions.format
  274. */
  275. /**
  276. * Alias for the format option.
  277. *
  278. * @type {string}
  279. * @see [format](annotations.labelOptions.format.html)
  280. * @sample highcharts/annotations/label-text/
  281. * Set labels text
  282. * @default undefined
  283. * @apioption annotations.crookedLine.labelOptions.text
  284. */
  285. /**
  286. * Callback JavaScript function to format
  287. * the annotation's label. Note that if a `format` or `text`
  288. * are defined, the format or text take precedence and
  289. * the formatter is ignored. `This` refers to a * point object.
  290. *
  291. * @type {function}
  292. * @sample highcharts/annotations/label-text/
  293. * Set labels text
  294. * @default function () {
  295. * return defined(this.y) ? this.y : 'Annotation label';
  296. * }
  297. */
  298. formatter: function () {
  299. return defined(this.y) ? this.y : 'Annotation label';
  300. },
  301. /**
  302. * How to handle the annotation's label that flow
  303. * outside the plot area. The justify option aligns the label
  304. * inside the plot area.
  305. *
  306. * @validvalue ["none", "justify"]
  307. * @sample highcharts/annotations/label-crop-overflow/
  308. * Crop or justify labels
  309. **/
  310. overflow: 'justify',
  311. /**
  312. * When either the borderWidth or the backgroundColor is set,
  313. * this is the padding within the box.
  314. *
  315. * @sample highcharts/annotations/label-presentation/
  316. * Set labels graphic options
  317. */
  318. padding: 5,
  319. /**
  320. * The shadow of the box. The shadow can be
  321. * an object configuration containing
  322. * `color`, `offsetX`, `offsetY`, `opacity` and `width`.
  323. *
  324. * @type {Boolean|Object}
  325. * @sample highcharts/annotations/label-presentation/
  326. * Set labels graphic options
  327. */
  328. shadow: false,
  329. /**
  330. * The name of a symbol to use for the border around the label.
  331. * Symbols are predefined functions on the Renderer object.
  332. *
  333. * @type {string}
  334. * @sample highcharts/annotations/shapes/
  335. * Available shapes for labels
  336. */
  337. shape: 'callout',
  338. /**
  339. * Styles for the annotation's label.
  340. *
  341. * @type {CSSObject}
  342. * @sample highcharts/annotations/label-presentation/
  343. * Set labels graphic options
  344. * @see [plotOptions.series.dataLabels.style](
  345. * plotOptions.series.dataLabels.style.html)
  346. */
  347. style: {
  348. fontSize: '11px',
  349. fontWeight: 'normal',
  350. color: 'contrast'
  351. },
  352. /**
  353. * Whether to [use HTML](http://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html)
  354. * to render the annotation's label.
  355. *
  356. * @type {boolean}
  357. * @default false
  358. */
  359. useHTML: false,
  360. /**
  361. * The vertical alignment of the annotation's label.
  362. *
  363. * @type {string}
  364. * @validvalue ["top", "middle", "bottom"]
  365. * @sample highcharts/annotations/label-position/
  366. * Set labels position
  367. */
  368. verticalAlign: 'bottom',
  369. /**
  370. * The x position offset of the label relative to the point.
  371. * Note that if a `distance` is defined, the distance takes
  372. * precedence over `x` and `y` options.
  373. *
  374. * @sample highcharts/annotations/label-position/
  375. * Set labels position
  376. */
  377. x: 0,
  378. /**
  379. * The y position offset of the label relative to the point.
  380. * Note that if a `distance` is defined, the distance takes
  381. * precedence over `x` and `y` options.
  382. *
  383. * @sample highcharts/annotations/label-position/
  384. * Set labels position
  385. */
  386. y: -16
  387. },
  388. /**
  389. * An array of labels for the annotation. For options that apply to
  390. * multiple labels, they can be added to the
  391. * [labelOptions](annotations.labelOptions.html).
  392. *
  393. * @type {Array<Object>}
  394. * @extends annotations.crookedLine.labelOptions
  395. * @apioption annotations.crookedLine.labels
  396. */
  397. /**
  398. * This option defines the point to which the label
  399. * will be connected.
  400. * It can be either the point which exists in the series - it is
  401. * referenced by the point's id - or a new point with defined x, y
  402. * properies and optionally axes.
  403. *
  404. * @type {string|MockPointOptions}
  405. * @sample highcharts/annotations/mock-point/
  406. * Attach annotation to a mock point
  407. * @apioption annotations.crookedLine.labels.point
  408. */
  409. /**
  410. * The x position of the point. Units can be either in axis
  411. * or chart pixel coordinates.
  412. *
  413. * @type {number}
  414. * @apioption annotations.crookedLine.labels.point.x
  415. */
  416. /**
  417. * The y position of the point. Units can be either in axis
  418. * or chart pixel coordinates.
  419. *
  420. * @type {number}
  421. * @apioption annotations.crookedLine.labels.point.y
  422. */
  423. /**
  424. * This number defines which xAxis the point is connected to.
  425. * It refers to either the axis id or the index of the axis
  426. * in the xAxis array. If the option is not configured or
  427. * the axis is not found the point's
  428. * x coordinate refers to the chart pixels.
  429. *
  430. * @type {number|string}
  431. * @apioption annotations.crookedLine.labels.point.xAxis
  432. */
  433. /**
  434. * This number defines which yAxis the point is connected to.
  435. * It refers to either the axis id or the index of the axis
  436. * in the yAxis array. If the option is not configured or
  437. * the axis is not found the point's
  438. * y coordinate refers to the chart pixels.
  439. *
  440. * @type {number|string}
  441. * @apioption annotations.crookedLine.labels.point.yAxis
  442. */
  443. /**
  444. * An array of shapes for the annotation. For options that apply to
  445. * multiple shapes, then can be added to the
  446. * [shapeOptions](annotations.shapeOptions.html).
  447. *
  448. * @type {Array<Object>}
  449. * @extends annotations.crookedLine.shapeOptions
  450. * @apioption annotations.crookedLine.shapes
  451. */
  452. /**
  453. * This option defines the point to which the shape will be
  454. * connected.
  455. * It can be either the point which exists in the series - it is
  456. * referenced by the point's id - or a new point with defined x, y
  457. * properties and optionally axes.
  458. *
  459. * @type {string|MockPointOptions}
  460. * @extends annotations.crookedLine.labels.point
  461. * @apioption annotations.crookedLine.shapes.point
  462. */
  463. /**
  464. * An array of points for the shape. This option is available
  465. * for shapes which can use multiple points such as path.
  466. * A point can be either a point object or a point's id.
  467. *
  468. * @type {Array<string|Highcharts.MockPoint.Options>}
  469. * @see [annotations.shapes.point](annotations.shapes.point.html)
  470. * @apioption annotations.crookedLine.shapes.points
  471. */
  472. /**
  473. * Id of the marker which will be drawn at the final
  474. * vertex of the path.
  475. * Custom markers can be defined in defs property.
  476. *
  477. * @type {string}
  478. * @see [defs.markers](defs.markers.html)
  479. * @sample highcharts/annotations/custom-markers/
  480. * Define a custom marker for annotations
  481. * @apioption annotations.crookedLine.shapes.markerEnd
  482. */
  483. /**
  484. * Id of the marker which will be drawn at the first
  485. * vertex of the path.
  486. * Custom markers can be defined in defs property.
  487. *
  488. * @type {string}
  489. * @see [defs.markers](defs.markers.html)
  490. * @sample {highcharts} highcharts/annotations/custom-markers/
  491. * Define a custom marker for annotations
  492. * @apioption annotations.crookedLine.shapes.markerStart
  493. */
  494. /**
  495. * Options for annotation's shapes. Each shape inherits options
  496. * from the shapeOptions object. An option from the shapeOptions
  497. * can be overwritten by config for a specific shape.
  498. *
  499. * @type {Object}
  500. */
  501. shapeOptions: {
  502. /**
  503. * The width of the shape.
  504. *
  505. * @type {number}
  506. * @sample highcharts/annotations/shape/
  507. * Basic shape annotation
  508. * @apioption annotations.crookedLine.shapeOptions.width
  509. **/
  510. /**
  511. * The height of the shape.
  512. *
  513. * @type {number}
  514. * @sample highcharts/annotations/shape/
  515. * Basic shape annotation
  516. * @apioption annotations.crookedLine.shapeOptions.height
  517. */
  518. /**
  519. * The color of the shape's stroke.
  520. *
  521. * @type {Color}
  522. * @sample highcharts/annotations/shape/
  523. * Basic shape annotation
  524. */
  525. stroke: 'rgba(0, 0, 0, 0.75)',
  526. /**
  527. * The pixel stroke width of the shape.
  528. *
  529. * @sample highcharts/annotations/shape/
  530. * Basic shape annotation
  531. */
  532. strokeWidth: 1,
  533. /**
  534. * The color of the shape's fill.
  535. *
  536. * @type {Color}
  537. * @sample highcharts/annotations/shape/
  538. * Basic shape annotation
  539. */
  540. fill: 'rgba(0, 0, 0, 0.75)',
  541. /**
  542. * The type of the shape, e.g. circle or rectangle.
  543. *
  544. * @type {string}
  545. * @sample highcharts/annotations/shape/
  546. * Basic shape annotation
  547. * @default 'rect'
  548. * @apioption annotations.crookedLine.shapeOptions.type
  549. */
  550. /**
  551. * The radius of the shape.
  552. *
  553. * @sample highcharts/annotations/shape/
  554. * Basic shape annotation
  555. */
  556. r: 0,
  557. /**
  558. * Defines additional snapping area around an annotation
  559. * making this annotation to focus. Defined in pixels.
  560. */
  561. snap: 2
  562. },
  563. /**
  564. * Options for annotation's control points. Each control point
  565. * inherits options from controlPointOptions object.
  566. * Options from the controlPointOptions can be overwritten
  567. * by options in a specific control point.
  568. *
  569. * @type {Annotation.ControlPoint.Options}
  570. * @apioption annotations.crookedLine.controlPointOptions
  571. */
  572. controlPointOptions: {
  573. symbol: 'circle',
  574. width: 10,
  575. height: 10,
  576. style: {
  577. stroke: 'black',
  578. 'stroke-width': 2,
  579. fill: 'white'
  580. },
  581. visible: false,
  582. /**
  583. * @function {Annotation.ControlPoint.Positioner}
  584. * @apioption annotations.crookedLine.controlPointOptions.positioner
  585. */
  586. events: {}
  587. },
  588. /**
  589. * @type {Object}
  590. */
  591. events: {},
  592. /**
  593. * The Z index of the annotation.
  594. *
  595. * @type {number}
  596. * @default 6
  597. */
  598. zIndex: 6
  599. },
  600. /**
  601. * Initialize the annotation.
  602. *
  603. * @param {Highcharts.Chart} - the chart
  604. * @param {AnnotationOptions} - the user options for the annotation
  605. */
  606. init: function () {
  607. this.linkPoints();
  608. this.addControlPoints();
  609. this.addShapes();
  610. this.addLabels();
  611. this.addClipPaths();
  612. this.setLabelCollector();
  613. },
  614. getLabelsAndShapesOptions: function (baseOptions, newOptions) {
  615. var mergedOptions = {};
  616. ['labels', 'shapes'].forEach(function (name) {
  617. if (baseOptions[name]) {
  618. mergedOptions[name] = splat(newOptions[name]).map(
  619. function (basicOptions, i) {
  620. return merge(baseOptions[name][i], basicOptions);
  621. }
  622. );
  623. }
  624. });
  625. return mergedOptions;
  626. },
  627. addShapes: function () {
  628. (this.options.shapes || []).forEach(function (shapeOptions, i) {
  629. var shape = this.initShape(shapeOptions, i);
  630. this.options.shapes[i] = shape.options;
  631. }, this);
  632. },
  633. addLabels: function () {
  634. (this.options.labels || []).forEach(function (labelOptions, i) {
  635. var label = this.initLabel(labelOptions, i);
  636. this.options.labels[i] = label.options;
  637. }, this);
  638. },
  639. addClipPaths: function () {
  640. this.setClipAxes();
  641. if (this.clipXAxis && this.clipYAxis) {
  642. this.clipRect = this.chart.renderer.clipRect(
  643. this.getClipBox()
  644. );
  645. }
  646. },
  647. setClipAxes: function () {
  648. var xAxes = this.chart.xAxis,
  649. yAxes = this.chart.yAxis,
  650. linkedAxes = reduce(
  651. (this.options.labels || [])
  652. .concat(this.options.shapes || []),
  653. function (axes, labelOrShape) {
  654. return [
  655. xAxes[
  656. labelOrShape &&
  657. labelOrShape.point &&
  658. labelOrShape.point.xAxis
  659. ] || axes[0],
  660. yAxes[
  661. labelOrShape &&
  662. labelOrShape.point &&
  663. labelOrShape.point.yAxis
  664. ] || axes[1]
  665. ];
  666. },
  667. []
  668. );
  669. this.clipXAxis = linkedAxes[0];
  670. this.clipYAxis = linkedAxes[1];
  671. },
  672. getClipBox: function () {
  673. return {
  674. x: this.clipXAxis.left,
  675. y: this.clipYAxis.top,
  676. width: this.clipXAxis.width,
  677. height: this.clipYAxis.height
  678. };
  679. },
  680. setLabelCollector: function () {
  681. var annotation = this;
  682. annotation.labelCollector = function () {
  683. return annotation.labels.reduce(
  684. function (labels, label) {
  685. if (!label.options.allowOverlap) {
  686. labels.push(label.graphic);
  687. }
  688. return labels;
  689. },
  690. []
  691. );
  692. };
  693. annotation.chart.labelCollectors.push(
  694. annotation.labelCollector
  695. );
  696. },
  697. /**
  698. * Set an annotation options.
  699. *
  700. * @param {AnnotationOptions} - user options for an annotation
  701. */
  702. setOptions: function (userOptions) {
  703. this.options = merge(this.defaultOptions, userOptions);
  704. },
  705. redraw: function (animation) {
  706. this.linkPoints();
  707. if (!this.graphic) {
  708. this.render();
  709. }
  710. if (this.clipRect) {
  711. this.clipRect.animate(this.getClipBox());
  712. }
  713. this.redrawItems(this.shapes, animation);
  714. this.redrawItems(this.labels, animation);
  715. controllableMixin.redraw.call(this, animation);
  716. },
  717. /**
  718. * @param {Array<(Annotation.Label|Annotation.Shape)>} items
  719. * @param {boolean} [animation]
  720. */
  721. redrawItems: function (items, animation) {
  722. var i = items.length;
  723. // needs a backward loop
  724. // labels/shapes array might be modified
  725. // due to destruction of the item
  726. while (i--) {
  727. this.redrawItem(items[i], animation);
  728. }
  729. },
  730. render: function () {
  731. var renderer = this.chart.renderer;
  732. this.graphic = renderer
  733. .g('annotation')
  734. .attr({
  735. zIndex: this.options.zIndex,
  736. visibility: this.options.visible ?
  737. 'visible' :
  738. 'hidden'
  739. })
  740. .add();
  741. this.shapesGroup = renderer
  742. .g('annotation-shapes')
  743. .add(this.graphic)
  744. .clip(this.chart.plotBoxClip);
  745. this.labelsGroup = renderer
  746. .g('annotation-labels')
  747. .attr({
  748. // hideOverlappingLabels requires translation
  749. translateX: 0,
  750. translateY: 0
  751. })
  752. .add(this.graphic);
  753. if (this.clipRect) {
  754. this.graphic.clip(this.clipRect);
  755. }
  756. this.addEvents();
  757. controllableMixin.render.call(this);
  758. },
  759. /**
  760. * Set the annotation's visibility.
  761. *
  762. * @param {Boolean} [visible] - Whether to show or hide an annotation.
  763. * If the param is omitted, the annotation's visibility is toggled.
  764. */
  765. setVisibility: function (visibility) {
  766. var options = this.options,
  767. visible = pick(visibility, !options.visible);
  768. this.graphic.attr(
  769. 'visibility',
  770. visible ? 'visible' : 'hidden'
  771. );
  772. if (!visible) {
  773. this.setControlPointsVisibility(false);
  774. }
  775. options.visible = visible;
  776. },
  777. setControlPointsVisibility: function (visible) {
  778. var setItemControlPointsVisibility = function (item) {
  779. item.setControlPointsVisibility(visible);
  780. };
  781. controllableMixin.setControlPointsVisibility.call(
  782. this,
  783. visible
  784. );
  785. this.shapes.forEach(setItemControlPointsVisibility);
  786. this.labels.forEach(setItemControlPointsVisibility);
  787. },
  788. /**
  789. * Destroy the annotation. This function does not touch the chart
  790. * that the annotation belongs to (all annotations are kept in
  791. * the chart.annotations array) - it is recommended to use
  792. * {@link Highcharts.Chart#removeAnnotation} instead.
  793. */
  794. destroy: function () {
  795. var chart = this.chart,
  796. destroyItem = function (item) {
  797. item.destroy();
  798. };
  799. this.labels.forEach(destroyItem);
  800. this.shapes.forEach(destroyItem);
  801. this.clipXAxis = null;
  802. this.clipYAxis = null;
  803. erase(chart.labelCollectors, this.labelCollector);
  804. eventEmitterMixin.destroy.call(this);
  805. controllableMixin.destroy.call(this);
  806. destroyObjectProperties(this, chart);
  807. },
  808. /**
  809. * See {@link Highcharts.Annotation#destroy}.
  810. */
  811. remove: function () {
  812. return this.destroy();
  813. },
  814. update: function (userOptions) {
  815. var chart = this.chart,
  816. labelsAndShapes = this.getLabelsAndShapesOptions(
  817. this.userOptions,
  818. userOptions
  819. ),
  820. userOptionsIndex = chart.annotations.indexOf(this),
  821. options = H.merge(true, this.userOptions, userOptions);
  822. options.labels = labelsAndShapes.labels;
  823. options.shapes = labelsAndShapes.shapes;
  824. this.destroy();
  825. this.constructor(chart, options);
  826. // Update options in chart options, used in exporting (#9767):
  827. chart.options.annotations[userOptionsIndex] = options;
  828. this.redraw();
  829. },
  830. /* *************************************************************
  831. * ITEM SECTION
  832. * Contains methods for handling a single item in an annotation
  833. **************************************************************** */
  834. /**
  835. * Initialisation of a single shape
  836. *
  837. * @param {Object} shapeOptions - a confg object for a single shape
  838. **/
  839. initShape: function (shapeOptions, index) {
  840. var options = merge(
  841. this.options.shapeOptions,
  842. {
  843. controlPointOptions: this.options.controlPointOptions
  844. },
  845. shapeOptions
  846. ),
  847. shape = new Annotation.shapesMap[options.type](
  848. this,
  849. options,
  850. index
  851. );
  852. shape.itemType = 'shape';
  853. this.shapes.push(shape);
  854. return shape;
  855. },
  856. /**
  857. * Initialisation of a single label
  858. *
  859. * @param {Object} labelOptions
  860. **/
  861. initLabel: function (labelOptions, index) {
  862. var options = merge(
  863. this.options.labelOptions,
  864. {
  865. controlPointOptions: this.options.controlPointOptions
  866. },
  867. labelOptions
  868. ),
  869. label = new ControllableLabel(
  870. this,
  871. options,
  872. index
  873. );
  874. label.itemType = 'label';
  875. this.labels.push(label);
  876. return label;
  877. },
  878. /**
  879. * Redraw a single item.
  880. *
  881. * @param {Annotation.Label|Annotation.Shape} item
  882. * @param {boolean} [animation]
  883. */
  884. redrawItem: function (item, animation) {
  885. item.linkPoints();
  886. if (!item.shouldBeDrawn()) {
  887. this.destroyItem(item);
  888. } else {
  889. if (!item.graphic) {
  890. this.renderItem(item);
  891. }
  892. item.redraw(
  893. H.pick(animation, true) && item.graphic.placed
  894. );
  895. if (item.points.length) {
  896. this.adjustVisibility(item);
  897. }
  898. }
  899. },
  900. /**
  901. * Hide or show annotaiton attached to points.
  902. *
  903. * @param {Annotation.Label|Annotation.Shape} item
  904. */
  905. adjustVisibility: function (item) { // #9481
  906. var hasVisiblePoints = false,
  907. label = item.graphic;
  908. item.points.forEach(function (point) {
  909. if (
  910. point.series.visible !== false &&
  911. point.visible !== false
  912. ) {
  913. hasVisiblePoints = true;
  914. }
  915. });
  916. if (!hasVisiblePoints) {
  917. label.hide();
  918. } else if (label.visibility === 'hidden') {
  919. label.show();
  920. }
  921. },
  922. /**
  923. * Destroy a single item.
  924. *
  925. * @param {Annotation.Label|Annotation.Shape} item
  926. */
  927. destroyItem: function (item) {
  928. // erase from shapes or labels array
  929. erase(this[item.itemType + 's'], item);
  930. item.destroy();
  931. },
  932. /*
  933. * @private
  934. */
  935. renderItem: function (item) {
  936. item.render(
  937. item.itemType === 'label' ?
  938. this.labelsGroup :
  939. this.shapesGroup
  940. );
  941. }
  942. }
  943. );
  944. /**
  945. * An object uses for mapping between a shape type and a constructor.
  946. * To add a new shape type extend this object with type name as a key
  947. * and a constructor as its value.
  948. **/
  949. Annotation.shapesMap = {
  950. 'rect': ControllableRect,
  951. 'circle': ControllableCircle,
  952. 'path': ControllablePath,
  953. 'image': ControllableImage
  954. };
  955. Annotation.types = {};
  956. Annotation.MockPoint = MockPoint;
  957. Annotation.ControlPoint = ControlPoint;
  958. H.extendAnnotation = function (
  959. Constructor,
  960. BaseConstructor,
  961. prototype,
  962. defaultOptions
  963. ) {
  964. BaseConstructor = BaseConstructor || Annotation;
  965. merge(
  966. true,
  967. Constructor.prototype,
  968. BaseConstructor.prototype,
  969. prototype
  970. );
  971. Constructor.prototype.defaultOptions = merge(
  972. Constructor.prototype.defaultOptions,
  973. defaultOptions || {}
  974. );
  975. };
  976. /* *********************************************************************
  977. *
  978. * EXTENDING CHART PROTOTYPE
  979. *
  980. ******************************************************************** */
  981. // Let chart.update() work with annotations
  982. H.Chart.prototype.collectionsWithUpdate.push('annotations');
  983. H.extend(H.Chart.prototype, /** @lends Highcharts.Chart# */ {
  984. initAnnotation: function (userOptions) {
  985. var Constructor =
  986. Annotation.types[userOptions.type] || Annotation,
  987. options = H.merge(
  988. Constructor.prototype.defaultOptions,
  989. userOptions
  990. ),
  991. annotation = new Constructor(this, options);
  992. this.annotations.push(annotation);
  993. return annotation;
  994. },
  995. /**
  996. * Add an annotation to the chart after render time.
  997. *
  998. * @param {AnnotationOptions} options
  999. * The annotation options for the new, detailed annotation.
  1000. * @param {boolean} [redraw]
  1001. *
  1002. * @return {Highcharts.Annotation} - The newly generated annotation.
  1003. */
  1004. addAnnotation: function (userOptions, redraw) {
  1005. var annotation = this.initAnnotation(userOptions);
  1006. this.options.annotations.push(annotation.options);
  1007. if (pick(redraw, true)) {
  1008. annotation.redraw();
  1009. }
  1010. return annotation;
  1011. },
  1012. /**
  1013. * Remove an annotation from the chart.
  1014. *
  1015. * @param {String|Annotation} idOrAnnotation - The annotation's id or
  1016. * direct annotation object.
  1017. */
  1018. removeAnnotation: function (idOrAnnotation) {
  1019. var annotations = this.annotations,
  1020. annotation = isString(idOrAnnotation) ? find(
  1021. annotations,
  1022. function (annotation) {
  1023. return annotation.options.id === idOrAnnotation;
  1024. }
  1025. ) : idOrAnnotation;
  1026. if (annotation) {
  1027. erase(this.options.annotations, annotation.options);
  1028. erase(annotations, annotation);
  1029. annotation.destroy();
  1030. }
  1031. },
  1032. drawAnnotations: function () {
  1033. this.plotBoxClip.attr(this.plotBox);
  1034. this.annotations.forEach(function (annotation) {
  1035. annotation.redraw();
  1036. });
  1037. }
  1038. });
  1039. H.Chart.prototype.callbacks.push(function (chart) {
  1040. chart.annotations = [];
  1041. if (!chart.options.annotations) {
  1042. chart.options.annotations = [];
  1043. }
  1044. chart.plotBoxClip = this.renderer.clipRect(this.plotBox);
  1045. chart.controlPointsGroup = chart.renderer
  1046. .g('control-points')
  1047. .attr({ zIndex: 99 })
  1048. .clip(chart.plotBoxClip)
  1049. .add();
  1050. chart.options.annotations.forEach(function (annotationOptions, i) {
  1051. var annotation = chart.initAnnotation(annotationOptions);
  1052. chart.options.annotations[i] = annotation.options;
  1053. });
  1054. chart.drawAnnotations();
  1055. addEvent(chart, 'redraw', chart.drawAnnotations);
  1056. addEvent(chart, 'destroy', function () {
  1057. chart.plotBoxClip.destroy();
  1058. chart.controlPointsGroup.destroy();
  1059. });
  1060. });