Просмотр исходного кода

rename reducer to statsCalculator

ryan 6 лет назад
Родитель
Сommit
a0fa5698e0

+ 17 - 17
packages/grafana-ui/src/components/TableReducePicker/TableReducePicker.story.tsx → packages/grafana-ui/src/components/StatsPicker/StatsPicker.story.tsx

@@ -3,45 +3,45 @@ 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';
+import { StatsPicker } from './StatsPicker';
 import { text, boolean } from '@storybook/addon-knobs';
 
 interface State {
-  reducers: string[];
+  stats: string[];
 }
 
 export class WrapperWithState extends PureComponent<any, State> {
   constructor(props: any) {
     super(props);
     this.state = {
-      reducers: this.toReducersArray(props.initialReducers),
+      stats: this.toStatsArray(props.initialReducers),
     };
   }
 
-  toReducersArray = (txt: string): string[] => {
+  toStatsArray = (txt: string): string[] => {
     return txt.split(',').map(v => v.trim());
   };
 
   componentDidUpdate(prevProps: any) {
     const { initialReducers } = this.props;
     if (initialReducers !== prevProps.initialReducers) {
-      this.setState({ reducers: this.toReducersArray(initialReducers) });
+      this.setState({ stats: this.toStatsArray(initialReducers) });
     }
   }
 
   render() {
-    const { placeholder, defaultReducer, allowMultiple } = this.props;
-    const { reducers } = this.state;
+    const { placeholder, defaultStat, allowMultiple } = this.props;
+    const { stats } = this.state;
 
     return (
-      <TableReducePicker
+      <StatsPicker
         placeholder={placeholder}
-        defaultReducer={defaultReducer}
+        defaultStat={defaultStat}
         allowMultiple={allowMultiple}
-        reducers={reducers}
-        onChange={(reducers: string[]) => {
-          action('Picked:')(reducers);
-          this.setState({ reducers });
+        stats={stats}
+        onChange={(stats: string[]) => {
+          action('Picked:')(stats);
+          this.setState({ stats });
         }}
       />
     );
@@ -52,16 +52,16 @@ const story = storiesOf('UI/TableReducePicker', module);
 story.addDecorator(withCenteredStory);
 story.add('picker', () => {
   const placeholder = text('Placeholder Text', '');
-  const defaultReducer = text('Default Reducer', '');
+  const defaultStat = text('Default Stat', '');
   const allowMultiple = boolean('Allow Multiple', false);
-  const initialReducers = text('Initial Reducers', '');
+  const initialStats = text('Initial Stats', '');
   return (
     <div>
       <WrapperWithState
         placeholder={placeholder}
-        defaultReducer={defaultReducer}
+        defaultStat={defaultStat}
         allowMultiple={allowMultiple}
-        initialReducers={initialReducers}
+        initialStats={initialStats}
       />
     </div>
   );

+ 17 - 17
packages/grafana-ui/src/components/TableReducePicker/TableReducePicker.tsx → packages/grafana-ui/src/components/StatsPicker/StatsPicker.tsx

@@ -4,19 +4,19 @@ import isArray from 'lodash/isArray';
 
 import { Select } from '../index';
 
-import { getTableReducers } from '../../utils/tableReducer';
+import { getStatsCalculators } from '../../utils/statsCalculator';
 import { SelectOptionItem } from '../Select/Select';
 
 interface Props {
   placeholder?: string;
-  onChange: (reducers: string[]) => void;
-  reducers: string[];
+  onChange: (stats: string[]) => void;
+  stats: string[];
   width?: number;
   allowMultiple?: boolean;
-  defaultReducer?: string;
+  defaultStat?: string;
 }
 
-export class TableReducePicker extends PureComponent<Props> {
+export class StatsPicker extends PureComponent<Props> {
   static defaultProps = {
     width: 12,
     allowMultiple: false,
@@ -31,25 +31,25 @@ export class TableReducePicker extends PureComponent<Props> {
   }
 
   checkInput = () => {
-    const { reducers, allowMultiple, defaultReducer, onChange } = this.props;
+    const { stats, allowMultiple, defaultStat, onChange } = this.props;
 
     // Check that the selected reducers are all real
     const notFound: string[] = [];
-    const current = getTableReducers(reducers, notFound);
+    const current = getStatsCalculators(stats, notFound);
     if (notFound.length > 0) {
-      console.warn('Unknown reducers', notFound, reducers);
+      console.warn('Unknown reducers', notFound, stats);
       onChange(current.map(reducer => reducer.value));
     }
 
     // Make sure there is only one
-    if (!allowMultiple && reducers.length > 1) {
-      console.warn('Removing extra reducers', reducers);
-      onChange([reducers[0]]);
+    if (!allowMultiple && stats.length > 1) {
+      console.warn('Removing extra stat', stats);
+      onChange([stats[0]]);
     }
 
     // Set the reducer from callback
-    if (defaultReducer && reducers.length < 1) {
-      onChange([defaultReducer]);
+    if (defaultStat && stats.length < 1) {
+      onChange([defaultStat]);
     }
   };
 
@@ -63,17 +63,17 @@ export class TableReducePicker extends PureComponent<Props> {
   };
 
   render() {
-    const { width, reducers, allowMultiple, defaultReducer, placeholder } = this.props;
-    const current = getTableReducers(reducers);
+    const { width, stats, allowMultiple, defaultStat, placeholder } = this.props;
+    const current = getStatsCalculators(stats);
 
     return (
       <Select
         width={width}
         value={current}
-        isClearable={!defaultReducer}
+        isClearable={!defaultStat}
         isMulti={allowMultiple}
         isSearchable={true}
-        options={getTableReducers()}
+        options={getStatsCalculators()}
         placeholder={placeholder}
         onChange={this.onSelectionChange}
       />

+ 73 - 0
packages/grafana-ui/src/utils/statsCalculator.test.ts

@@ -0,0 +1,73 @@
+import { parseCSV } from './processTableData';
+import { getStatsCalculators, StatID, calculateStats } from './statsCalculator';
+
+describe('Stats Calculators', () => {
+  const basicTable = parseCSV('a,b,c\n10,20,30\n20,30,40');
+
+  it('should load all standard stats', () => {
+    const names = [
+      StatID.sum,
+      StatID.max,
+      StatID.min,
+      StatID.logmin,
+      StatID.mean,
+      StatID.last,
+      StatID.first,
+      StatID.count,
+      StatID.range,
+      StatID.diff,
+      StatID.step,
+      StatID.delta,
+      // StatID.allIsZero,
+      // StatID.allIsNull,
+    ];
+    const notFound: string[] = [];
+    const stats = getStatsCalculators(names, notFound);
+    stats.forEach((stat, index) => {
+      expect(stat ? stat.value : '<missing>').toEqual(names[index]);
+    });
+    expect(notFound.length).toBe(0);
+  });
+
+  it('should fail to load unknown stats', () => {
+    const names = ['not a stat', StatID.max, StatID.min, 'also not a stat'];
+    const notFound: string[] = [];
+    const stats = getStatsCalculators(names, notFound);
+    expect(stats.length).toBe(2);
+    expect(notFound.length).toBe(2);
+  });
+
+  it('should calculate stats', () => {
+    const stats = calculateStats({
+      data: basicTable,
+      columnIndex: 0,
+      stats: ['first', 'last', 'mean'],
+    });
+
+    // First
+    expect(stats.first).toEqual(10);
+
+    // Last
+    expect(stats.last).toEqual(20);
+
+    // Mean
+    expect(stats.mean).toEqual(15);
+  });
+
+  it('should support a single stat also', () => {
+    const stats = calculateStats({
+      data: basicTable,
+      columnIndex: 0,
+      stats: ['first', 'last', 'mean'],
+    });
+
+    // First
+    expect(stats.first).toEqual(10);
+
+    // Last
+    expect(stats.last).toEqual(20);
+
+    // Mean
+    expect(stats.mean).toEqual(15);
+  });
+});

+ 326 - 0
packages/grafana-ui/src/utils/statsCalculator.ts

@@ -0,0 +1,326 @@
+// Libraries
+import isNumber from 'lodash/isNumber';
+
+import { TableData, NullValueMode } from '../types/index';
+
+export enum StatID {
+  sum = 'sum',
+  max = 'max',
+  min = 'min',
+  logmin = 'logmin',
+  mean = 'mean',
+  last = 'last',
+  first = 'first',
+  count = 'count',
+  range = 'range',
+  diff = 'diff',
+  delta = 'delta',
+  step = 'step',
+
+  allIsZero = 'allIsZero',
+  allIsNull = 'allIsNull',
+}
+
+export interface ColumnStats {
+  [key: string]: any;
+}
+
+// Internal function
+type StatCalculator = (data: TableData, columnIndex: number, ignoreNulls: boolean, nullAsZero: boolean) => ColumnStats;
+
+export interface StatCalculatorInfo {
+  value: string; // The ID - value maps directly to select component
+  label: string; // The name - label for Select component
+  description: string;
+  alias?: string; // optional secondary key.  'avg' vs 'mean', 'total' vs 'sum'
+
+  // Internal details
+  emptyInputResult?: any; // typically null, but some things like 'count' & 'sum' should be zero
+  standard: boolean; // The most common stats can all be calculated in a single pass
+  calculator?: StatCalculator;
+}
+
+/**
+ * @param ids list of stat names or null to get all of them
+ * @param notFound optional error object that will be filled with the names on unknown stats
+ */
+export function getStatsCalculators(ids?: string[], notFound?: string[]): StatCalculatorInfo[] {
+  if (ids === null || ids === undefined) {
+    return listOfStats;
+  }
+  return ids.reduce((list, id) => {
+    const stat = getById(id);
+    if (stat) {
+      list.push(stat);
+    } else if (notFound && id) {
+      notFound.push(id);
+    }
+    return list;
+  }, new Array<StatCalculatorInfo>());
+}
+
+export interface CalculateStatsOptions {
+  data: TableData;
+  columnIndex: number;
+  stats: string[]; // The stats to calculate
+  nullValueMode?: NullValueMode;
+}
+
+/**
+ * @returns an object with a key for each selected stat
+ */
+export function calculateStats(options: CalculateStatsOptions): ColumnStats {
+  const { data, columnIndex, stats, nullValueMode } = options;
+
+  if (!stats || stats.length < 1) {
+    return {};
+  }
+
+  const queue = getStatsCalculators(stats);
+
+  // Return early for empty tables
+  // This lets the concrete implementations assume at least one row
+  if (!data.rows || data.rows.length < 1) {
+    const stats = {} as ColumnStats;
+    queue.forEach(stat => {
+      stats[stat.value] = stat.emptyInputResult !== null ? stat.emptyInputResult : null;
+    });
+    return stats;
+  }
+
+  const ignoreNulls = nullValueMode === NullValueMode.Ignore;
+  const nullAsZero = nullValueMode === NullValueMode.AsZero;
+
+  // Avoid calculating all the standard stats if possible
+  if (queue.length === 1 && queue[0].calculator) {
+    return [queue[0].calculator(data, columnIndex, ignoreNulls, nullAsZero)];
+  }
+
+  // For now everything can use the standard stats
+  let values = standardStatsStat(data, columnIndex, ignoreNulls, nullAsZero);
+  queue.forEach(calc => {
+    if (!values.hasOwnProperty(calc.value) && calc.calculator) {
+      values = {
+        ...values,
+        ...calc.calculator(data, columnIndex, ignoreNulls, nullAsZero),
+      };
+    }
+  });
+  return values;
+}
+
+// ------------------------------------------------------------------------------
+//
+//  No Exported symbols below here.
+//
+// ------------------------------------------------------------------------------
+
+// private registry of all stats
+interface TableStatIndex {
+  [id: string]: StatCalculatorInfo;
+}
+const listOfStats: StatCalculatorInfo[] = [];
+const index: TableStatIndex = {};
+let hasBuiltIndex = false;
+
+function getById(id: string): StatCalculatorInfo | undefined {
+  if (!hasBuiltIndex) {
+    [
+      {
+        value: StatID.last,
+        label: 'Last',
+        description: 'Last Value (current)',
+        standard: true,
+        alias: 'current',
+        stat: calculateLast,
+      },
+      { value: StatID.first, label: 'First', description: 'First Value', standard: true, stat: calculateFirst },
+      { value: StatID.min, label: 'Min', description: 'Minimum Value', standard: true },
+      { value: StatID.max, label: 'Max', description: 'Maximum Value', standard: true },
+      { value: StatID.mean, label: 'Mean', description: 'Average Value', standard: true, alias: 'avg' },
+      {
+        value: StatID.sum,
+        label: 'Total',
+        description: 'The sum of all values',
+        emptyInputResult: 0,
+        standard: true,
+        alias: 'total',
+      },
+      { value: StatID.count, label: 'Count', description: 'Value Count', emptyInputResult: 0, standard: true },
+      {
+        value: StatID.range,
+        label: 'Range',
+        description: 'Difference between minimum and maximum values',
+        standard: true,
+      },
+      {
+        value: StatID.delta,
+        label: 'Delta',
+        description: 'Cumulative change in value', // HELP! not totally sure what this does
+        standard: true,
+      },
+      {
+        value: StatID.step,
+        label: 'Step',
+        description: 'Minimum interval between values',
+        standard: true,
+      },
+      {
+        value: StatID.diff,
+        label: 'Difference',
+        description: 'Difference between first and last values',
+        standard: true,
+      },
+      {
+        value: StatID.logmin,
+        label: 'Min (above zero)',
+        description: 'Used for log min scale',
+        standard: true,
+      },
+    ].forEach(calc => {
+      const { value, alias } = calc;
+      if (index.hasOwnProperty(value)) {
+        console.warn('Duplicate Stat', value, calc, index);
+      }
+      index[value] = calc;
+      if (alias) {
+        if (index.hasOwnProperty(alias)) {
+          console.warn('Duplicate Stat (alias)', alias, calc, index);
+        }
+        index[alias] = calc;
+      }
+      listOfStats.push(calc);
+    });
+    hasBuiltIndex = true;
+  }
+  return index[id];
+}
+
+function standardStatsStat(
+  data: TableData,
+  columnIndex: number,
+  ignoreNulls: boolean,
+  nullAsZero: boolean
+): ColumnStats {
+  const stats = {
+    sum: 0,
+    max: -Number.MAX_VALUE,
+    min: Number.MAX_VALUE,
+    logmin: Number.MAX_VALUE,
+    mean: null,
+    last: null,
+    first: null,
+    count: 0,
+    nonNullCount: 0,
+    allIsNull: true,
+    allIsZero: false,
+    range: null,
+    diff: null,
+    delta: 0,
+    step: 0,
+
+    // Just used for calcutations -- not exposed as a stat
+    previousDeltaUp: true,
+  } as ColumnStats;
+
+  for (let i = 0; i < data.rows.length; i++) {
+    let currentValue = data.rows[i][columnIndex];
+
+    if (currentValue === null) {
+      if (ignoreNulls) {
+        continue;
+      }
+      if (nullAsZero) {
+        currentValue = 0;
+      }
+    }
+
+    if (currentValue !== null) {
+      stats.last = currentValue;
+
+      const isFirst = stats.first === null;
+      if (isFirst) {
+        stats.first = currentValue;
+      }
+
+      if (isNumber(currentValue)) {
+        stats.sum += currentValue;
+        stats.allIsNull = false;
+        stats.nonNullCount++;
+
+        if (!isFirst) {
+          const step = currentValue - stats.last!;
+          if (stats.step > step) {
+            stats.step = step; // the minimum interval
+          }
+
+          if (stats.last! > currentValue) {
+            // counter reset
+            stats.previousDeltaUp = false;
+            if (i === data.rows.length - 1) {
+              // reset on last
+              stats.delta += currentValue;
+            }
+          } else {
+            if (stats.previousDeltaUp) {
+              stats.delta += step; // normal increment
+            } else {
+              stats.delta += currentValue; // account for counter reset
+            }
+            stats.previousDeltaUp = true;
+          }
+        }
+
+        if (currentValue > stats.max) {
+          stats.max = currentValue;
+        }
+
+        if (currentValue < stats.min) {
+          stats.min = currentValue;
+        }
+
+        if (currentValue < stats.logmin && currentValue > 0) {
+          stats.logmin = currentValue;
+        }
+      }
+
+      if (currentValue !== 0) {
+        stats.allIsZero = false;
+      }
+
+      stats.last = currentValue;
+    }
+  }
+
+  if (stats.max === -Number.MAX_VALUE) {
+    stats.max = null;
+  }
+
+  if (stats.min === Number.MAX_VALUE) {
+    stats.min = null;
+  }
+
+  if (stats.nonNullCount > 0) {
+    stats.mean = stats.sum! / stats.nonNullCount;
+  }
+
+  if (stats.max !== null && stats.min !== null) {
+    stats.range = stats.max - stats.min;
+  }
+
+  if (stats.first !== null && stats.last !== null) {
+    if (isNumber(stats.first) && isNumber(stats.last)) {
+      stats.diff = stats.last - stats.first;
+    }
+  }
+
+  return stats;
+}
+
+function calculateFirst(data: TableData, columnIndex: number, ignoreNulls: boolean, nullAsZero: boolean): ColumnStats {
+  return { first: data.rows[0][columnIndex] };
+}
+
+function calculateLast(data: TableData, columnIndex: number, ignoreNulls: boolean, nullAsZero: boolean): ColumnStats {
+  return { last: data.rows[data.rows.length - 1][columnIndex] };
+}

+ 0 - 84
packages/grafana-ui/src/utils/tableReducer.test.ts

@@ -1,84 +0,0 @@
-import { parseCSV } from './processTableData';
-import { reduceTableData, getTableReducers, TableReducerID } from './tableReducer';
-
-describe('Table Reducer', () => {
-  const basicTable = parseCSV('a,b,c\n10,20,30\n20,30,40');
-
-  it('should load all standard stats', () => {
-    const names = [
-      TableReducerID.sum,
-      TableReducerID.max,
-      TableReducerID.min,
-      TableReducerID.logmin,
-      TableReducerID.mean,
-      TableReducerID.last,
-      TableReducerID.first,
-      TableReducerID.count,
-      TableReducerID.range,
-      TableReducerID.diff,
-      TableReducerID.step,
-      TableReducerID.delta,
-      // TableReducerID.allIsZero,
-      // TableReducerID.allIsNull,
-    ];
-    const notFound: string[] = [];
-    const reducers = getTableReducers(names, notFound);
-    reducers.forEach((reducer, index) => {
-      expect(reducer ? reducer.value : '<missing>').toEqual(names[index]);
-    });
-    expect(notFound.length).toBe(0);
-  });
-
-  it('should fail to load unknown reducers', () => {
-    const names = ['not a reducer', TableReducerID.max, TableReducerID.min, 'also not a reducer'];
-    const notFound: string[] = [];
-    const reducers = getTableReducers(names, notFound);
-    expect(reducers.length).toBe(2);
-    expect(notFound.length).toBe(2);
-  });
-
-  it('should calculate stats', () => {
-    const reduced = reduceTableData(basicTable, {
-      columnIndexes: [0, 1],
-      stats: ['first', 'last', 'mean'],
-    });
-
-    expect(reduced.length).toBe(3);
-
-    // 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]);
-  });
-});

+ 0 - 373
packages/grafana-ui/src/utils/tableReducer.ts

@@ -1,373 +0,0 @@
-// Libraries
-import isNumber from 'lodash/isNumber';
-
-import { TableData, NullValueMode } from '../types/index';
-
-export enum TableReducerID {
-  sum = 'sum',
-  max = 'max',
-  min = 'min',
-  logmin = 'logmin',
-  mean = 'mean',
-  last = 'last',
-  first = 'first',
-  count = 'count',
-  range = 'range',
-  diff = 'diff',
-  delta = 'delta',
-  step = 'step',
-
-  allIsZero = 'allIsZero',
-  allIsNull = 'allIsNull',
-}
-
-/** Information about the reducing(stats) functions  */
-export interface TableReducerInfo {
-  value: string; // The ID - value maps directly to select component
-  label: string; // The name - label
-  description: string;
-  alias?: string; // optional secondary key.  'avg' vs 'mean', 'total' vs 'sum'
-
-  // Internal details
-  emptyInputResult?: any; // typically null, but some things like 'count' & 'sum' should be zero
-  standard: boolean; // The most common stats can all be calculated in a single pass
-  reducer?: TableReducer;
-}
-
-/**
- * Get a list of the known reducing functions
- * @param ids list of reducer names or null to get all of them
- * @param notFound optional error object that will be filled with the names on unknown reducers
- */
-export function getTableReducers(ids?: string[], notFound?: string[]): TableReducerInfo[] {
-  if (ids === null || ids === undefined) {
-    return listOfReducers;
-  }
-  return ids.reduce((list, id) => {
-    const reducer = getById(id);
-    if (reducer) {
-      list.push(reducer);
-    } else if (notFound && id) {
-      notFound.push(id);
-    }
-    return list;
-  }, new Array<TableReducerInfo>());
-}
-
-export interface TableReducerOptions {
-  columnIndexes?: number[];
-  nullValueMode?: NullValueMode;
-  stats: string[]; // The stats to calculate
-}
-
-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;
-
-  const queue = getTableReducers(options.stats);
-
-  // Return early for empty tables
-  // This lets the concrete implementations assume at least one row
-  if (!data.rows || data.rows.length < 1) {
-    return queue.map(stat => {
-      return {
-        columns,
-        rows: [indexes.map(v => stat.emptyInputResult)],
-        type: 'table',
-        columnMap: {},
-      };
-    });
-  }
-
-  // Avoid calculating all the standard stats if possible
-  if (queue.length === 1 && queue[0].reducer) {
-    return [
-      {
-        columns,
-        rows: [queue[0].reducer(data, indexes, ignoreNulls, nullAsZero)],
-        type: 'table',
-        columnMap: {},
-      },
-    ];
-  }
-
-  // For now everything can use the standard stats
-  const standard = standardStatsReducer(data, indexes, ignoreNulls, nullAsZero);
-  return queue.map(calc => {
-    const values = calc.standard
-      ? standard.map((s: any) => s[calc.value])
-      : calc.reducer!(data, indexes, ignoreNulls, nullAsZero);
-    return {
-      columns,
-      rows: [values],
-      type: 'table',
-      columnMap: {},
-    };
-  });
-}
-
-// ------------------------------------------------------------------------------
-//
-//  No Exported symbols below here.
-//
-// ------------------------------------------------------------------------------
-
-type TableReducer = (data: TableData, columnIndexes: number[], ignoreNulls: boolean, nullAsZero: boolean) => any[];
-
-// private registry of all reducers
-interface TableReducerIndex {
-  [id: string]: TableReducerInfo;
-}
-const listOfReducers: TableReducerInfo[] = [];
-const index: TableReducerIndex = {};
-let hasBuiltIndex = false;
-
-function getById(id: string): TableReducerInfo | undefined {
-  if (!hasBuiltIndex) {
-    [
-      {
-        value: TableReducerID.last,
-        label: 'Last',
-        description: 'Last Value (current)',
-        standard: true,
-        alias: 'current',
-        reducer: getLastRow,
-      },
-      { value: TableReducerID.first, label: 'First', description: 'First Value', standard: true, reducer: getFirstRow },
-      { value: TableReducerID.min, label: 'Min', description: 'Minimum Value', standard: true },
-      { value: TableReducerID.max, label: 'Max', description: 'Maximum Value', standard: true },
-      { value: TableReducerID.mean, label: 'Mean', description: 'Average Value', standard: true, alias: 'avg' },
-      {
-        value: TableReducerID.sum,
-        label: 'Total',
-        description: 'The sum of all values',
-        emptyInputResult: 0,
-        standard: true,
-        alias: 'total',
-      },
-      { value: TableReducerID.count, label: 'Count', description: 'Value Count', emptyInputResult: 0, standard: true },
-      {
-        value: TableReducerID.range,
-        label: 'Range',
-        description: 'Difference between minimum and maximum values',
-        standard: true,
-      },
-      {
-        value: TableReducerID.delta,
-        label: 'Delta',
-        description: 'Cumulative change in value', // HELP! not totally sure what this does
-        standard: true,
-      },
-      {
-        value: TableReducerID.step,
-        label: 'Step',
-        description: 'Minimum interval between values',
-        standard: true,
-      },
-      {
-        value: TableReducerID.diff,
-        label: 'Difference',
-        description: 'Difference between first and last values',
-        standard: true,
-      },
-      {
-        value: TableReducerID.logmin,
-        label: 'Min (above zero)',
-        description: 'Used for log min scale',
-        standard: true,
-      },
-    ].forEach(calc => {
-      const { value, alias } = calc;
-      if (index.hasOwnProperty(value)) {
-        console.warn('Duplicate Reducer', value, calc, index);
-      }
-      index[value] = calc;
-      if (alias) {
-        if (index.hasOwnProperty(alias)) {
-          console.warn('Duplicate Reducer (alias)', alias, calc, index);
-        }
-        index[alias] = calc;
-      }
-      listOfReducers.push(calc);
-    });
-    hasBuiltIndex = true;
-  }
-  return index[id];
-}
-
-/**
- * This will return an array of valid indexes and throw an error if invalid request
- */
-function verifyColumns(data: TableData, options: TableReducerOptions): number[] {
-  const { columnIndexes } = options;
-  if (!columnIndexes) {
-    return data.columns.map((v, idx) => idx);
-  }
-  columnIndexes.forEach(v => {
-    if (v < 0 || v >= data.columns.length) {
-      throw new Error('Invalid column selection: ' + v);
-    }
-  });
-  return columnIndexes;
-}
-
-interface StandardStats {
-  sum: number | null; // total
-  max: number | null;
-  min: number | null;
-  logmin: number;
-  mean: number | null; // avg
-  last: any; // current
-  first: any;
-  count: number;
-  nonNullCount: number;
-  range: number | null;
-  diff: number | null;
-  delta: number | null;
-  step: number | null;
-  allIsZero: boolean;
-  allIsNull: boolean;
-}
-
-function standardStatsReducer(
-  data: TableData,
-  columnIndexes: number[],
-  ignoreNulls: boolean,
-  nullAsZero: boolean
-): StandardStats[] {
-  const column = columnIndexes.map(idx => {
-    return {
-      sum: 0,
-      max: -Number.MAX_VALUE,
-      min: Number.MAX_VALUE,
-      logmin: Number.MAX_VALUE,
-      mean: null,
-      last: null,
-      first: null,
-      count: 0,
-      nonNullCount: 0,
-      allIsNull: true,
-      allIsZero: false,
-      range: null,
-      diff: null,
-      delta: 0,
-      step: 0,
-
-      // Just used for calcutations -- not exposed as a reducer
-      previousDeltaUp: true,
-    };
-  });
-
-  for (let i = 0; i < data.rows.length; i++) {
-    for (let x = 0; x < column.length; x++) {
-      const stats = column[x];
-      let currentValue = data.rows[i][x];
-
-      if (currentValue === null) {
-        if (ignoreNulls) {
-          continue;
-        }
-        if (nullAsZero) {
-          currentValue = 0;
-        }
-      }
-
-      if (currentValue !== null) {
-        stats.last = currentValue;
-
-        const isFirst = stats.first === null;
-        if (isFirst) {
-          stats.first = currentValue;
-        }
-
-        if (isNumber(currentValue)) {
-          stats.sum += currentValue;
-          stats.allIsNull = false;
-          stats.nonNullCount++;
-
-          if (!isFirst) {
-            const step = currentValue - stats.last!;
-            if (stats.step > step) {
-              stats.step = step; // the minimum interval
-            }
-
-            if (stats.last! > currentValue) {
-              // counter reset
-              stats.previousDeltaUp = false;
-              if (i === data.rows.length - 1) {
-                // reset on last
-                stats.delta += currentValue;
-              }
-            } else {
-              if (stats.previousDeltaUp) {
-                stats.delta += step; // normal increment
-              } else {
-                stats.delta += currentValue; // account for counter reset
-              }
-              stats.previousDeltaUp = true;
-            }
-          }
-
-          if (currentValue > stats.max) {
-            stats.max = currentValue;
-          }
-
-          if (currentValue < stats.min) {
-            stats.min = currentValue;
-          }
-
-          if (currentValue < stats.logmin && currentValue > 0) {
-            stats.logmin = currentValue;
-          }
-        }
-
-        if (currentValue !== 0) {
-          stats.allIsZero = false;
-        }
-
-        stats.last = currentValue;
-      }
-    }
-  }
-
-  for (let x = 0; x < column.length; x++) {
-    const stats = column[x] as StandardStats;
-
-    if (stats.max === -Number.MAX_VALUE) {
-      stats.max = null;
-    }
-
-    if (stats.min === Number.MAX_VALUE) {
-      stats.min = null;
-    }
-
-    if (stats.nonNullCount > 0) {
-      stats.mean = stats.sum! / stats.nonNullCount;
-    }
-
-    if (stats.max !== null && stats.min !== null) {
-      stats.range = stats.max - stats.min;
-    }
-
-    if (stats.first !== null && stats.last !== null) {
-      if (isNumber(stats.first) && isNumber(stats.last)) {
-        stats.diff = stats.last - stats.first;
-      }
-    }
-  }
-
-  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]);
-}