diff --git a/src/components/AppBar.tsx b/src/components/AppBar.tsx index 05780480..c91a9422 100644 --- a/src/components/AppBar.tsx +++ b/src/components/AppBar.tsx @@ -137,16 +137,8 @@ const HelpMenu = (): JSX.Element => { ); }; -const useRobustColorScalesSelector = (c: ColorsState) => ({ - useRobustColorScales: c.useRobustColorScales, - setUseRobustColorScales: c.setUseRobustColorScales, -}); - const ColorMenu = () => { const colors = useColors(); - const { useRobustColorScales, setUseRobustColorScales } = useColors( - useRobustColorScalesSelector - ); const content = (
@@ -167,13 +159,27 @@ const ColorMenu = () => { onChangeColorPalette={colors.setCategoricalPalette} /> - Robust Coloring + + Continuous Ints Enable + + Continuous Categories + + Enable + + + Robust Coloring + + Enable +
); diff --git a/src/components/ScalarValue.tsx b/src/components/ScalarValue.tsx index 416d037e..ff5e7351 100644 --- a/src/components/ScalarValue.tsx +++ b/src/components/ScalarValue.tsx @@ -41,7 +41,7 @@ const ScalarValue: FunctionComponent = ({ // eslint-disable-next-line react-hooks/exhaustive-deps const colorTransferFunctionSelector = useCallback( (d: Dataset) => - d.colorTransferFunctions[column.key]?.[filtered ? 'filtered' : 'full'][0], + d.colorTransferFunctions[column.key]?.[filtered ? 'filtered' : 'full'], [column.key, filtered] ); diff --git a/src/hooks/useColorTransferFunction.ts b/src/hooks/useColorTransferFunction.ts index 2c992799..3b3f4d1e 100644 --- a/src/hooks/useColorTransferFunction.ts +++ b/src/hooks/useColorTransferFunction.ts @@ -109,36 +109,40 @@ export const createConstantTransferFunction = ( return tf as ConstantTransferFunction; }; -const createColorTransferFunction = ( +export const createColorTransferFunction = ( data: ColumnData | undefined, dType: DataType | undefined, + robust: boolean = false, + continuousInts = false, + continuousCategories = false, classBreaks?: number[] ): TransferFunction => { - const robustColoring = useColors.getState().useRobustColorScales; - if (dType === undefined) return createConstantTransferFunction(unknownDataType); if (data === undefined) return createConstantTransferFunction(dType); - if (['int', 'bool', 'Category', 'str'].includes(dType.kind)) { - const uniqValues = _.uniq(data); + if (['bool', 'str'].includes(dType.kind)) { + return createCategoricalTransferFunction(_.uniq(data), dType); + } + if (dType.kind === 'int' && !continuousInts) { + const uniqValues = _.uniq(data); const tooManyInts = dType.kind === 'int' && uniqValues.length > MAX_VALUES_FOR_INT_CATEGORY; if (!tooManyInts) { - const transferFunction = createCategoricalTransferFunction( - uniqValues, - dType - ); - return transferFunction; + return createCategoricalTransferFunction(uniqValues, dType); } } - if (['int', 'float'].includes(dType.kind)) { + if (dType.kind === 'Category' && !continuousCategories) { + return createCategoricalTransferFunction(_.uniq(data), dType); + } + + if (['int', 'float', 'Category'].includes(dType.kind)) { const stats = makeStats(dType, data); return createContinuousTransferFunction( - (robustColoring ? stats?.p5 : stats?.min) ?? 0, - (robustColoring ? stats?.p95 : stats?.max) ?? 1, + (robust ? stats?.p5 : stats?.min) ?? 0, + (robust ? stats?.p95 : stats?.max) ?? 1, dType, classBreaks ); @@ -148,7 +152,19 @@ const createColorTransferFunction = ( }; // eslint-disable-next-line @typescript-eslint/no-explicit-any -export const useColorTransferFunction = (data: any[], dtype: DataType) => - useMemo(() => createColorTransferFunction(data, dtype), [dtype, data]); +export const useColorTransferFunction = (data: any[], dtype: DataType) => { + const colors = useColors(); + return useMemo( + () => + createColorTransferFunction( + data, + dtype, + colors.robust, + colors.continuousInts, + colors.continuousCategories + ), + [dtype, data] + ); +}; export default useColorTransferFunction; diff --git a/src/stores/colors.ts b/src/stores/colors.ts index cf576ad5..d850f649 100644 --- a/src/stores/colors.ts +++ b/src/stores/colors.ts @@ -17,11 +17,15 @@ export interface ColorsState { constantPalette: ConstantPalette; categoricalPalette: CategoricalPalette; continuousPalette: ContinuousPalette; - useRobustColorScales: boolean; + robust: boolean; + continuousInts: boolean; + continuousCategories: boolean; setConstantPalette: (palette?: ConstantPalette) => void; setCategoricalPalette: (palette?: CategoricalPalette) => void; setContinuousPalette: (palette?: ContinuousPalette) => void; - setUseRobustColorScales: (useRobust: boolean) => void; + setRobust: (robust: boolean) => void; + setContinuousInts: (continuous: boolean) => void; + setContinuousCategories: (continuous: boolean) => void; } export const useColors = create()( @@ -30,35 +34,26 @@ export const useColors = create()( constantPalette: defaultConstantPalette, categoricalPalette: defaultCategoricalPalette, continuousPalette: defaultContinuousPalette, - useRobustColorScales: false, + robust: false, + continuousInts: false, + continuousCategories: false, setConstantPalette: (palette) => { - set((state) => { - return { - ...state, - constantPalette: palette ?? defaultConstantPalette, - }; - }); + set({ constantPalette: palette ?? defaultConstantPalette }); }, setCategoricalPalette: (palette) => { - set((state) => { - return { - ...state, - categoricalPalette: palette ?? defaultCategoricalPalette, - }; - }); + set({ categoricalPalette: palette ?? defaultCategoricalPalette }); }, setContinuousPalette: (palette) => { - set((state) => { - return { - ...state, - continuousPalette: palette ?? defaultContinuousPalette, - }; - }); + set({ continuousPalette: palette ?? defaultContinuousPalette }); + }, + setRobust: (robust: boolean) => { + set({ robust }); + }, + setContinuousInts: (continuousInts: boolean) => { + set({ continuousInts }); }, - setUseRobustColorScales: (useRobustColorScales: boolean) => { - set((state) => { - return { ...state, useRobustColorScales }; - }); + setContinuousCategories: (continuousCategories: boolean) => { + set({ continuousCategories }); }, }), { diff --git a/src/stores/dataset/colorTransferFunctionFactory.tsx b/src/stores/dataset/colorTransferFunctionFactory.tsx index f918ec43..318692ab 100644 --- a/src/stores/dataset/colorTransferFunctionFactory.tsx +++ b/src/stores/dataset/colorTransferFunctionFactory.tsx @@ -1,83 +1,40 @@ import { - DataType, - isCategorical, - isFloat, - isNumerical, - isScalar, -} from '../../datatypes'; -import { - createCategoricalTransferFunction, - createConstantTransferFunction, - createContinuousTransferFunction, + createColorTransferFunction, TransferFunction, } from '../../hooks/useColorTransferFunction'; import _ from 'lodash'; -import { useColors } from '../../stores/colors'; -import { - ColumnData, - DataColumn, - DataStatistics, - isCategoricalColumn, - isScalarColumn, - TableData, -} from '../../types'; -import { Dataset } from './dataset'; - -export const makeApplicableColorTransferFunctions = ( - type: DataType, - data: ColumnData, - stats?: DataStatistics -): TransferFunction[] => { - const transferFunctions: TransferFunction[] = []; - - if ((isCategorical(type) || isScalar(type)) && !isFloat(type)) { - const uniqueValues = _.uniq(data); - const transFn = createCategoricalTransferFunction(uniqueValues, type); - transferFunctions.push(transFn); - } - - if (isNumerical(type)) { - const useRobustColoring = useColors.getState().useRobustColorScales; - - const min = useRobustColoring ? stats?.p5 : stats?.min; - const max = useRobustColoring ? stats?.p95 : stats?.max; - - transferFunctions.push( - createContinuousTransferFunction(min || 0, max || 1, type) - ); - } - - transferFunctions.push(createConstantTransferFunction(type)); - - return transferFunctions; -}; +import { DataColumn, TableData } from '../../types'; +import { useColors } from '../colors'; type ColumnsTransferFunctions = Record< string, - { full: TransferFunction[]; filtered: TransferFunction[] } + { full: TransferFunction; filtered: TransferFunction } >; export const makeColumnsColorTransferFunctions = ( columns: DataColumn[], data: TableData, - stats: Dataset['columnStats'], filteredMask: boolean[] ): ColumnsTransferFunctions => { - return columns - .filter((column) => isScalarColumn(column) || isCategoricalColumn(column)) - .reduce((a, column) => { - a[column.key] = { - full: makeApplicableColorTransferFunctions( - column.type, - data[column.key], - stats.full[column.key] - ), - filtered: makeApplicableColorTransferFunctions( - column.type, - data[column.key].filter((_, i) => filteredMask[i]), - stats.filtered[column.key] - ), - }; - return a; - }, {} as ColumnsTransferFunctions); + const colors = useColors.getState(); + + return columns.reduce((a, column) => { + a[column.key] = { + full: createColorTransferFunction( + data[column.key], + column.type, + colors.robust, + colors.continuousInts, + colors.continuousCategories + ), + filtered: createColorTransferFunction( + data[column.key].filter((_, i) => filteredMask[i]), + column.type, + colors.robust, + colors.continuousInts, + colors.continuousCategories + ), + }; + return a; + }, {} as ColumnsTransferFunctions); }; diff --git a/src/stores/dataset/dataset.ts b/src/stores/dataset/dataset.ts index 4916c015..154f204d 100644 --- a/src/stores/dataset/dataset.ts +++ b/src/stores/dataset/dataset.ts @@ -44,8 +44,8 @@ export interface Dataset { colorTransferFunctions: Record< string, { - full: TransferFunction[]; - filtered: TransferFunction[]; + full: TransferFunction; + filtered: TransferFunction; } >; recomputeColorTransferFunctions: () => void; @@ -430,7 +430,6 @@ export const useDataset = create( const newTransferFunctions = makeColumnsColorTransferFunctions( get().columns.filter(({ key }) => columnsToCompute.includes(key)), get().columnData, - get().columnStats, get().isIndexFiltered ); @@ -535,6 +534,8 @@ useDataset.subscribe( } ); +useColors.subscribe(useDataset.getState().recomputeColorTransferFunctions); + useDataset.subscribe( (state) => state.selectedIndices, useDataset.getState().recomputeColumnRelevance diff --git a/src/stores/dataset/statisticsFactory.tsx b/src/stores/dataset/statisticsFactory.tsx index 20265276..d559821f 100644 --- a/src/stores/dataset/statisticsFactory.tsx +++ b/src/stores/dataset/statisticsFactory.tsx @@ -1,4 +1,4 @@ -import { DataType, isNumerical } from '../../datatypes'; +import { DataType, isCategorical, isNumerical } from '../../datatypes'; import { max, mean, min, quantile, standardDeviation } from 'simple-statistics'; import { ColumnData, @@ -13,7 +13,7 @@ export const makeStats = ( data: ColumnData, mask?: boolean[] ): DataStatistics | undefined => { - if (!isNumerical(type)) { + if (!isNumerical(type) && !isCategorical(type)) { return; } diff --git a/src/widgets/DataGrid/Cell/CategoricalCell.tsx b/src/widgets/DataGrid/Cell/CategoricalCell.tsx index f7cb40d2..187cfcb5 100644 --- a/src/widgets/DataGrid/Cell/CategoricalCell.tsx +++ b/src/widgets/DataGrid/Cell/CategoricalCell.tsx @@ -22,7 +22,7 @@ const CategoricalCell: FunctionComponent = ({ value, column }) => { (d: Dataset) => d.colorTransferFunctions[column.key]?.[ tableView !== 'full' ? 'filtered' : 'full' - ][0], + ], [column.key, tableView] ); diff --git a/src/widgets/Histogram/Histogram.tsx b/src/widgets/Histogram/Histogram.tsx index 99feb64e..1188520a 100644 --- a/src/widgets/Histogram/Histogram.tsx +++ b/src/widgets/Histogram/Histogram.tsx @@ -81,7 +81,7 @@ const Histogram: Widget = () => { stackByColumnKey ? d.colorTransferFunctions[stackByColumnKey]?.[ filter ? 'filtered' : 'full' - ][0] + ] : createConstantTransferFunction(), [filter, stackByColumnKey] ); diff --git a/src/widgets/ScatterplotView/ScatterplotView.tsx b/src/widgets/ScatterplotView/ScatterplotView.tsx index 3e9dd600..b4560728 100644 --- a/src/widgets/ScatterplotView/ScatterplotView.tsx +++ b/src/widgets/ScatterplotView/ScatterplotView.tsx @@ -139,9 +139,7 @@ const ScatterplotView: Widget = () => { const transferFunctionSelector = useCallback( (d: Dataset) => colorByKey !== undefined && colorByKey.length > 0 - ? d.colorTransferFunctions[colorByKey]?.[ - filter ? 'filtered' : 'full' - ][0] + ? d.colorTransferFunctions[colorByKey]?.[filter ? 'filtered' : 'full'] : createConstantTransferFunction(), [colorByKey, filter] ); diff --git a/src/widgets/SimilarityMap/SimilarityMap.tsx b/src/widgets/SimilarityMap/SimilarityMap.tsx index 023c4dd1..9bd9c55f 100644 --- a/src/widgets/SimilarityMap/SimilarityMap.tsx +++ b/src/widgets/SimilarityMap/SimilarityMap.tsx @@ -243,9 +243,7 @@ const SimilarityMap: Widget = () => { const transferFunctionSelector = useCallback( (d: Dataset) => colorByKey !== undefined && colorByKey.length > 0 - ? d.colorTransferFunctions[colorByKey]?.[ - filter ? 'filtered' : 'full' - ][0] + ? d.colorTransferFunctions[colorByKey]?.[filter ? 'filtered' : 'full'] : createConstantTransferFunction(colorBy?.type ?? unknownDataType), [colorByKey, filter, colorBy?.type] );