UNPKG

highcharts

Version:
155 lines (154 loc) 5.18 kB
/* * * * (c) 2010-2024 Highsoft AS * Author: Sebastian Domas * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ 'use strict'; import DerivedComposition from '../DerivedComposition.js'; import HistogramSeriesDefaults from './HistogramSeriesDefaults.js'; import SeriesRegistry from '../../Core/Series/SeriesRegistry.js'; const { column: ColumnSeries } = SeriesRegistry.seriesTypes; import U from '../../Core/Utilities.js'; const { arrayMax, arrayMin, correctFloat, extend, isNumber, merge } = U; /* ************************************************************************** * * HISTOGRAM * ************************************************************************** */ /** * A dictionary with formulas for calculating number of bins based on the * base series **/ const binsNumberFormulas = { 'square-root': function (baseSeries) { return Math.ceil(Math.sqrt(baseSeries.options.data.length)); }, 'sturges': function (baseSeries) { return Math.ceil(Math.log(baseSeries.options.data.length) * Math.LOG2E); }, 'rice': function (baseSeries) { return Math.ceil(2 * Math.pow(baseSeries.options.data.length, 1 / 3)); } }; /** * Returns a function for mapping number to the closed (right opened) bins * @private * @param {Array<number>} bins * Width of the bins */ function fitToBinLeftClosed(bins) { return function (y) { let i = 1; while (bins[i] <= y) { i++; } return bins[--i]; }; } /* * * * Class * * */ /** * Histogram class * @private * @class * @name Highcharts.seriesTypes.histogram * @augments Highcharts.Series */ class HistogramSeries extends ColumnSeries { /* * * * Functions * * */ binsNumber() { const binsNumberOption = this.options.binsNumber; const binsNumber = binsNumberFormulas[binsNumberOption] || // #7457 (typeof binsNumberOption === 'function' && binsNumberOption); return Math.ceil((binsNumber && binsNumber(this.baseSeries)) || (isNumber(binsNumberOption) ? binsNumberOption : binsNumberFormulas['square-root'](this.baseSeries))); } derivedData(baseData, binsNumber, binWidth) { const series = this, max = correctFloat(arrayMax(baseData)), // Float correction needed, because first frequency value is not // corrected when generating frequencies (within for loop). min = correctFloat(arrayMin(baseData)), frequencies = [], bins = {}, data = []; let x; binWidth = series.binWidth = (correctFloat(isNumber(binWidth) ? (binWidth || 1) : (max - min) / binsNumber)); // #12077 negative pointRange causes wrong calculations, // browser hanging. series.options.pointRange = Math.max(binWidth, 0); // If binWidth is 0 then max and min are equaled, // increment the x with some positive value to quit the loop for (x = min; // This condition is needed because of the margin of error while // operating on decimal numbers. Without that, additional bin // was sometimes noticeable on the graph, because of too small // precision of float correction. x < max && (series.userOptions.binWidth || correctFloat(max - x) >= binWidth || // #13069 - Every add and subtract operation should // be corrected, due to general problems with // operations on float numbers in JS. correctFloat(correctFloat(min + (frequencies.length * binWidth)) - x) <= 0); x = correctFloat(x + binWidth)) { frequencies.push(x); bins[x] = 0; } if (bins[min] !== 0) { frequencies.push(min); bins[min] = 0; } const fitToBin = fitToBinLeftClosed(frequencies.map((elem) => parseFloat(elem))); for (const y of baseData) { bins[correctFloat(fitToBin(y))]++; } for (const key of Object.keys(bins)) { data.push({ x: Number(key), y: bins[key], x2: correctFloat(Number(key) + binWidth) }); } data.sort((a, b) => (a.x - b.x)); data[data.length - 1].x2 = max; return data; } setDerivedData() { const yData = this.baseSeries?.getColumn('y'); if (!yData?.length) { this.setData([]); return; } const data = this.derivedData(yData, this.binsNumber(), this.options.binWidth); this.setData(data, false); } } /* * * * Static Properties * * */ HistogramSeries.defaultOptions = merge(ColumnSeries.defaultOptions, HistogramSeriesDefaults); extend(HistogramSeries.prototype, { hasDerivedData: DerivedComposition.hasDerivedData }); DerivedComposition.compose(HistogramSeries); SeriesRegistry.registerSeriesType('histogram', HistogramSeries); /* * * * Default Export * * */ export default HistogramSeries;