From e177d56bae9fa33d6f6d0b7a403ad6e793c540cf Mon Sep 17 00:00:00 2001 From: hamed musallam Date: Wed, 16 Oct 2024 13:56:39 +0200 Subject: [PATCH] feat: control the margin top of the peaks label close #3112 feat: interactive positioning of peak labels --- package-lock.json | 44 ++-- package.json | 2 +- .../1d/peaks/PeakAnnotationsSpreadMode.tsx | 190 ++++++++++++++---- src/component/1d/peaks/Peaks.tsx | 31 ++- src/component/hooks/usePeaksLabelSettings.ts | 13 ++ .../settings-tabs/GeneralTabContent.tsx | 15 +- .../modal/setting/settingsValidation.ts | 4 + .../actions/changePeaksLabelPosition.ts | 15 ++ .../panelsPreferencesDefaultValues.ts | 2 +- .../reducer/preferences/preferencesReducer.ts | 15 +- .../workspaces/workspaceDefaultProperties.ts | 3 + 11 files changed, 263 insertions(+), 71 deletions(-) create mode 100644 src/component/hooks/usePeaksLabelSettings.ts create mode 100644 src/component/reducer/preferences/actions/changePeaksLabelPosition.ts diff --git a/package-lock.json b/package-lock.json index 8d1284bbe..153195436 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,7 +43,7 @@ "ml-tree-similarity": "^2.2.0", "multiplet-analysis": "^2.1.2", "nmr-correlation": "^2.3.3", - "nmr-load-save": "^1.1.1", + "nmr-load-save": "^1.2.0", "nmr-processing": "^12.12.3", "nmredata": "^0.9.11", "numeral": "^2.0.6", @@ -4998,9 +4998,9 @@ } }, "node_modules/brukerconverter": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/brukerconverter/-/brukerconverter-7.0.2.tgz", - "integrity": "sha512-RL0IXrJ6Fz07G3kXoXIX7Zp+8QU9oCEBGnD47A+QnmoJp/44hIZOoTI9dvhjjQFGk5ZQau+qrhnDKqR+du8X9w==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/brukerconverter/-/brukerconverter-7.0.3.tgz", + "integrity": "sha512-zxZWk6BO74hFRak9TUIQhRcZjkTdvn9EuCQgbbLT6uB3lxDMxy/wTVasubIOphwioKY8N075YdqHudVaq5XN2g==", "license": "MIT", "dependencies": { "cheminfo-types": "^1.7.3", @@ -10031,13 +10031,13 @@ } }, "node_modules/ml-spectra-processing": { - "version": "14.5.3", - "resolved": "https://registry.npmjs.org/ml-spectra-processing/-/ml-spectra-processing-14.5.3.tgz", - "integrity": "sha512-WYOnAOrCI5XKwonOtlR9oieFe/+ARBLocA+mZO89WLRCV7J/U/lFjxjOQv5/RXsoOF1udUUfxCZCjUMGfkhe5A==", + "version": "14.6.0", + "resolved": "https://registry.npmjs.org/ml-spectra-processing/-/ml-spectra-processing-14.6.0.tgz", + "integrity": "sha512-16DOpuM5s2ilD+lm7nDJosogvVmMSj9ohYIZDC8CTEtKeh2GOGQfNY8DDPkk2nVmMP46W0PuVb60ySpjdkF+0A==", "license": "MIT", "dependencies": { "binary-search": "^1.3.6", - "cheminfo-types": "^1.7.3", + "cheminfo-types": "^1.8.0", "fft.js": "^4.0.4", "is-any-array": "^2.0.1", "ml-matrix": "^6.11.1", @@ -10210,28 +10210,28 @@ } }, "node_modules/nmr-load-save": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/nmr-load-save/-/nmr-load-save-1.1.1.tgz", - "integrity": "sha512-I7lXVoY73N20C4mVjG21wiStNph3thAUoDMXc+8V+RkUwnh1XV69Ldwidu/MKtEuam2gBn1s5mlImoPvJyYrAQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/nmr-load-save/-/nmr-load-save-1.2.0.tgz", + "integrity": "sha512-CjfxjqV9U6DyAm/IlJ5ehaWaD9QJ3xjl2U0UZ2swlddjy/FDDz4Ym7IGDWNKpmqUhXGUck17KRSjAZRmq/ED1A==", "license": "MIT", "dependencies": { "@lukeed/uuid": "^2.0.1", "@types/lodash.merge": "^4.6.9", - "brukerconverter": "^7.0.0", - "cheminfo-types": "^1.7.3", + "brukerconverter": "^7.0.3", + "cheminfo-types": "^1.8.0", "convert-to-jcamp": "^5.4.11", - "filelist-utils": "^1.11.0", + "filelist-utils": "^1.11.2", "gyromagnetic-ratio": "^1.2.0", "is-any-array": "^2.0.1", "jcampconverter": "^9.6.4", "jeolconverter": "^1.0.3", "lodash.merge": "^4.6.2", - "ml-spectra-processing": "^14.5.1", + "ml-spectra-processing": "^14.6.0", "nmr-correlation": "^2.3.3", - "nmr-processing": "^13.0.0", - "nmredata": "^0.9.10", - "openchemlib": "^8.14.0", - "openchemlib-utils": "^6.0.1", + "nmr-processing": "^13.0.1", + "nmredata": "^0.9.11", + "openchemlib": "^8.15.0", + "openchemlib-utils": "^6.4.1", "sdf-parser": "^6.0.1", "varian-converter": "^1.0.0" } @@ -10252,9 +10252,9 @@ } }, "node_modules/nmr-load-save/node_modules/nmr-processing": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/nmr-processing/-/nmr-processing-13.0.0.tgz", - "integrity": "sha512-/fXvLlifGWs+qWVLHJQL0aGugbPtjNBK6ZoTQAGj8mbBe5pVLKYbc6SajGdcfn0Dl3jsr420ZP3cAgUbVohsiQ==", + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/nmr-processing/-/nmr-processing-13.0.1.tgz", + "integrity": "sha512-U1r4PjZzAqbrBqgBkn27jF87GoRrxRGT9zlXXVcmbf4dgcmKOCo3FGd+ZXR9VLgZVsL64BGkpM+H1EDjfglBqg==", "license": "MIT", "dependencies": { "@lukeed/uuid": "^2.0.1", diff --git a/package.json b/package.json index efd76b845..a07f3a54f 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "ml-tree-similarity": "^2.2.0", "multiplet-analysis": "^2.1.2", "nmr-correlation": "^2.3.3", - "nmr-load-save": "^1.1.1", + "nmr-load-save": "^1.2.0", "nmr-processing": "^12.12.3", "nmredata": "^0.9.11", "numeral": "^2.0.6", diff --git a/src/component/1d/peaks/PeakAnnotationsSpreadMode.tsx b/src/component/1d/peaks/PeakAnnotationsSpreadMode.tsx index 1bfa40e16..fda7f1a51 100644 --- a/src/component/1d/peaks/PeakAnnotationsSpreadMode.tsx +++ b/src/component/1d/peaks/PeakAnnotationsSpreadMode.tsx @@ -1,8 +1,18 @@ -import { memo } from 'react'; +import { memo, useState } from 'react'; +import { BsArrowsMove } from 'react-icons/bs'; +import { useMeasure } from 'react-use'; +import { useGlobal } from '../../context/GlobalContext'; +import { usePreferences } from '../../context/PreferencesContext'; +import { + ActionsButtonsPopover, + ActionsButtonsPopoverProps, +} from '../../elements/ActionsButtonsPopover'; +import useDraggable from '../../elements/draggable/useDraggable'; import { useHighlight } from '../../highlight'; +import { usePeaksLabelSettings } from '../../hooks/usePeaksLabelSettings'; +import { Margin } from '../../reducer/Reducer'; import { formatNumber } from '../../utility/formatNumber'; -import { getDecimalsCount } from '../utilities/getDecimalsCount'; import { resolve } from '../utilities/intersectionResolver'; import { PeakEditionListener } from './PeakEditionManager'; @@ -16,17 +26,79 @@ import { const notationWidth = 10; const notationMargin = 2; -function PeakAnnotationsTreeStyle(props: PeaksAnnotationsProps) { +function usePeaksPosition() { + const { viewerRef } = useGlobal(); + const { dispatch } = usePreferences(); + const { marginTop: originMarginTop } = usePeaksLabelSettings(); + const [isDragActive, setIsMoveActive] = useState(false); + const [marginTop, setMarginTop] = useState(originMarginTop); + + const { onPointerDown } = useDraggable({ + position: { x: 0, y: marginTop }, + onChange: (dragEvent) => { + const { action, position } = dragEvent; + const yOffset = Math.round(position.y); + switch (action) { + case 'start': { + setMarginTop(yOffset); + setIsMoveActive(true); + break; + } + case 'move': { + setMarginTop(yOffset); + + break; + } + case 'end': + dispatch({ + type: 'CHANGE_PEAKS_LABEL_POSITION', + payload: { + marginTop: yOffset, + }, + }); + setIsMoveActive(false); + break; + default: + break; + } + }, + parentElement: viewerRef, + }); + + return { marginTop, isDragActive, onPointerDown }; +} + +interface PeakAnnotationsSpreadModeProps + extends Omit { + height: number; + margin: Margin; +} + +function PeakAnnotationsSpreadMode(props: PeakAnnotationsSpreadModeProps) { const { peaks, peaksSource, spectrumColor, - xDomain, displayerKey, peakFormat, + margin, + height, } = props; + const [ref, boxSize] = useMeasure(); + const { marginTop, isDragActive, onPointerDown } = usePeaksPosition(); - const decimalsCount = getDecimalsCount(xDomain[1], peakFormat); + const actionsButtons: ActionsButtonsPopoverProps['buttons'] = [ + { + icon: , + onPointerDown: (event) => { + event.stopPropagation(); + onPointerDown(event); + }, + intent: 'none', + title: 'Move peaks label vertically', + style: { cursor: 'move' }, + }, + ]; const mapPeaks = resolve(peaks, { key: 'scaleX', @@ -35,42 +107,80 @@ function PeakAnnotationsTreeStyle(props: PeaksAnnotationsProps) { groupMargin: 10, }); + const boxHeight = Math.round(boxSize.height); + + let y = boxHeight + marginTop; + + if (y + boxHeight > height - margin.bottom) { + y = height - margin.bottom - boxHeight; + } + + if (marginTop < 0) { + y = boxHeight; + } return ( - - - {mapPeaks.map((group) => { - return ( - - {group.group.map((item, index) => { - const { id, x: value, scaleX, parentKeys } = item; - const startX = index * (notationWidth + notationMargin); - const x = scaleX - group.meta.groupStartX; - return ( - - ); - })} - - ); - })} + + + 0 ? 'visible' : 'hidden' }} + > + + + {mapPeaks.map((group) => { + return ( + + {group.group.map((item, index) => { + const { id, x: value, scaleX, parentKeys } = item; + const startX = index * (notationWidth + notationMargin); + const x = scaleX - group.meta.groupStartX; + return ( + + ); + })} + + ); + })} + + - + ); } @@ -127,4 +237,4 @@ function PeakAnnotation(props: PeakAnnotationProps) { ); } -export default memo(PeakAnnotationsTreeStyle); +export default memo(PeakAnnotationsSpreadMode); diff --git a/src/component/1d/peaks/Peaks.tsx b/src/component/1d/peaks/Peaks.tsx index 7e38f29e1..34acb3acd 100644 --- a/src/component/1d/peaks/Peaks.tsx +++ b/src/component/1d/peaks/Peaks.tsx @@ -7,6 +7,7 @@ import { useActiveSpectrumPeaksViewState } from '../../hooks/useActiveSpectrumPe import { useActiveSpectrumRangesViewState } from '../../hooks/useActiveSpectrumRangesViewState'; import { usePanelPreferences } from '../../hooks/usePanelPreferences'; import useSpectrum from '../../hooks/useSpectrum'; +import { Margin } from '../../reducer/Reducer'; import { useScaleX } from '../utilities/scale'; import PeakAnnotations from './PeakAnnotations'; @@ -104,11 +105,21 @@ function useMapPeaks(spectrum: Spectrum1D, filterBy: FilterPeaksBy) { interface InnerPeaksProps extends BasePeaksProps { spectrum: Spectrum1D; mode: PeaksMode; + height: number; + margin: Margin; } function InnerPeaks(props: InnerPeaksProps) { - const { peaksSource, spectrum, mode, displayerKey, xDomain, peakFormat } = - props; + const { + peaksSource, + spectrum, + mode, + displayerKey, + xDomain, + height, + margin, + peakFormat, + } = props; const peaks = useMapPeaks( spectrum, @@ -124,7 +135,8 @@ function InnerPeaks(props: InnerPeaksProps) { spectrumColor={spectrum.display.color} peakFormat={peakFormat} displayerKey={displayerKey} - xDomain={xDomain} + height={height} + margin={margin} /> ); } @@ -154,6 +166,8 @@ export default function Peaks(props) { }, displayerKey, xDomain, + height, + margin, } = useChartData(); const spectrum = useSpectrum(emptyData) as Spectrum1D; const peaksViewState = useActiveSpectrumPeaksViewState(); @@ -197,7 +211,16 @@ export default function Peaks(props) { return ( ); } diff --git a/src/component/hooks/usePeaksLabelSettings.ts b/src/component/hooks/usePeaksLabelSettings.ts new file mode 100644 index 000000000..c109d3ff1 --- /dev/null +++ b/src/component/hooks/usePeaksLabelSettings.ts @@ -0,0 +1,13 @@ +import { PeaksLabel } from 'nmr-load-save'; + +import { usePreferences } from '../context/PreferencesContext'; + +const peaksLabelDefaultValues: PeaksLabel = { + marginTop: 0, +}; + +export function usePeaksLabelSettings() { + const { current } = usePreferences(); + + return current?.peaksLabel || peaksLabelDefaultValues; +} diff --git a/src/component/modal/setting/settings-tabs/GeneralTabContent.tsx b/src/component/modal/setting/settings-tabs/GeneralTabContent.tsx index b8849cc68..860fab354 100644 --- a/src/component/modal/setting/settings-tabs/GeneralTabContent.tsx +++ b/src/component/modal/setting/settings-tabs/GeneralTabContent.tsx @@ -1,4 +1,4 @@ -import { Checkbox } from '@blueprintjs/core'; +import { Checkbox, Tag } from '@blueprintjs/core'; import { Controller, useFormContext } from 'react-hook-form'; import { LOGGER_LEVELS } from '../../../context/LoggerContext'; @@ -131,6 +131,19 @@ function GeneralTabContent() { /> + + + ); } diff --git a/src/component/modal/setting/settingsValidation.ts b/src/component/modal/setting/settingsValidation.ts index 4049d5e0a..7bbe54fa2 100644 --- a/src/component/modal/setting/settingsValidation.ts +++ b/src/component/modal/setting/settingsValidation.ts @@ -75,6 +75,9 @@ const exportValidation = object().shape({ svg: exportOptionValidationSchema, clipboard: exportOptionValidationSchema, }); +const peaksLabelValidation = object().shape({ + marginTop: number().integer().required().min(0), +}); export const validation: any = object().shape({ nuclei: nucleiValidation, @@ -84,4 +87,5 @@ export const validation: any = object().shape({ spectraColors: spectraColorsSchemaValidation, externalAPIs: externalAPIsValidation, export: exportValidation, + peaksLabel: peaksLabelValidation, }); diff --git a/src/component/reducer/preferences/actions/changePeaksLabelPosition.ts b/src/component/reducer/preferences/actions/changePeaksLabelPosition.ts new file mode 100644 index 000000000..8776e26ea --- /dev/null +++ b/src/component/reducer/preferences/actions/changePeaksLabelPosition.ts @@ -0,0 +1,15 @@ +import { Draft } from 'immer'; + +import { + ChangePeaksLabelPositionAction, + PreferencesState, +} from '../preferencesReducer'; +import { getActiveWorkspace } from '../utilities/getActiveWorkspace'; + +export function changePeaksLabelPosition( + draft: Draft, + action: ChangePeaksLabelPositionAction, +) { + const currentWorkspacePreferences = getActiveWorkspace(draft); + currentWorkspacePreferences.peaksLabel.marginTop = action.payload.marginTop; +} diff --git a/src/component/reducer/preferences/panelsPreferencesDefaultValues.ts b/src/component/reducer/preferences/panelsPreferencesDefaultValues.ts index 4142d8e98..493d73620 100644 --- a/src/component/reducer/preferences/panelsPreferencesDefaultValues.ts +++ b/src/component/reducer/preferences/panelsPreferencesDefaultValues.ts @@ -140,11 +140,11 @@ const getPeaksDefaultValues = ( intensity: { show: true, format: '0.00' }, fwhm: { show: true, format: '0.00000' }, mu: { show: false, format: '0.00000' }, + gamma: { show: false, format: '0.000' }, showDeleteAction: true, showEditPeakShapeAction: true, showKind: true, }; - return getPreferences(preferences, nucleus); }; diff --git a/src/component/reducer/preferences/preferencesReducer.ts b/src/component/reducer/preferences/preferencesReducer.ts index 0390bf9d5..a7efdc09d 100644 --- a/src/component/reducer/preferences/preferencesReducer.ts +++ b/src/component/reducer/preferences/preferencesReducer.ts @@ -25,7 +25,9 @@ import { setSpectraAnalysisPanelsPreferences, } from './actions/analyzeSpectra'; import { applyGeneralPreferences } from './actions/applyGeneralPreferences'; +import { changeExportSettings } from './actions/changeExportSettings'; import { changeInformationBlockPosition } from './actions/changeInformationBlockPosition'; +import { changePeaksLabelPosition } from './actions/changePeaksLabelPosition'; import { changePrintPageSettings } from './actions/changePrintPageSettings'; import { initPreferences } from './actions/initPreferences'; import { @@ -45,7 +47,6 @@ import { setVerticalSplitterPosition } from './actions/setVerticalSplitterPositi import { setWorkspace } from './actions/setWorkspace'; import { toggleInformationBlock } from './actions/toggleInformationBlock'; import { mapWorkspaces } from './utilities/mapWorkspaces'; -import { changeExportSettings } from './actions/changeExportSettings'; const LOCAL_STORAGE_SETTINGS_KEY = 'nmr-general-settings'; @@ -121,6 +122,7 @@ export type ChangeInformationBlockPosition = ActionType< coordination: { x: number; y: number }; } >; + export type ToggleInformationBlock = ActionType< 'TOGGLE_INFORMATION_BLOCK', { @@ -138,6 +140,12 @@ export type ChangeExportSettingsAction = ActionType< options: ExportSettings; } >; +export type ChangePeaksLabelPositionAction = ActionType< + 'CHANGE_PEAKS_LABEL_POSITION', + { + marginTop: number; + } +>; type PreferencesActions = | InitPreferencesAction @@ -156,7 +164,8 @@ type PreferencesActions = | ChangeInformationBlockPosition | ToggleInformationBlock | ChangePrintPageSettingsAction - | ChangeExportSettingsAction; + | ChangeExportSettingsAction + | ChangePeaksLabelPositionAction; export const WORKSPACES: Array<{ key: NMRiumWorkspace; @@ -311,6 +320,8 @@ function innerPreferencesReducer( return changePrintPageSettings(draft, action); case 'CHANGE_EXPORT_SETTINGS': return changeExportSettings(draft, action); + case 'CHANGE_PEAKS_LABEL_POSITION': + return changePeaksLabelPosition(draft, action); default: return draft; diff --git a/src/component/workspaces/workspaceDefaultProperties.ts b/src/component/workspaces/workspaceDefaultProperties.ts index f065587d5..a5e82e2df 100644 --- a/src/component/workspaces/workspaceDefaultProperties.ts +++ b/src/component/workspaces/workspaceDefaultProperties.ts @@ -261,4 +261,7 @@ export const workspaceDefaultProperties: Required = { useDefaultSettings: false, }, }, + peaksLabel: { + marginTop: 0, + }, };