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).