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);