diff --git a/src/renderer/viz/expressions/aggregation/viewport/ViewportHistogram.js b/src/renderer/viz/expressions/aggregation/viewport/ViewportHistogram.js index 6ad58e537..2daddb8c9 100644 --- a/src/renderer/viz/expressions/aggregation/viewport/ViewportHistogram.js +++ b/src/renderer/viz/expressions/aggregation/viewport/ViewportHistogram.js @@ -7,21 +7,24 @@ import { CLUSTER_FEATURE_COUNT } from '../../../../schema'; * Generates a histogram. * * The histogram can be based on a categorical expression, in which case each category will correspond to a histogram bar. - * The histogram can be based on a numeric expression, in which case the minimum and maximum will be computed automatically and bars will be generated - * at regular intervals between the minimum and maximum. The number of bars in this case is controllable through the `size` parameter. + * + * The histogram can be based on a numeric expression, the buckets for the histogram is controllable through the `sizeOrBuckets` parameter. + * For numeric values of sizeOrBuckets, the minimum and maximum will be computed automatically and bars will be generated at regular intervals between the minimum and maximum. + * When providing sizeOrBuckets as a list of buckets, the values will get assigned to the first bucket matching the criteria [bucketMin <= value < bucketMax]. * * Histograms are useful to get insights and create widgets outside the scope of CARTO VL, see the following example for more info. * * @param {Number} input - expression to base the histogram - * @param {Number} size - Optional (defaults to 20). Number of bars to use if `x` is a numeric expression + * @param {Number|Array} sizeOrBuckets - Optional (defaults to 20). Number of bars to use if `x` is a numeric expression; or user-defined buckets for numeric expressions. * @param {Number} weight - Optional. Weight each occurrence differently based on this weight, defaults to `1`, which will generate a simple, non-weighted count. * @return {Histogram} Histogram * * @example Create and use an histogram. (String) * const s = carto.expressions; * const viz = new carto.Viz(` - * \@categoryHistogram: viewportHistogram($type) - * \@numericHistogram: viewportHistogram($amount, 3, 1) + * \@categoryHistogram: viewportHistogram($type) + * \@numericHistogram: viewportHistogram($amount, 3, 1) + * \@userDefinedHistogram: viewportHistogram($amount, [[0, 10], [10, 20], [20, 30]], 1) * `); * ... * console.log(viz.variables.categoryHistogram.eval()); @@ -52,12 +55,13 @@ import { CLUSTER_FEATURE_COUNT } from '../../../../schema'; * @api */ export default class ViewportHistogram extends BaseExpression { - constructor (x, size = 20, weight = 1) { + constructor (x, sizeOrBuckets = 20, weight = 1) { checkMaxArguments(arguments, 3, 'viewportHistogram'); super({ x: implicitCast(x), weight: implicitCast(weight) }); this.type = 'histogram'; - this._size = size; + this._sizeOrBuckets = sizeOrBuckets; + this._hasBuckets = Array.isArray(sizeOrBuckets); this._isViewport = true; this.inlineMaker = () => null; @@ -81,7 +85,7 @@ export default class ViewportHistogram extends BaseExpression { } this._cached = this.x.type === 'number' - ? _getNumericValue(this._histogram, this._size) + ? (this._hasBuckets ? _getBucketsValue(this._histogram, this._sizeOrBuckets) : _getNumericValue(this._histogram, this._sizeOrBuckets)) : _getCategoryValue(this._histogram); return this._cached; @@ -236,6 +240,29 @@ function _getNumericValue (histogram, size) { }); } +function _getBucketsValue ([...histogram], buckets) { + const nBuckets = buckets.length; + const hist = Array(nBuckets).fill(0); + + for (let i = 0, len = histogram.length; i < len; i++) { + const x = histogram[i][0]; + for (let j = 0; j < nBuckets; j++) { + const bucket = buckets[j]; + if (x >= bucket[0] && x < bucket[1]) { + hist[j] += histogram[i][1]; + break; + } + } + } + + return hist.map((count, index) => { + return { + x: buckets[index], + y: count + }; + }); +} + function _getCategoryValue (histogram) { return [...histogram] .map(([x, y]) => { diff --git a/test/unit/renderer/viz/expressions/aggregation/viewportAggregation.test.js b/test/unit/renderer/viz/expressions/aggregation/viewportAggregation.test.js index 860f6d171..9bebf271b 100644 --- a/test/unit/renderer/viz/expressions/aggregation/viewportAggregation.test.js +++ b/test/unit/renderer/viz/expressions/aggregation/viewportAggregation.test.js @@ -166,6 +166,21 @@ describe('src/renderer/viz/expressions/viewportAggregation', () => { ]); }); + it('viewportHistogram($price, [[0, 1], [1, 2]], 1) should eval to the correct histogram', () => { + const viewportHistogram = s.viewportHistogram($price, [[0, 1.5], [1.5, 3]], 1); + fakeDrawMetadata(viewportHistogram); + expect(viewportHistogram.value).toEqual([ + { + x: [0, 1.5], + y: 2 + }, + { + x: [1.5, 3], + y: 2 + } + ]); + }); + it('viewportHistogram($cat) should eval to the correct histogram', () => { const viewportHistogram = s.viewportHistogram($cat); fakeDrawMetadata(viewportHistogram);