histogram.src.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. /**
  2. * @license @product.name@ JS v@product.version@ (@product.date@)
  3. *
  4. * (c) 2010-2017 Highsoft AS
  5. * Author: Sebastian Domas
  6. *
  7. * License: www.highcharts.com/license
  8. */
  9. 'use strict';
  10. import H from '../parts/Globals.js';
  11. import derivedSeriesMixin from '../mixins/derived-series.js';
  12. var objectEach = H.objectEach,
  13. seriesType = H.seriesType,
  14. correctFloat = H.correctFloat,
  15. isNumber = H.isNumber,
  16. arrayMax = H.arrayMax,
  17. arrayMin = H.arrayMin,
  18. merge = H.merge;
  19. /* ***************************************************************************
  20. *
  21. * HISTOGRAM
  22. *
  23. **************************************************************************** */
  24. /**
  25. * A dictionary with formulas for calculating number of bins based on the
  26. * base series
  27. **/
  28. var binsNumberFormulas = {
  29. 'square-root': function (baseSeries) {
  30. return Math.round(Math.sqrt(baseSeries.options.data.length));
  31. },
  32. 'sturges': function (baseSeries) {
  33. return Math.ceil(Math.log(baseSeries.options.data.length) * Math.LOG2E);
  34. },
  35. 'rice': function (baseSeries) {
  36. return Math.ceil(2 * Math.pow(baseSeries.options.data.length, 1 / 3));
  37. }
  38. };
  39. /**
  40. * Returns a function for mapping number to the closed (right opened) bins
  41. *
  42. * @param {number} binWidth - width of the bin
  43. * @returns {function}
  44. **/
  45. function fitToBinLeftClosed(bins) {
  46. return function (y) {
  47. var i = 1;
  48. while (bins[i] <= y) {
  49. i++;
  50. }
  51. return bins[--i];
  52. };
  53. }
  54. /**
  55. * Histogram class
  56. *
  57. * @constructor seriesTypes.histogram
  58. * @augments seriesTypes.column
  59. * @mixes DerivedSeriesMixin
  60. **/
  61. /**
  62. * A histogram is a column series which represents the distribution of the data
  63. * set in the base series. Histogram splits data into bins and shows their
  64. * frequencies.
  65. *
  66. * @product highcharts
  67. * @sample {highcharts} highcharts/demo/histogram/ Histogram
  68. * @since 6.0.0
  69. * @extends plotOptions.column
  70. * @excluding boostThreshold, pointInterval, pointIntervalUnit, stacking
  71. * @optionparent plotOptions.histogram
  72. **/
  73. seriesType('histogram', 'column', {
  74. /**
  75. * A preferable number of bins. It is a suggestion, so a histogram may have
  76. * a different number of bins. By default it is set to the square root
  77. * of the base series' data length. Available options are: `square-root`,
  78. * `sturges`, `rice`. You can also define a function which takes a
  79. * `baseSeries` as a parameter and should return a positive integer.
  80. *
  81. * @type {String|Number|Function}
  82. * @validvalue ["square-root", "sturges", "rice"]
  83. */
  84. binsNumber: 'square-root',
  85. /**
  86. * Width of each bin. By default the bin's width is calculated as
  87. * `(max - min) / number of bins`. This option takes precedence over
  88. * [binsNumber](#plotOptions.histogram.binsNumber).
  89. *
  90. * @type {Number}
  91. */
  92. binWidth: undefined,
  93. pointPadding: 0,
  94. groupPadding: 0,
  95. grouping: false,
  96. pointPlacement: 'between',
  97. tooltip: {
  98. headerFormat: '',
  99. pointFormat: '<span style="font-size: 10px">{point.x} - {point.x2}' +
  100. '</span><br/>' +
  101. '<span style="color:{point.color}">\u25CF</span>' +
  102. ' {series.name} <b>{point.y}</b><br/>'
  103. }
  104. }, merge(derivedSeriesMixin, {
  105. setDerivedData: function () {
  106. var data = this.derivedData(
  107. this.baseSeries.yData,
  108. this.binsNumber(),
  109. this.options.binWidth
  110. );
  111. this.setData(data, false);
  112. },
  113. derivedData: function (baseData, binsNumber, binWidth) {
  114. var max = arrayMax(baseData),
  115. min = arrayMin(baseData),
  116. frequencies = [],
  117. bins = {},
  118. data = [],
  119. x,
  120. fitToBin;
  121. binWidth = this.binWidth = correctFloat(
  122. isNumber(binWidth) ?
  123. (binWidth || 1) :
  124. (max - min) / binsNumber
  125. );
  126. // If binWidth is 0 then max and min are equaled,
  127. // increment the x with some positive value to quit the loop
  128. for (x = min; x < max; x = correctFloat(x + binWidth)) {
  129. frequencies.push(x);
  130. bins[x] = 0;
  131. }
  132. if (bins[min] !== 0) {
  133. frequencies.push(correctFloat(min));
  134. bins[correctFloat(min)] = 0;
  135. }
  136. fitToBin = fitToBinLeftClosed(
  137. frequencies.map(function (elem) {
  138. return parseFloat(elem);
  139. })
  140. );
  141. baseData.forEach(function (y) {
  142. var x = correctFloat(fitToBin(y));
  143. bins[x]++;
  144. });
  145. objectEach(bins, function (frequency, x) {
  146. data.push({
  147. x: Number(x),
  148. y: frequency,
  149. x2: correctFloat(Number(x) + binWidth)
  150. });
  151. });
  152. data.sort(function (a, b) {
  153. return a.x - b.x;
  154. });
  155. return data;
  156. },
  157. binsNumber: function () {
  158. var binsNumberOption = this.options.binsNumber;
  159. var binsNumber = binsNumberFormulas[binsNumberOption] ||
  160. // #7457
  161. (typeof binsNumberOption === 'function' && binsNumberOption);
  162. return Math.ceil(
  163. (binsNumber && binsNumber(this.baseSeries)) ||
  164. (
  165. isNumber(binsNumberOption) ?
  166. binsNumberOption :
  167. binsNumberFormulas['square-root'](this.baseSeries)
  168. )
  169. );
  170. }
  171. }));
  172. /**
  173. * A `histogram` series. If the [type](#series.histogram.type) option is not
  174. * specified, it is inherited from [chart.type](#chart.type).
  175. *
  176. * @type {Object}
  177. * @since 6.0.0
  178. * @extends series,plotOptions.histogram
  179. * @excluding dataParser,dataURL,data
  180. * @product highcharts
  181. * @apioption series.histogram
  182. */
  183. /**
  184. * An integer identifying the index to use for the base series, or a string
  185. * representing the id of the series.
  186. *
  187. * @type {Number|String}
  188. * @default undefined
  189. * @apioption series.histogram.baseSeries
  190. */
  191. /**
  192. * An array of data points for the series. For the `histogram` series type,
  193. * points are calculated dynamically. See
  194. * [histogram.baseSeries](#series.histogram.baseSeries).
  195. *
  196. * @type {Array<Object|Array>}
  197. * @since 6.0.0
  198. * @extends series.column.data
  199. * @product highcharts
  200. * @apioption series.histogram.data
  201. */