diff --git a/src/component/1d-2d/components/FloatMoleculeStructures/DraggableStructure.tsx b/src/component/1d-2d/components/FloatMoleculeStructures/DraggableStructure.tsx index 352f25526..f14332a01 100644 --- a/src/component/1d-2d/components/FloatMoleculeStructures/DraggableStructure.tsx +++ b/src/component/1d-2d/components/FloatMoleculeStructures/DraggableStructure.tsx @@ -14,20 +14,26 @@ import type { import { useDispatch } from '../../../context/DispatchContext.js'; import { useGlobal } from '../../../context/GlobalContext.js'; import { useHighlightColor } from '../../../hooks/useHighlightColor.js'; +import { useCheckExportStatus } from '../../../hooks/useViewportSize.js'; import { useMoleculeEditor } from '../../../modal/MoleculeStructureEditorModal.js'; import useAtomAssignment from '../../../panels/MoleculesPanel/useAtomAssignment.js'; import type { DisplayerMode } from '../../../reducer/Reducer.js'; import ActionsButton from './ActionsButton.js'; +interface DraggableMoleculeProps extends DraggableStructureProps { + width: number; + height: number; +} + interface DraggableStructureProps { + moleculeView: MoleculeView; zones: Zones; ranges: Ranges; - molecule: StateMoleculeExtended; - moleculeView: MoleculeView; activeTab: string; displayerMode: DisplayerMode; index?: number; + molecule: StateMoleculeExtended; } const AUTO_CROP_MARGIN = 30; @@ -55,25 +61,11 @@ const style = css` `; export function DraggableStructure(props: DraggableStructureProps) { - const { - zones, - ranges, - molecule, - activeTab, - displayerMode, - index, - moleculeView, - } = props; + const { molecule, moleculeView } = props; const { viewerRef } = useGlobal(); + const isExportProcessStart = useCheckExportStatus(); const dispatch = useDispatch(); const { modal, openMoleculeEditor } = useMoleculeEditor(); - const { - currentDiaIDsToHighlight, - handleOnAtomHover, - handleOnClickAtom, - assignedDiaIDsMerged, - } = useAtomAssignment({ zones, ranges, activeTab, displayerMode }); - const highlightColor = useHighlightColor(); function floatMoleculeHandler() { dispatch({ @@ -99,6 +91,11 @@ export function DraggableStructure(props: DraggableStructureProps) { if (!viewerRef) return null; + if (isExportProcessStart) { + const { width, height } = moleculeView.floating.bounding; + return ; + } + return ( {({ width, height }) => { - return ( - 0 - ? '#ff000080' - : highlightColor - } - atomHighlightOpacity={1} - highlights={ - currentDiaIDsToHighlight?.length > 0 - ? currentDiaIDsToHighlight - : assignedDiaIDsMerged - } - setHoverAtom={handleOnAtomHover} - setMolfile={(molfile) => { - dispatch({ - type: 'SET_MOLECULE', - payload: { - molfile, - id: molecule.id, - label: molecule.label, - }, - }); - }} - showAtomNumber={moleculeView.showAtomNumber} - /> - ); + return ; }} @@ -170,3 +130,62 @@ export function DraggableStructure(props: DraggableStructureProps) { ); } + +function DraggableMolecule(props: DraggableMoleculeProps) { + const { + zones, + ranges, + activeTab, + displayerMode, + molecule, + index, + moleculeView, + width, + height, + } = props; + const { + currentDiaIDsToHighlight, + handleOnAtomHover, + handleOnClickAtom, + assignedDiaIDsMerged, + } = useAtomAssignment({ zones, ranges, activeTab, displayerMode }); + const highlightColor = useHighlightColor(); + const dispatch = useDispatch(); + + return ( + 0 ? '#ff000080' : highlightColor + } + atomHighlightOpacity={1} + highlights={ + currentDiaIDsToHighlight?.length > 0 + ? currentDiaIDsToHighlight + : assignedDiaIDsMerged + } + setHoverAtom={handleOnAtomHover} + setMolfile={(molfile) => { + dispatch({ + type: 'SET_MOLECULE', + payload: { + molfile, + id: molecule.id, + label: molecule.label, + }, + }); + }} + showAtomNumber={moleculeView.showAtomNumber} + /> + ); +} diff --git a/src/component/1d-2d/components/SVGRootContainer.tsx b/src/component/1d-2d/components/SVGRootContainer.tsx index c3919e98c..3722c5ecc 100644 --- a/src/component/1d-2d/components/SVGRootContainer.tsx +++ b/src/component/1d-2d/components/SVGRootContainer.tsx @@ -16,7 +16,6 @@ export function SVGRootContainer(props: SVGRootContainerProps) { general: { spectraRendering }, }, } = usePreferences(); - const { width, height, margin, displayerKey } = useChartData(); const innerWidth = width - margin.left - margin.right; diff --git a/src/component/1d-2d/components/ViewerResponsiveWrapper.tsx b/src/component/1d-2d/components/ViewerResponsiveWrapper.tsx new file mode 100644 index 000000000..2352a2f82 --- /dev/null +++ b/src/component/1d-2d/components/ViewerResponsiveWrapper.tsx @@ -0,0 +1,25 @@ +import { useEffect } from 'react'; +import type { ReactNode } from 'react'; + +import { useDispatch } from '../../context/DispatchContext.js'; +import { useViewportSize } from '../../hooks/useViewportSize.js'; + +interface ViewerResponsiveWrapperProps { + width: number; + height: number; + children: ReactNode; +} + +export function ViewerResponsiveWrapper(props: ViewerResponsiveWrapperProps) { + const dispatch = useDispatch(); + const { width, height, children } = props; + const size = useViewportSize(); + + useEffect(() => { + if (!size) { + dispatch({ type: 'SET_DIMENSIONS', payload: { width, height } }); + } + }, [width, height, dispatch, size]); + + return children; +} diff --git a/src/component/1d/BrushTracker1D.tsx b/src/component/1d/BrushTracker1D.tsx new file mode 100644 index 000000000..694b03a9d --- /dev/null +++ b/src/component/1d/BrushTracker1D.tsx @@ -0,0 +1,388 @@ +import { useCallback, useRef, useState } from 'react'; +import { useOnOff } from 'react-science/ui'; + +import { + createRange, + isSpectrum1D, +} from '../../data/data1d/Spectrum1D/index.js'; +import { cutRange } from '../../data/data1d/Spectrum1D/ranges/createRange.js'; +import type { + BrushTrackerContext, + OnBrush, + OnClick, + OnZoom, +} from '../EventsTrackers/BrushTracker.js'; +import { BrushTracker } from '../EventsTrackers/BrushTracker.js'; +import { useChartData } from '../context/ChartContext.js'; +import { useDispatch } from '../context/DispatchContext.js'; +import { useMapKeyModifiers } from '../context/KeyModifierContext.js'; +import { useLogger } from '../context/LoggerContext.js'; +import { usePreferences } from '../context/PreferencesContext.js'; +import { useScale } from '../context/ScaleContext.js'; +import { useActiveSpectrum } from '../hooks/useActiveSpectrum.js'; +import { usePanelPreferences } from '../hooks/usePanelPreferences.js'; +import useSpectrum from '../hooks/useSpectrum.js'; +import MultipletAnalysisModal from '../modal/MultipletAnalysisModal.js'; +import { ZOOM_TYPES } from '../reducer/helper/Zoom1DManager.js'; +import getRange from '../reducer/helper/getRange.js'; +import type { Tool } from '../toolbar/ToolTypes.js'; +import { options } from '../toolbar/ToolTypes.js'; +import Events from '../utility/Events.js'; + +import { getXScale } from './utilities/scale.js'; + +export function BrushTracker1D({ children }) { + const state = useChartData(); + + const { + toolOptions: { selectedTool }, + + view: { + spectra: { activeTab }, + }, + } = state; + const brushStartRef = useRef(null); + const spectrum = useSpectrum(); + const dispatch = useDispatch(); + const { dispatch: dispatchPreferences } = usePreferences(); + const { showBoxPlot, showStocsy } = usePanelPreferences( + 'matrixGeneration', + activeTab, + ); + const { logger } = useLogger(); + const scaleState = useScale(); + + const { getModifiersKey, primaryKeyIdentifier } = useMapKeyModifiers(); + const activeSpectrum = useActiveSpectrum(); + + const [isOpenAnalysisModal, openAnalysisModal, closeAnalysisModal] = + useOnOff(false); + + const [brushData, setBrushData] = useState(null); + + function handleBrush(brushData) { + const { startX: startXInPixel, endX: endXInPixel, mouseButton } = brushData; + + if (mouseButton === 'secondary') { + const scaleX = getXScale(state); + if (!brushStartRef.current) { + brushStartRef.current = scaleX.invert(startXInPixel); + } + const shiftX = scaleX.invert(endXInPixel) - brushStartRef.current; + + dispatch({ type: 'MOVE', payload: { shiftX, shiftY: 0 } }); + } + } + + const handleBrushEnd = useCallback( + (brushData) => { + //reset the brush start + brushStartRef.current = null; + setBrushData(brushData); + + const keyModifiers = getModifiersKey(brushData as unknown as MouseEvent); + + const selectRange = getRange(state, { + startX: brushData.startX, + endX: brushData.endX, + }); + + if (brushData.mouseButton === 'main') { + const propagateEvent = () => { + if (!scaleState.scaleX || !scaleState.scaleY) return; + + const { startX, endX } = brushData; + const startXPPM = scaleState.scaleX().invert(startX); + const endXPPM = scaleState.scaleX().invert(endX); + Events.emit('brushEnd', { + ...brushData, + range: [startXPPM, endXPPM].sort((a, b) => a - b), + }); + }; + + let executeDefaultAction = false; + + switch (keyModifiers) { + // when Alt key is active + case primaryKeyIdentifier: { + switch (selectedTool) { + case options.integral.id: + dispatch({ + type: 'ADD_INTEGRAL', + payload: brushData, + }); + break; + case options.rangePicking.id: { + if (!spectrum) break; + + if (isSpectrum1D(spectrum)) { + const [from, to] = selectRange; + const range = createRange(spectrum, { + from, + to, + logger, + }); + + if (!range) break; + + dispatch({ + type: 'ADD_RANGE', + payload: { range }, + }); + } + + break; + } + case options.multipleSpectraAnalysis.id: + if (scaleState.scaleX) { + const { startX, endX } = brushData; + const start = scaleState.scaleX().invert(startX); + const end = scaleState.scaleX().invert(endX); + dispatchPreferences({ + type: 'ANALYZE_SPECTRA', + payload: { + start, + end, + nucleus: activeTab, + }, + }); + } + break; + + case options.peakPicking.id: + dispatch({ + type: 'ADD_PEAKS', + payload: brushData, + }); + break; + + case options.baselineCorrection.id: + dispatch({ + type: 'ADD_BASE_LINE_ZONE', + payload: { + startX: brushData.startX, + endX: brushData.endX, + }, + }); + break; + + case options.exclusionZones.id: + dispatch({ + type: 'ADD_EXCLUSION_ZONE', + payload: { startX: brushData.startX, endX: brushData.endX }, + }); + break; + case options.matrixGenerationExclusionZones.id: { + const [from, to] = selectRange; + dispatchPreferences({ + type: 'ADD_MATRIX_GENERATION_EXCLUSION_ZONE', + payload: { + zone: { from, to }, + nucleus: activeTab, + }, + }); + + break; + } + + default: + executeDefaultAction = true; + break; + } + break; + } + case 'shift[false]_ctrl[false]_alt[true]': { + switch (selectedTool) { + case options.rangePicking.id: { + openAnalysisModal(); + break; + } + default: + executeDefaultAction = true; + break; + } + break; + } + + default: { + executeDefaultAction = true; + + break; + } + } + + const tools = new Set(['zoom', 'databaseRangesSelection']); + const enableDefaultBrush = + !tools.has(selectedTool) || + (tools.has(selectedTool) && !brushData.shiftKey); + + if (executeDefaultAction && selectedTool != null) { + if (enableDefaultBrush) { + dispatch({ type: 'BRUSH_END', payload: brushData }); + } + + propagateEvent(); + } + } + }, + [ + getModifiersKey, + state, + selectedTool, + scaleState, + primaryKeyIdentifier, + dispatch, + spectrum, + logger, + dispatchPreferences, + activeTab, + openAnalysisModal, + ], + ); + + const handleOnDoubleClick = useCallback(() => { + dispatch({ + type: 'FULL_ZOOM_OUT', + payload: { zoomType: ZOOM_TYPES.STEP_HORIZONTAL }, + }); + }, [dispatch]); + + const handleZoom = useCallback( + (options) => { + if ( + (showBoxPlot || showStocsy) && + options.altKey && + (selectedTool === 'zoom' || + selectedTool === 'matrixGenerationExclusionZones') + ) { + //change the matrix generation vertical scale + dispatchPreferences({ + type: 'CHANGE_MATRIX_GENERATION_SCALE', + payload: { nucleus: activeTab, zoomOptions: options }, + }); + } else { + dispatch({ type: 'SET_ZOOM', payload: { options } }); + } + }, + [ + activeTab, + dispatch, + dispatchPreferences, + selectedTool, + showBoxPlot, + showStocsy, + ], + ); + + const mouseClick = useCallback( + (event) => { + if (!scaleState.scaleX) return; + + const xPPM = scaleState.scaleX().invert(event.x); + + Events.emit('mouseClick', { + ...event, + xPPM, + }); + + const keyModifiers = getModifiersKey(event as unknown as MouseEvent); + + switch (keyModifiers) { + case primaryKeyIdentifier: { + switch (selectedTool) { + case 'peakPicking': + dispatch({ + type: 'ADD_PEAK', + payload: event, + }); + break; + + case 'integral': + dispatch({ + type: 'CUT_INTEGRAL', + payload: { cutValue: xPPM }, + }); + break; + case 'rangePicking': { + if (!spectrum) break; + + if (isSpectrum1D(spectrum)) { + const cutRanges = cutRange(spectrum, xPPM); + + dispatch({ + type: 'CUT_RANGE', + payload: { ranges: cutRanges }, + }); + } + break; + } + case 'phaseCorrection': + dispatch({ + type: 'SET_ONE_DIMENSION_PIVOT_POINT', + payload: { + value: event.x, + }, + }); + + break; + case 'zoom': + case 'matrixGenerationExclusionZones': + if (!showStocsy || !event.shiftKey) break; + + dispatchPreferences({ + type: 'CHANGE_MATRIX_GENERATION_STOCSY_CHEMICAL_SHIFT', + payload: { nucleus: activeTab, chemicalShift: xPPM }, + }); + + break; + default: + break; + } + break; + } + default: + break; + } + }, + [ + activeTab, + dispatch, + dispatchPreferences, + getModifiersKey, + primaryKeyIdentifier, + scaleState, + selectedTool, + showStocsy, + spectrum, + ], + ); + + return ( + <> + + {children} + + {brushData && ( + + )} + + ); +} diff --git a/src/component/1d/Chart1D.tsx b/src/component/1d/SVGContent1D.tsx similarity index 52% rename from src/component/1d/Chart1D.tsx rename to src/component/1d/SVGContent1D.tsx index 1dfd8cf72..4ff43468f 100644 --- a/src/component/1d/Chart1D.tsx +++ b/src/component/1d/SVGContent1D.tsx @@ -1,4 +1,3 @@ -import { SVGRootContainer } from '../1d-2d/components/SVGRootContainer.js'; import SpectrumInfoBlock from '../1d-2d/components/SpectrumInfoBlock.js'; import { ApodizationLine } from './ApodizationLine.js'; @@ -14,43 +13,38 @@ import { Boxplot } from './matrix/Boxplot.js'; import { Stocsy } from './matrix/Stocsy.js'; import MultiAnalysisRanges from './multiAnalysis/MultiAnalysisRanges.js'; import MultiplicityTrees from './multiplicityTree/MultiplicityTrees.js'; -import { PeakEditionProvider } from './peaks/PeakEditionManager.js'; import Peaks from './peaks/Peaks.js'; import PeaksShapes from './peaks/PeaksShapes.js'; import Ranges from './ranges/Ranges.js'; import RangesIntegrals from './ranges/RangesIntegrals.js'; import BaseLineZones from './tool/BaseLineZones.js'; -function Chart1D({ mode }) { +export function SVGContent1D() { return ( - - - - - - - - - - - - - - - - - - - - + <> + + + + + + + + + + + + + + + + + + - - - - - - + + + + + ); } - -export default Chart1D; diff --git a/src/component/1d/Viewer1D.tsx b/src/component/1d/Viewer1D.tsx index b92817af7..7a2516aad 100644 --- a/src/component/1d/Viewer1D.tsx +++ b/src/component/1d/Viewer1D.tsx @@ -1,487 +1,87 @@ import type { ReactNode } from 'react'; -import { useCallback, useEffect, useReducer, useRef, useState } from 'react'; import { ResponsiveChart } from 'react-d3-utils'; -import { useOnOff } from 'react-science/ui'; -import { - createRange, - isSpectrum1D, -} from '../../data/data1d/Spectrum1D/index.js'; -import { cutRange } from '../../data/data1d/Spectrum1D/ranges/createRange.js'; +import { SVGRootContainer } from '../1d-2d/components/SVGRootContainer.js'; +import { ViewerResponsiveWrapper } from '../1d-2d/components/ViewerResponsiveWrapper.js'; import BrushXY, { BRUSH_TYPE } from '../1d-2d/tools/BrushXY.js'; import CrossLinePointer from '../1d-2d/tools/CrossLinePointer.js'; -import { ViewerResponsiveWrapper } from '../2d/Viewer2D.js'; -import type { - BrushTrackerContext, - OnBrush, - OnClick, - OnZoom, -} from '../EventsTrackers/BrushTracker.js'; -import { BrushTracker } from '../EventsTrackers/BrushTracker.js'; import { MouseTracker } from '../EventsTrackers/MouseTracker.js'; import { useChartData } from '../context/ChartContext.js'; -import { useDispatch } from '../context/DispatchContext.js'; -import { useMapKeyModifiers } from '../context/KeyModifierContext.js'; -import { useLogger } from '../context/LoggerContext.js'; -import { usePreferences } from '../context/PreferencesContext.js'; import { ScaleProvider } from '../context/ScaleContext.js'; -import { useActiveSpectrum } from '../hooks/useActiveSpectrum.js'; -import { usePanelPreferences } from '../hooks/usePanelPreferences.js'; -import useSpectrum from '../hooks/useSpectrum.js'; -import { useVerticalAlign } from '../hooks/useVerticalAlign.js'; import Spinner from '../loader/Spinner.js'; -import MultipletAnalysisModal from '../modal/MultipletAnalysisModal.js'; -import { ZOOM_TYPES } from '../reducer/helper/Zoom1DManager.js'; -import getRange from '../reducer/helper/getRange.js'; -import scaleReducer, { - scaleInitialState, - SET_SCALE, -} from '../reducer/scaleReducer.js'; -import type { Tool } from '../toolbar/ToolTypes.js'; -import { options } from '../toolbar/ToolTypes.js'; -import Events from '../utility/Events.js'; -import Chart1D from './Chart1D.js'; +import { BrushTracker1D } from './BrushTracker1D.js'; import FooterBanner from './FooterBanner.js'; +import { SVGContent1D } from './SVGContent1D.js'; +import { PeakEditionProvider } from './peaks/PeakEditionManager.js'; import BaseLine from './tool/BaseLine.js'; import PeakPointer from './tool/PeakPointer.js'; import { PivotIndicator } from './tool/PivotIndicator.js'; import XLabelPointer from './tool/XLabelPointer.js'; -import { getXScale } from './utilities/scale.js'; -interface Viewer1DProps { +interface InnerViewer1DProps { emptyText?: ReactNode; } +interface Viewer1DProps extends InnerViewer1DProps { + renderSvgContentOnly?: boolean; +} -function Viewer1D({ emptyText = undefined }: Viewer1DProps) { - const state = useChartData(); - - const { - toolOptions: { selectedTool }, - isLoading, - data, - mode, - width: widthProp, - height: heightProp, - margin, - xDomain, - xDomains, - yDomain, - yDomains, - view: { - spectra: { activeTab }, - }, - } = state; - const brushStartRef = useRef(null); - const verticalAlign = useVerticalAlign(); - const activeSpectrum = useActiveSpectrum(); - const spectrum = useSpectrum(); - const dispatch = useDispatch(); - const { dispatch: dispatchPreferences } = usePreferences(); - const { showBoxPlot, showStocsy } = usePanelPreferences( - 'matrixGeneration', - activeTab, - ); - const { logger } = useLogger(); +function InnerViewer1D(props: InnerViewer1DProps) { + const { emptyText } = props; + const { isLoading, data } = useChartData(); - const [scaleState, dispatchScale] = useReducer( - scaleReducer, - scaleInitialState, + return ( + + {({ height, width }) => ( + +
+ + + {data && data.length > 0 && ( + + + + + + + + + + + + + + + + + )} +
+
+ )} +
); +} - useEffect(() => { - if (xDomain.length > 0 && yDomain.length > 0 && widthProp && heightProp) { - dispatchScale({ - type: SET_SCALE, - payload: { - yDomain, - yDomains, - xDomain, - xDomains, - margin, - height: heightProp, - width: widthProp, - verticalAlign, - mode, - }, - }); - } - }, [ - verticalAlign, - heightProp, - margin, - mode, - widthProp, - xDomain, - xDomains, - yDomain, - yDomains, - ]); - - function handleBrush(brushData) { - const { startX: startXInPixel, endX: endXInPixel, mouseButton } = brushData; - - if (mouseButton === 'secondary') { - const scaleX = getXScale(state); - if (!brushStartRef.current) { - brushStartRef.current = scaleX.invert(startXInPixel); - } - const shiftX = scaleX.invert(endXInPixel) - brushStartRef.current; +export function Viewer1D(props: Viewer1DProps) { + const { renderSvgContentOnly = false, ...otherProps } = props; - dispatch({ type: 'MOVE', payload: { shiftX, shiftY: 0 } }); - } + if (renderSvgContentOnly) { + return ( + + + + ); } - const [isOpenAnalysisModal, openAnalysisModal, closeAnalysisModal] = - useOnOff(false); - - const [brushData, setBrushData] = useState(null); - - const { getModifiersKey, primaryKeyIdentifier } = useMapKeyModifiers(); - - const handleBrushEnd = useCallback( - (brushData) => { - //reset the brush start - brushStartRef.current = null; - setBrushData(brushData); - - const keyModifiers = getModifiersKey(brushData as unknown as MouseEvent); - - const selectRange = getRange(state, { - startX: brushData.startX, - endX: brushData.endX, - }); - - if (brushData.mouseButton === 'main') { - const propagateEvent = () => { - if (!scaleState.scaleX || !scaleState.scaleY) return; - - const { startX, endX } = brushData; - const startXPPM = scaleState.scaleX().invert(startX); - const endXPPM = scaleState.scaleX().invert(endX); - Events.emit('brushEnd', { - ...brushData, - range: [startXPPM, endXPPM].sort((a, b) => a - b), - }); - }; - - let executeDefaultAction = false; - - switch (keyModifiers) { - // when Alt key is active - case primaryKeyIdentifier: { - switch (selectedTool) { - case options.integral.id: - dispatch({ - type: 'ADD_INTEGRAL', - payload: brushData, - }); - break; - case options.rangePicking.id: { - if (!spectrum) break; - - if (isSpectrum1D(spectrum)) { - const [from, to] = selectRange; - const range = createRange(spectrum, { - from, - to, - logger, - }); - - if (!range) break; - - dispatch({ - type: 'ADD_RANGE', - payload: { range }, - }); - } - - break; - } - case options.multipleSpectraAnalysis.id: - if (scaleState.scaleX) { - const { startX, endX } = brushData; - const start = scaleState.scaleX().invert(startX); - const end = scaleState.scaleX().invert(endX); - dispatchPreferences({ - type: 'ANALYZE_SPECTRA', - payload: { - start, - end, - nucleus: activeTab, - }, - }); - } - break; - - case options.peakPicking.id: - dispatch({ - type: 'ADD_PEAKS', - payload: brushData, - }); - break; - - case options.baselineCorrection.id: - dispatch({ - type: 'ADD_BASE_LINE_ZONE', - payload: { - startX: brushData.startX, - endX: brushData.endX, - }, - }); - break; - - case options.exclusionZones.id: - dispatch({ - type: 'ADD_EXCLUSION_ZONE', - payload: { startX: brushData.startX, endX: brushData.endX }, - }); - break; - case options.matrixGenerationExclusionZones.id: { - const [from, to] = selectRange; - dispatchPreferences({ - type: 'ADD_MATRIX_GENERATION_EXCLUSION_ZONE', - payload: { - zone: { from, to }, - nucleus: activeTab, - }, - }); - - break; - } - - default: - executeDefaultAction = true; - break; - } - break; - } - case 'shift[false]_ctrl[false]_alt[true]': { - switch (selectedTool) { - case options.rangePicking.id: { - openAnalysisModal(); - break; - } - default: - executeDefaultAction = true; - break; - } - break; - } - - default: { - executeDefaultAction = true; - - break; - } - } - - const tools = new Set(['zoom', 'databaseRangesSelection']); - const enableDefaultBrush = - !tools.has(selectedTool) || - (tools.has(selectedTool) && !brushData.shiftKey); - - if (executeDefaultAction && selectedTool != null) { - if (enableDefaultBrush) { - dispatch({ type: 'BRUSH_END', payload: brushData }); - } - - propagateEvent(); - } - } - }, - [ - getModifiersKey, - state, - selectedTool, - scaleState, - primaryKeyIdentifier, - dispatch, - spectrum, - logger, - dispatchPreferences, - activeTab, - openAnalysisModal, - ], - ); - - const handleOnDoubleClick = useCallback(() => { - dispatch({ - type: 'FULL_ZOOM_OUT', - payload: { zoomType: ZOOM_TYPES.STEP_HORIZONTAL }, - }); - }, [dispatch]); - - const handleZoom = useCallback( - (options) => { - if ( - (showBoxPlot || showStocsy) && - options.altKey && - (selectedTool === 'zoom' || - selectedTool === 'matrixGenerationExclusionZones') - ) { - //change the matrix generation vertical scale - dispatchPreferences({ - type: 'CHANGE_MATRIX_GENERATION_SCALE', - payload: { nucleus: activeTab, zoomOptions: options }, - }); - } else { - dispatch({ type: 'SET_ZOOM', payload: { options } }); - } - }, - [ - activeTab, - dispatch, - dispatchPreferences, - selectedTool, - showBoxPlot, - showStocsy, - ], - ); - - const mouseClick = useCallback( - (event) => { - if (!scaleState.scaleX) return; - - const xPPM = scaleState.scaleX().invert(event.x); - - Events.emit('mouseClick', { - ...event, - xPPM, - }); - - const keyModifiers = getModifiersKey(event as unknown as MouseEvent); - - switch (keyModifiers) { - case primaryKeyIdentifier: { - switch (selectedTool) { - case 'peakPicking': - dispatch({ - type: 'ADD_PEAK', - payload: event, - }); - break; - - case 'integral': - dispatch({ - type: 'CUT_INTEGRAL', - payload: { cutValue: xPPM }, - }); - break; - case 'rangePicking': { - if (!spectrum) break; - - if (isSpectrum1D(spectrum)) { - const cutRanges = cutRange(spectrum, xPPM); - - dispatch({ - type: 'CUT_RANGE', - payload: { ranges: cutRanges }, - }); - } - break; - } - case 'phaseCorrection': - dispatch({ - type: 'SET_ONE_DIMENSION_PIVOT_POINT', - payload: { - value: event.x, - }, - }); - - break; - case 'zoom': - case 'matrixGenerationExclusionZones': - if (!showStocsy || !event.shiftKey) break; - - dispatchPreferences({ - type: 'CHANGE_MATRIX_GENERATION_STOCSY_CHEMICAL_SHIFT', - payload: { nucleus: activeTab, chemicalShift: xPPM }, - }); - - break; - default: - break; - } - break; - } - default: - break; - } - }, - [ - activeTab, - dispatch, - dispatchPreferences, - getModifiersKey, - primaryKeyIdentifier, - scaleState, - selectedTool, - showStocsy, - spectrum, - ], - ); - return ( - - - {({ height, width }) => ( - -
- - - {scaleState.scaleX && - scaleState.scaleY && - data && - data.length > 0 && ( - - - - - - - - - - - - - )} -
-
- )} -
- {brushData && ( - - )} + + ); } - -export default Viewer1D; diff --git a/src/component/1d/XAxis.tsx b/src/component/1d/XAxis.tsx index 27a442efc..02d57e57f 100644 --- a/src/component/1d/XAxis.tsx +++ b/src/component/1d/XAxis.tsx @@ -39,13 +39,12 @@ const gridStyles = css` interface XAxisProps { show?: boolean; showGrid?: boolean; - mode?: string; label?: string; } function XAxis(props: XAxisProps) { - const { show = true, showGrid = false, mode, label: labelProp } = props; - const { xDomain, height, width, margin } = useChartData(); + const { show = true, showGrid = false, label: labelProp } = props; + const { xDomain, height, width, margin, mode } = useChartData(); const { scaleX } = useScale(); const refAxis = useRef(null); diff --git a/src/component/2d/BrushTracker2D.tsx b/src/component/2d/BrushTracker2D.tsx new file mode 100644 index 000000000..53ef1e2f3 --- /dev/null +++ b/src/component/2d/BrushTracker2D.tsx @@ -0,0 +1,188 @@ +import { useCallback, useRef } from 'react'; + +import type { + OnBrush, + OnClick, + OnDoubleClick, + OnZoom, +} from '../EventsTrackers/BrushTracker.js'; +import { BrushTracker } from '../EventsTrackers/BrushTracker.js'; +import { useChartData } from '../context/ChartContext.js'; +import { useDispatch } from '../context/DispatchContext.js'; +import { useMapKeyModifiers } from '../context/KeyModifierContext.js'; +import { options } from '../toolbar/ToolTypes.js'; + +import { + get2DDimensionLayout, + getLayoutID, +} from './utilities/DimensionLayout.js'; +import { get2DXScale, get2DYScale } from './utilities/scale.js'; + +export function BrushTracker2D({ children }) { + const state = useChartData(); + const { + toolOptions: { selectedTool }, + } = state; + + const dispatch = useDispatch(); + const brushStartRef = useRef<{ x: number; y: number } | null>(null); + const { getModifiersKey, primaryKeyIdentifier } = useMapKeyModifiers(); + const DIMENSION = get2DDimensionLayout(state); + + function handleBrush(brushData) { + const { + startX: startXInPixel, + endX: endXInPixel, + startY: startYInPixel, + endY: endYInPixel, + mouseButton, + } = brushData; + + if (mouseButton === 'secondary') { + const scaleX = get2DXScale(state); + const scaleY = get2DYScale(state); + if (!brushStartRef.current) { + const x = scaleX.invert(startXInPixel); + const y = scaleY.invert(startYInPixel); + brushStartRef.current = { x, y }; + } + const { x, y } = brushStartRef.current; + const shiftX = scaleX.invert(endXInPixel) - x; + const shiftY = scaleY.invert(endYInPixel) - y; + + dispatch({ type: 'MOVE', payload: { shiftX, shiftY } }); + } + } + + const handleBrushEnd = useCallback( + (brushData) => { + //reset the brush start + brushStartRef.current = null; + + const modifierKey = getModifiersKey(brushData as unknown as MouseEvent); + let executeDefaultAction = false; + + if (brushData.mouseButton === 'main') { + const trackID = getLayoutID(DIMENSION, brushData); + if (trackID) { + switch (modifierKey) { + case primaryKeyIdentifier: { + switch (selectedTool) { + case options.zoom.id: { + executeDefaultAction = true; + break; + } + case options.zonePicking.id: { + dispatch({ type: 'ADD_2D_ZONE', payload: brushData }); + + break; + } + default: + break; + } + + break; + } + default: { + executeDefaultAction = true; + break; + } + } + const isNotDistanceMeasurementTool = + selectedTool !== 'zoom' || + (selectedTool === 'zoom' && !brushData.shiftKey); + + if ( + executeDefaultAction && + selectedTool != null && + isNotDistanceMeasurementTool + ) { + return dispatch({ + type: 'BRUSH_END', + payload: { + ...brushData, + trackID: getLayoutID(DIMENSION, brushData), + }, + }); + } + } + } + }, + [getModifiersKey, DIMENSION, selectedTool, primaryKeyIdentifier, dispatch], + ); + + const handleOnDoubleClick: OnDoubleClick = useCallback( + (e) => { + const { x: startX, y: startY } = e; + const trackID = getLayoutID(DIMENSION, { startX, startY }); + if (trackID) { + dispatch({ type: 'FULL_ZOOM_OUT', payload: { trackID } }); + } + }, + [DIMENSION, dispatch], + ); + + const handleZoom: OnZoom = (options) => { + const { x: startX, y: startY, shiftKey } = options; + const trackID = getLayoutID(DIMENSION, { startX, startY }); + + if (trackID) { + const isZoomWithScroll = + trackID === 'CENTER_2D' && shiftKey && selectedTool === 'zoom'; + const isMouseOver1DTrace = trackID !== 'CENTER_2D'; + const isTraceZoomActive = + selectedTool === 'phaseCorrectionTwoDimensions' && !shiftKey; + if (isZoomWithScroll || isMouseOver1DTrace || isTraceZoomActive) { + dispatch({ type: 'SET_ZOOM', payload: { options, trackID } }); + } else { + dispatch({ type: 'SET_2D_LEVEL', payload: { options } }); + } + } + }; + + const mouseClick: OnClick = useCallback( + (event) => { + const { x, y, shiftKey } = event; + if (shiftKey) { + switch (selectedTool) { + case 'phaseCorrectionTwoDimensions': + dispatch({ + type: 'SET_TWO_DIMENSION_PIVOT_POINT', + payload: { x, y }, + }); + break; + default: + break; + } + } else { + switch (selectedTool) { + case 'phaseCorrectionTwoDimensions': + dispatch({ type: 'ADD_PHASE_CORRECTION_TRACE', payload: { x, y } }); + break; + default: + break; + } + } + }, + [selectedTool, dispatch], + ); + + return ( + + {children} + + ); +} diff --git a/src/component/2d/Chart2D.tsx b/src/component/2d/SVGContent2D.tsx similarity index 64% rename from src/component/2d/Chart2D.tsx rename to src/component/2d/SVGContent2D.tsx index a33ab70ec..0d882a87c 100644 --- a/src/component/2d/Chart2D.tsx +++ b/src/component/2d/SVGContent2D.tsx @@ -1,15 +1,13 @@ import type { Spectrum1D } from 'nmr-load-save'; -import { memo } from 'react'; -import { SVGRootContainer } from '../1d-2d/components/SVGRootContainer.js'; import SpectrumInfoBlock from '../1d-2d/components/SpectrumInfoBlock.js'; -import { useChartData } from '../context/ChartContext.js'; import { ShareDataProvider } from '../context/ShareDataContext.js'; import XAxis from './XAxis.js'; import YAxis from './YAxis.js'; import { FidContainer } from './fid/FidContainer.js'; import { FTContainer } from './ft/FTContainer.js'; +import { useTracesSpectra } from './useTracesSpectra.js'; import IndicationLines from './zones/IndicationLines.js'; import Zones from './zones/Zones.js'; import ZonesAssignmentsLabels from './zones/ZonesAssignmentsLabels.js'; @@ -18,9 +16,9 @@ interface Chart2DProps { spectra?: Spectrum1D[]; } -function Chart2DInner({ spectra }: Chart2DProps) { +export function SVGContent2D({ spectra }: Chart2DProps) { return ( - + <> @@ -34,16 +32,11 @@ function Chart2DInner({ spectra }: Chart2DProps) { - + ); } -const MemoizedChart2D = memo(Chart2DInner); - -export default function Chart2D({ spectra }: Chart2DProps) { - const { width, height, margin, displayerKey } = useChartData(); - - return ( - - ); +export function SVGContent2DWithSpectra() { + const spectra = useTracesSpectra(); + return ; } diff --git a/src/component/2d/Viewer2D.tsx b/src/component/2d/Viewer2D.tsx index c0d97591a..7c468ceec 100644 --- a/src/component/2d/Viewer2D.tsx +++ b/src/component/2d/Viewer2D.tsx @@ -1,238 +1,55 @@ -import type { Spectrum1D } from 'nmr-load-save'; import type { ReactNode } from 'react'; -import { useCallback, useEffect, useMemo, useRef } from 'react'; import { ResponsiveChart } from 'react-d3-utils'; +import { SVGRootContainer } from '../1d-2d/components/SVGRootContainer.js'; +import { ViewerResponsiveWrapper } from '../1d-2d/components/ViewerResponsiveWrapper.js'; import BrushXY, { BRUSH_TYPE } from '../1d-2d/tools/BrushXY.js'; import CrossLinePointer from '../1d-2d/tools/CrossLinePointer.js'; -import type { - OnBrush, - OnClick, - OnDoubleClick, - OnZoom, -} from '../EventsTrackers/BrushTracker.js'; -import { BrushTracker } from '../EventsTrackers/BrushTracker.js'; import { MouseTracker } from '../EventsTrackers/MouseTracker.js'; import { useChartData } from '../context/ChartContext.js'; -import { useDispatch } from '../context/DispatchContext.js'; -import { useMapKeyModifiers } from '../context/KeyModifierContext.js'; -import { useViewportSize } from '../hooks/useViewportSize.js'; import Spinner from '../loader/Spinner.js'; import { options } from '../toolbar/ToolTypes.js'; import { PhaseTraces } from './1d-tracer/phase-correction-traces/index.js'; -import Chart2D from './Chart2D.js'; +import { BrushTracker2D } from './BrushTracker2D.js'; import FooterBanner from './FooterBanner.js'; +import { SVGContent2D } from './SVGContent2D.js'; import SlicingView from './SlicingView.js'; import { PivotIndicator } from './tools/PivotIndicator.js'; import XYLabelPointer from './tools/XYLabelPointer.js'; -import { - get2DDimensionLayout, - getLayoutID, -} from './utilities/DimensionLayout.js'; -import { get2DXScale, get2DYScale } from './utilities/scale.js'; +import { useTracesSpectra } from './useTracesSpectra.js'; +import { get2DDimensionLayout } from './utilities/DimensionLayout.js'; interface Viewer2DProps { - emptyText: ReactNode; + emptyText?: ReactNode; + renderSvgContentOnly?: boolean; } -function Viewer2D({ emptyText = undefined }: Viewer2DProps) { +function Viewer2D(props: Viewer2DProps) { + const { emptyText, renderSvgContentOnly = false } = props; const state = useChartData(); const { toolOptions: { selectedTool }, isLoading, data, margin, - view: { - spectra: { activeSpectra, activeTab }, - }, } = state; - const dispatch = useDispatch(); - const brushStartRef = useRef<{ x: number; y: number } | null>(null); - const { getModifiersKey, primaryKeyIdentifier } = useMapKeyModifiers(); - - const spectrumData: Spectrum1D[] = useMemo(() => { - const nuclei = activeTab.split(','); - - const traces: Spectrum1D[] = []; - for (const nucleus of nuclei) { - const spectra = activeSpectra[nucleus]; - if (spectra?.length === 1) { - const id = spectra[0].id; - const spectrum = data.find( - (datum) => - datum.id === id && !datum.info.isFid && datum.info.dimension === 1, - ) as Spectrum1D; - - if (spectrum) { - traces.push(spectrum); - } - } - } - return traces; - }, [activeTab, data, activeSpectra]); + const spectrumData = useTracesSpectra(); const DIMENSION = get2DDimensionLayout(state); - function handleBrush(brushData) { - const { - startX: startXInPixel, - endX: endXInPixel, - startY: startYInPixel, - endY: endYInPixel, - mouseButton, - } = brushData; - - if (mouseButton === 'secondary') { - const scaleX = get2DXScale(state); - const scaleY = get2DYScale(state); - if (!brushStartRef.current) { - const x = scaleX.invert(startXInPixel); - const y = scaleY.invert(startYInPixel); - brushStartRef.current = { x, y }; - } - const { x, y } = brushStartRef.current; - const shiftX = scaleX.invert(endXInPixel) - x; - const shiftY = scaleY.invert(endYInPixel) - y; - - dispatch({ type: 'MOVE', payload: { shiftX, shiftY } }); - } + if (renderSvgContentOnly) { + return ; } - const handleBrushEnd = useCallback( - (brushData) => { - //reset the brush start - brushStartRef.current = null; - - const modifierKey = getModifiersKey(brushData as unknown as MouseEvent); - let executeDefaultAction = false; - - if (brushData.mouseButton === 'main') { - const trackID = getLayoutID(DIMENSION, brushData); - if (trackID) { - switch (modifierKey) { - case primaryKeyIdentifier: { - switch (selectedTool) { - case options.zoom.id: { - executeDefaultAction = true; - break; - } - case options.zonePicking.id: { - dispatch({ type: 'ADD_2D_ZONE', payload: brushData }); - - break; - } - default: - break; - } - - break; - } - default: { - executeDefaultAction = true; - break; - } - } - const isNotDistanceMeasurementTool = - selectedTool !== 'zoom' || - (selectedTool === 'zoom' && !brushData.shiftKey); - - if ( - executeDefaultAction && - selectedTool != null && - isNotDistanceMeasurementTool - ) { - return dispatch({ - type: 'BRUSH_END', - payload: { - ...brushData, - trackID: getLayoutID(DIMENSION, brushData), - }, - }); - } - } - } - }, - [getModifiersKey, DIMENSION, selectedTool, primaryKeyIdentifier, dispatch], - ); - - const handleOnDoubleClick: OnDoubleClick = useCallback( - (e) => { - const { x: startX, y: startY } = e; - const trackID = getLayoutID(DIMENSION, { startX, startY }); - if (trackID) { - dispatch({ type: 'FULL_ZOOM_OUT', payload: { trackID } }); - } - }, - [DIMENSION, dispatch], - ); - - const handleZoom: OnZoom = (options) => { - const { x: startX, y: startY, shiftKey } = options; - const trackID = getLayoutID(DIMENSION, { startX, startY }); - - if (trackID) { - const isZoomWithScroll = - trackID === 'CENTER_2D' && shiftKey && selectedTool === 'zoom'; - const isMouseOver1DTrace = trackID !== 'CENTER_2D'; - const isTraceZoomActive = - selectedTool === 'phaseCorrectionTwoDimensions' && !shiftKey; - if (isZoomWithScroll || isMouseOver1DTrace || isTraceZoomActive) { - dispatch({ type: 'SET_ZOOM', payload: { options, trackID } }); - } else { - dispatch({ type: 'SET_2D_LEVEL', payload: { options } }); - } - } - }; - - const mouseClick: OnClick = useCallback( - (event) => { - const { x, y, shiftKey } = event; - if (shiftKey) { - switch (selectedTool) { - case 'phaseCorrectionTwoDimensions': - dispatch({ - type: 'SET_TWO_DIMENSION_PIVOT_POINT', - payload: { x, y }, - }); - break; - default: - break; - } - } else { - switch (selectedTool) { - case 'phaseCorrectionTwoDimensions': - dispatch({ type: 'ADD_PHASE_CORRECTION_TRACE', payload: { x, y } }); - break; - default: - break; - } - } - }, - [selectedTool, dispatch], - ); - return ( {({ width, height }) => ( {data && data.length > 0 && ( - + @@ -269,10 +86,11 @@ function Viewer2D({ emptyText = undefined }: Viewer2DProps) { - - + + + - + )} )} @@ -280,24 +98,4 @@ function Viewer2D({ emptyText = undefined }: Viewer2DProps) { ); } -interface ViewerResponsiveWrapperProps { - width: number; - height: number; - children: ReactNode; -} - -export function ViewerResponsiveWrapper(props: ViewerResponsiveWrapperProps) { - const dispatch = useDispatch(); - const { width, height, children } = props; - const size = useViewportSize(); - - useEffect(() => { - if (!size) { - dispatch({ type: 'SET_DIMENSIONS', payload: { width, height } }); - } - }, [width, height, dispatch, size]); - - return children; -} - export default Viewer2D; diff --git a/src/component/2d/useTracesSpectra.ts b/src/component/2d/useTracesSpectra.ts new file mode 100644 index 000000000..0f8750999 --- /dev/null +++ b/src/component/2d/useTracesSpectra.ts @@ -0,0 +1,34 @@ +import type { Spectrum1D } from 'nmr-load-save'; +import { useMemo } from 'react'; + +import { useChartData } from '../context/ChartContext.js'; + +export function useTracesSpectra() { + const { + data, + view: { + spectra: { activeSpectra, activeTab }, + }, + } = useChartData(); + + return useMemo(() => { + const nuclei = activeTab.split(','); + + const traces: Spectrum1D[] = []; + for (const nucleus of nuclei) { + const spectra = activeSpectra[nucleus]; + if (spectra?.length === 1) { + const id = spectra[0].id; + const spectrum = data.find( + (datum) => + datum.id === id && !datum.info.isFid && datum.info.dimension === 1, + ) as Spectrum1D; + + if (spectrum) { + traces.push(spectrum); + } + } + } + return traces; + }, [activeTab, data, activeSpectra]); +} diff --git a/src/component/context/ScaleContext.tsx b/src/component/context/ScaleContext.tsx index cb45a1749..a54369094 100644 --- a/src/component/context/ScaleContext.tsx +++ b/src/component/context/ScaleContext.tsx @@ -1,13 +1,30 @@ -import { createContext, useContext } from 'react'; +import type { ScaleLinear } from 'd3'; +import { createContext, useCallback, useContext, useMemo } from 'react'; -import type { ScaleState } from '../reducer/scaleReducer.js'; -import { scaleInitialState } from '../reducer/scaleReducer.js'; +import { getXScale, getYScale } from '../1d/utilities/scale.js'; +import { useVerticalAlign } from '../hooks/useVerticalAlign.js'; -export const scaleContext = createContext(scaleInitialState); -export const ScaleProvider = scaleContext.Provider; +import { useChartData } from './ChartContext.js'; + +type ScaleLinearNumberFunction = ( + spectrumId?: number | null | string, +) => ScaleLinear; + +interface ScaleState { + scaleX: ScaleLinearNumberFunction | null; + scaleY: ScaleLinearNumberFunction | null; + shiftY: number; +} +const scaleInitialState: ScaleState = { + scaleX: null, + scaleY: null, + shiftY: 0, +}; + +export const ScaleContext = createContext(scaleInitialState); export function useScale() { - return useContext(scaleContext); + return useContext(ScaleContext); } type CheckedScaleState = { @@ -16,10 +33,48 @@ type CheckedScaleState = { export function useScaleChecked(): CheckedScaleState { const scale = useScale(); - if (!scale.scaleX || !scale.scaleY) { throw new Error('scale cannot be null'); } return scale as CheckedScaleState; } + +export function ScaleProvider({ children }) { + const { mode, width, height, margin, xDomain, xDomains, yDomain, yDomains } = + useChartData(); + const verticalAlign = useVerticalAlign(); + + const scaleX = useCallback( + (spectrumId = null) => { + return getXScale({ width, margin, xDomains, xDomain, mode }, spectrumId); + }, + [margin, mode, width, xDomain, xDomains], + ); + + const scaleY = useCallback( + (spectrumId = null) => { + return getYScale( + { height, margin, yDomains, yDomain, verticalAlign }, + spectrumId, + ); + }, + [height, margin, verticalAlign, yDomain, yDomains], + ); + + const scaleState = useMemo(() => { + let shiftY = 0; + + if (verticalAlign === 'stack') { + shiftY = height / (Object.keys(yDomains).length + 2); + } else { + shiftY = 0; + } + + return { scaleX, scaleY, shiftY }; + }, [verticalAlign, scaleX, scaleY, height, yDomains]); + + return ( + {children} + ); +} diff --git a/src/component/hooks/useViewportSize.tsx b/src/component/hooks/useViewportSize.tsx index 5bd6ee31a..d513314db 100644 --- a/src/component/hooks/useViewportSize.tsx +++ b/src/component/hooks/useViewportSize.tsx @@ -7,3 +7,9 @@ export function useViewportSize() { return printOptions || exportOptions; } +export function useCheckExportStatus() { + const printOptions = usePrintPage(); + const exportOptions = useExportSettings(); + + return !!(printOptions || exportOptions); +} diff --git a/src/component/main/NMRiumViewer.tsx b/src/component/main/NMRiumViewer.tsx index 8449fd063..ae22f69bb 100644 --- a/src/component/main/NMRiumViewer.tsx +++ b/src/component/main/NMRiumViewer.tsx @@ -1,26 +1,91 @@ import type { CSSProperties, RefObject } from 'react'; import { useDeferredValue, useEffect } from 'react'; -import Viewer1D from '../1d/Viewer1D.js'; +import { Viewer1D } from '../1d/Viewer1D.js'; import FloatMoleculeStructures from '../1d-2d/components/FloatMoleculeStructures/index.js'; +import { SVGRootContainer } from '../1d-2d/components/SVGRootContainer.js'; import Viewer2D from '../2d/Viewer2D.js'; import { useChartData } from '../context/ChartContext.js'; -import { useViewportSize } from '../hooks/useViewportSize.js'; +import { + useCheckExportStatus, + useViewportSize, +} from '../hooks/useViewportSize.js'; import type { NMRiumProps } from './NMRium.js'; interface NMRiumViewerProps { - emptyText: NMRiumProps['emptyText']; viewerRef: RefObject; style?: CSSProperties; onRender?: () => void; + emptyText: NMRiumProps['emptyText']; +} +interface ViewerProps { + renderSvgContentOnly?: boolean; + emptyText: NMRiumProps['emptyText']; } export function NMRiumViewer(props: NMRiumViewerProps) { const { emptyText, viewerRef, onRender, style = {} } = props; - const { displayerMode, width, height } = useChartData(); - const renderDimension = useDeferredValue({ width, height }); const viewPort = useViewportSize(); + const { displayerMode } = useChartData(); + + useOnRender(onRender); + + const isExportingProcessStart = useCheckExportStatus(); + + if (isExportingProcessStart) { + return ( + + + + + ); + } + + return ( +
+ + +
+ ); +} + +function Viewer(props: ViewerProps) { + const { emptyText, renderSvgContentOnly = false } = props; + const { displayerMode } = useChartData(); + + if (displayerMode === '1D') { + return ( + + ); + } + + return ( + + ); +} + +function useOnRender(onRender) { + const { width, height } = useChartData(); + const renderDimension = useDeferredValue({ width, height }); + useEffect(() => { function handleRenderComplete() { if (typeof onRender !== 'function') { @@ -43,26 +108,4 @@ export function NMRiumViewer(props: NMRiumViewerProps) { cancelAnimationFrame(animationFrameId); }; }, [onRender, width, height, renderDimension.width, renderDimension.height]); - - return ( -
- - {displayerMode === '1D' ? ( - - ) : ( - - )} -
- ); } diff --git a/src/component/main/NMRiumViewerWrapper.tsx b/src/component/main/NMRiumViewerWrapper.tsx index 210dc6dee..1642ede37 100644 --- a/src/component/main/NMRiumViewerWrapper.tsx +++ b/src/component/main/NMRiumViewerWrapper.tsx @@ -9,6 +9,7 @@ import { useGetPanelOptions } from '../panels/hooks/useGetPanelOptions.js'; import type { NMRiumProps } from './NMRium.js'; import { NMRiumViewer } from './NMRiumViewer.js'; +// import { useViewportSize } from '../hooks/useViewportSize.js'; interface NMRiumViewerWrapperProps { viewerRef: RefObject; @@ -25,6 +26,7 @@ export function NMRiumViewerWrapper(props: NMRiumViewerWrapperProps) { display: { general = {} }, } = current; const items = useAccordionItems(); + // const isExportReady = useViewportSize(); function resizeHandler(value: SplitPaneSize) { dispatch({ @@ -41,6 +43,10 @@ export function NMRiumViewerWrapper(props: NMRiumViewerWrapperProps) { }); const hasDisplayedPanels = displayedPanels.length > 0; + // if(isExportReady) { + + // } + if (items?.length === 0 || !hasDisplayedPanels) { return ; } diff --git a/src/component/modal/MultipletAnalysisModal.tsx b/src/component/modal/MultipletAnalysisModal.tsx index 25522bad3..d1d02edeb 100644 --- a/src/component/modal/MultipletAnalysisModal.tsx +++ b/src/component/modal/MultipletAnalysisModal.tsx @@ -3,11 +3,13 @@ import { Dialog, DialogBody } from '@blueprintjs/core'; import { css } from '@emotion/react'; import { xGetFromToIndex, xyToXYObject } from 'ml-spectra-processing'; import { analyseMultiplet } from 'multiplet-analysis'; -import type { ActiveSpectrum, Spectrum } from 'nmr-load-save'; +import type { ActiveSpectrum } from 'nmr-load-save'; import { useEffect, useState } from 'react'; import { Axis, LineSeries, Plot } from 'react-plot'; import { isSpectrum2D } from '../../data/data2d/Spectrum2D/index.js'; +import { useChartData } from '../context/ChartContext.js'; +import { useScaleChecked } from '../context/ScaleContext.js'; const styles = css` background-color: white; @@ -78,9 +80,7 @@ const loaderStyles = css` `; interface InnerMultipleAnalysisProps { - data: Spectrum[]; activeSpectrum: ActiveSpectrum | null; - scaleX: any; startX: any; endX: any; } @@ -91,9 +91,7 @@ interface MultipletAnalysisModalProps extends InnerMultipleAnalysisProps { } export default function MultipletAnalysisModal({ - data, activeSpectrum, - scaleX, startX, endX, onClose, @@ -109,9 +107,7 @@ export default function MultipletAnalysisModal({ { setTimeout(() => { @@ -131,7 +129,7 @@ function InnerMultipleAnalysis(props: InnerMultipleAnalysisProps) { }, 400); }, []); - const { data, activeSpectrum, scaleX, startX, endX } = props; + const { activeSpectrum, startX, endX } = props; const [analysisData, setAnalysisData] = useState(); useEffect(() => { diff --git a/src/component/reducer/scaleReducer.ts b/src/component/reducer/scaleReducer.ts deleted file mode 100644 index ad1c737b9..000000000 --- a/src/component/reducer/scaleReducer.ts +++ /dev/null @@ -1,57 +0,0 @@ -import type { ScaleLinear } from 'd3'; -import { produce } from 'immer'; - -import { getXScale, getYScale } from '../1d/utilities/scale.js'; - -export const SET_X_SCALE = 'SET_X_SCALE'; -export const SET_Y_SCALE = 'SET_Y_SCALE'; -export const SET_SCALE = 'SET_SCALE'; - -type ScaleLinearNumberFunction = ( - spectrumId?: number | null | string, -) => ScaleLinear; - -export interface ScaleState { - scaleX: ScaleLinearNumberFunction | null; - scaleY: ScaleLinearNumberFunction | null; - shiftY: number; -} - -export const scaleInitialState: ScaleState = { - scaleX: null, - scaleY: null, - shiftY: 0, -}; - -function innerScaleReducer(draft: ScaleState, action) { - switch (action.type) { - case SET_X_SCALE: - draft.scaleX = (spectrumId = null) => getXScale(action, spectrumId); - break; - - case SET_Y_SCALE: - draft.scaleY = (spectrumId = null) => getYScale(action, spectrumId); - break; - - case SET_SCALE: { - const { yDomains, verticalAlign, height } = action.payload; - - if (verticalAlign === 'stack') { - draft.shiftY = height / (Object.keys(yDomains).length + 2); - } else { - draft.shiftY = 0; - } - - draft.scaleX = (spectrumId = null) => - getXScale(action.payload, spectrumId); - draft.scaleY = (spectrumId = null) => - getYScale(action.payload, spectrumId); - break; - } - default: - return draft; - } -} - -const scaleReducer = produce(innerScaleReducer); -export default scaleReducer; diff --git a/src/component/utility/export.ts b/src/component/utility/export.ts index c62f32d98..7ff02658a 100644 --- a/src/component/utility/export.ts +++ b/src/component/utility/export.ts @@ -439,27 +439,9 @@ function getBlob(targetElementID: string, options: GetBlobOptions): BlobObject { element.remove(); } - //append the floating molecules in svg element - const floatingMoleculesGroup = getMoleculesElement(rootElement); - _svg.append(floatingMoleculesGroup); - - const nmrCss = ` - - * { - font-family: Arial, Helvetica, sans-serif; - } - .grid line,.grid path{stroke:none;} .peaks-text{fill:#730000} .x path{stroke-width:1px} .x text{ - font-weight: bold; - } - .nmr-svg,.contours{ - background-color:white; - fill:white; - } - `; - const head = ``; const style = ``; const svg = `${head + style + _svg.innerHTML}`; @@ -468,41 +450,6 @@ function getBlob(targetElementID: string, options: GetBlobOptions): BlobObject { return { blob, width, height }; } -function getMatrix(element) { - const transform = globalThis - .getComputedStyle(element) - .getPropertyValue('transform'); - return new DOMMatrix(transform); -} - -function getMoleculesElement(rootRef) { - const nmriumViewer: any = (rootRef.getRootNode() as Document).querySelector( - `#nmrium-viewer`, - ); - - const floatingMoleculesGroup = document.createElement('g'); - - for (const element of nmriumViewer.querySelectorAll('.draggable-molecule')) { - const matrix = getMatrix(element); - const actionHeaderElement = element.querySelector( - '.float-molecule-actions', - ); - const molElement = element - .cloneNode(true) - .querySelector('svg[id^="molSVG"]'); - const group = document.createElement('g'); - group.append(molElement); - group.setAttribute( - 'transform', - `translate(${matrix.m41} ${ - matrix.m42 + actionHeaderElement.clientHeight - })`, - ); - floatingMoleculesGroup.append(group); - } - return floatingMoleculesGroup; -} - export { exportAsSVG, exportAsJSON,