You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

392 lines
12 KiB

2 years ago
  1. (function (Highcharts, HighchartsAdapter) {
  2. var UNDEFINED,
  3. ALIGN_FACTOR,
  4. ALLOWED_SHAPES,
  5. Chart = Highcharts.Chart,
  6. extend = Highcharts.extend,
  7. each = Highcharts.each;
  8. ALLOWED_SHAPES = ["path", "rect", "circle"];
  9. ALIGN_FACTOR = {
  10. top: 0,
  11. left: 0,
  12. center: 0.5,
  13. middle: 0.5,
  14. bottom: 1,
  15. right: 1
  16. };
  17. // Highcharts helper methods
  18. var inArray = HighchartsAdapter.inArray,
  19. merge = Highcharts.merge;
  20. function defaultOptions(shapeType) {
  21. var shapeOptions,
  22. options;
  23. options = {
  24. xAxis: 0,
  25. yAxis: 0,
  26. title: {
  27. style: {},
  28. text: "",
  29. x: 0,
  30. y: 0
  31. },
  32. shape: {
  33. params: {
  34. stroke: "#000000",
  35. fill: "transparent",
  36. strokeWidth: 2
  37. }
  38. }
  39. };
  40. shapeOptions = {
  41. circle: {
  42. params: {
  43. x: 0,
  44. y: 0
  45. }
  46. }
  47. };
  48. if (shapeOptions[shapeType]) {
  49. options.shape = merge(options.shape, shapeOptions[shapeType]);
  50. }
  51. return options;
  52. }
  53. function isArray(obj) {
  54. return Object.prototype.toString.call(obj) === '[object Array]';
  55. }
  56. function isNumber(n) {
  57. return typeof n === 'number';
  58. }
  59. function defined(obj) {
  60. return obj !== UNDEFINED && obj !== null;
  61. }
  62. function translatePath(d, xAxis, yAxis, xOffset, yOffset) {
  63. var len = d.length,
  64. i = 0;
  65. while (i < len) {
  66. if (typeof d[i] === 'number' && typeof d[i + 1] === 'number') {
  67. d[i] = xAxis.toPixels(d[i]) - xOffset;
  68. d[i + 1] = yAxis.toPixels(d[i + 1]) - yOffset;
  69. i += 2;
  70. } else {
  71. i += 1;
  72. }
  73. }
  74. return d;
  75. }
  76. // Define annotation prototype
  77. var Annotation = function () {
  78. this.init.apply(this, arguments);
  79. };
  80. Annotation.prototype = {
  81. /*
  82. * Initialize the annotation
  83. */
  84. init: function (chart, options) {
  85. var shapeType = options.shape && options.shape.type;
  86. this.chart = chart;
  87. this.options = merge({}, defaultOptions(shapeType), options);
  88. },
  89. /*
  90. * Render the annotation
  91. */
  92. render: function (redraw) {
  93. var annotation = this,
  94. chart = this.chart,
  95. renderer = annotation.chart.renderer,
  96. group = annotation.group,
  97. title = annotation.title,
  98. shape = annotation.shape,
  99. options = annotation.options,
  100. titleOptions = options.title,
  101. shapeOptions = options.shape;
  102. if (!group) {
  103. group = annotation.group = renderer.g();
  104. }
  105. if (!shape && shapeOptions && inArray(shapeOptions.type, ALLOWED_SHAPES) !== -1) {
  106. shape = annotation.shape = renderer[options.shape.type](shapeOptions.params);
  107. shape.add(group);
  108. }
  109. if (!title && titleOptions) {
  110. title = annotation.title = renderer.label(titleOptions);
  111. title.add(group);
  112. }
  113. group.add(chart.annotations.group);
  114. // link annotations to point or series
  115. annotation.linkObjects();
  116. if (redraw !== false) {
  117. annotation.redraw();
  118. }
  119. },
  120. /*
  121. * Redraw the annotation title or shape after options update
  122. */
  123. redraw: function () {
  124. var options = this.options,
  125. chart = this.chart,
  126. group = this.group,
  127. title = this.title,
  128. shape = this.shape,
  129. linkedTo = this.linkedObject,
  130. xAxis = chart.xAxis[options.xAxis],
  131. yAxis = chart.yAxis[options.yAxis],
  132. width = options.width,
  133. height = options.height,
  134. anchorY = ALIGN_FACTOR[options.anchorY],
  135. anchorX = ALIGN_FACTOR[options.anchorX],
  136. resetBBox = false,
  137. shapeParams,
  138. linkType,
  139. series,
  140. param,
  141. bbox,
  142. x,
  143. y;
  144. if (linkedTo) {
  145. linkType = (linkedTo instanceof Highcharts.Point) ? 'point' :
  146. (linkedTo instanceof Highcharts.Series) ? 'series' : null;
  147. if (linkType === 'point') {
  148. options.xValue = linkedTo.x;
  149. options.yValue = linkedTo.y;
  150. series = linkedTo.series;
  151. } else if (linkType === 'series') {
  152. series = linkedTo;
  153. }
  154. if (group.visibility !== series.group.visibility) {
  155. group.attr({
  156. visibility: series.group.visibility
  157. });
  158. }
  159. }
  160. // Based on given options find annotation pixel position
  161. x = (defined(options.xValue) ? xAxis.toPixels(options.xValue + xAxis.minPointOffset) - xAxis.minPixelPadding : options.x);
  162. y = defined(options.yValue) ? yAxis.toPixels(options.yValue) : options.y;
  163. if (isNaN(x) || isNaN(y) || !isNumber(x) || !isNumber(y)) {
  164. return;
  165. }
  166. if (title) {
  167. title.attr(options.title);
  168. title.css(options.title.style);
  169. resetBBox = true;
  170. }
  171. if (shape) {
  172. shapeParams = extend({}, options.shape.params);
  173. if (options.units === 'values') {
  174. for (param in shapeParams) {
  175. if (inArray(param, ['width', 'x']) > -1) {
  176. shapeParams[param] = xAxis.translate(shapeParams[param]);
  177. } else if (inArray(param, ['height', 'y']) > -1) {
  178. shapeParams[param] = yAxis.translate(shapeParams[param]);
  179. }
  180. }
  181. if (shapeParams.width) {
  182. shapeParams.width -= xAxis.toPixels(0) - xAxis.left;
  183. }
  184. if (shapeParams.x) {
  185. shapeParams.x += xAxis.minPixelPadding;
  186. }
  187. if (options.shape.type === 'path') {
  188. translatePath(shapeParams.d, xAxis, yAxis, x, y);
  189. }
  190. }
  191. // move the center of the circle to shape x/y
  192. if (options.shape.type === 'circle') {
  193. shapeParams.x += shapeParams.r;
  194. shapeParams.y += shapeParams.r;
  195. }
  196. resetBBox = true;
  197. shape.attr(shapeParams);
  198. }
  199. group.bBox = null;
  200. // If annotation width or height is not defined in options use bounding box size
  201. if (!isNumber(width)) {
  202. bbox = group.getBBox();
  203. width = bbox.width;
  204. }
  205. if (!isNumber(height)) {
  206. // get bbox only if it wasn't set before
  207. if (!bbox) {
  208. bbox = group.getBBox();
  209. }
  210. height = bbox.height;
  211. }
  212. // Calculate anchor point
  213. if (!isNumber(anchorX)) {
  214. anchorX = ALIGN_FACTOR.center;
  215. }
  216. if (!isNumber(anchorY)) {
  217. anchorY = ALIGN_FACTOR.center;
  218. }
  219. // Translate group according to its dimension and anchor point
  220. x = x - width * anchorX;
  221. y = y - height * anchorY;
  222. if (chart.animation && defined(group.translateX) && defined(group.translateY)) {
  223. group.animate({
  224. translateX: x,
  225. translateY: y
  226. });
  227. } else {
  228. group.translate(x, y);
  229. }
  230. },
  231. /*
  232. * Destroy the annotation
  233. */
  234. destroy: function () {
  235. var annotation = this,
  236. chart = this.chart,
  237. allItems = chart.annotations.allItems,
  238. index = allItems.indexOf(annotation);
  239. if (index > -1) {
  240. allItems.splice(index, 1);
  241. }
  242. each(['title', 'shape', 'group'], function (element) {
  243. if (annotation[element]) {
  244. annotation[element].destroy();
  245. annotation[element] = null;
  246. }
  247. });
  248. annotation.group = annotation.title = annotation.shape = annotation.chart = annotation.options = null;
  249. },
  250. /*
  251. * Update the annotation with a given options
  252. */
  253. update: function (options, redraw) {
  254. extend(this.options, options);
  255. // update link to point or series
  256. this.linkObjects();
  257. this.render(redraw);
  258. },
  259. linkObjects: function () {
  260. var annotation = this,
  261. chart = annotation.chart,
  262. linkedTo = annotation.linkedObject,
  263. linkedId = linkedTo && (linkedTo.id || linkedTo.options.id),
  264. options = annotation.options,
  265. id = options.linkedTo;
  266. if (!defined(id)) {
  267. annotation.linkedObject = null;
  268. } else if (!defined(linkedTo) || id !== linkedId) {
  269. annotation.linkedObject = chart.get(id);
  270. }
  271. }
  272. };
  273. // Add annotations methods to chart prototype
  274. extend(Chart.prototype, {
  275. annotations: {
  276. /*
  277. * Unified method for adding annotations to the chart
  278. */
  279. add: function (options, redraw) {
  280. var annotations = this.allItems,
  281. chart = this.chart,
  282. item,
  283. len;
  284. if (!isArray(options)) {
  285. options = [options];
  286. }
  287. len = options.length;
  288. while (len--) {
  289. item = new Annotation(chart, options[len]);
  290. annotations.push(item);
  291. item.render(redraw);
  292. }
  293. },
  294. /**
  295. * Redraw all annotations, method used in chart events
  296. */
  297. redraw: function () {
  298. each(this.allItems, function (annotation) {
  299. annotation.redraw();
  300. });
  301. }
  302. }
  303. });
  304. // Initialize on chart load
  305. Chart.prototype.callbacks.push(function (chart) {
  306. var options = chart.options.annotations,
  307. group;
  308. group = chart.renderer.g("annotations");
  309. group.attr({
  310. zIndex: 7
  311. });
  312. group.add();
  313. // initialize empty array for annotations
  314. chart.annotations.allItems = [];
  315. // link chart object to annotations
  316. chart.annotations.chart = chart;
  317. // link annotations group element to the chart
  318. chart.annotations.group = group;
  319. if (isArray(options) && options.length > 0) {
  320. chart.annotations.add(chart.options.annotations);
  321. }
  322. // update annotations after chart redraw
  323. Highcharts.addEvent(chart, 'redraw', function () {
  324. chart.annotations.redraw();
  325. });
  326. });
  327. }(Highcharts, HighchartsAdapter));