virool-pivot
Version:
A web-based exploratory visualization UI for Druid.io
213 lines (185 loc) • 5.76 kB
text/typescript
import { List } from 'immutable';
import { Class, Instance, isInstanceOf } from 'immutable-class';
import * as numeral from 'numeral';
import { $, Expression, ExpressionJS, Datum, ApplyAction, AttributeInfo, ChainExpression, helper } from 'plywood';
import { verifyUrlSafeName, makeTitle, makeUrlSafeName } from '../../utils/general/general';
function formatFnFactory(format: string): (n: number) => string {
return (n: number) => {
if (isNaN(n) || !isFinite(n)) return '-';
return numeral(n).format(format);
};
}
export interface MeasureValue {
name: string;
title?: string;
expression?: Expression;
format?: string;
}
export interface MeasureJS {
name: string;
title?: string;
expression?: ExpressionJS | string;
format?: string;
}
var check: Class<MeasureValue, MeasureJS>;
export class Measure implements Instance<MeasureValue, MeasureJS> {
static DEFAULT_FORMAT = '0,0.0 a';
static INTEGER_FORMAT = '0,0 a';
static isMeasure(candidate: any): candidate is Measure {
return isInstanceOf(candidate, Measure);
}
static getMeasure(measures: List<Measure>, measureName: string): Measure {
if (!measureName) return null;
measureName = measureName.toLowerCase(); // Case insensitive
return measures.find(measure => measure.name.toLowerCase() === measureName);
}
/**
* Look for all instances of aggregateAction($blah) and return the blahs
* @param ex
* @returns {string[]}
*/
static getAggregateReferences(ex: Expression): string[] {
var references: string[] = [];
ex.forEach((ex: Expression) => {
if (ex instanceof ChainExpression) {
var actions = ex.actions;
for (var action of actions) {
if (action.isAggregate()) {
references = references.concat(action.getFreeReferences());
}
}
}
});
return helper.deduplicateSort(references);
}
/**
* Look for all instances of countDistinct($blah) and return the blahs
* @param ex
* @returns {string[]}
*/
static getCountDistinctReferences(ex: Expression): string[] {
var references: string[] = [];
ex.forEach((ex: Expression) => {
if (ex instanceof ChainExpression) {
var actions = ex.actions;
for (var action of actions) {
if (action.action === 'countDistinct') {
references = references.concat(action.getFreeReferences());
}
}
}
});
return helper.deduplicateSort(references);
}
static measuresFromAttributeInfo(attribute: AttributeInfo): Measure[] {
var { name, special } = attribute;
var $main = $('main');
var ref = $(name);
if (special) {
if (special === 'unique' || special === 'theta') {
return [
new Measure({
name: makeUrlSafeName(name),
expression: $main.countDistinct(ref)
})
];
} else if (special === 'histogram') {
return [
new Measure({
name: makeUrlSafeName(name + '_p95'),
expression: $main.quantile(ref, 0.95)
}),
new Measure({
name: makeUrlSafeName(name + '_p99'),
expression: $main.quantile(ref, 0.99)
})
];
}
}
var expression = $main.sum(ref);
var makerAction = attribute.makerAction;
if (makerAction) {
switch (makerAction.action) {
case 'min':
expression = $main.min(ref);
break;
case 'max':
expression = $main.max(ref);
break;
//default: // sum, count
}
}
return [new Measure({
name: makeUrlSafeName(name),
expression
})];
}
static fromJS(parameters: MeasureJS): Measure {
var name = parameters.name;
return new Measure({
name,
title: parameters.title,
expression: parameters.expression ? Expression.fromJSLoose(parameters.expression) : $('main').sum($(name)),
format: parameters.format
});
}
public name: string;
public title: string;
public expression: Expression;
public format: string;
public formatFn: (n: number) => string;
constructor(parameters: MeasureValue) {
var name = parameters.name;
verifyUrlSafeName(name);
this.name = name;
this.title = parameters.title || makeTitle(name);
var expression = parameters.expression;
if (!expression) throw new Error('measure must have expression');
this.expression = expression;
var format = parameters.format || Measure.DEFAULT_FORMAT;
if (format[0] === '(') throw new Error('can not have format that uses ( )');
this.format = format;
this.formatFn = formatFnFactory(format);
}
public valueOf(): MeasureValue {
return {
name: this.name,
title: this.title,
expression: this.expression,
format: this.format
};
}
public toJS(): MeasureJS {
var js: MeasureJS = {
name: this.name,
title: this.title,
expression: this.expression.toJS()
};
if (this.format !== Measure.DEFAULT_FORMAT) js.format = this.format;
return js;
}
public toJSON(): MeasureJS {
return this.toJS();
}
public toString(): string {
return `[Measure: ${this.name}]`;
}
public equals(other: Measure): boolean {
return Measure.isMeasure(other) &&
this.name === other.name &&
this.title === other.title &&
this.expression.equals(other.expression) &&
this.format === other.format;
}
public toApplyAction(): ApplyAction {
var { name, expression } = this;
return new ApplyAction({
name: name,
expression: expression
});
}
public formatDatum(datum: Datum): string {
return this.formatFn(datum[this.name] as number);
}
}
check = Measure;