From 05cf895cc0d8d2dfcbb80c72c9d7ece201963d47 Mon Sep 17 00:00:00 2001 From: Dominik Haentsch Date: Thu, 7 Dec 2023 15:03:48 +0100 Subject: [PATCH] feat: use useColorTransfer hook for colorscale of bboxes --- src/hooks/useColorTransferFunction.ts | 34 ++++++----- src/lenses/BoundingBoxLens/index.tsx | 72 ++++++------------------ src/widgets/DataGrid/Cell/HeaderCell.tsx | 1 + 3 files changed, 38 insertions(+), 69 deletions(-) diff --git a/src/hooks/useColorTransferFunction.ts b/src/hooks/useColorTransferFunction.ts index 1939342c..129d6c40 100644 --- a/src/hooks/useColorTransferFunction.ts +++ b/src/hooks/useColorTransferFunction.ts @@ -111,48 +111,52 @@ export const createConstantTransferFunction = ( export const createColorTransferFunction = ( data: ColumnData | undefined, - dType: DataType | undefined, + dtype: DataType | undefined, robust = false, continuousInts = false, continuousCategories = false, classBreaks?: number[] ): TransferFunction => { - if (dType === undefined) return createConstantTransferFunction(unknownDataType); - if (data === undefined) return createConstantTransferFunction(dType); + if (dtype === undefined) return createConstantTransferFunction(unknownDataType); + if (data === undefined) return createConstantTransferFunction(dtype); - if (['bool', 'str'].includes(dType.kind)) { - return createCategoricalTransferFunction(_.uniq(data), dType); + while (dtype.kind === 'Sequence') { + dtype = dtype.dtype; } - if (dType.kind === 'int' && !continuousInts) { + 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; + dtype.kind === 'int' && uniqValues.length > MAX_VALUES_FOR_INT_CATEGORY; if (!tooManyInts) { - return createCategoricalTransferFunction(uniqValues, dType); + return createCategoricalTransferFunction(uniqValues, dtype); } } - if (dType.kind === 'Category' && !continuousCategories) { - return createCategoricalTransferFunction(_.uniq(data), dType); + if (dtype.kind === 'Category' && !continuousCategories) { + return createCategoricalTransferFunction(_.uniq(data), dtype); } - if (['int', 'float', 'Category'].includes(dType.kind)) { - const stats = makeStats(dType, data); + if (['int', 'float', 'Category'].includes(dtype.kind)) { + const stats = makeStats(dtype, data); return createContinuousTransferFunction( (robust ? stats?.p5 : stats?.min) ?? 0, (robust ? stats?.p95 : stats?.max) ?? 1, - dType, + dtype, classBreaks ); } - return createConstantTransferFunction(dType); + return createConstantTransferFunction(dtype); }; // eslint-disable-next-line @typescript-eslint/no-explicit-any -export const useColorTransferFunction = (data: any[], dtype: DataType) => { +export const useColorTransferFunction = (data: any[], dtype: DataType | undefined) => { const colors = useColors(); return useMemo( () => diff --git a/src/lenses/BoundingBoxLens/index.tsx b/src/lenses/BoundingBoxLens/index.tsx index 7fb0d558..77ea114a 100644 --- a/src/lenses/BoundingBoxLens/index.tsx +++ b/src/lenses/BoundingBoxLens/index.tsx @@ -1,11 +1,10 @@ import useResizeObserver from '@react-hook/resize-observer'; -import { useRef, useCallback } from 'react'; +import { useRef } from 'react'; import { Lens } from '../../types'; import tw, { styled } from 'twin.macro'; -import { CategoricalDataType, SequenceDataType } from '../../datatypes'; -import { ColorsState, useColors } from '../../stores/colors'; -import { Dataset, useDataset } from '../../stores/dataset'; +import { CategoricalDataType } from '../../datatypes'; import * as d3 from 'd3'; +import { useColorTransferFunction } from '../../hooks'; const Container = styled.div` ${tw`relative h-full w-full overflow-hidden`} @@ -40,7 +39,6 @@ const BoundingBoxLens: Lens = ({ urls, values, columns }) => { let boxes: [number[]] | [] = []; let categories: number[] | [] = []; - let invertedCategories = (index: number) => index.toString(); // Check if single bounding box or multiple if (bboxColumnIndex != -1) { @@ -51,37 +49,23 @@ const BoundingBoxLens: Lens = ({ urls, values, columns }) => { if (categoryColumnIndex != -1) { categories = [values[categoryColumnIndex] as number]; - - invertedCategories = (index) => - (columns[categoryColumnIndex].type as CategoricalDataType) - .invertedCategories[index]; } else if (categoriesColumnIndex != -1) { categories = values[categoriesColumnIndex] as [number]; - - invertedCategories = (index) => - ( - (columns[categoriesColumnIndex].type as SequenceDataType) - .dtype as CategoricalDataType - ).invertedCategories[index]; } - // eslint-disable-next-line react-hooks/exhaustive-deps - const colorTransferFunctionSelector = useCallback( - (d: Dataset) => { - if (categoryColumnIndex != -1) { - return d.colorTransferFunctions[columns[categoryColumnIndex].key]?.[ - 'full' - ]; - } - return undefined; - }, - [columns, categoryColumnIndex] + const categoricalColumn = + columns[categoryColumnIndex] ?? columns[categoriesColumnIndex]; + const categoricalDtype = ( + categoricalColumn?.type?.kind === 'Sequence' + ? categoricalColumn?.type?.dtype + : categoricalColumn?.type + ) as CategoricalDataType | undefined; + + const colorTransferFunction = useColorTransferFunction( + categories, + categoricalDtype ); - const colorPaletteSelector = (c: ColorsState) => c.continuousPalette; - const colorPalette = useColors(colorPaletteSelector); - const colorTransferFunction = useDataset(colorTransferFunctionSelector); - useResizeObserver(container.current, () => drawBoundingBoxes()); const drawBoundingBoxes = () => { @@ -93,26 +77,6 @@ const BoundingBoxLens: Lens = ({ urls, values, columns }) => { // Remove previous svg elements d3.select(svgRef.current).select('g').remove(); - let colorFunc: (i: number) => string; - - if (categories.length === 0) { - colorFunc = (i: number) => colorPalette.scale().colors(boxes.length)[i]; - } else { - if (colorTransferFunction !== undefined) { - colorFunc = (i: number) => colorTransferFunction(categories[i]).hex(); - } else { - colorFunc = (i: number) => { - const dtype = ( - columns[categoriesColumnIndex].type as SequenceDataType - ).dtype as CategoricalDataType; - const index = dtype.categories[invertedCategories(categories[i])]; - return colorPalette - .scale() - .colors(Object.keys(dtype.categories).length)[index]; - }; - } - } - const image = imgRef.current; // Natural dimensions of the image @@ -148,7 +112,7 @@ const BoundingBoxLens: Lens = ({ urls, values, columns }) => { const y = box[1] * renderedHeight + offsetHeight; const width = (box[2] - box[0]) * renderedWidth; const height = (box[3] - box[1]) * renderedHeight; - const boxColor = colorFunc(i); + const boxColor = colorTransferFunction(i); d3.select(svgRef.current) .select('g') @@ -159,18 +123,18 @@ const BoundingBoxLens: Lens = ({ urls, values, columns }) => { .attr('height', height) .attr('stroke', 'firebrick') .attr('stroke-width', 2) - .attr('stroke', boxColor) + .attr('stroke', boxColor.css()) .attr('fill', 'none'); if (categories.length > 0) { d3.select(svgRef.current) .select('g') .append('text') - .text(invertedCategories(categories[i])) + .text(categoricalDtype?.invertedCategories[categories[i]] ?? '') .attr('x', x) .attr('y', y - 3) .attr('fontsize', 12) - .attr('fill', boxColor); + .attr('fill', boxColor.css()); } } }; diff --git a/src/widgets/DataGrid/Cell/HeaderCell.tsx b/src/widgets/DataGrid/Cell/HeaderCell.tsx index 52ba7c14..50ffa49b 100644 --- a/src/widgets/DataGrid/Cell/HeaderCell.tsx +++ b/src/widgets/DataGrid/Cell/HeaderCell.tsx @@ -90,6 +90,7 @@ const HeaderCell: FunctionComponent = ({ style, columnIndex }) => { () => (
{column.name}
+
{column.type.kind}
editable optional