Skip to content

Commit

Permalink
fix: set sorting/grouping
Browse files Browse the repository at this point in the history
  • Loading branch information
sgratzl committed Jan 31, 2021
1 parent d6d706a commit 2157411
Show file tree
Hide file tree
Showing 11 changed files with 66 additions and 47 deletions.
4 changes: 2 additions & 2 deletions packages/components/src/math/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ export function autoAssignColors(colors: readonly string[], start = 0): (v: stri

export const schemeTableau10 = [
'#4e79a7',
'#f28e2c',
'#e15759',
'#76b7b2',
'#59a14f',
Expand All @@ -69,10 +68,11 @@ export const schemeTableau10 = [
'#ff9da7',
'#9c755f',
'#bab0ab',
'#f28e2c',
];
/**
* default
*/
export function defaultCategoricalColorScale(): (v: string) => string {
return autoAssignColors(schemeTableau10, 1);
return autoAssignColors(schemeTableau10, 0);
}
1 change: 0 additions & 1 deletion packages/docs/src/data/random/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ const defaultColumn: Partial<LineUpLiteColumn<IRandomRow>> = {
minWidth: 30,
width: 150,
maxWidth: 400,
// canHide: false, // TODO
};

export default function create(darkTheme: boolean) {
Expand Down
2 changes: 0 additions & 2 deletions packages/hooks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@

This package is part of the LineUp-lite ecosystem located at the main [Github Monorepo](https://github.com/sgratzl/lineup-lite).

TODO

## Privacy Policy

LineUp-lite is a client only library. The library or any of its integrations doesn't track you or transfers your data to any server.
Expand Down
5 changes: 1 addition & 4 deletions packages/hooks/src/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,7 @@ import {
categoricalSetGroupCompare,
} from './sort';
import { categoricalGroupBy, dateGroupBy, numberGroupBy, textGroupBy, categoricalSetGroupBy } from './grouping';

export function statsAggregate<T>(v: T) {
return v;
}
import { statsAggregate } from './hooks';

function guessName(acc: string) {
return acc
Expand Down
18 changes: 3 additions & 15 deletions packages/hooks/src/grouping/categoricalSetGroupBy.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { ColumnInstance, Row } from 'react-table';
import type { ICategoricalStats } from '@lineup-lite/components';
import { baseGroupBy } from './internal';
import { categoricalSortFunc, compareAsc } from '../sort/internal';

export function categoricalSetGroupBy<D extends object>(
rows: Row<D>[],
Expand All @@ -9,20 +10,7 @@ export function categoricalSetGroupBy<D extends object>(
if (rows.length === 0) {
return {};
}
// TODO
const base = baseGroupBy(rows, column);
if ((column as any).statsValue == null) {
return base;
}
// create a new one but this time sorted
const stats = (column as any).statsValue as ICategoricalStats;
const sortedGrouped: Record<string, Row<D>[]> = {};
stats.categories.forEach((cat) => {
if (base[cat] != null) {
sortedGrouped[cat] = base[cat];
delete base[cat];
}
});
// assign the rest with unknown values
return Object.assign(sortedGrouped, base);
const sorter = stats ? categoricalSortFunc(stats.categories) : compareAsc;
return baseGroupBy(rows, column, (d) => (d == null ? null : (Array.from(d) as string[]).sort(sorter).join(',')));
}
9 changes: 0 additions & 9 deletions packages/hooks/src/grouping/columnSpecificGroupByFn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,3 @@ export function columnSpecificGroupByFn<D extends object = {}>(
}
return defaultGroupByFn(rows, columnId);
}

// return rows.reduce((prev, row) => {
// // TODO: Might want to implement a key serializer here so
// // irregular column values can still be grouped if needed?
// const resKey = `${row.values[columnId]}`;
// prev[resKey] = Array.isArray(prev[resKey]) ? prev[resKey] : ([] as Row<D>[]);
// prev[resKey].push(row);
// return prev;
// }, {} as Record<string, Row<D>[]>);
17 changes: 14 additions & 3 deletions packages/hooks/src/grouping/internal.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
import type { ColumnInstance, Row } from 'react-table';
import { identity } from '../renderers/utils';
import { compareAsc } from '../sort/internal';
import { MISSING_GROUP } from './histGroupBy';

export function baseGroupBy<D extends object>(rows: Row<D>[], column: ColumnInstance<D>): Record<string, Row<D>[]> {
export function baseGroupBy<D extends object>(
rows: Row<D>[],
column: ColumnInstance<D>,
acc: (v: any) => string | null = identity
): Record<string, Row<D>[]> {
const r: Record<string, Row<D>[]> = {};
for (const row of rows) {
const value = row.values[column.id] ?? MISSING_GROUP;
const value = acc(row.values[column.id]) ?? MISSING_GROUP;
if (!r[value]) {
r[value] = [row];
} else {
r[value]!.push(row);
}
}
return r;
// sort by key asc
const sorted: Record<string, Row<D>[]> = {};
for (const key of Object.keys(r).sort(compareAsc)) {
sorted[key] = r[key];
}
return sorted;
}
13 changes: 11 additions & 2 deletions packages/hooks/src/hooks/useStats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@ export interface StatsType {
(values: readonly any[], preFilterStats?: any): any;
}

export function statsAggregate<T>(v: T[]): T[] {
if (Array.isArray(v)) {
const copy: T[] & { _agg?: boolean } = v.slice();
copy._agg = true;
return copy;
}
return v;
}

function DummyComponent() {
return null;
}
Expand All @@ -67,7 +76,6 @@ function useInstance<D extends object>(instance: TableInstance<D>) {
UseGroupByInstanceProps<D>;

instance.allColumns.forEach((col) => {
// TODO do proper options
const extended = (col as unknown) as UseStatsColumnProps &
UseStatsColumnOptions<D> &
UseGroupByColumnProps<D> &
Expand Down Expand Up @@ -96,7 +104,8 @@ function useInstance<D extends object>(instance: TableInstance<D>) {
const grouped = extendedInstance.onlyGroupedFlatRows ?? [];
grouped.forEach((group) => {
const value = group.values[col.id];
if (!Array.isArray(value)) {
// not an array or the grouped row
if (!Array.isArray(value) || !(value as { _agg?: boolean })._agg || col.id === (group as any).groupByID) {
return;
}
// compute stats
Expand Down
27 changes: 23 additions & 4 deletions packages/hooks/src/sort/categoricalSetSort.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,34 @@ import {
} from '@lineup-lite/components';
import { compareAsc } from './internal';

function toMode(s: ICategoricalStats, bin: number) {
if (bin < 0 || s.hist.length <= bin) {
return 4;
}
const binValue = s.hist[bin].count;
const maxCount = s.flatItems.length;
if (maxCount === binValue) {
return 1;
}
if (binValue > 0) {
return 2;
}
return 3;
}

export function categoricalSetGroupCompare(a: Row<any>, b: Row<any>, columnId: string): number {
const av: ICategoricalStats = a.values[columnId];
const bv: ICategoricalStats = b.values[columnId];

if (isCategoricalStats(av) && isCategoricalStats(bv)) {
// TODO
const ai = av.categories.indexOf(av.maxBin.x0);
const bi = av.categories.indexOf(bv.maxBin.x0);
return compareAsc(ai, bi);
for (let i = 0; i < av.hist.length; i++) {
const avi = toMode(av, i);
const bvi = toMode(bv, i);
if (avi !== bvi) {
return compareAsc(avi, bvi);
}
}
return 0;
}
return compareAsc(av, bv);
}
Expand Down
8 changes: 3 additions & 5 deletions packages/hooks/src/sort/categoricalSort.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { RowCompareFunction } from './interfaces';
import type { Row } from 'react-table';
import { ICategoricalStats, isCategoricalStats } from '@lineup-lite/components';
import { compareAsc } from './internal';
import { categoricalSortFunc, compareAsc } from './internal';

export function categoricalGroupCompare(a: Row<any>, b: Row<any>, columnId: string): number {
const av: ICategoricalStats = a.values[columnId];
Expand All @@ -19,12 +19,10 @@ export function categoricalGroupCompare(a: Row<any>, b: Row<any>, columnId: stri
* generates a comparator that sorts by the given set of category order
*/
export function categoricalSort(categories: readonly string[]): RowCompareFunction {
const lookup = new Map(categories.map((cat, i) => [cat, i]));
const sorter = categoricalSortFunc(categories);
return (a, b, columnId) => {
const va = a.values[columnId];
const vb = b.values[columnId];
const aIndex = va == null || !lookup.has(va) ? Number.POSITIVE_INFINITY : lookup.get(va)!;
const bIndex = vb == null || !lookup.has(vb) ? Number.POSITIVE_INFINITY : lookup.get(vb)!;
return compareAsc(aIndex, bIndex);
return sorter(va, vb);
};
}
9 changes: 9 additions & 0 deletions packages/hooks/src/sort/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,12 @@ export function compareAsc<T>(va: T, vb: T): number {
}
return va < vb ? -1 : 1;
}

export function categoricalSortFunc(categories: readonly string[]): (a: string, b: string) => number {
const lookup = new Map(categories.map((cat, i) => [cat, i]));
return (va, vb) => {
const aIndex = va == null || !lookup.has(va) ? Number.POSITIVE_INFINITY : lookup.get(va)!;
const bIndex = vb == null || !lookup.has(vb) ? Number.POSITIVE_INFINITY : lookup.get(vb)!;
return compareAsc(aIndex, bIndex);
};
}

0 comments on commit 2157411

Please sign in to comment.