UNPKG

virool-pivot

Version:

A web-based exploratory visualization UI for Druid.io

220 lines (171 loc) 8.19 kB
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());