Commit 1c5714ef authored by Patrik Tomov's avatar Patrik Tomov
Browse files

Refactoring visualization

parent d2cd5412
Loading
Loading
Loading
Loading
+81 −142
Original line number Diff line number Diff line
import React, { useCallback, useMemo, useRef } from 'react';
import React, { useMemo } from 'react';
import {
	Button,
	Grid,
@@ -12,15 +12,9 @@ import { Help } from '@mui/icons-material';

import CanvasHeatmap from '../../../../visualization/CanvasHeatmap';
import useHeatmapManagement from '../../../../../hooks/visualization/useHeatmapManagement';
import {
	binHeight,
	binWidth
} from '../../../../visualization/visualizationConfig';
import { binHeight } from '../../../../visualization/visualizationConfig';
import SelectPrimaryFaceDialog from '../../../../projects-view/dialogs/SelectPrimaryFaceDialog';
import {
	LinkageStrategy,
	Transform
} from '../../../../../types/BatchVisualizationTypes';
import { LinkageStrategy } from '../../../../../types/BatchVisualizationTypes';
import OpenSingleTaskConfirmationDialog from '../../../../projects-view/dialogs/OpenSingleTaskConfirmationDialog';

const BatchVisualizationPanel = () => {
@@ -30,54 +24,38 @@ const BatchVisualizationPanel = () => {
		scaleAxisX,
		scaleAxisY,
		colorScale,
		hiddenRowFaceNames,
		hiddenColumnFaceNames,
		selectedHiddenRows,
		selectedHiddenColumns,
		setSelectedHiddenRows,
		setSelectedHiddenColumns,
		selectionMode,
		toggleSelectionMode,
		draggedRowIndex,
		targetRowIndex,
		draggedColumnIndex,
		targetColumnIndex,
		handleRowMouseDown,
		handleColumnMouseDown,
		handleMouseUp,
		setTargetRowIndex,
		setTargetColumnIndex,
		showHidden,
		handleShowFaces,
		openSelectPrimaryFace,
		setOpenSelectPrimaryFace,
		filesPair,
		handleRowOrColumnClick,
		handleOpenPairTask,
		calculateRowPositions,
		clusteringSteps,
		handleShowDendrogram,
		localLinkageStrategy,
		setLocalLinkageStrategy,
		lastClickedRowName,
		lastClickedColumnName,
		hideRowAndColumn,
		openSingleTaskConfirmation,
		setOpenSingleTaskConfirmation,
		hiddenItems,
		rowState,
		columnState,
		onCellClick,
		transform,
		handleHeatmapDrag,
		finalizeDrag,
		openSelectPrimaryFace,
		localLinkageStrategy,
		filesPair,
		handleOpenSingleTask,
		lastClickedRowIndex,
		lastClickedColumnIndex,
		isPairClicked,
		handleCellClick,
		handleKeyDown,
		openSingleTaskConfirmation,
		clusteringSteps,
		distance,
		calculateRowPositions,
		showHidden,
		hideBoth,
		distance
		setHiddenItems,
		toggleSelectionMode,
		setOpenSelectPrimaryFace,
		setLocalLinkageStrategy,
		setOpenSingleTaskConfirmation,
		handleShowDendrogram
	} = useHeatmapManagement();

	const transform = useRef<Transform>({
		scale: 1,
		translateX: 0,
		translateY: 0
	}).current;

	console.log('rendering BatchVisualizationPanel');
	const rowPositions = useMemo(
		() => calculateRowPositions(heatmapRows, scaleAxisY, binHeight),
		[heatmapRows]
@@ -88,62 +66,6 @@ const BatchVisualizationPanel = () => {
		[clusteringSteps]
	);

	const handleMouseMoveInternal = useCallback(
		(event: React.MouseEvent<HTMLDivElement>) => {
			if (selectionMode) {
				const rect = event.currentTarget.getBoundingClientRect();

				if (draggedRowIndex !== null) {
					const yPosition =
						(event.clientY - rect.top - transform.translateY) / transform.scale;
					const newIndex = Math.floor((yPosition - 100) / binHeight);

					if (newIndex >= 0 && newIndex < heatmapRows.length) {
						if (newIndex === draggedRowIndex) {
							setTargetRowIndex(null);
						} else {
							setTargetRowIndex(newIndex);
						}
					}
				}
				if (draggedColumnIndex !== null) {
					const xPosition =
						(event.clientX - rect.left - transform.translateX) /
						transform.scale;
					const newColumnIndex = Math.floor((xPosition - 100) / binWidth);
					if (
						newColumnIndex >= 0 &&
						newColumnIndex < heatmapRows[0].columns.length
					) {
						if (newColumnIndex === draggedColumnIndex) {
							setTargetColumnIndex(null);
						} else {
							setTargetColumnIndex(newColumnIndex);
						}
					}
				}
			}
		},
		[
			selectionMode,
			draggedRowIndex,
			draggedColumnIndex,
			transform.scale,
			transform.translateX,
			transform.translateY,
			heatmapRows
		]
	);

	const handleKeyDown = useCallback(
		(event: React.KeyboardEvent<HTMLDivElement>) => {
			if (event.key === 'S' || event.key === 's') {
				toggleSelectionMode();
			}
		},
		[]
	);

	if (!heatmapRows.length) {
		return (
			<Grid container>
@@ -167,8 +89,13 @@ const BatchVisualizationPanel = () => {
						<InputLabel id="row-select-label">Hidden Rows</InputLabel>
						<Select
							multiple
							value={selectedHiddenRows}
							onChange={e => setSelectedHiddenRows(e.target.value as string[])}
							value={hiddenItems.selectedRows}
							onChange={e =>
								setHiddenItems({
									...hiddenItems,
									selectedRows: e.target.value as string[]
								})
							}
							sx={{ width: '150px', height: '30px' }}
							displayEmpty
							renderValue={selected => (selected.length > 0 ? selected[0] : '')}
@@ -181,7 +108,7 @@ const BatchVisualizationPanel = () => {
								}
							}}
						>
							{hiddenRowFaceNames.map(rowFaceName => (
							{hiddenItems.rows.map(rowFaceName => (
								<MenuItem key={rowFaceName} value={rowFaceName}>
									{rowFaceName}
								</MenuItem>
@@ -193,9 +120,12 @@ const BatchVisualizationPanel = () => {
						<InputLabel id="column-select-label">Hidden Columns</InputLabel>
						<Select
							multiple
							value={selectedHiddenColumns}
							value={hiddenItems.selectedColumns}
							onChange={e =>
								setSelectedHiddenColumns(e.target.value as string[])
								setHiddenItems({
									...hiddenItems,
									selectedColumns: e.target.value as string[]
								})
							}
							sx={{ width: '150px', height: '30px' }}
							displayEmpty
@@ -209,7 +139,7 @@ const BatchVisualizationPanel = () => {
								}
							}}
						>
							{hiddenColumnFaceNames.map(colFaceName => (
							{hiddenItems.columns.map(colFaceName => (
								<MenuItem key={colFaceName} value={colFaceName}>
									{colFaceName}
								</MenuItem>
@@ -221,7 +151,10 @@ const BatchVisualizationPanel = () => {
						<Button
							variant="contained"
							onClick={() =>
								showHidden(selectedHiddenRows, selectedHiddenColumns)
								showHidden(
									hiddenItems.selectedRows,
									hiddenItems.selectedColumns
								)
							}
							sx={{ height: '30px', marginTop: '22px' }}
						>
@@ -229,14 +162,16 @@ const BatchVisualizationPanel = () => {
						</Button>
					</Grid>

					{lastClickedRowName && (
					{rowState.lastClickedName && (
						<Grid item>
							<InputLabel style={{ fontWeight: 'bold', width: '115px' }}>
								{lastClickedRowName}
								{rowState.lastClickedName}
							</InputLabel>
							<Button
								variant="contained"
								onClick={() => hideRowAndColumn(lastClickedRowName, true)}
								onClick={() =>
									hideRowAndColumn(rowState.lastClickedName as string, true)
								}
								sx={{ height: '30px', width: '115px' }}
							>
								Hide face
@@ -244,26 +179,31 @@ const BatchVisualizationPanel = () => {
						</Grid>
					)}

					{lastClickedColumnName && (
					{columnState.lastClickedName !== null && (
						<Grid item>
							<InputLabel style={{ fontWeight: 'bold', width: '115px' }}>
								{lastClickedColumnName}
								{columnState.lastClickedName}
							</InputLabel>
							<Button
								variant="contained"
								onClick={() => hideRowAndColumn(lastClickedColumnName, false)}
								onClick={() =>
									hideRowAndColumn(columnState.lastClickedName as string, false)
								}
								sx={{ height: '30px', width: '115px' }}
							>
								Hide face
							</Button>
						</Grid>
					)}
					{lastClickedRowName && lastClickedColumnName && (
					{rowState.lastClickedName && columnState.lastClickedName && (
						<Grid item>
							<Button
								variant="contained"
								onClick={() =>
									hideBoth(lastClickedRowName, lastClickedColumnName)
									hideBoth(
										rowState.lastClickedName as string,
										columnState.lastClickedName as string
									)
								}
								sx={{
									height: '30px',
@@ -312,31 +252,35 @@ const BatchVisualizationPanel = () => {
							Make clustering
						</Button>
					</Grid>
					{lastClickedRowName && (
					{rowState.lastClickedName && (
						<Grid item>
							<Button
								variant="contained"
								onClick={() => handleOpenSingleTask(lastClickedRowName)}
								onClick={() =>
									handleOpenSingleTask(rowState.lastClickedName as string)
								}
								sx={{ height: '30px', width: '115px' }}
							>
								Open task
							</Button>
						</Grid>
					)}
					{lastClickedColumnName && (
					{columnState.lastClickedName && (
						<Grid item>
							<Button
								variant="contained"
								onClick={() => handleOpenSingleTask(lastClickedColumnName)}
								onClick={() =>
									handleOpenSingleTask(columnState.lastClickedName as string)
								}
								sx={{ height: '30px', width: '115px' }}
							>
								Open task
							</Button>
						</Grid>
					)}
					{lastClickedRowName &&
						lastClickedColumnName &&
						lastClickedRowName !== lastClickedColumnName && (
					{rowState.lastClickedName &&
						columnState.lastClickedName &&
						rowState.lastClickedName !== columnState.lastClickedName && (
							<Grid item>
								<Button
									variant="contained"
@@ -400,12 +344,13 @@ const BatchVisualizationPanel = () => {
				) : (
					<div
						style={{
							width: '800px',
							height: '600px',
							position: 'relative'
							width: '100%',
							height: 'calc(100vh - 325px)',
							display: 'flex',
							outline: 'none'
						}}
						onMouseMove={handleMouseMoveInternal}
						onMouseUp={handleMouseUp}
						onMouseMove={handleHeatmapDrag}
						onMouseUp={finalizeDrag}
						onKeyDown={handleKeyDown}
						role="button"
						tabIndex={0}
@@ -415,20 +360,14 @@ const BatchVisualizationPanel = () => {
							scaleAxisX={scaleAxisX}
							scaleAxisY={scaleAxisY}
							colorScale={colorScale}
							canvasWidth={800}
							canvasHeight={600}
							handleRowMouseDown={handleRowMouseDown}
							handleColumnMouseDown={handleColumnMouseDown}
							targetRowIndex={targetRowIndex}
							targetColumnIndex={targetColumnIndex}
							handleShowFaces={handleShowFaces}
							handleCellClick={handleCellClick}
							handleRowOrColumnClick={handleRowOrColumnClick}
							onCellClick={onCellClick}
							rowState={rowState}
							columnState={columnState}
							selectionMode={selectionMode}
							transform={transform}
							clusteringSteps={memoizedClusteringSteps}
							rowPositions={rowPositions}
							lastClickedRowIndex={lastClickedRowIndex}
							lastClickedColumnIndex={lastClickedColumnIndex}
						/>
					</div>
				)}
+43 −49
Original line number Diff line number Diff line
@@ -2,8 +2,10 @@ import React, { useRef, useEffect, useCallback } from 'react';
import { ScaleLinear } from 'd3-scale';

import {
	ColumnState,
	HeatmapRowData,
	PairStep,
	RowState,
	Transform
} from '../../types/BatchVisualizationTypes';

@@ -14,26 +16,24 @@ type CanvasHeatmapProps = {
	scaleAxisX: ScaleLinear<number, number>;
	scaleAxisY: ScaleLinear<number, number>;
	colorScale: (value: number) => string;
	canvasWidth: number;
	canvasHeight: number;
	handleRowMouseDown: (rowIndex: number, rowName: string) => void;
	handleColumnMouseDown: (columnIndex: number, columnName: string) => void;
	targetRowIndex: number | null;
	targetColumnIndex: number | null;
	handleShowFaces: (rowFaceName: string, columnFaceName: string) => void;
	handleCellClick: (
	handleRowOrColumnClick: (
		type: 'row' | 'column',
		rowIndex: number,
		rowName: string
	) => void;
	onCellClick: (
		rowFaceName: string,
		rowIndex: number,
		columnFaceName: string,
		columnIndex: number,
		distance: number
	) => void;
	rowState: RowState;
	columnState: ColumnState;
	selectionMode: boolean;
	transform: Transform;
	clusteringSteps: PairStep[] | null;
	rowPositions: Record<string, number>;
	lastClickedRowIndex: number | null;
	lastClickedColumnIndex: number | null;
};

const CanvasHeatmap: React.FC<CanvasHeatmapProps> = ({
@@ -41,21 +41,16 @@ const CanvasHeatmap: React.FC<CanvasHeatmapProps> = ({
	scaleAxisX,
	scaleAxisY,
	colorScale,
	canvasWidth,
	canvasHeight,
	handleRowMouseDown,
	handleColumnMouseDown,
	targetRowIndex,
	targetColumnIndex,
	handleShowFaces,
	handleCellClick,
	handleRowOrColumnClick,
	onCellClick,
	rowState,
	columnState,
	selectionMode,
	transform,
	clusteringSteps,
	rowPositions,
	lastClickedRowIndex,
	lastClickedColumnIndex
	rowPositions
}) => {
	console.log('rendering CanvasHeatmap');
	const canvasRef = useRef<HTMLCanvasElement>(null);
	const isDragging = useRef(false);
	const lastPosition = useRef({ x: 0, y: 0 });
@@ -113,8 +108,8 @@ const CanvasHeatmap: React.FC<CanvasHeatmapProps> = ({
	const highlightTargetRowsColumns = (context: CanvasRenderingContext2D) => {
		const labelPaddingX = 100;
		const labelPaddingY = 100;
		if (targetRowIndex !== null) {
			const targetY = scaleAxisY(targetRowIndex) + labelPaddingY;
		if (rowState.targetIndex !== null) {
			const targetY = scaleAxisY(rowState.targetIndex) + labelPaddingY;
			context.fillStyle = 'rgba(0, 0, 255, 0.3)';
			context.fillRect(
				labelPaddingX,
@@ -123,8 +118,8 @@ const CanvasHeatmap: React.FC<CanvasHeatmapProps> = ({
				binHeight
			);
		}
		if (targetColumnIndex !== null) {
			const targetX = scaleAxisX(targetColumnIndex) + labelPaddingX;
		if (columnState.targetIndex !== null) {
			const targetX = scaleAxisX(columnState.targetIndex) + labelPaddingX;
			context.fillStyle = 'rgba(0, 255, 0, 0.3)';
			context.fillRect(
				targetX,
@@ -133,8 +128,8 @@ const CanvasHeatmap: React.FC<CanvasHeatmapProps> = ({
				heatmapRows.length * binHeight
			);
		}
		if (lastClickedRowIndex !== null) {
			const targetY = scaleAxisY(lastClickedRowIndex) + labelPaddingY;
		if (rowState.lastClickedIndex !== null) {
			const targetY = scaleAxisY(rowState.lastClickedIndex) + labelPaddingY;
			context.strokeStyle = 'rgba(0, 0, 255, 1)';
			context.lineWidth = 3;
			context.strokeRect(
@@ -145,8 +140,8 @@ const CanvasHeatmap: React.FC<CanvasHeatmapProps> = ({
			);
		}

		if (lastClickedColumnIndex !== null) {
			const targetX = scaleAxisX(lastClickedColumnIndex) + labelPaddingX;
		if (columnState.lastClickedIndex !== null) {
			const targetX = scaleAxisX(columnState.lastClickedIndex) + labelPaddingX;
			context.strokeStyle = 'rgba(0, 255, 0, 1)';
			context.lineWidth = 3;
			context.strokeRect(
@@ -270,7 +265,7 @@ const CanvasHeatmap: React.FC<CanvasHeatmapProps> = ({
		const context = canvas.getContext('2d');
		if (!context) return;

		context.clearRect(0, 0, canvasWidth, canvasHeight);
		context.clearRect(0, 0, 1000, 1000);
		context.save();
		context.translate(transform.translateX, transform.translateY);
		context.scale(transform.scale, transform.scale);
@@ -292,12 +287,10 @@ const CanvasHeatmap: React.FC<CanvasHeatmapProps> = ({
		scaleAxisX,
		scaleAxisY,
		colorScale,
		canvasWidth,
		canvasHeight,
		targetRowIndex,
		targetColumnIndex,
		lastClickedRowIndex,
		lastClickedColumnIndex,
		rowState.targetIndex,
		columnState.targetIndex,
		rowState.lastClickedIndex,
		columnState.lastClickedIndex,
		clusteringSteps
	]);

@@ -374,7 +367,7 @@ const CanvasHeatmap: React.FC<CanvasHeatmapProps> = ({
		heatmapRows.forEach((row, rowIndex) => {
			const y = scaleAxisY(rowIndex) + labelPaddingY;
			if (clickX < labelPaddingX && clickY > y && clickY < y + binHeight) {
				handleRowMouseDown(rowIndex, row.faceName);
				handleRowOrColumnClick('row', rowIndex, row.faceName);
				rowClicked = true;
			}
		});
@@ -390,7 +383,7 @@ const CanvasHeatmap: React.FC<CanvasHeatmapProps> = ({
			heatmapRows[0].columns.forEach((col, colIndex) => {
				const x = scaleAxisX(colIndex) + labelPaddingX;
				if (clickY < labelPaddingY && clickX > x && clickX < x + binWidth) {
					handleColumnMouseDown(colIndex, col.faceName);
					handleRowOrColumnClick('column', colIndex, col.faceName);
				}
			});
		}
@@ -411,7 +404,7 @@ const CanvasHeatmap: React.FC<CanvasHeatmapProps> = ({
					clickY > y &&
					clickY < y + binHeight
				) {
					handleCellClick(
					onCellClick(
						row.faceName,
						rowIndex,
						col.faceName,
@@ -440,14 +433,7 @@ const CanvasHeatmap: React.FC<CanvasHeatmapProps> = ({

			handleRectangleClick(clickX, clickY);
		},
		[
			heatmapRows,
			scaleAxisY,
			scaleAxisX,
			handleRowMouseDown,
			handleColumnMouseDown,
			handleShowFaces
		]
		[heatmapRows, scaleAxisY, scaleAxisX, handleRowOrColumnClick, selectionMode]
	);

	useEffect(() => {
@@ -467,9 +453,17 @@ const CanvasHeatmap: React.FC<CanvasHeatmapProps> = ({

	return (
		<canvas
			width={
				canvasRef.current?.parentElement
					? canvasRef.current.parentElement.offsetWidth - 2
					: 800
			}
			height={
				canvasRef.current?.parentElement
					? canvasRef.current.parentElement.offsetHeight - 2
					: 600
			}
			ref={canvasRef}
			width={canvasWidth}
			height={canvasHeight}
			style={{ border: '1px solid black' }}
			onMouseDown={e => {
				handleCanvasMouseDown(e);
+166 −0
Original line number Diff line number Diff line
import { useCallback } from 'react';
import { ScaleLinear } from 'd3-scale';

import { BatchDistancesDto } from '../../types/BatchProcessingTypes';
import {
	HeatmapRowData,
	LinkageStrategy,
	PairStep,
	SerializableClusterNode
} from '../../types/BatchVisualizationTypes';
import { BatchProcessingTaskService } from '../../services/BatchProcessingTaskService';

function useDendrogram(
	filteredHeatmapRows: HeatmapRowData[],
	setFilteredHeatmapRows: (rows: HeatmapRowData[]) => void,
	setClusteringSteps: (steps: PairStep[]) => void,
	batchDistancesDto: BatchDistancesDto | null,
	localLinkageStrategy: LinkageStrategy
) {
	const calculateRowPositions = useCallback(
		(
			heatmapRows: HeatmapRowData[],
			scaleAxisY: ScaleLinear<number, number>,
			binHeight: number
		): Record<string, number> => {
			const positions: Record<string, number> = {};
			heatmapRows.forEach((row, rowIndex) => {
				const y = scaleAxisY(rowIndex) + binHeight / 2;
				positions[row.faceName] = y;
			});
			return positions;
		},
		[filteredHeatmapRows]
	);

	const prepareDataForDendrogram = (): BatchDistancesDto | null => {
		if (!filteredHeatmapRows || !batchDistancesDto) {
			return null;
		}

		const heatmapRowData = filteredHeatmapRows;
		const validFaceNames = new Set(heatmapRowData.map(row => row.faceName));
		const validIndexes = new Set(
			heatmapRowData.flatMap(row => row.columns.map(column => column.faceIndex))
		);

		// Filter BatchDistancesDto to only include faceDistances with faceNames in HeatmapRowData
		const filteredFaceDistances = batchDistancesDto.faceDistances
			.filter(faceDistance => validFaceNames.has(faceDistance.faceName))
			.map(faceDistance => ({
				...faceDistance,
				// Filter faceDistances array to only include distances at valid indexes
				faceDistances: faceDistance.faceDistances.filter((_, index) =>
					validIndexes.has(index)
				),
				faceDeviations: faceDistance.faceDeviations.filter((_, index) =>
					validIndexes.has(index)
				)
			}));

		return {
			...batchDistancesDto,
			faceDistances: filteredFaceDistances,
			linkageStrategy: localLinkageStrategy
		};
	};

	const getClusterSteps = (node: SerializableClusterNode): PairStep[] => {
		const steps: PairStep[] = [];

		const traverse = (currentNode: SerializableClusterNode): string => {
			if (currentNode.children.length === 2) {
				const [leftChild, rightChild] = currentNode.children;

				const leftName = traverse(leftChild);
				const rightName = traverse(rightChild);

				if (currentNode.distance !== null) {
					steps.push({
						pair: [leftName, rightName],
						resultName: currentNode.name,
						distance: currentNode.distance
					});
				}

				return currentNode.name;
			}

			return currentNode.name;
		};

		traverse(node);

		steps.sort((a, b) => a.distance - b.distance);
		return steps;
	};

	const reorderRows = (clusteringSteps: PairStep[]) => {
		type RowType = (typeof filteredHeatmapRows)[0];

		const faceMap = new Map<string, RowType>();
		filteredHeatmapRows.forEach(row => {
			faceMap.set(row.faceName, row);
		});

		const visited = new Set<string>();

		const collectRows = (clusterName: string): RowType[] => {
			const step = clusteringSteps.find(
				step => step.resultName === clusterName
			);

			if (!step) {
				if (visited.has(clusterName)) return [];
				const row = faceMap.get(clusterName);
				if (row) {
					visited.add(clusterName);
					return [row];
				}
				return [];
			}

			const [left, right] = step.pair;
			return [...collectRows(left), ...collectRows(right)];
		};

		const orderedRows: RowType[] = [];
		clusteringSteps.forEach(step => {
			step.pair.forEach(item => {
				if (!visited.has(item)) {
					orderedRows.push(...collectRows(item));
				}
			});
		});

		const orderedFaceNames = orderedRows.map(row => row.faceName);
		orderedRows.forEach(row => {
			row.columns.sort(
				(a, b) =>
					orderedFaceNames.indexOf(a.faceName) -
					orderedFaceNames.indexOf(b.faceName)
			);
		});

		setFilteredHeatmapRows(orderedRows);
	};

	const handleShowDendrogram = async () => {
		const actualDistances = prepareDataForDendrogram();
		if (!actualDistances) return;
		const clusterRoot =
			await BatchProcessingTaskService.prepareDataForDendrogram(
				actualDistances
			);
		const clusteringSteps = getClusterSteps(clusterRoot);
		reorderRows(clusteringSteps);
		setClusteringSteps(clusteringSteps);
	};

	return {
		calculateRowPositions,
		handleShowDendrogram
	};
}

export default useDendrogram;
+330 −383

File changed.

Preview size limit exceeded, changes collapsed.

+23 −0
Original line number Diff line number Diff line
@@ -28,3 +28,26 @@ export enum LinkageStrategy {
	COMPLETE = 'COMPLETE',
	AVERAGE = 'AVERAGE'
}

export type RowState = {
	draggedIndex: number | null;
	targetIndex: number | null;
	clickedName: string | null;
	lastClickedName: string | null;
	lastClickedIndex: number | null;
};

export type ColumnState = {
	draggedIndex: number | null;
	targetIndex: number | null;
	clickedName: string | null;
	lastClickedName: string | null;
	lastClickedIndex: number | null;
};

export type HiddenItems = {
	rows: string[];
	columns: string[];
	selectedRows: string[];
	selectedColumns: string[];
};