Skip to content

Commit

Permalink
feat: globally switch between continuous and discrete coloring of ints
Browse files Browse the repository at this point in the history
  • Loading branch information
neindochoh committed Oct 27, 2023
1 parent 3bb12ed commit ded266b
Show file tree
Hide file tree
Showing 11 changed files with 104 additions and 133 deletions.
28 changes: 17 additions & 11 deletions src/components/AppBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = (
<div tw="flex flex-col w-72 pb-1">
Expand All @@ -167,13 +159,27 @@ const ColorMenu = () => {
onChangeColorPalette={colors.setCategoricalPalette}
/>
</Menu.Item>
<Menu.Title>Robust Coloring</Menu.Title>

<Menu.Title>Continuous Ints</Menu.Title>
<Menu.Switch
value={useRobustColorScales}
onChange={setUseRobustColorScales}
value={colors.continuousInts}
onChange={colors.setContinuousInts}
>
Enable
</Menu.Switch>

<Menu.Title>Continuous Categories</Menu.Title>
<Menu.Switch
value={colors.continuousCategories}
onChange={colors.setContinuousCategories}
>
Enable
</Menu.Switch>

<Menu.Title>Robust Coloring</Menu.Title>
<Menu.Switch value={colors.robust} onChange={colors.setRobust}>
Enable
</Menu.Switch>
</Menu>
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion src/components/ScalarValue.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const ScalarValue: FunctionComponent<Props> = ({
// 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]
);

Expand Down
46 changes: 31 additions & 15 deletions src/hooks/useColorTransferFunction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Check failure on line 115 in src/hooks/useColorTransferFunction.ts

View workflow job for this annotation

GitHub Actions / 🔍 Check

Type boolean trivially inferred from a boolean literal, remove type annotation

Check failure on line 115 in src/hooks/useColorTransferFunction.ts

View workflow job for this annotation

GitHub Actions / 🔍 Check

Type boolean trivially inferred from a boolean literal, remove type annotation
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
);
Expand All @@ -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]

Check warning on line 166 in src/hooks/useColorTransferFunction.ts

View workflow job for this annotation

GitHub Actions / 🔍 Check

React Hook useMemo has missing dependencies: 'colors.continuousCategories', 'colors.continuousInts', and 'colors.robust'. Either include them or remove the dependency array

Check warning on line 166 in src/hooks/useColorTransferFunction.ts

View workflow job for this annotation

GitHub Actions / 🔍 Check

React Hook useMemo has missing dependencies: 'colors.continuousCategories', 'colors.continuousInts', and 'colors.robust'. Either include them or remove the dependency array
);
};

export default useColorTransferFunction;
45 changes: 20 additions & 25 deletions src/stores/colors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ColorsState>()(
Expand All @@ -30,35 +34,26 @@ export const useColors = create<ColorsState>()(
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 });
},
}),
{
Expand Down
93 changes: 25 additions & 68 deletions src/stores/dataset/colorTransferFunctionFactory.tsx
Original file line number Diff line number Diff line change
@@ -1,83 +1,40 @@
import {
DataType,
isCategorical,
isFloat,
isNumerical,
isScalar,
} from '../../datatypes';
import {
createCategoricalTransferFunction,
createConstantTransferFunction,
createContinuousTransferFunction,
createColorTransferFunction,
TransferFunction,
} from '../../hooks/useColorTransferFunction';
import _ from 'lodash';

Check warning on line 5 in src/stores/dataset/colorTransferFunctionFactory.tsx

View workflow job for this annotation

GitHub Actions / 🔍 Check

'_' is defined but never used

Check warning on line 5 in src/stores/dataset/colorTransferFunctionFactory.tsx

View workflow job for this annotation

GitHub Actions / 🔍 Check

'_' is defined but never used
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);
};
7 changes: 4 additions & 3 deletions src/stores/dataset/dataset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ export interface Dataset {
colorTransferFunctions: Record<
string,
{
full: TransferFunction[];
filtered: TransferFunction[];
full: TransferFunction;
filtered: TransferFunction;
}
>;
recomputeColorTransferFunctions: () => void;
Expand Down Expand Up @@ -430,7 +430,6 @@ export const useDataset = create(
const newTransferFunctions = makeColumnsColorTransferFunctions(
get().columns.filter(({ key }) => columnsToCompute.includes(key)),
get().columnData,
get().columnStats,
get().isIndexFiltered
);

Expand Down Expand Up @@ -535,6 +534,8 @@ useDataset.subscribe(
}
);

useColors.subscribe(useDataset.getState().recomputeColorTransferFunctions);

useDataset.subscribe(
(state) => state.selectedIndices,
useDataset.getState().recomputeColumnRelevance
Expand Down
4 changes: 2 additions & 2 deletions src/stores/dataset/statisticsFactory.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -13,7 +13,7 @@ export const makeStats = (
data: ColumnData,
mask?: boolean[]
): DataStatistics | undefined => {
if (!isNumerical(type)) {
if (!isNumerical(type) && !isCategorical(type)) {
return;
}

Expand Down
2 changes: 1 addition & 1 deletion src/widgets/DataGrid/Cell/CategoricalCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const CategoricalCell: FunctionComponent<Props> = ({ value, column }) => {
(d: Dataset) =>
d.colorTransferFunctions[column.key]?.[
tableView !== 'full' ? 'filtered' : 'full'
][0],
],
[column.key, tableView]
);

Expand Down
2 changes: 1 addition & 1 deletion src/widgets/Histogram/Histogram.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ const Histogram: Widget = () => {
stackByColumnKey
? d.colorTransferFunctions[stackByColumnKey]?.[
filter ? 'filtered' : 'full'
][0]
]
: createConstantTransferFunction(),
[filter, stackByColumnKey]
);
Expand Down
4 changes: 1 addition & 3 deletions src/widgets/ScatterplotView/ScatterplotView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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]
);
Expand Down
4 changes: 1 addition & 3 deletions src/widgets/SimilarityMap/SimilarityMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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]
);
Expand Down

0 comments on commit ded266b

Please sign in to comment.