ControllableMixin.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. /* *
  2. *
  3. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  4. *
  5. * */
  6. 'use strict';
  7. import ControlPoint from '../ControlPoint.js';
  8. import MockPoint from '../MockPoint.js';
  9. import Tooltip from '../../../Core/Tooltip.js';
  10. import U from '../../../Core/Utilities.js';
  11. var isObject = U.isObject, isString = U.isString, merge = U.merge, splat = U.splat;
  12. /**
  13. * An object which denots a controllable's anchor positions - relative and
  14. * absolute.
  15. *
  16. * @private
  17. * @interface Highcharts.AnnotationAnchorObject
  18. */ /**
  19. * Relative to the plot area position
  20. * @name Highcharts.AnnotationAnchorObject#relativePosition
  21. * @type {Highcharts.BBoxObject}
  22. */ /**
  23. * Absolute position
  24. * @name Highcharts.AnnotationAnchorObject#absolutePosition
  25. * @type {Highcharts.BBoxObject}
  26. */
  27. /**
  28. * @interface Highcharts.AnnotationControllable
  29. */ /**
  30. * @name Highcharts.AnnotationControllable#annotation
  31. * @type {Highcharts.Annotation}
  32. */ /**
  33. * @name Highcharts.AnnotationControllable#chart
  34. * @type {Highcharts.Chart}
  35. */ /**
  36. * @name Highcharts.AnnotationControllable#collection
  37. * @type {string}
  38. */ /**
  39. * @private
  40. * @name Highcharts.AnnotationControllable#controlPoints
  41. * @type {Array<Highcharts.AnnotationControlPoint>}
  42. */ /**
  43. * @name Highcharts.AnnotationControllable#points
  44. * @type {Array<Highcharts.Point>}
  45. */
  46. /* eslint-disable no-invalid-this, valid-jsdoc */
  47. /**
  48. * It provides methods for handling points, control points
  49. * and points transformations.
  50. *
  51. * @private
  52. * @mixin
  53. * @name Highcharts.AnnotationControllableMixin
  54. */
  55. var controllableMixin = {
  56. /**
  57. * Init the controllable
  58. */
  59. init: function (annotation, options, index) {
  60. this.annotation = annotation;
  61. this.chart = annotation.chart;
  62. this.options = options;
  63. this.points = [];
  64. this.controlPoints = [];
  65. this.index = index;
  66. this.linkPoints();
  67. this.addControlPoints();
  68. },
  69. /**
  70. * Redirect attr usage on the controllable graphic element.
  71. */
  72. attr: function () {
  73. this.graphic.attr.apply(this.graphic, arguments);
  74. },
  75. /**
  76. * Get the controllable's points options.
  77. *
  78. * @return {Array<Highcharts.PointOptionsObject>}
  79. * An array of points' options.
  80. */
  81. getPointsOptions: function () {
  82. var options = this.options;
  83. return (options.points || (options.point && splat(options.point)));
  84. },
  85. /**
  86. * Utility function for mapping item's options
  87. * to element's attribute
  88. *
  89. * @param {Highcharts.AnnotationsLabelsOptions|Highcharts.AnnotationsShapesOptions} options
  90. *
  91. * @return {Highcharts.SVGAttributes}
  92. * Mapped options.
  93. */
  94. attrsFromOptions: function (options) {
  95. var map = this.constructor.attrsMap, attrs = {}, key, mappedKey, styledMode = this.chart.styledMode;
  96. for (key in options) { // eslint-disable-line guard-for-in
  97. mappedKey = map[key];
  98. if (mappedKey &&
  99. (!styledMode ||
  100. ['fill', 'stroke', 'stroke-width']
  101. .indexOf(mappedKey) === -1)) {
  102. attrs[mappedKey] = options[key];
  103. }
  104. }
  105. return attrs;
  106. },
  107. /**
  108. * Returns object which denotes anchor position - relative and absolute.
  109. *
  110. * @param {Highcharts.AnnotationPointType} point
  111. * A point like object.
  112. *
  113. * @return {Highcharts.AnnotationAnchorObject} a controllable anchor
  114. */
  115. anchor: function (point) {
  116. var plotBox = point.series.getPlotBox(), chart = point.series.chart, box = point.mock ?
  117. point.toAnchor() :
  118. Tooltip.prototype.getAnchor.call({
  119. chart: point.series.chart
  120. }, point), anchor = {
  121. x: box[0] + (this.options.x || 0),
  122. y: box[1] + (this.options.y || 0),
  123. height: box[2] || 0,
  124. width: box[3] || 0
  125. };
  126. return {
  127. relativePosition: anchor,
  128. absolutePosition: merge(anchor, {
  129. x: anchor.x + (point.mock ? plotBox.translateX : chart.plotLeft),
  130. y: anchor.y + (point.mock ? plotBox.translateY : chart.plotTop)
  131. })
  132. };
  133. },
  134. /**
  135. * Map point's options to a point-like object.
  136. *
  137. * @param {string|Function|Highcharts.AnnotationMockPointOptionsObject|Highcharts.AnnotationPointType} pointOptions
  138. * Point's options.
  139. *
  140. * @param {Highcharts.AnnotationPointType} point
  141. * A point-like instance.
  142. *
  143. * @return {Highcharts.AnnotationPointType|null}
  144. * if the point is found/set returns this point, otherwise null
  145. */
  146. point: function (pointOptions, point) {
  147. if (pointOptions && pointOptions.series) {
  148. return pointOptions;
  149. }
  150. if (!point || point.series === null) {
  151. if (isObject(pointOptions)) {
  152. point = new MockPoint(this.chart, this, pointOptions);
  153. }
  154. else if (isString(pointOptions)) {
  155. point = this.chart.get(pointOptions) || null;
  156. }
  157. else if (typeof pointOptions === 'function') {
  158. var pointConfig = pointOptions.call(point, this);
  159. point = pointConfig.series ?
  160. pointConfig :
  161. new MockPoint(this.chart, this, pointOptions);
  162. }
  163. }
  164. return point;
  165. },
  166. /**
  167. * Find point-like objects based on points options.
  168. *
  169. * @return {Array<Annotation.PointLike>} an array of point-like objects
  170. */
  171. linkPoints: function () {
  172. var pointsOptions = this.getPointsOptions(), points = this.points, len = (pointsOptions && pointsOptions.length) || 0, i, point;
  173. for (i = 0; i < len; i++) {
  174. point = this.point(pointsOptions[i], points[i]);
  175. if (!point) {
  176. points.length = 0;
  177. return;
  178. }
  179. if (point.mock) {
  180. point.refresh();
  181. }
  182. points[i] = point;
  183. }
  184. return points;
  185. },
  186. /**
  187. * Add control points to a controllable.
  188. */
  189. addControlPoints: function () {
  190. var controlPointsOptions = this.options.controlPoints;
  191. (controlPointsOptions || []).forEach(function (controlPointOptions, i) {
  192. var options = merge(this.options.controlPointOptions, controlPointOptions);
  193. if (!options.index) {
  194. options.index = i;
  195. }
  196. controlPointsOptions[i] = options;
  197. this.controlPoints.push(new ControlPoint(this.chart, this, options));
  198. }, this);
  199. },
  200. /**
  201. * Check if a controllable should be rendered/redrawn.
  202. *
  203. * @return {boolean}
  204. * Whether a controllable should be drawn.
  205. */
  206. shouldBeDrawn: function () {
  207. return Boolean(this.points.length);
  208. },
  209. /**
  210. * Render a controllable.
  211. */
  212. render: function (_parentGroup) {
  213. this.controlPoints.forEach(function (controlPoint) {
  214. controlPoint.render();
  215. });
  216. },
  217. /**
  218. * Redraw a controllable.
  219. *
  220. * @param {boolean} [animation]
  221. */
  222. redraw: function (animation) {
  223. this.controlPoints.forEach(function (controlPoint) {
  224. controlPoint.redraw(animation);
  225. });
  226. },
  227. /**
  228. * Transform a controllable with a specific transformation.
  229. *
  230. * @param {string} transformation a transformation name
  231. * @param {number|null} cx origin x transformation
  232. * @param {number|null} cy origin y transformation
  233. * @param {number} p1 param for the transformation
  234. * @param {number} [p2] param for the transformation
  235. */
  236. transform: function (transformation, cx, cy, p1, p2) {
  237. if (this.chart.inverted) {
  238. var temp = cx;
  239. cx = cy;
  240. cy = temp;
  241. }
  242. this.points.forEach(function (point, i) {
  243. this.transformPoint(transformation, cx, cy, p1, p2, i);
  244. }, this);
  245. },
  246. /**
  247. * Transform a point with a specific transformation
  248. * If a transformed point is a real point it is replaced with
  249. * the mock point.
  250. *
  251. * @param {string} transformation a transformation name
  252. * @param {number|null} cx origin x transformation
  253. * @param {number|null} cy origin y transformation
  254. * @param {number} p1 param for the transformation
  255. * @param {number|undefined} p2 param for the transformation
  256. * @param {number} i index of the point
  257. */
  258. transformPoint: function (transformation, cx, cy, p1, p2, i) {
  259. var point = this.points[i];
  260. if (!point.mock) {
  261. point = this.points[i] = MockPoint.fromPoint(point);
  262. }
  263. point[transformation](cx, cy, p1, p2);
  264. },
  265. /**
  266. * Translate a controllable.
  267. *
  268. * @param {number} dx translation for x coordinate
  269. * @param {number} dy translation for y coordinate
  270. **/
  271. translate: function (dx, dy) {
  272. this.transform('translate', null, null, dx, dy);
  273. },
  274. /**
  275. * Translate a specific point within a controllable.
  276. *
  277. * @param {number} dx translation for x coordinate
  278. * @param {number} dy translation for y coordinate
  279. * @param {number} i index of the point
  280. **/
  281. translatePoint: function (dx, dy, i) {
  282. this.transformPoint('translate', null, null, dx, dy, i);
  283. },
  284. /**
  285. * Translate shape within controllable item.
  286. * Replaces `controllable.translate` method.
  287. *
  288. * @param {number} dx translation for x coordinate
  289. * @param {number} dy translation for y coordinate
  290. */
  291. translateShape: function (dx, dy) {
  292. var chart = this.annotation.chart,
  293. // Annotation.options
  294. shapeOptions = this.annotation.userOptions,
  295. // Chart.options.annotations
  296. annotationIndex = chart.annotations.indexOf(this.annotation), chartOptions = chart.options.annotations[annotationIndex];
  297. this.translatePoint(dx, dy, 0);
  298. // Options stored in:
  299. // - chart (for exporting)
  300. // - current config (for redraws)
  301. chartOptions[this.collection][this.index].point = this.options.point;
  302. shapeOptions[this.collection][this.index].point = this.options.point;
  303. },
  304. /**
  305. * Rotate a controllable.
  306. *
  307. * @param {number} cx origin x rotation
  308. * @param {number} cy origin y rotation
  309. * @param {number} radians
  310. **/
  311. rotate: function (cx, cy, radians) {
  312. this.transform('rotate', cx, cy, radians);
  313. },
  314. /**
  315. * Scale a controllable.
  316. *
  317. * @param {number} cx origin x rotation
  318. * @param {number} cy origin y rotation
  319. * @param {number} sx scale factor x
  320. * @param {number} sy scale factor y
  321. */
  322. scale: function (cx, cy, sx, sy) {
  323. this.transform('scale', cx, cy, sx, sy);
  324. },
  325. /**
  326. * Set control points' visibility.
  327. *
  328. * @param {boolean} visible
  329. */
  330. setControlPointsVisibility: function (visible) {
  331. this.controlPoints.forEach(function (controlPoint) {
  332. controlPoint.setVisibility(visible);
  333. });
  334. },
  335. /**
  336. * Destroy a controllable.
  337. */
  338. destroy: function () {
  339. if (this.graphic) {
  340. this.graphic = this.graphic.destroy();
  341. }
  342. if (this.tracker) {
  343. this.tracker = this.tracker.destroy();
  344. }
  345. this.controlPoints.forEach(function (controlPoint) {
  346. controlPoint.destroy();
  347. });
  348. this.chart = null;
  349. this.points = null;
  350. this.controlPoints = null;
  351. this.options = null;
  352. if (this.annotation) {
  353. this.annotation = null;
  354. }
  355. },
  356. /**
  357. * Update a controllable.
  358. *
  359. * @param {Object} newOptions
  360. */
  361. update: function (newOptions) {
  362. var annotation = this.annotation, options = merge(true, this.options, newOptions), parentGroup = this.graphic.parentGroup;
  363. this.destroy();
  364. this.constructor(annotation, options);
  365. this.render(parentGroup);
  366. this.redraw();
  367. }
  368. };
  369. export default controllableMixin;