adding simple widget to pick the reducer

This commit is contained in:
ryan 2019-03-12 10:53:28 -07:00
parent b7963f8e87
commit 9016c18088
4 changed files with 179 additions and 22 deletions

View File

@ -0,0 +1,42 @@
import React, { PureComponent } from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
import { TableReducePicker } from './TableReducePicker';
interface State {
reducers: string[];
}
export class WrapperWithState extends PureComponent<any, State> {
constructor(props: any) {
super(props);
this.state = {
reducers: [], // nothing?
};
}
render() {
return (
<TableReducePicker
{...this.props}
reducers={this.state.reducers}
onChange={(reducers: string[]) => {
action('Reduce')(reducers);
this.setState({ reducers });
}}
/>
);
}
}
const story = storiesOf('UI/TableReducePicker', module);
story.addDecorator(withCenteredStory);
story.add('default', () => {
return (
<div>
<WrapperWithState />
</div>
);
});

View File

@ -0,0 +1,57 @@
import React, { PureComponent } from 'react';
import isArray from 'lodash/isArray';
import { Select } from '../index';
import { getTableReducers } from '../../utils/tableReducer';
import { SelectOptionItem } from '../Select/Select';
interface Props {
onChange: (reducers: string[]) => void;
reducers: string[];
width?: number;
}
export class TableReducePicker extends PureComponent<Props> {
static defaultProps = {
width: 12,
};
onSelectionChange = (item: SelectOptionItem) => {
const { onChange } = this.props;
if (isArray(item)) {
onChange(item.map(v => v.value));
} else {
onChange([item.value]);
}
};
render() {
const { width, reducers } = this.props;
const allReducers = getTableReducers();
// Need to transform the data structure to work well with Select
const reducerOptions = allReducers.map(info => {
return {
label: info.name,
value: info.key,
description: info.description,
};
});
const current = reducerOptions.filter(options => reducers.includes(options.value));
return (
<Select
width={width}
value={current}
isMulti={true}
isSearchable={true}
options={reducerOptions}
placeholder="Choose"
onChange={this.onSelectionChange}
/>
);
}
}

View File

@ -2,17 +2,49 @@ import { parseCSV } from './processTableData';
import { reduceTableData } from './tableReducer';
describe('Table Reducer', () => {
it('should calculate average', () => {
const table = parseCSV('a,b,c\n1,2,3\n4,5,6');
const basicTable = parseCSV('a,b,c\n10,20,30\n20,30,40');
const reduced = reduceTableData(table, {
stats: ['last'],
it('should calculate stats', () => {
const reduced = reduceTableData(basicTable, {
columnIndexes: [0, 1],
stats: ['first', 'last', 'mean'],
});
expect(reduced.length).toBe(1);
expect(reduced[0].rows.length).toBe(1);
expect(reduced[0].rows[0]).toEqual(table.rows[1]);
expect(reduced.length).toBe(3);
console.log('REDUCE', reduced[0].rows);
// First
expect(reduced[0].rows[0]).toEqual([10, 20]);
// Last
expect(reduced[1].rows[0]).toEqual([20, 30]);
// Mean
expect(reduced[2].rows[0]).toEqual([15, 25]);
});
it('should support a single stat also', () => {
// First
let reduced = reduceTableData(basicTable, {
columnIndexes: [0, 1],
stats: ['first'],
});
expect(reduced.length).toBe(1);
expect(reduced[0].rows[0]).toEqual([10, 20]);
// Last
reduced = reduceTableData(basicTable, {
columnIndexes: [0, 1],
stats: ['last'],
});
expect(reduced.length).toBe(1);
expect(reduced[0].rows[0]).toEqual([20, 30]);
// Mean
reduced = reduceTableData(basicTable, {
columnIndexes: [0, 1],
stats: ['mean'],
});
expect(reduced.length).toBe(1);
expect(reduced[0].rows[0]).toEqual([15, 25]);
});
});

View File

@ -4,12 +4,7 @@ import isNumber from 'lodash/isNumber';
import { TableData, NullValueMode } from '../types/index';
/** Reduce each column in a table to a single value */
export type TableReducer = (
data: TableData,
columnIndexes: number[],
ignoreNulls: boolean,
nullAsZero: boolean
) => any[];
type TableReducer = (data: TableData, columnIndexes: number[], ignoreNulls: boolean, nullAsZero: boolean) => any[];
/** Information about the reducing(stats) functions */
export interface TableReducerInfo {
@ -33,6 +28,26 @@ export interface TableReducerOptions {
}
export function reduceTableData(data: TableData, options: TableReducerOptions): TableData[] {
const indexes = verifyColumns(data, options);
const columns = indexes.map(v => data.columns[v]);
const ignoreNulls = options.nullValueMode === NullValueMode.Ignore;
const nullAsZero = options.nullValueMode === NullValueMode.AsZero;
// Return early for empty tables
if (!data.rows || data.rows.length < 1) {
const val = nullAsZero ? 0 : null;
const rows = [indexes.map(v => val)];
return options.stats.map(stat => {
return {
columns,
rows,
type: 'table',
columnMap: {},
};
});
}
if (registry == null) {
registry = new Map<string, TableReducerInfo>();
reducers.forEach(calc => {
@ -43,12 +58,6 @@ export function reduceTableData(data: TableData, options: TableReducerOptions):
});
}
const indexes = verifyColumns(data, options);
const columns = indexes.map(v => data.columns[v]);
const ignoreNulls = options.nullValueMode === NullValueMode.Ignore;
const nullAsZero = options.nullValueMode === NullValueMode.AsZero;
const queue = options.stats.map(key => {
const c = registry!.get(key);
if (!c) {
@ -128,8 +137,15 @@ const reducers: TableReducerInfo[] = [
{ key: 'min', name: 'Min', description: 'Minimum Value', standard: true },
{ key: 'max', name: 'Max', description: 'Maximum Value', standard: true },
{ key: 'mean', name: 'Mean', description: 'Average Value', standard: true, alias: 'avg' },
{ key: 'first', name: 'First', description: 'First Value', standard: true },
{ key: 'last', name: 'Last', description: 'Last Value (current)', standard: true, alias: 'current' },
{ key: 'first', name: 'First', description: 'First Value', standard: true, reducer: getFirstRow },
{
key: 'last',
name: 'Last',
description: 'Last Value (current)',
standard: true,
alias: 'current',
reducer: getLastRow,
},
{ key: 'count', name: 'Count', description: 'Value Count', standard: true },
{ key: 'range', name: 'Range', description: 'Difference between minimum and maximum values', standard: true },
{ key: 'diff', name: 'Difference', description: 'Difference between first and last values', standard: true },
@ -235,3 +251,13 @@ function standardStatsReducer(
return column;
}
function getFirstRow(data: TableData, columnIndexes: number[], ignoreNulls: boolean, nullAsZero: boolean): any[] {
const row = data.rows[0];
return columnIndexes.map(idx => row[idx]);
}
function getLastRow(data: TableData, columnIndexes: number[], ignoreNulls: boolean, nullAsZero: boolean): any[] {
const row = data.rows[data.rows.length - 1];
return columnIndexes.map(idx => row[idx]);
}