diff --git a/src/component/2d/ft/Contours.tsx b/src/component/2d/ft/Contours.tsx
index 184ddcc7cc..aac485beec 100644
--- a/src/component/2d/ft/Contours.tsx
+++ b/src/component/2d/ft/Contours.tsx
@@ -1,7 +1,7 @@
import debounce from 'lodash/debounce';
import get from 'lodash/get';
import { Spectrum2D } from 'nmr-load-save';
-import { memo, useMemo, useRef } from 'react';
+import { memo, useEffect, useMemo, useRef } from 'react';
import {
drawContours,
@@ -16,6 +16,8 @@ import { PathBuilder } from '../../utility/PathBuilder';
import { getSpectraByNucleus } from '../../utility/getSpectraByNucleus';
import { useScale2DX, useScale2DY } from '../utilities/scale';
+import { ContoursProvider, useContours } from './ContoursContext';
+
interface ContoursPathsProps {
id: string;
color: string;
@@ -77,19 +79,19 @@ function ContoursPaths({
const preferences = usePreferences();
const level = useContoursLevel(spectrum, sign);
- const contours = useMemo(() => {
- const { contours, timeout } = drawContours(
- level,
- spectrum,
- sign === 'negative',
- );
- if (timeout) {
+ const contours = useContours();
+ const signContours = contours?.[spectrumID][sign] || {
+ contours: [],
+ timeout: false,
+ };
+
+ useEffect(() => {
+ if (signContours.timeout) {
onTimeout();
}
- return contours;
- }, [spectrum, level, onTimeout, sign]);
+ }, [onTimeout, signContours.timeout]);
- const path = usePath(spectrum, contours);
+ const path = usePath(spectrum, signContours.contours);
const opacity =
activeSpectrum === null || spectrumID === activeSpectrum.id
@@ -169,5 +171,9 @@ export default function Contours() {
) as Spectrum2D[];
}, [activeTab, spectra]);
- return ;
+ return (
+
+
+
+ );
}
diff --git a/src/component/2d/ft/ContoursContext.tsx b/src/component/2d/ft/ContoursContext.tsx
new file mode 100644
index 0000000000..f7b1986a3c
--- /dev/null
+++ b/src/component/2d/ft/ContoursContext.tsx
@@ -0,0 +1,71 @@
+import { DrawContourResult } from 'ml-conrec';
+import { Spectrum2D } from 'nmr-load-save';
+import { createContext, ReactNode, useContext, useMemo } from 'react';
+
+import { drawContours } from '../../../data/data2d/Spectrum2D/contours';
+import useSpectraByActiveNucleus from '../../hooks/useSpectraPerNucleus';
+
+interface Contour {
+ positive: DrawContourResult<'basic'>;
+ negative: DrawContourResult<'basic'>;
+}
+
+type Contours = Record;
+
+const ContoursContext = createContext({});
+
+interface ContoursProviderProps {
+ children: ReactNode;
+}
+
+function getContours(spectrum: Spectrum2D, negative = false) {
+ //TODO change the static contour options.
+ return drawContours(
+ spectrum,
+ {
+ contourLevels: [0, 100],
+ numberOfLayers: 10,
+ },
+ negative,
+ );
+}
+
+export function ContoursProvider({ children }: ContoursProviderProps) {
+ const spectra = useSpectraByActiveNucleus() as Spectrum2D[];
+
+ // TODO: Move the contour options from the `display` object within the spectrum object to `view` to prevent recalculating the contours when those options change.
+ const contours = useMemo(() => {
+ const contours: Contours = {};
+ for (const spectrum of spectra) {
+ const {
+ id,
+ info: { isFt },
+ } = spectrum;
+
+ if (!isFt) {
+ continue;
+ }
+
+ const positive = getContours(spectrum);
+ const negative = getContours(spectrum, true);
+ contours[id] = { positive, negative };
+ }
+ return contours;
+ }, [spectra]);
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function useContours() {
+ const context = useContext(ContoursContext);
+
+ if (!context) {
+ throw new Error('Contours context was not found');
+ }
+
+ return context;
+}
diff --git a/src/component/hooks/useContourCache.ts b/src/component/hooks/useContourCache.ts
new file mode 100644
index 0000000000..0e9e7187da
--- /dev/null
+++ b/src/component/hooks/useContourCache.ts
@@ -0,0 +1,16 @@
+import { BasicContour } from 'ml-conrec/lib/BasicContourDrawer';
+import { useState } from 'react';
+
+export interface ContourResult {
+ contours: BasicContour[];
+ timeout: boolean;
+}
+
+export type ContourCache = Record<
+ string,
+ { positive?: ContourResult; negative?: ContourResult }
+>;
+const initialContourCache: ContourCache = {};
+export function useContourCache() {
+ return useState(initialContourCache);
+}
diff --git a/src/data/data2d/Spectrum2D/contours.ts b/src/data/data2d/Spectrum2D/contours.ts
index bbace79122..f428666904 100644
--- a/src/data/data2d/Spectrum2D/contours.ts
+++ b/src/data/data2d/Spectrum2D/contours.ts
@@ -3,7 +3,11 @@ import { Conrec } from 'ml-conrec';
import { xMaxAbsoluteValue } from 'ml-spectra-processing';
import { Spectrum2D } from 'nmr-load-save';
-import { calculateSanPlot } from '../../utilities/calculateSanPlot';
+// import {
+// ContourCache,
+// ContourResult,
+// } from '../../../component/hooks/useContourCache';
+// import { calculateSanPlot } from '../../utilities/calculateSanPlot';
interface Level {
positive: ContourItem;
@@ -44,6 +48,14 @@ interface ReturnContoursManager {
checkLevel: () => Level;
}
+interface ContoursCalcOptions {
+ boundary: [number, number];
+ negative?: boolean;
+ timeout?: number;
+ nbLevels: number;
+ data: NmrData2DFt['rr'];
+}
+
function getDefaultContoursLevel(spectrum: Spectrum2D, quadrant = 'rr') {
const { data, info } = spectrum;
@@ -174,35 +186,11 @@ function range(from: number, to: number, step: number) {
return result;
}
-function drawContours(
- level: ContourItem,
- spectrum: Spectrum2D,
- negative = false,
- quadrant = 'rr',
-) {
- const { contourLevels, numberOfLayers } = level;
-
- return getContours({
- negative,
- boundary: contourLevels,
- nbLevels: numberOfLayers,
- data: spectrum.data[quadrant],
- });
-}
-
-interface ContoursCalcOptions {
- boundary: [number, number];
- negative?: boolean;
- timeout?: number;
- nbLevels: number;
- data: NmrData2DFt['rr'];
-}
-
function getContours(options: ContoursCalcOptions) {
const {
boundary,
negative = false,
- timeout = 2000,
+ timeout = 4000,
nbLevels,
data,
} = options;
@@ -235,6 +223,135 @@ function getContours(options: ContoursCalcOptions) {
});
}
+function drawContours(
+ spectrum: Spectrum2D,
+ level: ContourItem,
+ negative = false,
+ quadrant = 'rr',
+) {
+ const { contourLevels, numberOfLayers } = level;
+
+ return getContours({
+ boundary: contourLevels,
+ nbLevels: numberOfLayers,
+ data: spectrum.data[quadrant],
+ negative,
+ });
+}
+
+// function drawContours(
+// level: ContourItem,
+// spectrum: Spectrum2D,
+// contourCache: ContourCache,
+// sign: 'positive' | 'negative' = 'positive',
+// quadrant = 'rr',
+// ) {
+// const { contourLevels, numberOfLayers } = level;
+
+// const nbLevels = Math.min(
+// numberOfLayers,
+// contourLevels[1] - contourLevels[0],
+// );
+
+// const { id, data } = spectrum;
+// if (!contourCache[id]) {
+// contourCache[id] = {};
+// }
+
+// if (!(sign in contourCache[id])) {
+// contourCache[id][sign] = { contours: [], timeout: false };
+// }
+
+// const oneSenseContours = (contourCache[id][sign] as ContourResult).contours;
+// const selectedLevels = getRange(
+// Math.max(0, contourLevels[0]),
+// contourLevels[1],
+// nbLevels,
+// ).map((e) => Math.round(e));
+
+// const levels = selectedLevels.filter((level) => !oneSenseContours[level]);
+
+// if (levels.length > 0 && levels.length < numberOfLayers) {
+// const totalSize = data[quadrant].z[0].length * data[quadrant].z.length;
+// if (totalSize < 5e6) {
+// addAditionalLevels(levels, oneSenseContours, numberOfLayers);
+// }
+// }
+
+// const { contours, timeout } = getContours({
+// levels,
+// sign,
+// data: data[quadrant],
+// });
+
+// if (sign === 'negative') {
+// levels.reverse();
+// }
+// for (const [i, level] of contours.entries()) {
+// oneSenseContours[levels[i]] = level;
+// }
+// contourCache[id][sign] = { contours: oneSenseContours, timeout };
+
+// return {
+// contours: selectedLevels.map((level) => oneSenseContours[level]),
+// timeout,
+// };
+// }
+
+// function addAditionalLevels(
+// levels: number[],
+// oneSenseContours: ContourResult['contours'],
+// numberOfLayers: number,
+// ) {
+// for (let i = 100; levels.length < numberOfLayers && i <= 0; i--) {
+// if (!oneSenseContours[i] && !levels[i]) levels.push(i);
+// }
+// levels.sort((a, b) => a - b);
+// }
+
+// interface ContoursCalcOptions {
+// levels: number[];
+// sign?: 'positive' | 'negative';
+// timeout?: number;
+// data: NmrData2DFt['rr'];
+// }
+
+// function getContours(options: ContoursCalcOptions) {
+// const { levels, sign = 'positive', timeout = 4000, data } = options;
+// const max = Math.max(Math.abs(data.minZ), Math.abs(data.maxZ));
+// let _range = levels.map((level) => calculateValueOfLevel(level, max));
+
+// if (sign === 'negative') {
+// _range = _range.map((value) => -value);
+// if (_range.filter((value) => value >= data.minZ).length === 0) {
+// return emptyResult(_range);
+// }
+// } else if (_range.filter((value) => value <= data.maxZ).length === 0) {
+// return emptyResult(_range);
+// }
+
+// if (_range.every((r) => r === 0)) {
+// return emptyResult(_range);
+// }
+// const ys = getRange(data.minY, data.maxY, data.z.length);
+// const xs = getRange(data.minX, data.maxX, data.z[0].length);
+
+// const conrec = new Conrec(data.z, { xs, ys, swapAxes: false });
+// return conrec.drawContour({
+// contourDrawer: 'basic',
+// levels: Array.from(_range),
+// timeout,
+// });
+// }
+
+// function emptyResult(_range: number[]) {
+// const emptyLine: number[] = [];
+// return {
+// contours: _range.map((r) => ({ zValue: r, lines: emptyLine })),
+// timeout: false,
+// };
+// }
+
/**
* calculate the intensity value in the Z matrix based in the max value of Z matrix
* and the contour level (0-100).