virool-pivot
Version:
A web-based exploratory visualization UI for Druid.io
220 lines (171 loc) • 8.19 kB
text/typescript
import { List } from 'immutable';
import { Class, Instance, isInstanceOf, immutableArraysEqual } from 'immutable-class';
import { Timezone, Duration, day, hour } from 'chronoshift';
import { $, Expression, RefExpression, TimeRange, TimeBucketAction, SortAction, NumberRange, Range } from 'plywood';
import { immutableListsEqual } from '../../utils/general/general';
import { Dimension } from '../dimension/dimension';
import { Filter } from '../filter/filter';
import { SplitCombine, SplitCombineJS, SplitCombineContext } from '../split-combine/split-combine';
import { NumberBucketAction } from "plywood";
import { getDefaultGranularityForKind, getBestBucketUnitForRange } from "../granularity/granularity";
function withholdSplit(splits: List<SplitCombine>, split: SplitCombine, allowIndex: number): List<SplitCombine> {
return <List<SplitCombine>>splits.filter((s, i) => {
return i === allowIndex || !s.equalsByExpression(split);
});
}
function swapSplit(splits: List<SplitCombine>, split: SplitCombine, other: SplitCombine, allowIndex: number): List<SplitCombine> {
return <List<SplitCombine>>splits.map((s, i) => {
return (i === allowIndex || !s.equalsByExpression(split)) ? s : other;
});
}
export type SplitsValue = List<SplitCombine>;
export type SplitsJS = SplitCombineJS | SplitCombineJS[];
export type SplitContext = SplitCombineContext;
var check: Class<SplitsValue, SplitsJS>;
export class Splits implements Instance<SplitsValue, SplitsJS> {
static EMPTY: Splits;
static isSplits(candidate: any): candidate is Splits {
return isInstanceOf(candidate, Splits);
}
static fromSplitCombine(splitCombine: SplitCombine): Splits {
return new Splits(<List<SplitCombine>>List([splitCombine]));
}
static fromJS(parameters: SplitsJS, context?: SplitContext): Splits {
if (!Array.isArray(parameters)) parameters = [parameters as any];
return new Splits(List((parameters as SplitCombineJS[]).map(splitCombine => SplitCombine.fromJS(splitCombine, context))));
}
public splitCombines: List<SplitCombine>;
constructor(parameters: SplitsValue) {
this.splitCombines = parameters;
}
public valueOf(): SplitsValue {
return this.splitCombines;
}
public toJS(): SplitsJS {
return this.splitCombines.toArray().map(splitCombine => splitCombine.toJS());
}
public toJSON(): SplitsJS {
return this.toJS();
}
public toString() {
return this.splitCombines.map(splitCombine => splitCombine.toString()).join(',');
}
public equals(other: Splits): boolean {
return Splits.isSplits(other) &&
immutableListsEqual(this.splitCombines, other.splitCombines);
}
public replaceByIndex(index: number, replace: SplitCombine): Splits {
var { splitCombines } = this;
if (splitCombines.size === index) return this.insertByIndex(index, replace);
var replacedSplit = splitCombines.get(index);
splitCombines = <List<SplitCombine>>splitCombines.map((s, i) => i === index ? replace : s);
splitCombines = swapSplit(splitCombines, replace, replacedSplit, index);
return new Splits(splitCombines);
}
public insertByIndex(index: number, insert: SplitCombine): Splits {
var { splitCombines } = this;
splitCombines = <List<SplitCombine>>splitCombines.splice(index, 0, insert);
splitCombines = withholdSplit(splitCombines, insert, index);
return new Splits(splitCombines);
}
public addSplit(split: SplitCombine): Splits {
var { splitCombines } = this;
return this.insertByIndex(splitCombines.size, split);
}
public removeSplit(split: SplitCombine): Splits {
return new Splits(<List<SplitCombine>>this.splitCombines.filter(s => s !== split));
}
public changeSortAction(sort: SortAction): Splits {
return new Splits(<List<SplitCombine>>this.splitCombines.map(s => s.changeSortAction(sort)));
}
public getTitle(dimensions: List<Dimension>): string {
return this.splitCombines.map(s => s.getDimension(dimensions).title).join(', ');
}
public length(): number {
return this.splitCombines.size;
}
public forEach(sideEffect: (value?: SplitCombine, key?: number, iter?: List<SplitCombine>) => any, context?: any): number {
return this.splitCombines.forEach(sideEffect, context);
}
public get(index: number): SplitCombine {
return this.splitCombines.get(index);
}
public first(): SplitCombine {
return this.splitCombines.first();
}
public last(): SplitCombine {
return this.splitCombines.last();
}
public findSplitForDimension(dimension: Dimension): SplitCombine {
var dimensionExpression = dimension.expression;
return this.splitCombines.find((s) => s.expression.equals(dimensionExpression));
}
public hasSplitOn(dimension: Dimension): boolean {
return Boolean(this.findSplitForDimension(dimension));
}
public replace(search: SplitCombine, replace: SplitCombine): Splits {
return new Splits(<List<SplitCombine>>this.splitCombines.map((s) => s.equals(search) ? replace : s));
}
public map(mapper: (value?: SplitCombine, key?: number) => SplitCombine, context?: any): Splits {
return new Splits(<List<SplitCombine>>this.splitCombines.map(mapper, context));
}
public toArray(): SplitCombine[] {
return this.splitCombines.toArray();
}
public removeBucketingFrom(expressions: Expression[]) {
var changed = false;
var newSplitCombines = <List<SplitCombine>>this.splitCombines.map((splitCombine) => {
if (!splitCombine.bucketAction) return splitCombine;
var splitCombineExpression = splitCombine.expression;
if (expressions.every(ex => !ex.equals(splitCombineExpression))) return splitCombine;
changed = true;
return splitCombine.changeBucketAction(null);
});
return changed ? new Splits(newSplitCombines) : this;
}
public updateWithFilter(filter: Filter, dimensions: List<Dimension>): Splits {
if (filter.isRelative()) throw new Error('can not be a relative filter');
var changed = false;
var newSplitCombines = <List<SplitCombine>>this.splitCombines.map((splitCombine) => {
if (splitCombine.bucketAction) return splitCombine;
var splitExpression = splitCombine.expression;
var splitDimension = dimensions.find(d => splitExpression.equals(d.expression));
var splitKind = splitDimension.kind;
if (!splitDimension || !(splitKind === 'time' || splitKind === 'number')) return splitCombine;
changed = true;
var selectionSet = filter.getLiteralSet(splitExpression);
var extent = selectionSet ? selectionSet.extent() : null;
if (splitKind === 'time') {
return splitCombine.changeBucketAction(new TimeBucketAction({
duration: TimeRange.isTimeRange(extent) ? (getBestBucketUnitForRange(extent, false, splitDimension.bucketedBy, splitDimension.granularities) as Duration) :
(getDefaultGranularityForKind('time', splitDimension.bucketedBy, splitDimension.granularities) as TimeBucketAction).duration
}));
} else if (splitKind === 'number') {
return splitCombine.changeBucketAction(new NumberBucketAction({
size: extent ? (getBestBucketUnitForRange(extent, false, splitDimension.bucketedBy, splitDimension.granularities) as number) :
(getDefaultGranularityForKind('number', splitDimension.bucketedBy, splitDimension.granularities) as NumberBucketAction).size
}));
}
throw new Error('unknown extent type');
});
return changed ? new Splits(newSplitCombines) : this;
}
public constrainToDimensions(dimensions: List<Dimension>): Splits {
var changed = false;
var splitCombines: SplitCombine[] = [];
this.splitCombines.forEach((split) => {
if (split.getDimension(dimensions)) {
splitCombines.push(split);
} else {
changed = true;
// Potential special handling for time split would go here
}
});
return changed ? new Splits(List(splitCombines)) : this;
}
public timezoneDependant(): boolean {
return this.splitCombines.some((splitCombine) => splitCombine.timezoneDependant());
}
}
check = Splits;
Splits.EMPTY = new Splits(<List<SplitCombine>>List());