Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use useColorTransfer hook for colorscale of bboxes #385

Merged
merged 1 commit into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 19 additions & 15 deletions src/hooks/useColorTransferFunction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
() =>
Expand Down
72 changes: 18 additions & 54 deletions src/lenses/BoundingBoxLens/index.tsx
Original file line number Diff line number Diff line change
@@ -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`}
Expand Down Expand Up @@ -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) {
Expand All @@ -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 = () => {
Expand All @@ -93,26 +77,6 @@ const BoundingBoxLens: Lens = ({ urls, values, columns }) => {
// Remove previous svg elements
d3.select(svgRef.current).select<SVGSVGElement>('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
Expand Down Expand Up @@ -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<SVGSVGElement>('g')
Expand All @@ -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<SVGSVGElement>('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());
}
}
};
Expand Down
1 change: 1 addition & 0 deletions src/widgets/DataGrid/Cell/HeaderCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ const HeaderCell: FunctionComponent<Props> = ({ style, columnIndex }) => {
() => (
<div tw="flex flex-col max-w-xl">
<div tw="font-bold break-all">{column.name}</div>
<div tw="font-bold">{column.type.kind}</div>
<div tw="text-gray-700 space-x-1.5 text-xs">
<span css={[!column.editable && tw`line-through`]}>editable</span>
<span css={[!column.optional && tw`line-through`]}>optional</span>
Expand Down