virool-pivot
Version:
A web-based exploratory visualization UI for Druid.io
318 lines (276 loc) • 7.99 kB
text/typescript
import { Class, Instance, isInstanceOf, isImmutableClass } from 'immutable-class';
import { $, Set, valueFromJS, valueToJS, FilterAction, LimitAction } from 'plywood';
import { hasOwnProperty } from '../../../common/utils/general/general';
const NULL_COLOR = '#666666';
//const OTHERS_COLOR = '#AAAAAA';
const NORMAL_COLORS = [
'#2D95CA',
'#EFB925',
'#DA4E99',
'#4CC873',
'#745CBD',
'#EA7136',
'#E68EE0',
'#218C35',
'#B0B510',
'#904064'
];
function valuesToJS(values: Lookup<any>): Lookup<any> {
var valuesJS: Lookup<any> = {};
for (var i = 0; i < NORMAL_COLORS.length; i++) {
if (!hasOwnProperty(values, i)) continue;
valuesJS[i] = valueToJS(values[i]);
}
return valuesJS;
}
function valueEquals(v1: any, v2: any): boolean {
if (v1 === v2) return true;
if (!v1 !== !v2) return false;
if (v1.toISOString && v2.toISOString) return v1.valueOf() === v2.valueOf();
if (isImmutableClass(v1)) return v1.equals(v2);
return false;
}
function valuesEqual(values1: Lookup<any>, values2: Lookup<any>): boolean {
if (!Boolean(values1) === Boolean(values2)) return false;
if (values1 === values2) return true;
if (!values1 !== !values2) return false;
if (typeof values1 !== typeof values2) return false;
for (var i = 0; i < NORMAL_COLORS.length; i++) {
var v1 = values1[i];
var v2 = values2[i];
if (hasOwnProperty(values1, i) !== hasOwnProperty(values2, i)) return false;
if (!valueEquals(v1, v2)) return false;
}
return true;
}
function cloneValues(values: Lookup<any>): Lookup<any> {
var newValues: Lookup<any> = {};
for (var i = 0; i < NORMAL_COLORS.length; i++) {
if (!hasOwnProperty(values, i)) continue;
newValues[i] = values[i];
}
return newValues;
}
export interface ColorsValue {
dimension: string;
values?: Lookup<any>;
hasNull?: boolean;
limit?: number;
}
export interface ColorsJS {
dimension: string;
values?: Lookup<any>;
hasNull?: boolean;
limit?: number;
}
var check: Class<ColorsValue, ColorsJS>;
export class Colors implements Instance<ColorsValue, ColorsJS> {
static isColors(candidate: any): candidate is Colors {
return isInstanceOf(candidate, Colors);
}
static fromLimit(dimension: string, limit: number): Colors {
return new Colors({ dimension, limit });
}
static fromValues(dimension: string, values: any[]): Colors {
var valueLookup: Lookup<any> = {};
var hasNull = false;
var n = Math.min(values.length, NORMAL_COLORS.length + 1);
var i = 0;
var j = 0;
while (i < n) {
var v = values[i];
if (v === null) {
hasNull = true;
} else {
valueLookup[j] = v;
j++;
}
i++;
}
return new Colors({
dimension,
hasNull,
values: valueLookup
});
}
static fromJS(parameters: ColorsJS): Colors {
var value: ColorsValue = {
dimension: parameters.dimension,
limit: parameters.limit
};
var valuesJS = parameters.values;
if (valuesJS) {
var hasNull = Boolean(parameters.hasNull);
var values: Lookup<any> = {};
for (var i = 0; i < NORMAL_COLORS.length; i++) {
if (!hasOwnProperty(valuesJS, i)) continue;
var vJS = valuesJS[i];
if (vJS === null) {
hasNull = true; // Back compat (there might be a null in values)
} else {
values[i] = valueFromJS(vJS);
}
}
value.values = values;
value.hasNull = hasNull;
}
return new Colors(value);
}
public dimension: string;
public values: Lookup<any>;
public hasNull: boolean;
public limit: number;
constructor(parameters: ColorsValue) {
this.dimension = parameters.dimension;
if (!this.dimension) throw new Error('must have a dimension');
this.values = parameters.values;
this.hasNull = parameters.hasNull;
this.limit = parameters.limit;
if (!this.values && !this.limit) throw new Error('must have values or limit');
}
public valueOf(): ColorsValue {
return {
dimension: this.dimension,
values: this.values,
hasNull: this.hasNull,
limit: this.limit
};
}
public toJS(): ColorsJS {
var js: ColorsJS = {
dimension: this.dimension
};
if (this.values) js.values = valuesToJS(this.values);
if (this.hasNull) js.hasNull = true;
if (this.limit) js.limit = this.limit;
return js;
}
public toJSON(): ColorsJS {
return this.toJS();
}
public toString(): string {
return `[Colors: ${this.dimension}]`;
}
public equals(other: Colors): boolean {
return Colors.isColors(other) &&
valuesEqual(this.values, other.values) &&
this.hasNull === other.hasNull &&
this.limit === other.limit;
}
public numColors(): number {
var { values, limit } = this;
if (values) {
return Object.keys(values).length + Number(this.hasNull);
}
return limit;
}
public toArray(): any[] {
var { values, hasNull } = this;
if (!values) return null;
var vs: any[] = [];
if (hasNull) vs.push(null);
for (var i = 0; i < NORMAL_COLORS.length; i++) {
if (!hasOwnProperty(values, i)) continue;
vs.push(values[i]);
}
return vs;
}
public toSet(): Set {
if (!this.values) return null;
return Set.fromJS(this.toArray());
}
public toHavingFilter(segmentName?: string): FilterAction {
var { dimension, values } = this;
if (!segmentName) segmentName = dimension;
if (!values) return null;
return new FilterAction({
expression: $(segmentName).in(this.toSet())
});
}
public toLimitAction(): LimitAction {
return new LimitAction({
limit: this.numColors()
});
}
public toggle(v: any): Colors {
return this.has(v) ? this.remove(v) : this.add(v);
}
public valueIndex(v: any): number {
var { values } = this;
if (!values) return -1;
for (var i = 0; i < NORMAL_COLORS.length; i++) {
if (!hasOwnProperty(values, i)) continue;
if (valueEquals(values[i], v)) return i;
}
return -1;
}
public nextIndex(): number {
var { values } = this;
if (!values) return 0;
for (var i = 0; i < NORMAL_COLORS.length; i++) {
if (hasOwnProperty(values, i)) continue;
return i;
}
return -1;
}
public has(v: any): boolean {
if (v == null) return this.hasNull;
return this.valueIndex(v) !== -1;
}
public add(v: any): Colors {
if (this.has(v)) return this;
var value = this.valueOf();
if (v === null) {
value.hasNull = true;
} else {
var idx = this.nextIndex();
if (idx === -1) return this;
value.values = value.values ? cloneValues(value.values) : {};
value.values[idx] = v;
delete value.limit;
}
return new Colors(value);
}
public remove(v: any): Colors {
if (!this.has(v)) return this;
var value = this.valueOf();
if (v == null) {
value.hasNull = false;
} else {
var idx = this.valueIndex(v);
if (idx === -1) return this;
value.values = cloneValues(value.values);
delete value.values[idx];
delete value.limit;
}
return new Colors(value);
}
public getColors(valuesToColor: any[]): string[] {
var { values, limit, hasNull } = this;
if (values) {
return valuesToColor.map(value => {
if (value === null && hasNull) return NULL_COLOR;
var colorIdx = this.valueIndex(value);
return colorIdx === -1 ? null : NORMAL_COLORS[colorIdx];
});
} else {
var colors: string[] = [];
var colorIdx = 0;
for (var i = 0; i < valuesToColor.length; i++) {
if (i < limit) {
var v = valuesToColor[i];
if (v === null) {
colors.push(NULL_COLOR);
} else {
colors.push(NORMAL_COLORS[colorIdx]);
colorIdx++;
}
} else {
colors.push(null);
}
}
return colors;
}
}
}
check = Colors;