Skip to content
Open
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
8 changes: 6 additions & 2 deletions src/component/context/processings_mutations_context.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { initializeContoursLevels } from '../../data/data2d/Spectrum2D/contours.
import type { LiveEdit, State } from '../reducer/Reducer.ts';
import { setDomain, setMode } from '../reducer/actions/DomainActions.ts';
import { updateView } from '../reducer/actions/FiltersActions.ts';
import { restoreLastZoomDomain } from '../reducer/helper/ZoomHistoryManager.ts';
import { restoreLastZoomUnlessDomainChanged } from '../reducer/helper/ZoomHistoryManager.ts';
import { getActiveSpectrum } from '../reducer/helper/getActiveSpectrum.ts';

import { useChartData } from './ChartContext.tsx';
Expand Down Expand Up @@ -207,6 +207,10 @@ export function useProcessingsMutationsAPI() {
const operation = spectrum.processings[operationIndex];
operation.enabled = !operation.enabled;

// `processSpectrum` mutates the spectrum in place, so capture the current
// FID/FT state before processing to detect a FID <-> FT transition.
const wasFt = spectrum.info.isFt;

resetSelectedTool();
await submit(spectrum, indexSpectrum, (draft, processedSpectrum) => {
if (isSpectrum2D(processedSpectrum) && processedSpectrum.info.isFt) {
Expand All @@ -216,7 +220,7 @@ export function useProcessingsMutationsAPI() {

setDomain(draft);
setMode(draft);
restoreLastZoomDomain(draft);
restoreLastZoomUnlessDomainChanged(draft, wasFt);
});
});

Expand Down
8 changes: 6 additions & 2 deletions src/component/reducer/actions/FiltersActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ import {
getDefaultTwoDimensionsPhaseCorrectionTraceOptions,
getInitialState,
} from '../Reducer.js';
import { restoreLastZoomDomain } from '../helper/ZoomHistoryManager.js';
import { restoreLastZoomUnlessDomainChanged } from '../helper/ZoomHistoryManager.js';
import { findStrongestPeak } from '../helper/findStrongestPeak.js';
import { getActiveSpectrum } from '../helper/getActiveSpectrum.js';
import getRange from '../helper/getRange.js';
Expand Down Expand Up @@ -1658,6 +1658,10 @@ export function handleEnableFilter(
const { id, enabled } = action.payload;
const datum = draft.data[activeSpectrum.index];

// `enableFilter` reprocesses the spectrum in place, so capture the current
// FID/FT state before to detect a FID <-> FT transition.
const wasFt = datum.info.isFt;

//apply filter into the spectrum
if (isSpectrum1D(datum)) {
Filters1DManager.enableFilter(datum, { id, enabled });
Expand All @@ -1674,7 +1678,7 @@ export function handleEnableFilter(
resetSelectedTool(draft);
setDomain(draft);
setMode(draft);
restoreLastZoomDomain(draft);
restoreLastZoomUnlessDomainChanged(draft, wasFt);
}

function deleteFilter(datum: Spectrum, id?: string) {
Expand Down
42 changes: 41 additions & 1 deletion src/component/reducer/helper/ZoomHistoryManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import type { Draft } from 'immer';
import type { BrushAxis } from '../../EventsTrackers/brush_tracker.types.ts';
import type { State } from '../Reducer.js';

import { getSpectrum } from './getSpectrum.js';

interface HistoryItem {
xDomain: number[];
yDomain: number[];
Expand Down Expand Up @@ -117,11 +119,31 @@ export function addToBrushHistory(
}
}

export function restoreLastZoomDomain(draft: Draft<State>) {
interface RestoreLastZoomDomainOptions {
/**
* Set to true when the displayed spectrum switched between FID and FT.
* A FID <-> FT transition regenerates the x axis, so any zoom domain stored
* for the previous state is expressed in a different scale and must be
* discarded instead of restored (otherwise the previous zoom is applied to
* the new axis, e.g. FFT keeps the FID zoom range and only flips the scale).
*/
isFtStateChanged?: boolean;
}

export function restoreLastZoomDomain(
draft: Draft<State>,
options: RestoreLastZoomDomainOptions = {},
) {
const zoomHistory = zoomHistoryManager(
draft.zoom.history,
draft.view.spectra.activeTab,
);

if (options.isFtStateChanged) {
zoomHistory.clear();
return false;
}

const zoomValue = zoomHistory.getLast();

if (!zoomValue) {
Expand All @@ -133,3 +155,21 @@ export function restoreLastZoomDomain(draft: Draft<State>) {

return true;
}

/**
* Restore the last zoom domain, unless the active spectrum switched between FID
* and FT since `previousIsFt` was captured. This is the shared criterion for
* every action that re-processes a spectrum (processings panel, legacy filter
* panel, FFT): a FID <-> FT transition regenerates the x axis, so the stored
* zoom is stale and the freshly computed full domain must be kept instead.
*/
export function restoreLastZoomUnlessDomainChanged(
draft: Draft<State>,
previousIsFt: boolean,
) {
const currentIsFt = getSpectrum(draft)?.info.isFt ?? previousIsFt;

return restoreLastZoomDomain(draft, {
isFtStateChanged: previousIsFt !== currentIsFt,
});
}
90 changes: 90 additions & 0 deletions src/component/reducer/helper/__tests__/ZoomHistoryManager.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import type { Draft } from 'immer';
import { expect, test } from 'vitest';

import type { State } from '../../Reducer.ts';
import {
addToBrushHistory,
restoreLastZoomDomain,
restoreLastZoomUnlessDomainChanged,
} from '../ZoomHistoryManager.ts';

/**
* Minimal draft exposing only what `addToBrushHistory`,
* `restoreLastZoomDomain` and `restoreLastZoomUnlessDomainChanged` read from
* the reducer state.
*/
function createDraft(isFt: boolean): Draft<State> {
return {
displayerMode: '1D',
xDomain: [0, 100],
yDomain: [0, 1],
yDomains: {},
data: [{ info: { isFt } }],
view: {
spectra: {
activeTab: '1H',
activeSpectra: { '1H': [{ index: 0, selected: true }] },
},
},
zoom: { history: {} },
} as unknown as Draft<State>;
}

test('restores the last zoom domain when no FID/FT transition happened', () => {
const draft = createDraft(false);

// user brushes a zoom on the current (FID) spectrum
addToBrushHistory(draft, { axis: 'X', xDomain: [20, 40], yDomain: [0, 1] });
expect(draft.xDomain).toStrictEqual([20, 40]);

// toggling a processing that does not change the FID/FT state resets the
// domain and then restores the user zoom
draft.xDomain = [-10, 10];
const restored = restoreLastZoomUnlessDomainChanged(draft, false);

expect(restored).toBe(true);
expect(draft.xDomain).toStrictEqual([20, 40]);
});

test('discards the stale zoom domain on a FID -> FT transition (FFT enabled)', () => {
const draft = createDraft(false);

// user brushes a zoom while the spectrum is in the FID state
addToBrushHistory(draft, { axis: 'X', xDomain: [20, 40], yDomain: [0, 1] });

// enabling FFT switches to FT and recomputes the full domain
draft.data[0].info.isFt = true;
draft.xDomain = [-10, 10];
const restored = restoreLastZoomUnlessDomainChanged(draft, false);

// the FID zoom must not be applied on the regenerated FT axis
expect(restored).toBe(false);
expect(draft.xDomain).toStrictEqual([-10, 10]);
expect(draft.zoom.history['1H']).toStrictEqual([]);
});

test('discards the stale zoom domain on a FT -> FID transition (FFT disabled)', () => {
const draft = createDraft(true);

// user brushes a zoom while the spectrum is in the FT state
addToBrushHistory(draft, { axis: 'X', xDomain: [2, 4], yDomain: [0, 1] });

// disabling FFT switches back to FID and recomputes the full domain
draft.data[0].info.isFt = false;
draft.xDomain = [0, 100];
const restored = restoreLastZoomUnlessDomainChanged(draft, true);

expect(restored).toBe(false);
expect(draft.xDomain).toStrictEqual([0, 100]);
expect(draft.zoom.history['1H']).toStrictEqual([]);
});

test('restoreLastZoomDomain keeps its default behaviour (restore when possible)', () => {
const draft = createDraft(false);

addToBrushHistory(draft, { axis: 'X', xDomain: [20, 40], yDomain: [0, 1] });
draft.xDomain = [-10, 10];

expect(restoreLastZoomDomain(draft)).toBe(true);
expect(draft.xDomain).toStrictEqual([20, 40]);
});
Loading