import debounce from 'lodash/debounce.js'; import { memo, useMemo, useRef } from 'react'; import type { LevelSign } from '../../../data/data2d/Spectrum2D/contours.js'; import { drawContours } from '../../../data/data2d/Spectrum2D/contours.js'; import { useChartData } from '../../context/ChartContext.js'; import { usePreferences } from '../../context/PreferencesContext.js'; import { useToaster } from '../../context/ToasterContext.js'; import type { SpectrumFTData } from '../../hooks/use2DReducer.tsx'; import { use2DReducer } from '../../hooks/use2DReducer.tsx'; import { useActiveSpectrum } from '../../hooks/useActiveSpectrum.js'; import { PathBuilder } from '../../utility/PathBuilder.js'; import { useScale2DX, useScale2DY } from '../utilities/scale.js'; interface ContoursPathsProps { id: string; color: string; sign: LevelSign; spectrum: SpectrumFTData; onTimeout: () => void; } interface ContoursInnerProps { spectra: SpectrumFTData[]; } function usePath(contours: ReturnType['contours']) { const scaleX = useScale2DX(); const scaleY = useScale2DY(); const pathBuilder = new PathBuilder(); for (const element of contours) { if (element.lines) { const lines = element.lines; if (lines.length < 1e6) { for (let i = 0; i < lines.length; i += 4) { pathBuilder.moveTo(scaleX(lines[i]), scaleY(lines[i + 1])); pathBuilder.lineTo(scaleX(lines[i + 2]), scaleY(lines[i + 3])); } } } } pathBuilder.closePath(); return pathBuilder.toString(); } const useContoursLevel = (spectrumID: string, sign: LevelSign) => { const { view: { spectraContourLevels }, } = useChartData(); const level = spectraContourLevels[spectrumID]?.[sign]; return level; }; function ContoursPaths({ id: spectrumID, sign, color, spectrum, onTimeout, }: ContoursPathsProps) { const activeSpectrum = useActiveSpectrum(); const preferences = usePreferences(); const level = useContoursLevel(spectrumID, sign); const contours = useMemo(() => { const { contours, timeout } = drawContours( level, spectrum, sign === 'negative', ); if (timeout) { onTimeout(); } return contours; }, [spectrum, level, onTimeout, sign]); const path = usePath(contours); const opacity = activeSpectrum === null || spectrumID === activeSpectrum.id ? '1' : // TODO: make sure preferences are not a lie and remove the optional chaining. (preferences?.current?.general?.dimmedSpectraOpacity ?? 0.1); return ( ); } function ContoursInner({ spectra }: ContoursInnerProps) { const toaster = useToaster(); const debounceAlert = useRef( debounce(() => { toaster.show({ message: 'Too many contour lines, only showing the first ones', intent: 'danger', }); }, 2000), ); function timeoutHandler() { debounceAlert.current(); } return ( {spectra?.map((spectrum) => { return ( {spectrum.display.isPositiveVisible && ( )} {spectrum.display.isNegativeVisible && ( )} ); })} ); } const MemoizedContours = memo(ContoursInner); export default function Contours() { const spectra = use2DReducer(); return ; }