Indicators.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. /* *
  2. *
  3. * License: www.highcharts.com/license
  4. *
  5. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  6. *
  7. * */
  8. 'use strict';
  9. import H from '../../Core/Globals.js';
  10. import requiredIndicator from '../../Mixins/IndicatorRequired.js';
  11. import U from '../../Core/Utilities.js';
  12. var addEvent = U.addEvent, error = U.error, extend = U.extend, isArray = U.isArray, pick = U.pick, seriesType = U.seriesType, splat = U.splat;
  13. import '../../Core/Series/Series.js';
  14. var Series = H.Series, seriesTypes = H.seriesTypes, ohlcProto = H.seriesTypes.ohlc.prototype, generateMessage = requiredIndicator.generateMessage;
  15. /**
  16. * The parameter allows setting line series type and use OHLC indicators. Data
  17. * in OHLC format is required.
  18. *
  19. * @sample {highstock} stock/indicators/use-ohlc-data
  20. * Plot line on Y axis
  21. *
  22. * @type {boolean}
  23. * @product highstock
  24. * @apioption plotOptions.line.useOhlcData
  25. */
  26. /* eslint-disable no-invalid-this */
  27. addEvent(H.Series, 'init', function (eventOptions) {
  28. var series = this, options = eventOptions.options;
  29. if (options.useOhlcData &&
  30. options.id !== 'highcharts-navigator-series') {
  31. extend(series, {
  32. pointValKey: ohlcProto.pointValKey,
  33. keys: ohlcProto.keys,
  34. pointArrayMap: ohlcProto.pointArrayMap,
  35. toYData: ohlcProto.toYData
  36. });
  37. }
  38. });
  39. addEvent(Series, 'afterSetOptions', function (e) {
  40. var options = e.options, dataGrouping = options.dataGrouping;
  41. if (dataGrouping &&
  42. options.useOhlcData &&
  43. options.id !== 'highcharts-navigator-series') {
  44. dataGrouping.approximation = 'ohlc';
  45. }
  46. });
  47. /* eslint-enable no-invalid-this */
  48. /**
  49. * The SMA series type.
  50. *
  51. * @private
  52. * @class
  53. * @name Highcharts.seriesTypes.sma
  54. *
  55. * @augments Highcharts.Series
  56. */
  57. seriesType('sma', 'line',
  58. /**
  59. * Simple moving average indicator (SMA). This series requires `linkedTo`
  60. * option to be set.
  61. *
  62. * @sample stock/indicators/sma
  63. * Simple moving average indicator
  64. *
  65. * @extends plotOptions.line
  66. * @since 6.0.0
  67. * @excluding allAreas, colorAxis, dragDrop, joinBy, keys,
  68. * navigatorOptions, pointInterval, pointIntervalUnit,
  69. * pointPlacement, pointRange, pointStart, showInNavigator,
  70. * stacking, useOhlcData
  71. * @product highstock
  72. * @requires stock/indicators/indicators
  73. * @optionparent plotOptions.sma
  74. */
  75. {
  76. /**
  77. * The name of the series as shown in the legend, tooltip etc. If not
  78. * set, it will be based on a technical indicator type and default
  79. * params.
  80. *
  81. * @type {string}
  82. */
  83. name: void 0,
  84. tooltip: {
  85. /**
  86. * Number of decimals in indicator series.
  87. */
  88. valueDecimals: 4
  89. },
  90. /**
  91. * The main series ID that indicator will be based on. Required for this
  92. * indicator.
  93. *
  94. * @type {string}
  95. */
  96. linkedTo: void 0,
  97. /**
  98. * Whether to compare indicator to the main series values
  99. * or indicator values.
  100. *
  101. * @sample {highstock} stock/plotoptions/series-comparetomain/
  102. * Difference between comparing SMA values to the main series
  103. * and its own values.
  104. *
  105. * @type {boolean}
  106. */
  107. compareToMain: false,
  108. /**
  109. * Paramters used in calculation of regression series' points.
  110. */
  111. params: {
  112. /**
  113. * The point index which indicator calculations will base. For
  114. * example using OHLC data, index=2 means the indicator will be
  115. * calculated using Low values.
  116. */
  117. index: 0,
  118. /**
  119. * The base period for indicator calculations. This is the number of
  120. * data points which are taken into account for the indicator
  121. * calculations.
  122. */
  123. period: 14
  124. }
  125. },
  126. /**
  127. * @lends Highcharts.Series.prototype
  128. */
  129. {
  130. processData: function () {
  131. var series = this, compareToMain = series.options.compareToMain, linkedParent = series.linkedParent;
  132. Series.prototype.processData.apply(series, arguments);
  133. if (linkedParent && linkedParent.compareValue && compareToMain) {
  134. series.compareValue = linkedParent.compareValue;
  135. }
  136. return;
  137. },
  138. bindTo: {
  139. series: true,
  140. eventName: 'updatedData'
  141. },
  142. hasDerivedData: true,
  143. useCommonDataGrouping: true,
  144. nameComponents: ['period'],
  145. nameSuffixes: [],
  146. calculateOn: 'init',
  147. // Defines on which other indicators is this indicator based on.
  148. requiredIndicators: [],
  149. requireIndicators: function () {
  150. var obj = {
  151. allLoaded: true
  152. };
  153. // Check whether all required indicators are loaded, else return
  154. // the object with missing indicator's name.
  155. this.requiredIndicators.forEach(function (indicator) {
  156. if (seriesTypes[indicator]) {
  157. seriesTypes[indicator].prototype.requireIndicators();
  158. }
  159. else {
  160. obj.allLoaded = false;
  161. obj.needed = indicator;
  162. }
  163. });
  164. return obj;
  165. },
  166. init: function (chart, options) {
  167. var indicator = this, requiredIndicators = indicator.requireIndicators();
  168. // Check whether all required indicators are loaded.
  169. if (!requiredIndicators.allLoaded) {
  170. return error(generateMessage(indicator.type, requiredIndicators.needed));
  171. }
  172. Series.prototype.init.call(indicator, chart, options);
  173. // Make sure we find series which is a base for an indicator
  174. chart.linkSeries();
  175. indicator.dataEventsToUnbind = [];
  176. /**
  177. * @private
  178. * @return {void}
  179. */
  180. function recalculateValues() {
  181. var oldData = indicator.points || [], oldDataLength = (indicator.xData || []).length, processedData = indicator.getValues(indicator.linkedParent, indicator.options.params) || {
  182. values: [],
  183. xData: [],
  184. yData: []
  185. }, croppedDataValues = [], overwriteData = true, oldFirstPointIndex, oldLastPointIndex, croppedData, min, max, i;
  186. // We need to update points to reflect changes in all,
  187. // x and y's, values. However, do it only for non-grouped
  188. // data - grouping does it for us (#8572)
  189. if (oldDataLength &&
  190. !indicator.hasGroupedData &&
  191. indicator.visible &&
  192. indicator.points) {
  193. // When data is cropped update only avaliable points (#9493)
  194. if (indicator.cropped) {
  195. if (indicator.xAxis) {
  196. min = indicator.xAxis.min;
  197. max = indicator.xAxis.max;
  198. }
  199. croppedData = indicator.cropData(processedData.xData, processedData.yData, min, max);
  200. for (i = 0; i < croppedData.xData.length; i++) {
  201. // (#10774)
  202. croppedDataValues.push([
  203. croppedData.xData[i]
  204. ].concat(splat(croppedData.yData[i])));
  205. }
  206. oldFirstPointIndex = processedData.xData.indexOf(indicator.xData[0]);
  207. oldLastPointIndex = processedData.xData.indexOf(indicator.xData[indicator.xData.length - 1]);
  208. // Check if indicator points should be shifted (#8572)
  209. if (oldFirstPointIndex === -1 &&
  210. oldLastPointIndex === processedData.xData.length - 2) {
  211. if (croppedDataValues[0][0] === oldData[0].x) {
  212. croppedDataValues.shift();
  213. }
  214. }
  215. indicator.updateData(croppedDataValues);
  216. // Omit addPoint() and removePoint() cases
  217. }
  218. else if (processedData.xData.length !== oldDataLength - 1 &&
  219. processedData.xData.length !== oldDataLength + 1) {
  220. overwriteData = false;
  221. indicator.updateData(processedData.values);
  222. }
  223. }
  224. if (overwriteData) {
  225. indicator.xData = processedData.xData;
  226. indicator.yData = processedData.yData;
  227. indicator.options.data = processedData.values;
  228. }
  229. // Removal of processedXData property is required because on
  230. // first translate processedXData array is empty
  231. if (indicator.bindTo.series === false) {
  232. delete indicator.processedXData;
  233. indicator.isDirty = true;
  234. indicator.redraw();
  235. }
  236. indicator.isDirtyData = false;
  237. }
  238. if (!indicator.linkedParent) {
  239. return error('Series ' +
  240. indicator.options.linkedTo +
  241. ' not found! Check `linkedTo`.', false, chart);
  242. }
  243. indicator.dataEventsToUnbind.push(addEvent(indicator.bindTo.series ?
  244. indicator.linkedParent : indicator.linkedParent.xAxis, indicator.bindTo.eventName, recalculateValues));
  245. if (indicator.calculateOn === 'init') {
  246. recalculateValues();
  247. }
  248. else {
  249. var unbinder = addEvent(indicator.chart, indicator.calculateOn, function () {
  250. recalculateValues();
  251. // Call this just once, on init
  252. unbinder();
  253. });
  254. }
  255. return indicator;
  256. },
  257. getName: function () {
  258. var name = this.name, params = [];
  259. if (!name) {
  260. (this.nameComponents || []).forEach(function (component, index) {
  261. params.push(this.options.params[component] +
  262. pick(this.nameSuffixes[index], ''));
  263. }, this);
  264. name = (this.nameBase || this.type.toUpperCase()) +
  265. (this.nameComponents ? ' (' + params.join(', ') + ')' : '');
  266. }
  267. return name;
  268. },
  269. getValues: function (series, params) {
  270. var period = params.period, xVal = series.xData, yVal = series.yData, yValLen = yVal.length, range = 0, sum = 0, SMA = [], xData = [], yData = [], index = -1, i, SMAPoint;
  271. if (xVal.length < period) {
  272. return;
  273. }
  274. // Switch index for OHLC / Candlestick / Arearange
  275. if (isArray(yVal[0])) {
  276. index = params.index ? params.index : 0;
  277. }
  278. // Accumulate first N-points
  279. while (range < period - 1) {
  280. sum += index < 0 ? yVal[range] : yVal[range][index];
  281. range++;
  282. }
  283. // Calculate value one-by-one for each period in visible data
  284. for (i = range; i < yValLen; i++) {
  285. sum += index < 0 ? yVal[i] : yVal[i][index];
  286. SMAPoint = [xVal[i], sum / period];
  287. SMA.push(SMAPoint);
  288. xData.push(SMAPoint[0]);
  289. yData.push(SMAPoint[1]);
  290. sum -= (index < 0 ?
  291. yVal[i - range] :
  292. yVal[i - range][index]);
  293. }
  294. return {
  295. values: SMA,
  296. xData: xData,
  297. yData: yData
  298. };
  299. },
  300. destroy: function () {
  301. this.dataEventsToUnbind.forEach(function (unbinder) {
  302. unbinder();
  303. });
  304. Series.prototype.destroy.apply(this, arguments);
  305. }
  306. });
  307. /**
  308. * A `SMA` series. If the [type](#series.sma.type) option is not specified, it
  309. * is inherited from [chart.type](#chart.type).
  310. *
  311. * @extends series,plotOptions.sma
  312. * @since 6.0.0
  313. * @product highstock
  314. * @excluding dataParser, dataURL, useOhlcData
  315. * @requires stock/indicators/indicators
  316. * @apioption series.sma
  317. */
  318. ''; // adds doclet above to the transpiled file