Skip to content

Commit

Permalink
restructure and merge tasks and sorter
Browse files Browse the repository at this point in the history
  • Loading branch information
sgratzl committed Dec 15, 2018
1 parent e53237a commit 56b0690
Show file tree
Hide file tree
Showing 9 changed files with 751 additions and 717 deletions.
41 changes: 29 additions & 12 deletions src/internal/math.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ export interface IBuilder<T, R> {
build(): R;
}

export function boxplotBuilder(): IBuilder<number, IAdvancedBoxPlotData> {
export function boxplotBuilder(): IBuilder<number, IAdvancedBoxPlotData> & { buildArr: (s: Float32Array) => IAdvancedBoxPlotData} {
// filter out NaN
let min = Number.POSITIVE_INFINITY;
let max = Number.NEGATIVE_INFINITY;
Expand All @@ -208,9 +208,14 @@ export function boxplotBuilder(): IBuilder<number, IAdvancedBoxPlotData> {
max = v;
}
sum += v;
};

const pushAndSave = (v: number) => {
push(v);
values.push(v);
};


const invalid = {
min: NaN,
max: NaN,
Expand All @@ -227,9 +232,9 @@ export function boxplotBuilder(): IBuilder<number, IAdvancedBoxPlotData> {

const buildImpl = (s: ArrayLike<number>) => {
const valid = length - missing;
const median = quantile(s, 0.5)!;
const q1 = quantile(s, 0.25)!;
const q3 = quantile(s, 0.75)!;
const median = quantile(s, 0.5, valid)!;
const q1 = quantile(s, 0.25, valid)!;
const q3 = quantile(s, 0.75, valid)!;

const iqr = q3 - q1;
const left = q1 - 1.5 * iqr;
Expand All @@ -239,7 +244,7 @@ export function boxplotBuilder(): IBuilder<number, IAdvancedBoxPlotData> {
// look for the closests value which is bigger than the computed left
let whiskerLow = left;
// tslint:disable-next-line:prefer-for-of
for (let i = 0; i < s.length; ++i) {
for (let i = 0; i < valid; ++i) {
const v = s[i];
if (left < v) {
whiskerLow = v;
Expand All @@ -251,7 +256,7 @@ export function boxplotBuilder(): IBuilder<number, IAdvancedBoxPlotData> {
// look for the closests value which is smaller than the computed right
let whiskerHigh = right;
const reversedOutliers: number[] = [];
for (let i = s.length - 1; i >= 0; --i) {
for (let i = valid - 1; i >= 0; --i) {
const v = s[i];
if (v < right) {
whiskerHigh = v;
Expand Down Expand Up @@ -289,7 +294,22 @@ export function boxplotBuilder(): IBuilder<number, IAdvancedBoxPlotData> {
return buildImpl(s);
};

return {push, build, pushAll: pushAll(push)};
const buildArr = (vs: Float32Array) => {
const s = vs.slice().sort();
// tslint:disable-next-line:prefer-for-of
for (let j = 0; j < vs.length; ++j) {
push(vs[j]);
}
// missing are the last
return buildImpl(s);
};

return {
push: pushAndSave,
build,
buildArr,
pushAll: pushAll(pushAndSave)
};
}

/**
Expand Down Expand Up @@ -790,14 +810,11 @@ function sortWorkerMain(self: IPoorManWorkerScope) {
const boxplotStats = (r: IBoxPlottatsMessageRequest) => {
const data = r.data ? r.data : <Float32Array>refs.get(r.ref)!;
const b = boxplotBuilder();
// tslint:disable-next-line:prefer-for-of
for (let i = 0; i < data.length; ++i) {
b.push(data[i]);
}

self.postMessage(<IBoxPlotStatsMessageResponse>{
type: r.type,
uid: r.uid,
stats: b.build()
stats: b.buildArr(data)
});
};

Expand Down
15 changes: 6 additions & 9 deletions src/model/Column.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import AEventDispatcher from '../internal/AEventDispatcher';
import AEventDispatcher, {IEventListener} from '../internal/AEventDispatcher';
import {ISequence} from '../internal/interable';
import {similar} from '../internal/math';
import {fixCSS} from '../internal/utils';
import {IColumnDump} from '../provider/interfaces';
import {isSortingAscByDefault} from './annotations';
import {defaultGroup} from './Group';
import {IColumnDesc, IDataRow, IGroup} from './interfaces';
import {ECompareValueType, IColumnDesc, IDataRow, IGroup} from './interfaces';
import Ranking, {ISortCriteria} from './Ranking';
import {IEventListener} from '../internal/AEventDispatcher';
import {isSortingAscByDefault} from './annotations';
import {IColumnDump} from '../provider/interfaces';
import {ECompareValueType} from '../provider/sort';
import {ISequence} from '../internal/interable';

export {IColumnDesc} from './interfaces';
export {ECompareValueType} from '../provider/sort';
export {ECompareValueType, IColumnDesc} from './interfaces';

export interface IFlatColumn {
readonly col: Column;
Expand Down
26 changes: 14 additions & 12 deletions src/model/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,16 +107,18 @@ export function isGroup(item: IGroupData | IGroupItem): item is IGroupData {
return item && (<IGroupItem>item).group == null; // use .group as separator
}

/** @internal */
export function toGroupMeta(index: number, total: number): IGroupMeta {
if (total === 1) {
return 'first last';
}
if (index === 0) {
return 'first';
}
if (index === total - 1) {
return 'last';
}
return 'inner';
export enum ECompareValueType {
BINARY,
COUNT, // count max to the number of rows
UINT8,
UINT16,
UINT32,
INT8,
INT16,
INT32,
FLOAT,
FLOAT_ASC,
DOUBLE,
DOUBLE_ASC,
STRING
}
195 changes: 195 additions & 0 deletions src/provider/DirectRenderTasks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
import Column, {IDataRow, Ranking, IndicesArray, IGroup, IOrderedGroup, INumberColumn, IDateColumn, ICategoricalLikeColumn} from '../model';
import {ARenderTasks, IRenderTaskExectutor, taskNow} from './tasks';
import {toIndexArray, sortComplex, getNumberOfBins} from '../internal';
import {CompareLookup} from './sort';
import {ISequence} from '../internal/interable';

/**
* @internal
*/
export function sortDirect(indices: IndicesArray, _singleCall: boolean, lookups?: CompareLookup) {
const order = toIndexArray(indices);
if (lookups) {
sortComplex(order, lookups.sortOrders);
}
return Promise.resolve(order);
}

export class DirectRenderTasks extends ARenderTasks implements IRenderTaskExectutor {

protected readonly cache = new Map<string, any>();

setData(data: IDataRow[]) {
this.data = data;
this.cache.clear();
}


dirtyColumn(col: Column, type: 'data' | 'summary' | 'group') {
if (type === 'group') {
return; // not cached
}
this.cache.delete(`${col.id}:summary`);
this.cache.delete(`${col.id}:summary:raw`);
this.cache.delete(`${col.id}:summary:b`);
this.cache.delete(`${col.id}:summary:braw`);

if (type === 'summary') {
return;
}
this.cache.delete(`${col.id}:data`);
this.cache.delete(`${col.id}:data:raw`);
this.cache.delete(`${col.id}:data:b`);
this.cache.delete(`${col.id}:data:braw`);
}

dirtyRanking(ranking: Ranking, type: 'data' | 'summary' | 'group') {
for (const col of ranking.flatColumns) {
this.dirtyColumn(col, type);
}
}

preCompute() {
// dummy
}

preComputeData() {
// dummy
}

preComputeCol() {
// dummy
}

copyData2Summary() {
// dummy
}

copyCache(col: Column, from: Column) {
const fromPrefix = `${from.id}:`;

for (const key of Array.from(this.cache.keys()).sort()) {
if (!key.startsWith(fromPrefix)) {
continue;
}
const tkey = `${col.id}:${key.slice(fromPrefix.length)}`;
this.cache.set(tkey, this.cache.get(key)!);
}
}

readonly sort = sortDirect;

groupCompare(ranking: Ranking, group: IGroup, rows: IndicesArray) {
return taskNow(ranking.toGroupCompareValue(this.byOrder(rows), group));
}

groupRows<T>(_col: Column, group: IOrderedGroup, _key: string, compute: (rows: ISequence<IDataRow>) => T) {
return taskNow(compute(this.byOrder(group.order)));
}

groupExampleRows<T>(_col: Column, group: IOrderedGroup, _key: string, compute: (rows: ISequence<IDataRow>) => T) {
return taskNow(compute(this.byOrder(group.order.slice(0, 5))));
}

groupBoxPlotStats(col: Column & INumberColumn, group: IOrderedGroup, raw?: boolean) {
const {summary, data} = this.summaryBoxPlotStatsD(col, raw);
return taskNow({group: this.boxplotBuilder(group.order, col, raw).next(Infinity).value!, summary, data});
}

groupNumberStats(col: Column & INumberColumn, group: IOrderedGroup, raw?: boolean) {
const {summary, data} = this.summaryNumberStatsD(col, raw);
return taskNow({group: this.normalizedStatsBuilder(group.order, col, summary.hist.length, raw).next(Infinity).value!, summary, data});
}

groupCategoricalStats(col: Column & ICategoricalLikeColumn, group: IOrderedGroup) {
const {summary, data} = this.summaryCategoricalStatsD(col);
return taskNow({group: this.categoricalStatsBuilder(group.order, col).next(Infinity).value!, summary, data});
}

groupDateStats(col: Column & IDateColumn, group: IOrderedGroup) {
const {summary, data} = this.summaryDateStatsD(col);
return taskNow({group: this.dateStatsBuilder(group.order, col, summary).next(Infinity).value!, summary, data});
}

summaryBoxPlotStats(col: Column & INumberColumn, raw?: boolean) {
return taskNow(this.summaryBoxPlotStatsD(col, raw));
}

summaryNumberStats(col: Column & INumberColumn, raw?: boolean) {
return taskNow(this.summaryNumberStatsD(col, raw));
}

summaryCategoricalStats(col: Column & ICategoricalLikeColumn) {
return taskNow(this.summaryCategoricalStatsD(col));
}

summaryDateStats(col: Column & IDateColumn) {
return taskNow(this.summaryDateStatsD(col));
}

private summaryNumberStatsD(col: Column & INumberColumn, raw?: boolean) {
return this.cached('summary', col, () => {
const ranking = col.findMyRanker()!.getOrder();
const data = this.dataNumberStats(col, raw);
return {summary: this.normalizedStatsBuilder(ranking, col, data.hist.length, raw).next(Infinity).value!, data};
}, raw ? ':raw' : '', col.findMyRanker()!.getOrderLength() === 0);
}

private summaryBoxPlotStatsD(col: Column & INumberColumn, raw?: boolean) {
return this.cached('summary', col, () => {
const ranking = col.findMyRanker()!.getOrder();
const data = this.dataBoxPlotStats(col, raw);
return {summary: this.boxplotBuilder(ranking, col, raw).next(Infinity).value!, data};
}, raw ? ':braw' : ':b' , col.findMyRanker()!.getOrderLength() === 0);
}

private summaryCategoricalStatsD(col: Column & ICategoricalLikeColumn) {
return this.cached('summary', col, () => {
const ranking = col.findMyRanker()!.getOrder();
const data = this.dataCategoricalStats(col);
return {summary: this.categoricalStatsBuilder(ranking, col).next(Infinity).value!, data};
}, '', col.findMyRanker()!.getOrderLength() === 0);
}

private summaryDateStatsD(col: Column & IDateColumn) {
return this.cached('summary', col, () => {
const ranking = col.findMyRanker()!.getOrder();
const data = this.dataDateStats(col);
return {summary: this.dateStatsBuilder(ranking, col, data).next(Infinity).value!, data};
}, '', col.findMyRanker()!.getOrderLength() === 0);
}

private cached<T>(prefix: string, col: Column, creator: () => T, suffix: string = '', dontCache = false): T {
const key = `${col.id}:${prefix}${suffix}`;
if (this.cache.has(key)) {
return this.cache.get(key)!;
}
const s = creator();
if (!dontCache) {
this.cache.set(key, s);
}
return s;
}

dataBoxPlotStats(col: Column & INumberColumn, raw?: boolean) {
return this.cached('data', col, () => this.boxplotBuilder(null, col, raw).next(Infinity).value!, raw ? ':braw' : ':b');
}

dataNumberStats(col: Column & INumberColumn, raw?: boolean) {
return this.cached('data', col, () => this.normalizedStatsBuilder(null, col, getNumberOfBins(this.data.length), raw).next(Infinity).value!, raw ? ':raw' : '');
}

dataCategoricalStats(col: Column & ICategoricalLikeColumn) {
return this.cached('data', col, () => this.categoricalStatsBuilder(null, col).next(Infinity).value!);
}

dataDateStats(col: Column & IDateColumn) {
return this.cached('data', col, () => this.dateStatsBuilder(null, col).next(Infinity).value!);
}

terminate() {
this.cache.clear();
}
}


Loading

0 comments on commit 56b0690

Please sign in to comment.