HistogramSeries.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. /* *
  2. *
  3. * Copyright (c) 2010-2021 Highsoft AS
  4. * Author: Sebastian Domas
  5. *
  6. * License: www.highcharts.com/license
  7. *
  8. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  9. *
  10. * */
  11. 'use strict';
  12. var __extends = (this && this.__extends) || (function () {
  13. var extendStatics = function (d, b) {
  14. extendStatics = Object.setPrototypeOf ||
  15. ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
  16. function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
  17. return extendStatics(d, b);
  18. };
  19. return function (d, b) {
  20. extendStatics(d, b);
  21. function __() { this.constructor = d; }
  22. d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
  23. };
  24. })();
  25. import DerivedSeriesMixin from '../../Mixins/DerivedSeries.js';
  26. import SeriesRegistry from '../../Core/Series/SeriesRegistry.js';
  27. var ColumnSeries = SeriesRegistry.seriesTypes.column;
  28. import U from '../../Core/Utilities.js';
  29. var arrayMax = U.arrayMax, arrayMin = U.arrayMin, correctFloat = U.correctFloat, extend = U.extend, isNumber = U.isNumber, merge = U.merge, objectEach = U.objectEach;
  30. /* ************************************************************************** *
  31. * HISTOGRAM
  32. * ************************************************************************** */
  33. /**
  34. * A dictionary with formulas for calculating number of bins based on the
  35. * base series
  36. **/
  37. var binsNumberFormulas = {
  38. 'square-root': function (baseSeries) {
  39. return Math.ceil(Math.sqrt(baseSeries.options.data.length));
  40. },
  41. 'sturges': function (baseSeries) {
  42. return Math.ceil(Math.log(baseSeries.options.data.length) * Math.LOG2E);
  43. },
  44. 'rice': function (baseSeries) {
  45. return Math.ceil(2 * Math.pow(baseSeries.options.data.length, 1 / 3));
  46. }
  47. };
  48. /**
  49. * Returns a function for mapping number to the closed (right opened) bins
  50. * @private
  51. * @param {Array<number>} bins - Width of the bins
  52. * @return {Function}
  53. **/
  54. function fitToBinLeftClosed(bins) {
  55. return function (y) {
  56. var i = 1;
  57. while (bins[i] <= y) {
  58. i++;
  59. }
  60. return bins[--i];
  61. };
  62. }
  63. /* *
  64. *
  65. * Class
  66. *
  67. * */
  68. /**
  69. * Histogram class
  70. * @private
  71. * @class
  72. * @name Highcharts.seriesTypes.histogram
  73. * @augments Highcharts.Series
  74. */
  75. var HistogramSeries = /** @class */ (function (_super) {
  76. __extends(HistogramSeries, _super);
  77. function HistogramSeries() {
  78. /* *
  79. *
  80. * Static Properties
  81. *
  82. * */
  83. var _this = _super !== null && _super.apply(this, arguments) || this;
  84. _this.data = void 0;
  85. _this.options = void 0;
  86. _this.points = void 0;
  87. _this.userOptions = void 0;
  88. return _this;
  89. /* eslint-enable valid-jsdoc */
  90. }
  91. /* *
  92. *
  93. * Functions
  94. *
  95. * */
  96. /* eslint-disable valid-jsdoc */
  97. HistogramSeries.prototype.binsNumber = function () {
  98. var binsNumberOption = this.options.binsNumber;
  99. var binsNumber = binsNumberFormulas[binsNumberOption] ||
  100. // #7457
  101. (typeof binsNumberOption === 'function' && binsNumberOption);
  102. return Math.ceil((binsNumber && binsNumber(this.baseSeries)) ||
  103. (isNumber(binsNumberOption) ?
  104. binsNumberOption :
  105. binsNumberFormulas['square-root'](this.baseSeries)));
  106. };
  107. HistogramSeries.prototype.derivedData = function (baseData, binsNumber, binWidth) {
  108. var series = this, max = correctFloat(arrayMax(baseData)),
  109. // Float correction needed, because first frequency value is not
  110. // corrected when generating frequencies (within for loop).
  111. min = correctFloat(arrayMin(baseData)), frequencies = [], bins = {}, data = [], x, fitToBin;
  112. binWidth = series.binWidth = (correctFloat(isNumber(binWidth) ?
  113. (binWidth || 1) :
  114. (max - min) / binsNumber));
  115. // #12077 negative pointRange causes wrong calculations,
  116. // browser hanging.
  117. series.options.pointRange = Math.max(binWidth, 0);
  118. // If binWidth is 0 then max and min are equaled,
  119. // increment the x with some positive value to quit the loop
  120. for (x = min;
  121. // This condition is needed because of the margin of error while
  122. // operating on decimal numbers. Without that, additional bin
  123. // was sometimes noticeable on the graph, because of too small
  124. // precision of float correction.
  125. x < max &&
  126. (series.userOptions.binWidth ||
  127. correctFloat(max - x) >= binWidth ||
  128. // #13069 - Every add and subtract operation should
  129. // be corrected, due to general problems with
  130. // operations on float numbers in JS.
  131. correctFloat(correctFloat(min + (frequencies.length * binWidth)) -
  132. x) <= 0); x = correctFloat(x + binWidth)) {
  133. frequencies.push(x);
  134. bins[x] = 0;
  135. }
  136. if (bins[min] !== 0) {
  137. frequencies.push(min);
  138. bins[min] = 0;
  139. }
  140. fitToBin = fitToBinLeftClosed(frequencies.map(function (elem) {
  141. return parseFloat(elem);
  142. }));
  143. baseData.forEach(function (y) {
  144. var x = correctFloat(fitToBin(y));
  145. bins[x]++;
  146. });
  147. objectEach(bins, function (frequency, x) {
  148. data.push({
  149. x: Number(x),
  150. y: frequency,
  151. x2: correctFloat(Number(x) + binWidth)
  152. });
  153. });
  154. data.sort(function (a, b) {
  155. return a.x - b.x;
  156. });
  157. data[data.length - 1].x2 = max;
  158. return data;
  159. };
  160. HistogramSeries.prototype.setDerivedData = function () {
  161. var yData = this.baseSeries.yData;
  162. if (!yData.length) {
  163. this.setData([]);
  164. return;
  165. }
  166. var data = this.derivedData(yData, this.binsNumber(), this.options.binWidth);
  167. this.setData(data, false);
  168. };
  169. /**
  170. * A histogram is a column series which represents the distribution of the
  171. * data set in the base series. Histogram splits data into bins and shows
  172. * their frequencies.
  173. *
  174. * @sample {highcharts} highcharts/demo/histogram/
  175. * Histogram
  176. *
  177. * @extends plotOptions.column
  178. * @excluding boostThreshold, dragDrop, pointInterval, pointIntervalUnit,
  179. * stacking, boostBlending
  180. * @product highcharts
  181. * @since 6.0.0
  182. * @requires modules/histogram
  183. * @optionparent plotOptions.histogram
  184. */
  185. HistogramSeries.defaultOptions = merge(ColumnSeries.defaultOptions, {
  186. /**
  187. * A preferable number of bins. It is a suggestion, so a histogram may
  188. * have a different number of bins. By default it is set to the square
  189. * root of the base series' data length. Available options are:
  190. * `square-root`, `sturges`, `rice`. You can also define a function
  191. * which takes a `baseSeries` as a parameter and should return a
  192. * positive integer.
  193. *
  194. * @type {"square-root"|"sturges"|"rice"|number|function}
  195. */
  196. binsNumber: 'square-root',
  197. /**
  198. * Width of each bin. By default the bin's width is calculated as
  199. * `(max - min) / number of bins`. This option takes precedence over
  200. * [binsNumber](#plotOptions.histogram.binsNumber).
  201. *
  202. * @type {number}
  203. */
  204. binWidth: void 0,
  205. pointPadding: 0,
  206. groupPadding: 0,
  207. grouping: false,
  208. pointPlacement: 'between',
  209. tooltip: {
  210. headerFormat: '',
  211. pointFormat: ('<span style="font-size: 10px">{point.x} - {point.x2}' +
  212. '</span><br/>' +
  213. '<span style="color:{point.color}">\u25CF</span>' +
  214. ' {series.name} <b>{point.y}</b><br/>')
  215. }
  216. });
  217. return HistogramSeries;
  218. }(ColumnSeries));
  219. extend(HistogramSeries.prototype, {
  220. addBaseSeriesEvents: DerivedSeriesMixin.addBaseSeriesEvents,
  221. addEvents: DerivedSeriesMixin.addEvents,
  222. destroy: DerivedSeriesMixin.destroy,
  223. hasDerivedData: DerivedSeriesMixin.hasDerivedData,
  224. init: DerivedSeriesMixin.init,
  225. setBaseSeries: DerivedSeriesMixin.setBaseSeries
  226. });
  227. SeriesRegistry.registerSeriesType('histogram', HistogramSeries);
  228. /* *
  229. *
  230. * Default Export
  231. *
  232. * */
  233. export default HistogramSeries;
  234. /* *
  235. *
  236. * API Options
  237. *
  238. * */
  239. /**
  240. * A `histogram` series. If the [type](#series.histogram.type) option is not
  241. * specified, it is inherited from [chart.type](#chart.type).
  242. *
  243. * @extends series,plotOptions.histogram
  244. * @excluding data, dataParser, dataURL, boostThreshold, boostBlending
  245. * @product highcharts
  246. * @since 6.0.0
  247. * @requires modules/histogram
  248. * @apioption series.histogram
  249. */
  250. /**
  251. * An integer identifying the index to use for the base series, or a string
  252. * representing the id of the series.
  253. *
  254. * @type {number|string}
  255. * @apioption series.histogram.baseSeries
  256. */
  257. ''; // adds doclets above to transpiled file