Skip to content

Commit

Permalink
Debounce slicing without affecting UI responsiveness
Browse files Browse the repository at this point in the history
  • Loading branch information
axelboc committed Feb 21, 2022
1 parent 59beee2 commit ef9614f
Show file tree
Hide file tree
Showing 4 changed files with 27 additions and 41 deletions.
8 changes: 2 additions & 6 deletions packages/app/src/__tests__/CorePack.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { mockValues } from '@h5web/shared';
import { screen, act } from '@testing-library/react';
import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

import {
Expand Down Expand Up @@ -135,11 +135,7 @@ test('visualize 1D slice of a 3D dataset with and without autoscale', async () =

// Check that entire dataset is fetched
d0Slider.focus();

act(() => {
userEvent.keyboard('{ArrowUp}');
jest.advanceTimersByTime(100); // account for debouncing of `dimMapping` state
});
userEvent.keyboard('{ArrowUp}');

await expect(screen.findByRole('figure')).resolves.toBeVisible();
d0Slider.blur(); // remove focus to avoid state update after unmount
Expand Down
28 changes: 7 additions & 21 deletions packages/app/src/__tests__/DimensionMapper.test.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { act, screen, within } from '@testing-library/react';
import { screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

import { renderApp, selectExplorerNode, selectVisTab } from '../test-utils';
import { Vis } from '../vis-packs/core/visualizations';

test('display mapping for X axis when visualizing 2D dataset as Line', async () => {
jest.useFakeTimers('modern');
await renderApp();
await selectExplorerNode('nD_datasets/twoD');
await selectVisTab(Vis.Line);
Expand All @@ -25,24 +24,17 @@ test('display mapping for X axis when visualizing 2D dataset as Line', async ()
const d0Slider = screen.getByRole('slider');
expect(d0Slider).toHaveAttribute('aria-valueNow', '0');

act(() => {
// Ensure that the swap from [0, 'x'] to ['x', 0] works
userEvent.click(xDimsButtons[0]);
jest.advanceTimersByTime(100); // account for debouncing of `dimMapping` state
});
// Ensure that the swap from [0, 'x'] to ['x', 0] works
userEvent.click(xDimsButtons[0]);

expect(xDimsButtons[0]).toBeChecked();
await waitFor(() => expect(xDimsButtons[0]).toBeChecked());
expect(xDimsButtons[1]).not.toBeChecked();

const d1Slider = screen.getByRole('slider');
expect(d1Slider).toHaveAttribute('aria-valueNow', '0');

jest.runOnlyPendingTimers();
jest.useRealTimers();
});

test('display mappings for X and Y axes when visualizing 2D dataset as Heatmap', async () => {
jest.useFakeTimers('modern');
await renderApp();
await selectExplorerNode('nD_datasets/twoD');
await selectVisTab(Vis.Heatmap);
Expand All @@ -62,19 +54,13 @@ test('display mappings for X and Y axes when visualizing 2D dataset as Heatmap',
expect(xD1Button).toBeChecked();
expect(yD0Button).toBeChecked();

act(() => {
// Ensure that the swap from ['y', 'x'] to ['x', 'y'] works
userEvent.click(xD0Button);
jest.advanceTimersByTime(100); // account for debouncing of `dimMapping` state
});
// Ensure that the swap from ['y', 'x'] to ['x', 'y'] works
userEvent.click(xD0Button);

expect(xD0Button).toBeChecked();
await waitFor(() => expect(xD0Button).toBeChecked());
expect(xD1Button).not.toBeChecked();
expect(yD0Button).not.toBeChecked();
expect(yD1Button).toBeChecked();

jest.runOnlyPendingTimers();
jest.useRealTimers();
});

test('display one dimension slider and mappings for X and Y axes when visualizing 3D dataset as Matrix', async () => {
Expand Down
9 changes: 6 additions & 3 deletions packages/app/src/dimension-mapper/DimensionMapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,13 @@ function DimensionMapper(props: Props) {
<SlicingSlider
key={`${index}`} // eslint-disable-line react/no-array-index-key
dimension={index}
slicingIndex={val}
maxIndex={rawDims[index] - 1}
mapperState={mapperState}
onChange={onChange}
initialValue={val}
onChange={(newVal: number) => {
const newMapperState = [...mapperState];
newMapperState[index] = newVal;
onChange(newMapperState);
}}
/>
) : undefined
)}
Expand Down
23 changes: 12 additions & 11 deletions packages/app/src/dimension-mapper/SlicingSlider.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
import { useMeasure } from '@react-hookz/web';
import { useEffect, useRef } from 'react';
import { useDebouncedCallback, useMeasure } from '@react-hookz/web';
import { useEffect, useRef, useState } from 'react';
import ReactSlider from 'react-slider';

import styles from './SlicingSlider.module.css';
import type { DimensionMapping } from './models';

const MIN_HEIGHT_PER_MARK = 25;

interface Props {
dimension: number;
slicingIndex: number;
maxIndex: number;
mapperState: DimensionMapping;
onChange: (mapperState: DimensionMapping) => void;
initialValue: number;
onChange: (value: number) => void;
}

function SlicingSlider(props: Props) {
const { dimension, slicingIndex, maxIndex, mapperState, onChange } = props;
const { dimension, maxIndex, initialValue, onChange } = props;

const [value, setValue] = useState(initialValue);

const [containerSize, containerRef] = useMeasure<HTMLDivElement>();
const sliderRef = useRef<ReactSlider>(null);

const onDebouncedChange = useDebouncedCallback(onChange, [onChange], 250);

useEffect(() => {
sliderRef.current?.resize();
}, [containerSize]);
Expand All @@ -44,11 +46,10 @@ function SlicingSlider(props: Props) {
markClassName={styles.mark}
orientation="vertical"
invert
value={slicingIndex}
value={value}
onChange={(value) => {
const newMapperState = [...mapperState];
newMapperState[dimension] = value;
onChange(newMapperState);
setValue(value);
onDebouncedChange(value);
}}
renderThumb={(thumbProps, state) => (
<div {...thumbProps} className={styles.thumb}>
Expand Down

0 comments on commit ef9614f

Please sign in to comment.