diff --git a/package-lock.json b/package-lock.json index 2fdcb0f9a..90d182933 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,7 +39,7 @@ "ml-stat": "^1.3.3", "multiplet-analysis": "^2.1.2", "nmr-correlation": "^2.3.3", - "nmr-load-save": "^0.23.11", + "nmr-load-save": "^0.24.0", "nmr-processing": "^11.7.0", "nmredata": "^0.9.9", "numeral": "^2.0.6", @@ -9827,9 +9827,9 @@ } }, "node_modules/nmr-load-save": { - "version": "0.23.11", - "resolved": "https://registry.npmjs.org/nmr-load-save/-/nmr-load-save-0.23.11.tgz", - "integrity": "sha512-tvZDBfiZYy7xBUdx7MVVoQ3M3DzhsEKZCgkjkVk0tGVNcy1q9C1WYT48nSJXhgTMNslfH8GH9f2ojSTFnHoUDQ==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/nmr-load-save/-/nmr-load-save-0.24.0.tgz", + "integrity": "sha512-08Kouo4DjkJj6CPr4fgXO82oLZzexTqRqZOCtP2TDor+N7wae7yo8MtwotrPLY4VQDHoiRIy6A96KEuWviimZQ==", "dependencies": { "@lukeed/uuid": "^2.0.1", "@types/lodash.merge": "^4.6.7", @@ -14122,4 +14122,4 @@ } } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index e20211f37..7d6cbd9a8 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "ml-stat": "^1.3.3", "multiplet-analysis": "^2.1.2", "nmr-correlation": "^2.3.3", - "nmr-load-save": "^0.23.11", + "nmr-load-save": "^0.24.0", "nmr-processing": "^11.7.0", "nmredata": "^0.9.9", "numeral": "^2.0.6", @@ -148,4 +148,4 @@ "vite": "^5.0.10", "vitest": "^1.0.4" } -} +} \ No newline at end of file diff --git a/src/component/1d/integral/Integration.tsx b/src/component/1d/integral/Integration.tsx index e4ee53496..c7f8663aa 100644 --- a/src/component/1d/integral/Integration.tsx +++ b/src/component/1d/integral/Integration.tsx @@ -15,6 +15,7 @@ export function Integration({ integral, nucleus, max }: IntegralProps) { const { x, y } = integral; const { scaleRatio } = useActiveSpectrumIntegralsViewState(); const path = useIntegralPath({ x, y, max, scaleRatio }); + const { showIntegralsValues } = useActiveSpectrumIntegralsViewState(); const integralPreferences = usePanelPreferences('integrals', nucleus); return ( @@ -27,10 +28,12 @@ export function Integration({ integral, nucleus, max }: IntegralProps) { d={path} /> - + {showIntegralsValues && ( + + )} ); } diff --git a/src/component/1d/ranges/Range.tsx b/src/component/1d/ranges/Range.tsx index 40e44b59c..381db940a 100644 --- a/src/component/1d/ranges/Range.tsx +++ b/src/component/1d/ranges/Range.tsx @@ -12,6 +12,7 @@ import { filterForIDsWithAssignment } from '../../assignment/utilities/filterFor import { useDispatch } from '../../context/DispatchContext'; import { ResizerWithScale } from '../../elements/ResizerWithScale'; import { HighlightEventSource, useHighlight } from '../../highlight'; +import { useActiveSpectrumRangesViewState } from '../../hooks/useActiveSpectrumRangesViewState'; import { useResizerStatus } from '../../hooks/useResizerStatus'; import { options } from '../../toolbar/ToolTypes'; import { IntegralIndicator } from '../integral/IntegralIndicator'; @@ -60,6 +61,7 @@ function Range({ const scaleX = useScaleX(); const dispatch = useDispatch(); + const { showIntegralsValues } = useActiveSpectrumRangesViewState(); const isBlockedByEditing = selectedTool && selectedTool === options.editRange.id; @@ -145,12 +147,14 @@ function Range({ data-no-export="true" /> - + {showIntegralsValues && ( + + )} ); }} diff --git a/src/component/elements/formik/FormikColorInput.tsx b/src/component/elements/formik/FormikColorInput.tsx deleted file mode 100644 index 9b0aad32e..000000000 --- a/src/component/elements/formik/FormikColorInput.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { useFormikContext } from 'formik'; -import lodashGet from 'lodash/get'; -import { useCallback, memo } from 'react'; - -import ColorInput, { ColorInputProps } from '../ColorInput'; - -interface FormikColorPickerProps extends ColorInputProps { - name: string; -} - -function FormikColorPicker(props: FormikColorPickerProps) { - const { onColorChange = () => null, name, ...colorPickerProps } = props; - - const { values, setFieldValue } = useFormikContext(); - - const colorChangeHandler = useCallback( - (color) => { - onColorChange(color); - void setFieldValue(name, color.hex); - }, - [name, onColorChange, setFieldValue], - ); - - return ( - - ); -} - -export default memo(FormikColorPicker); diff --git a/src/component/elements/formik/FormikColorPickerDropdown.tsx b/src/component/elements/formik/FormikColorPickerDropdown.tsx new file mode 100644 index 000000000..4030e4818 --- /dev/null +++ b/src/component/elements/formik/FormikColorPickerDropdown.tsx @@ -0,0 +1,28 @@ +import { useFormikContext } from 'formik'; +import lodashGet from 'lodash/get'; +import { ColorPickerDropdown, ColorPickerProps } from 'react-science/ui'; + +interface FormikColorPickerDropdownProps extends ColorPickerProps { + name: string; +} + +function FormikColorPickerDropdown(props: FormikColorPickerDropdownProps) { + const { onChangeComplete = () => null, name, ...colorPickerProps } = props; + + const { values, setFieldValue } = useFormikContext(); + + function colorChangeHandler(color) { + onChangeComplete(color); + void setFieldValue(name, color.hex); + } + + return ( + + ); +} + +export default FormikColorPickerDropdown; diff --git a/src/component/hooks/useActiveSpectrumIntegralsViewState.ts b/src/component/hooks/useActiveSpectrumIntegralsViewState.ts index 9d9246827..2aa38a8ea 100644 --- a/src/component/hooks/useActiveSpectrumIntegralsViewState.ts +++ b/src/component/hooks/useActiveSpectrumIntegralsViewState.ts @@ -6,6 +6,7 @@ import { useActiveSpectrum } from './useActiveSpectrum'; export const defaultIntegralsViewState: IntegralsViewState = { scaleRatio: 1, + showIntegralsValues: true, }; export function useActiveSpectrumIntegralsViewState() { diff --git a/src/component/hooks/useActiveSpectrumRangesViewState.ts b/src/component/hooks/useActiveSpectrumRangesViewState.ts index 7c408cad5..8637e6a07 100644 --- a/src/component/hooks/useActiveSpectrumRangesViewState.ts +++ b/src/component/hooks/useActiveSpectrumRangesViewState.ts @@ -8,6 +8,7 @@ export const defaultRangesViewState: RangesViewState = { showPeaks: false, showMultiplicityTrees: false, showIntegrals: false, + showIntegralsValues: true, showJGraph: false, displayingMode: 'spread', integralsScaleRatio: 1, diff --git a/src/component/loader/DropZone.tsx b/src/component/loader/DropZone.tsx index 72bf126bd..c535bbb38 100644 --- a/src/component/loader/DropZone.tsx +++ b/src/component/loader/DropZone.tsx @@ -54,7 +54,8 @@ const containerStyle = css` function DropZone(props) { const { width, height } = useChartData(); const dispatch = useDispatch(); - const { dispatch: dispatchPreferences, current } = usePreferences(); + const { dispatch: dispatchPreferences, current: workspacePreferences } = + usePreferences(); const preferences = usePreferences(); const isToolEnabled = useCheckToolsVisibility(); const openImportMetaInformationModal = useMetaInformationImportationModal(); @@ -76,12 +77,13 @@ function DropZone(props) { parseMetaFileResult = await parseMetaFile(metaFile); } const { nmrLoaders: sourceSelector } = preferences.current; + const { onLoadProcessing, spectraColors } = workspacePreferences; const { nmriumState, containsNmrium } = await readDropFiles( fileCollection, { sourceSelector, logger: logger.child({ context: 'nmr-processing' }), - onLoadProcessing: current.onLoadProcessing, + onLoadProcessing, experimentalFeatures, }, ); @@ -101,6 +103,7 @@ function DropZone(props) { nmriumState, containsNmrium, parseMetaFileResult, + spectraColors, }, }); } diff --git a/src/component/modal/setting/GeneralSettings.tsx b/src/component/modal/setting/GeneralSettings.tsx index 7489f8997..8bfb34b3c 100644 --- a/src/component/modal/setting/GeneralSettings.tsx +++ b/src/component/modal/setting/GeneralSettings.tsx @@ -33,6 +33,7 @@ import GeneralTabContent from './settings-tabs/GeneralTabContent'; import ImportationFiltersTabContent from './settings-tabs/ImportationFiltersTabContent'; import InfoBlockTabContent from './settings-tabs/InfoBlockTabContent'; import OnLoadProcessingTabContent from './settings-tabs/OnLoadProcessingTabContent'; +import SpectraColorsTabContent from './settings-tabs/SpectraColorsTabContent'; import ToolsTabContent from './settings-tabs/ToolsTabContent'; import { validation } from './settingsValidation'; @@ -422,6 +423,11 @@ function GeneralSettingsModal({ height }: GeneralSettingsModalProps) { + + + + + diff --git a/src/component/modal/setting/settings-tabs/SpectraColorsTabContent.tsx b/src/component/modal/setting/settings-tabs/SpectraColorsTabContent.tsx new file mode 100644 index 000000000..5197c4900 --- /dev/null +++ b/src/component/modal/setting/settings-tabs/SpectraColorsTabContent.tsx @@ -0,0 +1,258 @@ +import { useFormikContext } from 'formik'; +import { SpectraColors, Workspace } from 'nmr-load-save'; +import { CSSProperties, useCallback, useMemo } from 'react'; +import { FaPlus, FaTimes } from 'react-icons/fa'; + +import { useChartData } from '../../../context/ChartContext'; +import Button from '../../../elements/Button'; +import { GroupPane } from '../../../elements/GroupPane'; +import ReactTable, { Column } from '../../../elements/ReactTable/ReactTable'; +import FormikColorPickerDropdown from '../../../elements/formik/FormikColorPickerDropdown'; +import FormikInput from '../../../elements/formik/FormikInput'; +import { convertPathArrayToString } from '../../../utility/convertPathArrayToString'; +import { getSpectraObjectPaths } from '../../../utility/getSpectraObjectPaths'; + +const styles: Record<'input' | 'column', CSSProperties> = { + input: { + width: '100%', + }, + column: { + padding: '2px', + }, +}; + +const colorInputStyle: CSSProperties = { + padding: 0, + minWidth: '80px', + width: '80px', + ...styles.column, +}; + +type SpectraColorsKeys = keyof SpectraColors; + +function getObjectKey(key: SpectraColorsKeys) { + return `spectraColors.${key}`; +} + +function SpectraColorsTabContent() { + const { values, setFieldValue } = useFormikContext(); + const { data } = useChartData(); + const { datalist, paths } = useMemo( + () => getSpectraObjectPaths(data), + [data], + ); + const { oneDimension = [], twoDimensions = [] } = values?.spectraColors || {}; + + const addHandler = useCallback( + (data: readonly any[], index: number, key: SpectraColorsKeys) => { + let columns: any[] = []; + const emptyField = + key === 'oneDimension' + ? { + value: null, + jpath: ['info', 'experiment'], + color: 'red', + } + : { + value: null, + jpath: ['info', 'experiment'], + positiveColor: 'red', + negativeColor: 'blue', + }; + if (data && Array.isArray(data)) { + columns = [...data.slice(0, index), emptyField, ...data.slice(index)]; + } else { + columns.push(emptyField); + } + void setFieldValue(getObjectKey(key), columns); + }, + [setFieldValue], + ); + + const deleteHandler = useCallback( + (data, index: number, key: SpectraColorsKeys) => { + const _fields = data.filter((_, columnIndex) => columnIndex !== index); + void setFieldValue(getObjectKey(key), _fields); + }, + [setFieldValue], + ); + + return ( + + + + + ); +} + +interface SpectraColorsProps { + data: any; + onAdd: (data: any, index: number, key: SpectraColorsKeys) => void; + onDelete: (data: any, index: number, key: SpectraColorsKeys) => void; + datalist: string[]; + paths: Record; + baseObjectPath: SpectraColorsKeys; + groupLabel: string; +} +function SpectraColorsFields(props: SpectraColorsProps) { + const { onAdd, onDelete, datalist, paths, data, baseObjectPath, groupLabel } = + props; + + const COLUMNS: Array> = useMemo(() => { + const objectPath = getObjectKey(baseObjectPath); + const baseColumns = [ + { + Header: '#', + style: { width: '25px', ...styles.column }, + accessor: (_, index) => index + 1, + }, + { + Header: 'Field', + style: { padding: 0, ...styles.column }, + Cell: ({ row }) => { + return ( + { + return paths?.[key.toString().trim()] || key; + }} + mapValue={(paths) => convertPathArrayToString(paths)} + datalist={datalist} + /> + ); + }, + }, + { + Header: 'Value', + style: { padding: 0, ...styles.column }, + Cell: ({ row }) => { + return ( + + ); + }, + }, + ]; + + const operationsField = { + Header: '', + style: { width: '70px', ...styles.column }, + id: 'operation-button', + Cell: ({ data, row }) => { + const record: any = row.original; + return ( + + onAdd(data, row.index + 1, baseObjectPath)} + > + + + {!record?.name && ( + onDelete(data, row.index, baseObjectPath)} + > + + + )} + + ); + }, + }; + + if (baseObjectPath === 'oneDimension') { + const colorField = { + Header: 'Color', + style: colorInputStyle, + Cell: ({ row }) => ( + + ), + }; + + return [...baseColumns, colorField, operationsField]; + } + + const colorFields = [ + { + Header: 'Positive color', + style: colorInputStyle, + Cell: ({ row }) => ( + + ), + }, + { + Header: 'Negative color', + style: colorInputStyle, + Cell: ({ row }) => ( + + ), + }, + ]; + + return [...baseColumns, ...colorFields, operationsField]; + }, [baseObjectPath, datalist, onAdd, onDelete, paths]); + + return ( + { + return ( + onAdd(data, 0, baseObjectPath)} + /> + ); + }} + > + + + ); +} + +function FieldsBlockHeader({ onAdd, text }) { + return ( + + {text} + + + Add custom color + + + ); +} + +export default SpectraColorsTabContent; diff --git a/src/component/modal/setting/settingsValidation.ts b/src/component/modal/setting/settingsValidation.ts index 4a59d05c7..3ccfe8d45 100644 --- a/src/component/modal/setting/settingsValidation.ts +++ b/src/component/modal/setting/settingsValidation.ts @@ -81,7 +81,21 @@ const infoBlockValidation = object({ fields: array().of( object({ label: string().required(), - jpath: array().of(string()).min(1), + jpath: array().of(string()).required().min(1), + }), + ), +}); +const spectraColorsSchemaValidation = object({ + oneDimension: array().of( + object({ + value: string().required(), + jpath: array().of(string()).required().min(1), + }), + ), + twoDimensions: array().of( + object({ + value: string().required(), + jpath: array().of(string()).required().min(1), }), ), }); @@ -95,5 +109,6 @@ export const validation: any = lazy((obj: Workspace) => databases: databasesValidation, infoBlock: infoBlockValidation, general: generalValidation, + spectraColors: spectraColorsSchemaValidation, }), ); diff --git a/src/component/panels/IntegralsPanel/IntegralPanel.tsx b/src/component/panels/IntegralsPanel/IntegralPanel.tsx index b78981870..044c1fa65 100644 --- a/src/component/panels/IntegralsPanel/IntegralPanel.tsx +++ b/src/component/panels/IntegralsPanel/IntegralPanel.tsx @@ -1,6 +1,6 @@ /** @jsxImportSource @emotion/react */ import { css } from '@emotion/react'; -import { SvgNmrSum } from 'cheminfo-font'; +import { SvgNmrIntegrate, SvgNmrSum } from 'cheminfo-font'; import lodashGet from 'lodash/get'; import { Spectrum1D } from 'nmr-load-save'; import { Info1D, Integrals } from 'nmr-processing'; @@ -22,6 +22,7 @@ import PreferencesHeader from '../header/PreferencesHeader'; import IntegralTable from './IntegralTable'; import IntegralsPreferences from './IntegralsPreferences'; +import { useActiveSpectrumIntegralsViewState } from '../../hooks/useActiveSpectrumIntegralsViewState'; const style = css` .sum-button { @@ -31,6 +32,10 @@ const style = css` align-items: center; justify-content: center; } + + button { + margin-right: 2px; + } `; export interface IntegralPanelInnerProps { @@ -49,10 +54,19 @@ function IntegralPanelInner({ const [filterIsActive, setFilterIsActive] = useState(false); const dispatch = useDispatch(); + const { showIntegralsValues } = useActiveSpectrumIntegralsViewState(); + const modal = useModal(); const [isFlipped, setFlipStatus] = useState(false); const settingRef = useRef(); + function handleShowIntegralsValues() { + dispatch({ + type: 'TOGGLE_INTEGRALS_VIEW_PROPERTY', + payload: { key: 'showIntegralsValues' }, + }); + } + const yesHandler = useCallback(() => { dispatch({ type: 'DELETE_INTEGRAL', payload: {} }); }, [dispatch]); @@ -180,6 +194,20 @@ function IntegralPanelInner({ > + + + )} {isFlipped && ( diff --git a/src/component/panels/IntegralsPanel/IntegralsPreferences.tsx b/src/component/panels/IntegralsPanel/IntegralsPreferences.tsx index ed2beae5b..d042c33cd 100644 --- a/src/component/panels/IntegralsPanel/IntegralsPreferences.tsx +++ b/src/component/panels/IntegralsPanel/IntegralsPreferences.tsx @@ -10,7 +10,7 @@ import { import { usePreferences } from '../../context/PreferencesContext'; import Label from '../../elements/Label'; -import FormikColorInput from '../../elements/formik/FormikColorInput'; +import FormikColorPickerDropdown from '../../elements/formik/FormikColorPickerDropdown'; import { formatFieldLabelStyle } from '../../elements/formik/FormikColumnFormatField'; import FormikInput from '../../elements/formik/FormikInput'; import useNucleus from '../../hooks/useNucleus'; @@ -109,7 +109,7 @@ function IntegralsPreferences(props, ref) { - + diff --git a/src/component/panels/RangesPanel/RangesHeader.tsx b/src/component/panels/RangesPanel/RangesHeader.tsx index c995c946b..0c8a4b0ad 100644 --- a/src/component/panels/RangesPanel/RangesHeader.tsx +++ b/src/component/panels/RangesPanel/RangesHeader.tsx @@ -66,6 +66,7 @@ function RangesHeader({ showMultiplicityTrees, showJGraph, showIntegrals, + showIntegralsValues, showPeaks, displayingMode, } = useActiveSpectrumRangesViewState(); @@ -111,6 +112,12 @@ function RangesHeader({ }); } + function handleShowIntegralsValues() { + dispatch({ + type: 'TOGGLE_RANGES_VIEW_PROPERTY', + payload: { key: 'showIntegralsValues' }, + }); + } function handleShowIntegrals() { dispatch({ type: 'TOGGLE_RANGES_VIEW_PROPERTY', @@ -258,6 +265,21 @@ function RangesHeader({ style={{ pointerEvents: 'none', fontSize: '12px' }} /> + + + - + , action: Action) { ); case 'CUT_INTEGRAL': return IntegralsActions.handleCutIntegral(draft, action); + case 'TOGGLE_INTEGRALS_VIEW_PROPERTY': + return IntegralsActions.handleToggleIntegralsViewProperty( + draft, + action, + ); case 'SET_X_DOMAIN': return DomainActions.handleSetXDomain(draft, action); diff --git a/src/component/reducer/actions/DatabaseActions.ts b/src/component/reducer/actions/DatabaseActions.ts index 820536d42..0bb5408a7 100644 --- a/src/component/reducer/actions/DatabaseActions.ts +++ b/src/component/reducer/actions/DatabaseActions.ts @@ -40,7 +40,7 @@ function handleResurrectSpectrumFromJcamp( }, display: { ...spectrum.display, - ...get1DColor(spectrum.display, draft.usedColors), + ...get1DColor(spectrum.display, { usedColors: draft.usedColors }), }, }; diff --git a/src/component/reducer/actions/IntegralsActions.ts b/src/component/reducer/actions/IntegralsActions.ts index 8c77d2370..05c8f0288 100644 --- a/src/component/reducer/actions/IntegralsActions.ts +++ b/src/component/reducer/actions/IntegralsActions.ts @@ -1,7 +1,7 @@ import { v4 } from '@lukeed/uuid'; import { Draft, original } from 'immer'; import { xyIntegration } from 'ml-spectra-processing'; -import { Spectrum1D } from 'nmr-load-save'; +import { IntegralsViewState, Spectrum1D } from 'nmr-load-save'; import { Integral } from 'nmr-processing'; import { @@ -20,6 +20,8 @@ import { getActiveSpectrum } from '../helper/getActiveSpectrum'; import getRange from '../helper/getRange'; import { getSpectrum } from '../helper/getSpectrum'; import { ActionType } from '../types/ActionType'; +import { setIntegralsViewProperty } from '../helper/setIntegralsViewProperty'; +import { FilterType } from '../../utility/filterType'; type ChangeIntegralSumAction = ActionType< 'CHANGE_INTEGRAL_SUM', @@ -40,6 +42,13 @@ type ChangeIntegralRelativeValueAction = ActionType< >; type CutIntegralAction = ActionType<'CUT_INTEGRAL', { cutValue: number }>; +type ToggleIntegralsViewAction = ActionType< + 'TOGGLE_INTEGRALS_VIEW_PROPERTY', + { + key: keyof FilterType; + } +>; + export type IntegralsActions = | ChangeIntegralSumAction | AddIntegralAction @@ -47,6 +56,7 @@ export type IntegralsActions = | ChangeIntegralAction | ChangeIntegralRelativeValueAction | CutIntegralAction + | ToggleIntegralsViewAction | ActionType<'CHANGE_INTEGRALS_SUM_FLAG'>; function handleChangeIntegralSum( @@ -219,6 +229,22 @@ function handleCutIntegral(draft: Draft, action: CutIntegralAction) { updateIntegralsRelativeValues(spectrum); } +function toggleIntegralsViewProperty( + draft: Draft, + key: keyof FilterType, +) { + setIntegralsViewProperty(draft, key, (flag) => !flag); +} + +//action +function handleToggleIntegralsViewProperty( + draft: Draft, + action: ToggleIntegralsViewAction, +) { + const { key } = action.payload; + toggleIntegralsViewProperty(draft, key); +} + export { handleCutIntegral, handleChangeIntegralSum, @@ -227,4 +253,5 @@ export { handleChangeIntegral, handleChangeIntegralsRelativeValue, handleChangeIntegralsSumFlag, + handleToggleIntegralsViewProperty, }; diff --git a/src/component/reducer/actions/LoadActions.ts b/src/component/reducer/actions/LoadActions.ts index 7eaa6c9ae..c6e70476b 100644 --- a/src/component/reducer/actions/LoadActions.ts +++ b/src/component/reducer/actions/LoadActions.ts @@ -2,7 +2,7 @@ import { Draft } from 'immer'; import lodashMerge from 'lodash/merge'; import lodashMergeWith from 'lodash/mergeWith'; import { buildCorrelationData, CorrelationData } from 'nmr-correlation'; -import { Spectrum, ViewState, NmriumState } from 'nmr-load-save'; +import { Spectrum, ViewState, NmriumState, SpectraColors } from 'nmr-load-save'; import { ParseResult } from 'papaparse'; import { initiateDatum1D } from '../../../data/data1d/Spectrum1D'; @@ -28,6 +28,7 @@ interface InputProps extends InitiateProps { usedColors?: UsedColors; parseMetaFileResult?: ParseResult | null; resetSourceObject?: boolean; + spectraColors?: SpectraColors; } type SetIsLoadingAction = ActionType< @@ -84,6 +85,7 @@ function setData(draft: Draft, input: InputProps) { const { nmriumState: { data, view }, parseMetaFileResult = null, + spectraColors = { oneDimension: [], twoDimensions: [] }, } = input || { nmriumState: { data: { spectra: [], molecules: [], correlations: {} } }, multipleAnalysis: {}, @@ -119,6 +121,7 @@ function setData(draft: Draft, input: InputProps) { initSpectra(spectra, { usedColors: draft.usedColors, molecules: draft.molecules, + spectraColors, }), ); setCorrelation(draft, correlations); @@ -138,16 +141,28 @@ function initSpectra( options: { usedColors: UsedColors; molecules: StateMoleculeExtended[]; + spectraColors: SpectraColors; }, ) { const spectra: any = []; - const { usedColors, molecules } = options; + const { usedColors, molecules, spectraColors } = options; for (const spectrum of inputSpectra) { const { info } = spectrum; if (info.dimension === 1) { - spectra.push(initiateDatum1D(spectrum, { usedColors, molecules })); + spectra.push( + initiateDatum1D(spectrum, { + usedColors, + molecules, + colors: spectraColors.oneDimension, + }), + ); } else if (info.dimension === 2) { - spectra.push(initiateDatum2D({ ...spectrum }, { usedColors })); + spectra.push( + initiateDatum2D( + { ...spectrum }, + { usedColors, colors: spectraColors.twoDimensions }, + ), + ); } } return spectra; diff --git a/src/component/reducer/actions/SpectrumsActions.ts b/src/component/reducer/actions/SpectrumsActions.ts index 769872242..d58bb7467 100644 --- a/src/component/reducer/actions/SpectrumsActions.ts +++ b/src/component/reducer/actions/SpectrumsActions.ts @@ -597,17 +597,18 @@ function handleRecolorSpectraBasedOnDistinctValue( } } else { //reset spectra colors - const usedColor = { '1d': [], '2d': [] }; - + const usedColors = { '1d': [], '2d': [] }; for (const spectrum of spectra) { if (isSpectrum1D(spectrum)) { - spectrum.display.color = get1DColor(spectrum, usedColor, true).color; + spectrum.display.color = get1DColor(spectrum, { + usedColors, + regenerate: true, + }).color; } else { - const { positiveColor, negativeColor } = get2DColor( - spectrum, - usedColor, - true, - ); + const { positiveColor, negativeColor } = get2DColor(spectrum, { + usedColors, + regenerate: true, + }); spectrum.display.positiveColor = positiveColor; spectrum.display.negativeColor = negativeColor; } diff --git a/src/component/workspaces/workspaceDefaultProperties.ts b/src/component/workspaces/workspaceDefaultProperties.ts index 168fae145..37b8472fd 100644 --- a/src/component/workspaces/workspaceDefaultProperties.ts +++ b/src/component/workspaces/workspaceDefaultProperties.ts @@ -197,4 +197,45 @@ export const workspaceDefaultProperties: Required = { ], }, }, + spectraColors: { + oneDimension: [], + twoDimensions: [ + { + jpath: ['info', 'experiment'], + value: 'cosy', + positiveColor: 'darkblue', + negativeColor: 'blue', + }, + { + jpath: ['info', 'experiment'], + value: 'noesy', + positiveColor: 'pink', + negativeColor: 'yellow', + }, + { + jpath: ['info', 'experiment'], + value: 'roesy', + positiveColor: 'pink', + negativeColor: 'yellow', + }, + { + jpath: ['info', 'experiment'], + value: 'tocsy', + positiveColor: 'green', + negativeColor: 'yellow', + }, + { + jpath: ['info', 'experiment'], + value: 'hsqc', + positiveColor: 'black', + negativeColor: 'yellow', + }, + { + jpath: ['info', 'experiment'], + value: 'hmbc', + positiveColor: 'darkviolet', + negativeColor: 'yellow', + }, + ], + }, }; diff --git a/src/data/PredictionManager.ts b/src/data/PredictionManager.ts index dfd9c9948..7b4ddff10 100644 --- a/src/data/PredictionManager.ts +++ b/src/data/PredictionManager.ts @@ -340,26 +340,23 @@ function generated2DSpectrum(params: { experiment, }); const spectralWidth = getSpectralWidth(experiment, options); - const datum = initiateDatum2D( - { - data: { rr: { ...minMaxContent, noise: 0.01 } }, - display: { - positiveColor: color, - negativeColor: adjustAlpha(color, 40), - }, - info: { - name: SpectrumName, - title: SpectrumName, - nucleus: nuclei, - originFrequency: frequency, - baseFrequency: frequency, - pulseSequence: 'prediction', - spectralWidth, - experiment, - }, + const datum = initiateDatum2D({ + data: { rr: { ...minMaxContent, noise: 0.01 } }, + display: { + positiveColor: color, + negativeColor: adjustAlpha(color, 40), }, - [], - ); + info: { + name: SpectrumName, + title: SpectrumName, + nucleus: nuclei, + originFrequency: frequency, + baseFrequency: frequency, + pulseSequence: 'prediction', + spectralWidth, + experiment, + }, + }); datum.zones.values = mapZones(zones); return datum; } diff --git a/src/data/data1d/Spectrum1D/get1DColor.ts b/src/data/data1d/Spectrum1D/get1DColor.ts index e1bb9fd4b..8a30e5dbe 100644 --- a/src/data/data1d/Spectrum1D/get1DColor.ts +++ b/src/data/data1d/Spectrum1D/get1DColor.ts @@ -1,11 +1,29 @@ +import { SpectrumOneDimensionColor } from 'nmr-load-save'; + +import { UsedColors } from '../../../types/UsedColors'; import { generateColor } from '../../utilities/generateColor'; +import { getCustomColor } from '../../utilities/getCustomColor'; + +interface GetOnDimensionColorOption { + usedColors?: UsedColors; + colors?: SpectrumOneDimensionColor[]; + regenerate?: boolean; +} -export function get1DColor(options, usedColors, regenerate = false) { +export function get1DColor(spectrum, options: GetOnDimensionColorOption) { + const { regenerate = false, usedColors = {}, colors } = options; let color = 'black'; - if (options?.display?.color === undefined || regenerate) { - color = generateColor(false, usedColors['1d'] || []); + + if (!spectrum?.display?.color || regenerate) { + const customColor = getCustomColor(spectrum, colors); + + if (customColor) { + color = customColor; + } else { + color = generateColor(false, usedColors?.['1d'] || []); + } } else { - color = options.display.color; + color = spectrum.display.color; } if (usedColors['1d']) { diff --git a/src/data/data1d/Spectrum1D/initiateDatum1D.ts b/src/data/data1d/Spectrum1D/initiateDatum1D.ts index ce7fb735d..c7cc69532 100644 --- a/src/data/data1d/Spectrum1D/initiateDatum1D.ts +++ b/src/data/data1d/Spectrum1D/initiateDatum1D.ts @@ -1,5 +1,5 @@ import { v4 } from '@lukeed/uuid'; -import { Spectrum1D } from 'nmr-load-save'; +import { Spectrum1D, SpectrumOneDimensionColor } from 'nmr-load-save'; import { FiltersManager } from 'nmr-processing'; import { UsedColors } from '../../../types/UsedColors'; @@ -16,13 +16,14 @@ import { initiateRanges } from './ranges/initiateRanges'; export interface InitiateDatum1DOptions { usedColors?: UsedColors; molecules?: StateMoleculeExtended[]; + colors?: SpectrumOneDimensionColor[]; } export function initiateDatum1D( spectrum: any, options: InitiateDatum1DOptions = {}, ): Spectrum1D { - const { usedColors = {}, molecules = [] } = options; + const { usedColors, colors, molecules = [] } = options; const { integrals, ranges, ...restSpectrum } = spectrum; const spectrumObj: Spectrum1D = { ...restSpectrum }; @@ -32,7 +33,7 @@ export function initiateDatum1D( isVisible: true, isRealSpectrumVisible: true, ...spectrum.display, - ...get1DColor(spectrum, usedColors), + ...get1DColor(spectrum, { usedColors, colors }), }; spectrumObj.info = { diff --git a/src/data/data2d/Spectrum2D/get2DColor.ts b/src/data/data2d/Spectrum2D/get2DColor.ts index a7268d4e4..ee4acb1eb 100644 --- a/src/data/data2d/Spectrum2D/get2DColor.ts +++ b/src/data/data2d/Spectrum2D/get2DColor.ts @@ -1,18 +1,39 @@ -import { Color2D } from 'nmr-load-save'; +import { Color2D, SpectrumTwoDimensionsColor } from 'nmr-load-save'; +import { UsedColors } from '../../../types/UsedColors'; import { generate2DColor } from '../../utilities/generateColor'; +import { getCustomColor } from '../../utilities/getCustomColor'; + +interface GetTwoDimensionsColorOption { + usedColors?: UsedColors; + colors?: SpectrumTwoDimensionsColor[]; + regenerate?: boolean; +} + +export function get2DColor( + spectrum, + options: GetTwoDimensionsColorOption, +): Color2D { + const { regenerate = false, usedColors = {}, colors } = options; -export function get2DColor(options, usedColors, regenerate = false): Color2D { let color: Partial = {}; if ( - options?.display?.negativeColor === undefined || - options?.display?.positiveColor === undefined || + spectrum?.display?.negativeColor === undefined || + spectrum?.display?.positiveColor === undefined || regenerate ) { - color = generate2DColor(options.info.experiment, usedColors['2d'] || []); + const customColor = getCustomColor(spectrum, colors); + if (customColor) { + color = customColor; + } else { + color = generate2DColor( + spectrum.info.experiment, + usedColors?.['2d'] || [], + ); + } } else { const { positiveColor = 'red', negativeColor = 'blue' } = - options?.display || {}; + spectrum?.display || {}; color = { positiveColor, negativeColor }; } if (usedColors['2d']) { diff --git a/src/data/data2d/Spectrum2D/initiateDatum2D.ts b/src/data/data2d/Spectrum2D/initiateDatum2D.ts index 9e7ab6b4e..79c6cf4ee 100644 --- a/src/data/data2d/Spectrum2D/initiateDatum2D.ts +++ b/src/data/data2d/Spectrum2D/initiateDatum2D.ts @@ -1,7 +1,8 @@ import { v4 } from '@lukeed/uuid'; -import { Spectrum2D } from 'nmr-load-save'; +import { Spectrum2D, SpectrumTwoDimensionsColor } from 'nmr-load-save'; import { FiltersManager } from 'nmr-processing'; +import { UsedColors } from '../../../types/UsedColors'; import { initiateFilters } from '../../initiateFilters'; import { DEFAULT_CONTOURS_OPTIONS } from './contours'; @@ -10,7 +11,16 @@ import { initiateZones } from './zones/initiateZones'; const defaultMinMax = { z: [], minX: 0, minY: 0, maxX: 0, maxY: 0 }; -export function initiateDatum2D(spectrum: any, usedColors = {}): Spectrum2D { +export interface InitiateDatum2DOptions { + usedColors?: UsedColors; + colors?: SpectrumTwoDimensionsColor[]; +} + +export function initiateDatum2D( + spectrum: any, + options: InitiateDatum2DOptions = {}, +): Spectrum2D { + const { usedColors, colors } = options; const datum: any = { ...spectrum }; datum.id = spectrum.id || v4(); @@ -22,7 +32,7 @@ export function initiateDatum2D(spectrum: any, usedColors = {}): Spectrum2D { contourOptions: DEFAULT_CONTOURS_OPTIONS, dimension: 2, ...spectrum.display, - ...get2DColor(spectrum, usedColors), + ...get2DColor(spectrum, { usedColors, colors }), }; datum.info = { diff --git a/src/data/utilities/getCustomColor.ts b/src/data/utilities/getCustomColor.ts new file mode 100644 index 000000000..64cfb586a --- /dev/null +++ b/src/data/utilities/getCustomColor.ts @@ -0,0 +1,63 @@ +import lodashGet from 'lodash/get'; +import { + Color2D, + Spectrum1D, + Spectrum2D, + SpectrumOneDimensionColor, + SpectrumTwoDimensionsColor, +} from 'nmr-load-save'; + +type ReturnColor = ColorType extends SpectrumOneDimensionColor + ? string + : Color2D; + +function getValue(spectrum: Spectrum1D | Spectrum2D, jpath: string[]) { + const value = lodashGet(spectrum, jpath); + + if (typeof value === 'string') { + return value; + } + + if (['number', 'boolean'].includes(typeof value)) { + return String(value); + } + + if (value) { + return JSON.stringify(value); + } + + return value; +} + +function isInstanceOfOneDimensionColor( + item: SpectrumOneDimensionColor | SpectrumTwoDimensionsColor, +): item is SpectrumOneDimensionColor { + return 'color' in item; +} + +export function getCustomColor< + SpectrumType extends Spectrum1D | Spectrum2D, + ColorType extends SpectrumOneDimensionColor | SpectrumTwoDimensionsColor, +>(spectrum: SpectrumType, colors?: ColorType[]): ReturnColor | null { + if (!colors || colors.length === 0) return null; + + let color: ReturnColor | null = null; + for (const item of colors) { + const value = getValue(spectrum, item.jpath); + if ( + value && + item.value && + value.toLocaleLowerCase().includes(item.value.toLocaleLowerCase()) + ) { + if (isInstanceOfOneDimensionColor(item)) { + color = item.color as ReturnColor; + } else { + const { negativeColor, positiveColor } = item; + color = { negativeColor, positiveColor } as ReturnColor; + } + break; + } + } + + return color; +}
{text}