controllableMixin.js 11 KB

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