Skip to content

Commit

Permalink
feat: preview spectrum from database (#1561)
Browse files Browse the repository at this point in the history
* feat: add new option to database preferences

* feat: customize loading text

* refactor: replace deprecated function

* feat: load jcamp from database

* feat: preference group component

* feat: database spectrum preferences
define the bottom margin and color for the preview spectrum / resurrect spectra from ranges
close #1563

* feat: add spectrum from jcamp if jampURL is exists
  • Loading branch information
hamed-musallam committed May 27, 2022
1 parent 2ebdeb1 commit 24174cc
Show file tree
Hide file tree
Showing 12 changed files with 231 additions and 75 deletions.
4 changes: 2 additions & 2 deletions src/component/1d/Chart1D.tsx
Expand Up @@ -4,8 +4,8 @@ import ExclusionZonesAnnotations from './ExclusionZonesAnnotations';
import IntegralsSeries from './IntegralsSeries';
import LinesSeries from './LinesSeries';
import PeakAnnotations from './PeakAnnotations';
import ResurrectedDatabaseRanges from './ResurrectedDatabaseRanges';
import XAxis from './XAxis';
import DatabaseElements from './database/DatabaseElements';
import JGraph from './jCouplingGraph/JGraph';
import MultiAnalysisRanges from './multiAnalysis/MultiAnalysisRanges';
import Ranges from './ranges/Ranges';
Expand Down Expand Up @@ -38,7 +38,7 @@ function Chart1D({ mode, width, height, margin, displayerKey }) {
<MultiAnalysisRanges />
<BaseLineZones />
<ExclusionZonesAnnotations />
<ResurrectedDatabaseRanges />
<DatabaseElements />

<g className="container" style={{ pointerEvents: 'none' }}>
<XAxis showGrid mode={mode} />
Expand Down
22 changes: 22 additions & 0 deletions src/component/1d/database/DatabaseElements.tsx
@@ -0,0 +1,22 @@
import { HighlightedSource, useHighlightData } from '../../highlight';
import { usePanelPreferences } from '../../hooks/usePanelPreferences';

import DatabaseSpectrum from './DatabaseSpectrum';
import ResurrectedDatabaseRanges from './ResurrectedDatabaseRanges';

function DatabaseElements() {
const { highlight } = useHighlightData();
const { previewJcamp } = usePanelPreferences('database');
const { jcampURL } = highlight?.sourceData?.extra || {};
if (highlight.sourceData?.type !== HighlightedSource.DATABASE) {
return null;
}

if (previewJcamp && jcampURL) {
return <DatabaseSpectrum />;
}

return <ResurrectedDatabaseRanges />;
}

export default DatabaseElements;
82 changes: 82 additions & 0 deletions src/component/1d/database/DatabaseSpectrum.tsx
@@ -0,0 +1,82 @@
import { useCallback, useContext, useEffect, useState } from 'react';

import { addJcamp } from '../../../data/SpectraManager';
import { Datum1D } from '../../../data/types/data1d';
import { useChartData } from '../../context/ChartContext';
import { useScaleChecked } from '../../context/ScaleContext';
import { useAlert } from '../../elements/popup/Alert';
import { HighlightedSource, useHighlightData } from '../../highlight';
import { usePanelPreferences } from '../../hooks/usePanelPreferences';
import { spinnerContext } from '../../loader/SpinnerContext';
import { loadFile } from '../../utility/FileUtility';
import { getYScale } from '../utilities/scale';

function DatabaseSpectrum() {
const { displayerKey, height, verticalAlign, yDomain, margin } =
useChartData();
const [path, setPath] = useState<string>();
const [isLoading, setLoading] = useState<boolean>(false);
const { highlight } = useHighlightData();
const { scaleX } = useScaleChecked();
const alert = useAlert();
const { color, marginBottom } = usePanelPreferences('database');
const { jcampURL: jcampRelativeURL, baseURL } =
highlight?.sourceData?.extra || [];
const getSpinner = useContext(spinnerContext);

const scaleY = useCallback(
() =>
getYScale({
height: height,
margin: { top: margin.top, bottom: margin.bottom + marginBottom },
verticalAlign,
yDomain,
}),
[height, margin, marginBottom, verticalAlign, yDomain],
);

useEffect(() => {
void (async () => {
try {
setLoading(true);

const jcampURL = new URL(jcampRelativeURL, baseURL);
const result = await loadFile(jcampURL);
const spectra = [];
addJcamp(spectra, result, {}, {});
setLoading(false);
const spectrum = spectra?.[0] || null;
if (spectrum) {
const { x, re: y } = (spectrum as Datum1D)?.data;
let path = `M ${scaleX()(x[0])} ${scaleY()(y[0])} `;
path += x.slice(1).reduce((accumulator, point, i) => {
accumulator += ` L ${scaleX()(point)} ${scaleY()(y[i + 1])}`;
return accumulator;
}, '');
setPath(path);
}
} catch (e) {
alert.error('Failed to Load spectrum');
}
})();
}, [alert, baseURL, jcampRelativeURL, scaleX, scaleY]);

if (highlight.sourceData?.type !== HighlightedSource.DATABASE) {
return null;
}

return isLoading ? (
<foreignObject width="100%" height="100%">
{getSpinner('Load Jcamp ....')}
</foreignObject>
) : (
<g
clipPath={`url(#${displayerKey}clip-chart-1d)`}
className="database-spectrum"
>
<path stroke={color} fill="none" d={path} />
</g>
);
}

export default DatabaseSpectrum;
@@ -1,13 +1,13 @@
import { extent } from 'd3';
import { rangesToXY } from 'nmr-processing';

import { Datum1D } from '../../data/types/data1d';
import { useChartData } from '../context/ChartContext';
import { useScaleChecked } from '../context/ScaleContext';
import { HighlightedSource, useHighlightData } from '../highlight';
import useSpectrum from '../hooks/useSpectrum';

import { getYScale } from './utilities/scale';
import { Datum1D } from '../../../data/types/data1d';
import { useChartData } from '../../context/ChartContext';
import { useScaleChecked } from '../../context/ScaleContext';
import { HighlightedSource, useHighlightData } from '../../highlight';
import { usePanelPreferences } from '../../hooks/usePanelPreferences';
import useSpectrum from '../../hooks/useSpectrum';
import { getYScale } from '../utilities/scale';

const emptyData = { info: { originFrequency: 400 } };

Expand All @@ -16,13 +16,15 @@ function ResurrectedDatabaseRanges() {
const { info } = useSpectrum(emptyData) as Datum1D;
const { highlight } = useHighlightData();
const { scaleX } = useScaleChecked();
const { color, marginBottom } = usePanelPreferences('database');

if (highlight.sourceData?.type !== HighlightedSource.DATABASE) {
return null;
}

const fullHeight = height - margin.bottom;
const blockHight = fullHeight / 4;
const translateY = fullHeight - blockHight - marginBottom;

const { ranges } = highlight.sourceData.extra || [];

Expand Down Expand Up @@ -82,8 +84,8 @@ function ResurrectedDatabaseRanges() {
/>
</g>
<path
transform={`translate(0,${fullHeight - blockHight * 2})`}
stroke="black"
transform={`translate(0,${translateY})`}
stroke={color}
fill="none"
d={path}
/>
Expand Down
2 changes: 1 addition & 1 deletion src/component/elements/popup/Alert/Provider.tsx
Expand Up @@ -84,7 +84,7 @@ function Provider({

const show = useCallback(
(message = '', options: any = {}) => {
const id = Math.random().toString(36).substr(2, 9);
const id = Math.random().toString(36).substring(2, 9);

const alertOptions = {
position: options.position || position,
Expand Down
6 changes: 4 additions & 2 deletions src/component/loader/DefaultSpinnerComponent.tsx
Expand Up @@ -35,7 +35,9 @@ const styles = css`
}
`;

export default function DefaultSpinnerComponent() {
export default function DefaultSpinnerComponent({
loadingText = 'Loading ...',
}) {
return (
<div css={styles}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
Expand All @@ -44,7 +46,7 @@ export default function DefaultSpinnerComponent() {
<path d="M520.5 78.1z" />
</g>
</svg>
<p>Loading ...</p>
<p>{loadingText}</p>
</div>
);
}
6 changes: 3 additions & 3 deletions src/component/loader/SpinnerContext.tsx
Expand Up @@ -2,11 +2,11 @@ import { createContext, ReactElement } from 'react';

import DefaultSpinnerComponent from './DefaultSpinnerComponent';

export function defaultGetSpinner() {
return <DefaultSpinnerComponent />;
export function defaultGetSpinner(loadingText = 'Loading ...') {
return <DefaultSpinnerComponent loadingText={loadingText} />;
}

export const spinnerContext =
createContext<() => ReactElement>(defaultGetSpinner);
createContext<(loadingText?: string) => ReactElement>(defaultGetSpinner);

export const SpinnerProvider = spinnerContext.Provider;
48 changes: 39 additions & 9 deletions src/component/panels/databasePanel/DatabasePanel.tsx
Expand Up @@ -18,9 +18,13 @@ import ToggleButton from '../../elements/ToggleButton';
import { useAlert } from '../../elements/popup/Alert';
import { useFormatNumberByNucleus } from '../../hooks/useFormatNumberByNucleus';
import useToolsFunctions from '../../hooks/useToolsFunctions';
import { RESURRECTING_SPECTRUM_FROM_RANGES } from '../../reducer/types/Types';
import {
LOAD_JCAMP_FILE,
RESURRECTING_SPECTRUM_FROM_RANGES,
} from '../../reducer/types/Types';
import { options } from '../../toolbar/ToolTypes';
import Events from '../../utility/Events';
import { loadFile } from '../../utility/FileUtility';
import { tablePanelStyle } from '../extra/BasicPanelStyle';
import NoTableData from '../extra/placeholder/NoTableData';
import DefaultPanelHeader from '../header/DefaultPanelHeader';
Expand Down Expand Up @@ -159,7 +163,14 @@ function DatabasePanelInner({ nucleus, selectedTool }: DatabaseInnerProps) {
const hideLoading = await alert.showLoading(`load ${label} database`);

try {
_database = await fetch(url).then((response) => response.json());
_database = await fetch(url)
.then((response) => response.json())
.then((databaseRecords) =>
databaseRecords.map((record) => ({
...record,
baseURL: url,
})),
);
} catch (e) {
alert.error(`Failed to load ${url}`);
} finally {
Expand All @@ -181,14 +192,33 @@ function DatabasePanelInner({ nucleus, selectedTool }: DatabaseInnerProps) {

const resurrectHandler = useCallback(
(row) => {
const { index } = row.original;
const { ranges, solvent, names = [] } = result.data[index];
dispatch({
type: RESURRECTING_SPECTRUM_FROM_RANGES,
payload: { ranges, info: { solvent, nucleus, name: names[0] } },
});
const { index, baseURL, jcampURL: jcampRelativeURL } = row.original;
if (jcampRelativeURL) {
void (async () => {
const hideLoading = await alert.showLoading(
`load jcamp in progress...`,
);

try {
const jcampURL = new URL(jcampRelativeURL, baseURL);

const result = await loadFile(jcampURL);
dispatch({ type: LOAD_JCAMP_FILE, files: [{ binary: result }] });
} catch (e) {
alert.error(`Failed to load Jcamp`);
} finally {
hideLoading();
}
})();
} else {
const { ranges, solvent, names = [] } = result.data[index];
dispatch({
type: RESURRECTING_SPECTRUM_FROM_RANGES,
payload: { ranges, info: { solvent, nucleus, name: names[0] } },
});
}
},
[dispatch, nucleus, result.data],
[alert, dispatch, nucleus, result.data],
);

const clearHandler = useCallback(() => {
Expand Down
73 changes: 24 additions & 49 deletions src/component/panels/databasePanel/DatabasePreferences.tsx
Expand Up @@ -3,58 +3,19 @@ import {
useCallback,
useImperativeHandle,
useRef,
CSSProperties,
forwardRef,
} from 'react';

import { usePreferences } from '../../context/PreferencesContext';
import Label from '../../elements/Label';
import FormikColorInput from '../../elements/formik/FormikColorInput';
import FormikColumnFormatField from '../../elements/formik/FormikColumnFormatField';
import FormikForm from '../../elements/formik/FormikForm';
import FormikInput from '../../elements/formik/FormikInput';
import { useAlert } from '../../elements/popup/Alert';
import { usePanelPreferences } from '../../hooks/usePanelPreferences';

const styles: Record<
| 'container'
| 'groupContainer'
| 'header'
| 'inputContainer'
| 'input'
| 'inputLabel',
CSSProperties
> = {
container: {
padding: 10,
backgroundColor: '#f1f1f1',
height: '100%',
overflowY: 'auto',
},
groupContainer: {
padding: '5px',
borderRadius: '5px',
margin: '10px 0px',
backgroundColor: 'white',
},

header: {
borderBottom: '1px solid #e8e8e8',
paddingBottom: '5px',
fontWeight: 'bold',
color: '#4a4a4a',
},
inputContainer: {
flex: 4,
borderRadius: '5px',
},
inputLabel: {
flex: 2,
fontSize: '11px',
fontWeight: 'bold',
color: '#232323',
},
input: {
width: '100px',
},
};
import { PreferencesContainer } from '../extra/preferences/PreferencesContainer';
import { PreferencesGroup } from '../extra/preferences/PreferencesGroup';

function DatabasePreferences(props, ref) {
const preferences = usePreferences();
Expand Down Expand Up @@ -85,11 +46,25 @@ function DatabasePreferences(props, ref) {
}));

return (
<div style={styles.container}>
<PreferencesContainer>
<FormikForm onSubmit={saveHandler} ref={formRef}>
<div style={styles.groupContainer}>
<PreferencesGroup>
<FormikColumnFormatField
label="Preview jcamp"
checkControllerName="previewJcamp"
hideFormatField
/>
<FormikColorInput name="color" />
<Label
title="Margin bottom (px) :"
style={{ label: { padding: 0, flex: 4 }, wrapper: { flex: 8 } }}
>
<FormikInput name="marginBottom" type="number" />
</Label>
</PreferencesGroup>
<PreferencesGroup header="Table Preferences">
<FormikColumnFormatField
label="structure"
label="Structure"
checkControllerName="showSmiles"
hideFormatField
/>
Expand Down Expand Up @@ -128,9 +103,9 @@ function DatabasePreferences(props, ref) {
checkControllerName="showMultiplicity"
hideFormatField
/>
</div>
</PreferencesGroup>
</FormikForm>
</div>
</PreferencesContainer>
);
}

Expand Down

0 comments on commit 24174cc

Please sign in to comment.