diff --git a/src/component/2d/ft/Contours.tsx b/src/component/2d/ft/Contours.tsx index ecf15e3598..8f798c0104 100644 --- a/src/component/2d/ft/Contours.tsx +++ b/src/component/2d/ft/Contours.tsx @@ -75,19 +75,8 @@ function ContoursPaths({ }: ContoursPathsProps) { const activeSpectrum = useActiveSpectrum(); const preferences = usePreferences(); - const level = useContoursLevel(spectrum, sign); - const contours = useMemo(() => { - const { contours, timeout } = drawContours( - level, - spectrum, - sign === 'negative', - ); - if (timeout) { - onTimeout(); - } - return contours; - }, [spectrum, level, onTimeout, sign]); + const contours = useContours(spectrum, sign, onTimeout); const path = usePath(spectrum, contours); @@ -171,3 +160,19 @@ export default function Contours() { return ; } + +function useContours(spectrum: Spectrum2D, sign, onTimeout) { + const cache = useRef(new Map()); + const level = useContoursLevel(spectrum, sign); + + return useMemo(() => { + const { contours, timeout } = drawContours(spectrum, level, { + negative: sign === 'negative', + cache: cache.current, + }); + if (timeout) { + onTimeout(); + } + return contours; + }, [spectrum, level, onTimeout, sign]); +} diff --git a/src/data/data2d/Spectrum2D/contours.ts b/src/data/data2d/Spectrum2D/contours.ts index 75a409f74c..1bb7f329f8 100644 --- a/src/data/data2d/Spectrum2D/contours.ts +++ b/src/data/data2d/Spectrum2D/contours.ts @@ -1,5 +1,6 @@ import type { NmrData2DFt } from 'cheminfo-types'; import { Conrec } from 'ml-conrec'; +import { BasicContour } from 'ml-conrec/lib/BasicContourDrawer'; import { xMaxAbsoluteValue } from 'ml-spectra-processing'; import type { Spectrum2D } from 'nmr-load-save'; @@ -174,12 +175,18 @@ function range(from: number, to: number, step: number) { return result; } +interface DrawContoursOptions { + negative?: boolean; + quadrant?: 'rr' | 'ri' | 'ir' | 'ii'; + cache: Map; +} + function drawContours( - level: ContourItem, spectrum: Spectrum2D, - negative = false, - quadrant = 'rr', + level: ContourItem, + options: DrawContoursOptions, ) { + const { negative = false, quadrant = 'rr', cache } = options; const { contourLevels, numberOfLayers } = level; return getContours({ @@ -187,6 +194,7 @@ function drawContours( boundary: contourLevels, nbLevels: numberOfLayers, data: spectrum.data[quadrant], + cache, }); } @@ -196,6 +204,7 @@ interface ContoursCalcOptions { timeout?: number; nbLevels: number; data: NmrData2DFt['rr']; + cache: Map; } function getContours(options: ContoursCalcOptions) { @@ -205,34 +214,33 @@ function getContours(options: ContoursCalcOptions) { timeout = 2000, nbLevels, data, + cache, } = options; - const xs = getRange(data.minX, data.maxX, data.z[0].length); - const ys = getRange(data.minY, data.maxY, data.z.length); - const conrec = new Conrec(data.z, { xs, ys, swapAxes: false }); - const max = Math.max(Math.abs(data.minZ), Math.abs(data.maxZ)); - const minLevel = calculateValueOfLevel(boundary[0], max); - const maxLevel = calculateValueOfLevel(boundary[1], max); - const diffRange = boundary[1] - boundary[0]; + const range = calculateRange(boundary, data, nbLevels, negative); - let _range = getRange(minLevel, maxLevel, Math.min(nbLevels, diffRange), 2); - if (negative) { - _range = _range.map((value) => -value); + if (isZeroRange(range)) { + return createEmptyResult(range); } - if (_range.every((r) => r === 0)) { - const emptyLine: number[] = []; + const diffRange = boundary[1] - boundary[0]; + + if (cache.has(diffRange)) { return { - contours: _range.map((r) => ({ zValue: r, lines: emptyLine })), + contours: cache.get(diffRange) ?? [], timeout: false, }; } - return conrec.drawContour({ + const conrec = initializeConrec(data); + const result = conrec.drawContour({ contourDrawer: 'basic', - levels: Array.from(_range), + levels: range, timeout, }); + cache.set(diffRange, result.contours); + + return result; } /** @@ -251,6 +259,38 @@ function calculateValueOfLevel(level: number, max: number, invert = false) { return (max * (2 ** (level / 10) - 1)) / (2 ** 10 - 1); } +function calculateRange( + boundary: [number, number], + data: ContoursCalcOptions['data'], + nbLevels: number, + negative: boolean, +): number[] { + const max = Math.max(Math.abs(data.minZ), Math.abs(data.maxZ)); + const minLevel = calculateValueOfLevel(boundary[0], max); + const maxLevel = calculateValueOfLevel(boundary[1], max); + const diffRange = boundary[1] - boundary[0]; + + const range = getRange(minLevel, maxLevel, Math.min(nbLevels, diffRange), 2); + return negative ? range.map((value) => -value) : range; +} + +function isZeroRange(range: number[]): boolean { + return range.every((r) => r === 0); +} + +function createEmptyResult(range: number[]) { + return { + contours: range.map((r) => ({ zValue: r, lines: [] })), + timeout: false, + }; +} + +function initializeConrec(data: ContoursCalcOptions['data']): Conrec { + const xs = getRange(data.minX, data.maxX, data.z[0].length); + const ys = getRange(data.minY, data.maxY, data.z.length); + return new Conrec(data.z, { xs, ys, swapAxes: false }); +} + export { drawContours, contoursManager, diff --git a/src/data/data2d/__tests__/Datum2D.test.ts b/src/data/data2d/__tests__/Datum2D.test.ts index 9a0f499c2f..2008dbac4f 100644 --- a/src/data/data2d/__tests__/Datum2D.test.ts +++ b/src/data/data2d/__tests__/Datum2D.test.ts @@ -1,7 +1,8 @@ import { readFileSync } from 'node:fs'; import path from 'node:path'; -import { expect, test } from 'vitest'; +import type { BasicContour } from 'ml-conrec/lib/BasicContourDrawer'; +import { test, expect } from 'vitest'; import { addJcamp } from '../../SpectraManager.js'; import { drawContours } from '../Spectrum2D/contours.js'; @@ -9,6 +10,7 @@ import { drawContours } from '../Spectrum2D/contours.js'; test('Datum2D', () => { const jcamp = readFileSync(path.join(__dirname, './data/cosy.jdx'), 'utf8'); const spectra: any[] = []; + const cache = new Map(); addJcamp( spectra, @@ -26,13 +28,14 @@ test('Datum2D', () => { ); const positive = drawContours( - { contourLevels: [10, 100], numberOfLayers: 10 }, spectra[0], + { contourLevels: [10, 100], numberOfLayers: 10 }, + { cache }, ); const negative = drawContours( - { contourLevels: [10, 100], numberOfLayers: 10 }, spectra[0], - true, + { contourLevels: [10, 100], numberOfLayers: 10 }, + { cache, negative: true }, ); expect(positive.contours).toHaveLength(10); expect(positive.timeout).toBeFalsy();