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: zoom in/out with the mouse wheel #2964

Merged
merged 4 commits into from
Mar 15, 2024
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
4 changes: 2 additions & 2 deletions src/component/1d/Viewer1D.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -293,8 +293,8 @@ function Viewer1D({ emptyText = undefined }: Viewer1DProps) {
}, [dispatch]);

const handleZoom = useCallback<OnZoom>(
(event) => {
dispatch({ type: 'SET_ZOOM', payload: { event } });
(options) => {
dispatch({ type: 'SET_ZOOM', payload: { options } });
},
[dispatch],
);
Expand Down
9 changes: 5 additions & 4 deletions src/component/2d/Viewer2D.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -163,18 +163,19 @@ function Viewer2D({ emptyText = undefined }: Viewer2DProps) {
[DIMENSION, dispatch],
);

const handleZoom: OnZoom = (event) => {
const { x: startX, y: startY, shiftKey } = event;
const handleZoom: OnZoom = (options) => {
const { x: startX, y: startY, shiftKey } = options;
const trackID = getLayoutID(DIMENSION, { startX, startY });

if (trackID) {
if (
(trackID === 'CENTER_2D' && shiftKey) ||
trackID !== 'CENTER_2D' ||
(selectedTool === 'phaseCorrectionTwoDimensions' && !shiftKey)
) {
dispatch({ type: 'SET_ZOOM', payload: { event, trackID } });
dispatch({ type: 'SET_ZOOM', payload: { options, trackID } });
} else {
dispatch({ type: 'SET_2D_LEVEL', payload: event });
dispatch({ type: 'SET_2D_LEVEL', payload: { options } });
}
}
};
Expand Down
13 changes: 8 additions & 5 deletions src/component/EventsTrackers/BrushTracker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,12 @@ interface Position {
}
export type OnClick = (element: React.MouseEvent & Position) => void;
export type { OnClick as OnDoubleClick };
export type OnZoom = (
event: Pick<React.WheelEvent, 'deltaY' | 'shiftKey' | 'deltaMode'> & Position,
) => void;
export type ZoomOptions = Pick<
React.WheelEvent,
'deltaY' | 'shiftKey' | 'deltaMode' | 'altKey'
> &
Position;
export type OnZoom = (options: ZoomOptions) => void;
export type OnBrush = (state: BrushTrackerContext) => void;

interface BrushTrackerProps {
Expand Down Expand Up @@ -207,8 +210,8 @@ export function BrushTracker({
const x = event.clientX - boundingRect.x;
const y = event.clientY - boundingRect.y;

const { deltaY, deltaX, shiftKey, deltaMode } = event;
onZoom({ deltaY: deltaY || deltaX, shiftKey, deltaMode, x, y });
const { deltaY, deltaX, shiftKey, altKey, deltaMode } = event;
onZoom({ deltaY: deltaY || deltaX, shiftKey, altKey, deltaMode, x, y });
},
[onZoom],
);
Expand Down
116 changes: 102 additions & 14 deletions src/component/reducer/actions/ToolsActions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { v4 } from '@lukeed/uuid';
import { zoomIdentity } from 'd3';
import { Draft } from 'immer';
import { Spectrum, Spectrum1D, Spectrum2D } from 'nmr-load-save';
import { BaselineCorrectionZone } from 'nmr-processing';
Expand All @@ -7,7 +8,8 @@ import { contoursManager } from '../../../data/data2d/Spectrum2D/contours';
import { Nucleus } from '../../../data/types/common/Nucleus';
import { getYScale, getXScale } from '../../1d/utilities/scale';
import { LAYOUT, Layout } from '../../2d/utilities/DimensionLayout';
import { get2DYScale } from '../../2d/utilities/scale';
import { get2DXScale, get2DYScale } from '../../2d/utilities/scale';
import { ZoomOptions } from '../../EventsTrackers/BrushTracker';
import { defaultRangesViewState } from '../../hooks/useActiveSpectrumRangesViewState';
import { Tool, options as Tools } from '../../toolbar/ToolTypes';
import groupByInfoKey from '../../utility/GroupByInfoKey';
Expand Down Expand Up @@ -83,18 +85,15 @@ type BrushEndAction = ActionType<'BRUSH_END', BrushBoundary>;

type ZoomAction = ActionType<
'SET_ZOOM',
{ event: any; trackID?: Layout; selectedTool?: Tool }
{ options: ZoomOptions; trackID?: Layout; selectedTool?: Tool }
>;

type ZoomOutAction = ActionType<
'FULL_ZOOM_OUT',
{ zoomType?: ZoomType; trackID?: Layout }
>;
type SetActiveTabAction = ActionType<'SET_ACTIVE_TAB', { tab?: Nucleus }>;
type LevelChangeAction = ActionType<
'SET_2D_LEVEL',
{ deltaY: number; shiftKey: boolean }
>;
type LevelChangeAction = ActionType<'SET_2D_LEVEL', { options: ZoomOptions }>;

export type ToolsActions =
| ActionType<
Expand Down Expand Up @@ -323,16 +322,98 @@ function handleBrushEnd(draft: Draft<State>, action: BrushEndAction) {
addToBrushHistory(draft, { trackID, xDomain: domainX, yDomain: domainY });
}

interface ZoomWithScroll1DOptions {
zoomOptions: ZoomOptions;
direction?: 'Horizontal';
dimension: '1D';
}
interface ZoomWithScroll2DOptions {
zoomOptions: ZoomOptions;
direction: 'Horizontal' | 'Vertical' | 'Both';
dimension: '2D';
}

function zoomWithScroll(
draft: Draft<State>,
options: ZoomWithScroll1DOptions | ZoomWithScroll2DOptions,
) {
const { zoomOptions, direction = 'Horizontal', dimension } = options;

let scaleX;
let scaleY;

if (dimension === '1D') {
scaleX = getXScale(draft);
} else {
scaleX = get2DXScale(draft);
scaleY = get2DYScale(draft);
}

const scaleRatio = toScaleRatio(zoomOptions, { invert: true });

if (direction === 'Both' || direction === 'Horizontal') {
const { x } = zoomOptions;
const domain = zoomIdentity
.translate(x, 0)
.scale(scaleRatio)
.translate(-x, 0)
.rescaleX(scaleX)
.domain();
const {
originDomain: {
xDomain: [x1, x2],
},
} = draft;
draft.xDomain = [
domain[0] < x1 ? x1 : domain[0],
domain[1] > x2 ? x2 : domain[1],
];
}

if (
dimension === '2D' &&
(direction === 'Both' || direction === 'Vertical')
) {
const { y } = zoomOptions;
const domain = zoomIdentity
.translate(y, 0)
.scale(scaleRatio)
.translate(-y, 0)
.rescaleX(scaleY)
.domain();
const {
originDomain: {
yDomain: [x1, x2],
},
} = draft;
draft.yDomain = [
domain[0] < x1 ? x1 : domain[0],
domain[1] > x2 ? x2 : domain[1],
];
}
}

function handleZoom(draft: Draft<State>, action: ZoomAction) {
const { event, trackID } = action.payload;
const { options, trackID } = action.payload;
const {
displayerMode,
yDomains,
toolOptions: { selectedTool },
} = draft;
const scaleRatio = toScaleRatio(event);
const scaleRatio = toScaleRatio(options);
switch (displayerMode) {
case '2D': {
const { shiftKey } = options;

if (selectedTool === 'zoom' && shiftKey) {
zoomWithScroll(draft, {
zoomOptions: options,
dimension: '2D',
direction: 'Both',
});
return;
}

// change the vertical scale for traces in 2D phase correction
if (
selectedTool === 'phaseCorrectionTwoDimensions' &&
Expand All @@ -348,7 +429,7 @@ function handleZoom(draft: Draft<State>, action: ZoomAction) {
const id = getSpectrumID(draft, index);
if (id) {
const domain = yDomains[id];
yDomains[id] = wheelZoom(event, domain);
yDomains[id] = wheelZoom(options, domain);
}
}
}
Expand All @@ -358,17 +439,23 @@ function handleZoom(draft: Draft<State>, action: ZoomAction) {

case '1D': {
const activeSpectra = getActiveSpectra(draft);
const { shiftKey } = options;
if (selectedTool === 'zoom' && shiftKey) {
zoomWithScroll(draft, { zoomOptions: options, dimension: '1D' });

return;
}

if (!activeSpectra) {
// rescale the spectra
for (const key of Object.keys(yDomains)) {
const domain = yDomains[key];
yDomains[key] = wheelZoom(event, domain);
yDomains[key] = wheelZoom(options, domain);
}
return;
}

if (activeSpectra.length === 1 && event.shiftKey) {
if (activeSpectra.length === 1 && shiftKey) {
switch (selectedTool) {
case 'rangePicking': {
setRangesViewProperty(
Expand All @@ -393,7 +480,7 @@ function handleZoom(draft: Draft<State>, action: ZoomAction) {
for (const activeSpectrum of activeSpectra) {
const domain = yDomains?.[activeSpectrum?.id];
if (domain) {
yDomains[activeSpectrum?.id] = wheelZoom(event, domain);
yDomains[activeSpectrum?.id] = wheelZoom(options, domain);
}
}
}
Expand Down Expand Up @@ -431,6 +518,7 @@ function zoomOut(draft: Draft<State>, action: ZoomOutAction) {
if (zoomValue) {
draft.xDomain = zoomValue.xDomain;
} else {
draft.xDomain = xDomain;
setZoom(draft, { scale: 0.8 });
}
break;
Expand Down Expand Up @@ -620,7 +708,7 @@ function handelSetActiveTab(draft: Draft<State>, action: SetActiveTabAction) {
}

function levelChangeHandler(draft: Draft<State>, action: LevelChangeAction) {
const { deltaY, shiftKey } = action.payload;
const { deltaY, altKey } = action.payload.options;
const {
data,
view: {
Expand All @@ -645,7 +733,7 @@ function levelChangeHandler(draft: Draft<State>, action: LevelChangeAction) {
for (const spectrum of spectra as Spectrum2D[]) {
const contourOptions = spectrum.display.contourOptions;
const zoom = contoursManager(spectrum.id, levels, contourOptions);
levels[spectrum.id] = zoom.wheel(deltaY, shiftKey);
levels[spectrum.id] = zoom.wheel(deltaY, altKey);
}
} catch (error) {
// TODO: handle error.
Expand Down
16 changes: 10 additions & 6 deletions src/component/reducer/helper/Zoom1DManager.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { zoomIdentity, scaleLinear } from 'd3';
import { Draft } from 'immer';

import { ZoomOptions } from '../../EventsTrackers/BrushTracker';
import { State } from '../Reducer';

import { getActiveSpectrum } from './getActiveSpectrum';
Expand All @@ -14,27 +15,30 @@ export const ZOOM_TYPES = {

export type ZoomType = keyof typeof ZOOM_TYPES;

function toScaleRatio(event: WheelEvent, zoomOptions: ZoomOptions = {}) {
function toScaleRatio(
options: ZoomOptions,
zoomOptions: ScaleRationOptions = {},
) {
const { factor = 1, invert = false } = zoomOptions;

const deltaY =
Math.abs(event.deltaY) < 100 ? event.deltaY * 100 : event.deltaY;
Math.abs(options.deltaY) < 100 ? options.deltaY * 100 : options.deltaY;
const delta = deltaY * (invert ? -0.001 : 0.001) * factor;
const ratio = delta < 0 ? -1 / (delta - 1) : 1 + delta;

return ratio;
}

interface ZoomOptions {
interface ScaleRationOptions {
factor?: number;
invert?: boolean;
}
function wheelZoom(
event: WheelEvent,
options: ZoomOptions,
domain: number[],
zoomOptions: ZoomOptions = {},
scaleOptions: ScaleRationOptions = {},
): number[] {
const ratio = toScaleRatio(event, zoomOptions);
const ratio = toScaleRatio(options, scaleOptions);
const [min, max] = domain;
return [min * ratio, max * ratio];
}
Expand Down
10 changes: 5 additions & 5 deletions src/data/data2d/Spectrum2D/contours.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ interface ContourOptions {
negative: ContourItem;
}
interface WheelOptions {
shiftKey: boolean;
altKey: boolean;
contourOptions: ContourOptions;
currentLevel: Level;
}
Expand Down Expand Up @@ -67,20 +67,20 @@ function contoursManager(

const currentLevel = spectraLevels[spectrumID];

const wheel = (value, shiftKey) =>
prepareWheel(value, { shiftKey, contourOptions, currentLevel });
const wheel = (value, altKey) =>
prepareWheel(value, { altKey, contourOptions, currentLevel });
const getLevel = () => currentLevel;
const checkLevel = () => prepareCheckLevel(currentLevel, contourOptions);
return { wheel, getLevel, checkLevel };
}

function prepareWheel(value: number, options: WheelOptions) {
const { shiftKey, currentLevel, contourOptions } = options;
const { altKey, currentLevel, contourOptions } = options;
const sign = Math.sign(value);
const positiveBoundary = contourOptions.positive.contourLevels;
const negativeBoundary = contourOptions.negative.contourLevels;

if (shiftKey) {
if (altKey) {
if (
(currentLevel.positive === positiveBoundary[0] && sign === -1) ||
(currentLevel.positive >= positiveBoundary[1] && sign === 1)
Expand Down
Loading