Skip to content

Commit

Permalink
feat: float molecule structures over the spectrum (#1525)
Browse files Browse the repository at this point in the history
* feat: svg draggable component

* feat: context to pass the root svg element down on the nodes tree

* refactor: resizer component to use the new useDraggable hook

* refactor: set button content pointer-events
one

* feat: float structures

* refactor: remove root context and use global context instead

* refactor: rename component name

* chore: fix eslint

* chore: chain conditions

* style: structures toolbar
  • Loading branch information
hamed-musallam committed May 4, 2022
1 parent 902993d commit 843173d
Show file tree
Hide file tree
Showing 25 changed files with 638 additions and 236 deletions.
3 changes: 3 additions & 0 deletions src/component/1d/Chart1D.tsx
@@ -1,3 +1,5 @@
import FloatMoleculeStructures from '../tool/FloatMoleculeStructures';

import ExclusionZonesAnnotations from './ExclusionZonesAnnotations';
import IntegralsSeries from './IntegralsSeries';
import LinesSeries from './LinesSeries';
Expand Down Expand Up @@ -41,6 +43,7 @@ function Chart1D({ mode, width, height, margin, displayerKey }) {
<g className="container" style={{ pointerEvents: 'none' }}>
<XAxis showGrid mode={mode} />
</g>
<FloatMoleculeStructures />
</svg>
);
}
Expand Down
21 changes: 14 additions & 7 deletions src/component/1d/IntegralResizable.tsx
Expand Up @@ -4,6 +4,7 @@ import { useCallback } from 'react';

import { useChartData } from '../context/ChartContext';
import { useDispatch } from '../context/DispatchContext';
import { useGlobal } from '../context/GlobalContext';
import { useScaleChecked } from '../context/ScaleContext';
import Resizer from '../elements/resizer/Resizer';
import { HighlightedSource, useHighlight } from '../highlight/index';
Expand Down Expand Up @@ -46,9 +47,10 @@ interface IntegralResizableProps {

function IntegralResizable({ integralData }: IntegralResizableProps) {
const { height, margin } = useChartData();
const { viewerRef } = useGlobal();
const { scaleX } = useScaleChecked();
const dispatch = useDispatch();
const { id, from, to, integral } = integralData;
const { id, integral } = integralData;
const highlight = useHighlight([id], {
type: HighlightedSource.INTEGRAL,
extra: { id },
Expand Down Expand Up @@ -78,22 +80,27 @@ function IntegralResizable({ integralData }: IntegralResizableProps) {
highlight.hide();
}, [highlight]);

const x0 = from ? scaleX()(from) : 0;
const x1 = to ? scaleX()(to) : 0;
const from = integralData.from ? scaleX()(integralData.from) : 0;
const to = integralData.to ? scaleX()(integralData.to) : 0;

return (
<g
css={highlight.isActive ? stylesHighlighted : stylesOnHover}
onMouseEnter={handleOnEnterNotation}
onMouseLeave={handleOnMouseLeaveNotation}
>
<Resizer
tag="svg"
initialPosition={{ x1: x1, x2: x0 }}
initialPosition={{ x1: to, x2: from }}
onEnd={handleOnStopResizing}
parentElement={viewerRef}
key={`${id}_${to}_${from}`}
>
{(x1, x2) => (
<g>
{({ x1, x2 }, isActive) => (
<g
css={
highlight.isActive || isActive ? stylesHighlighted : stylesOnHover
}
>
<rect
x="0"
y="0"
Expand Down
31 changes: 19 additions & 12 deletions src/component/1d/multiAnalysis/AnalysisRange.tsx
Expand Up @@ -3,6 +3,7 @@ import { css } from '@emotion/react';
import { useCallback } from 'react';

import { useDispatch } from '../../context/DispatchContext';
import { useGlobal } from '../../context/GlobalContext';
import { useScaleChecked } from '../../context/ScaleContext';
import DeleteButton from '../../elements/DeleteButton';
import Resizer from '../../elements/resizer/Resizer';
Expand Down Expand Up @@ -46,11 +47,10 @@ interface AnalysisRangeProps {
}

function AnalysisRange({ rangeData, columnKey }: AnalysisRangeProps) {
const { from, to } = rangeData;
const highlight = useHighlight([columnKey]);
const { scaleX } = useScaleChecked();
const dispatch = useDispatch();

const { viewerRef } = useGlobal();
const deleteHandler = useCallback(() => {
dispatch({ type: DELETE_ANALYZE_SPECTRA_RANGE, colKey: columnKey });
}, [columnKey, dispatch]);
Expand All @@ -65,22 +65,29 @@ function AnalysisRange({ rangeData, columnKey }: AnalysisRangeProps) {
[columnKey, dispatch, rangeData],
);

const from = scaleX()(rangeData.from);
const to = scaleX()(rangeData.to);

return (
<g
{...highlight.onHover}
css={[
styles.common,
highlight.isActive ? styles.Highlighted : styles.hover,
]}
>
<g {...highlight.onHover}>
<Resizer
tag="svg"
onEnd={resizeEndHandler}
initialPosition={{ x2: scaleX()(from), x1: scaleX()(to) }}
initialPosition={{ x2: from, x1: to }}
parentElement={viewerRef}
key={`${columnKey}_${to}_${from}`}
>
{(x1, x2) => (
{({ x1, x2 }, isActive) => (
<>
<g transform={`translate(0,25)`}>
<g
transform={`translate(0,25)`}
css={[
styles.common,
highlight.isActive || isActive
? styles.Highlighted
: styles.hover,
]}
>
<rect
x="0"
width={x2 - x1}
Expand Down
32 changes: 20 additions & 12 deletions src/component/1d/ranges/Range.tsx
Expand Up @@ -10,6 +10,7 @@ import {
} from '../../assignment/AssignmentsContext';
import { filterForIDsWithAssignment } from '../../assignment/utilities/filterForIDsWithAssignment';
import { useDispatch } from '../../context/DispatchContext';
import { useGlobal } from '../../context/GlobalContext';
import { useScaleChecked } from '../../context/ScaleContext';
import Resizer from '../../elements/resizer/Resizer';
import { HighlightedSource, useHighlight } from '../../highlight';
Expand Down Expand Up @@ -69,7 +70,8 @@ function Range({
selectedTool,
startEditMode,
}: RangeProps) {
const { id, from, to, integration, signals } = rangeData;
const { viewerRef } = useGlobal();
const { id, integration, signals } = rangeData;
const assignmentData = useAssignmentData();
const assignmentRange = useAssignment(id);
const highlightRange = useHighlight(
Expand Down Expand Up @@ -136,30 +138,36 @@ function Range({
},
[assignmentRange, isBlockedByEditing, selectedTool],
);

const from = scaleX()(rangeData.from);
const to = scaleX()(rangeData.to);
return (
<g
data-test-id="range"
style={{ outline: 'none' }}
css={
isBlockedByEditing ||
highlightRange.isActive ||
assignmentRange.isActive
? stylesHighlighted
: stylesOnHover
}
key={id}
onMouseEnter={mouseEnterHandler}
onMouseLeave={mouseLeaveHandler}
onClick={assignHandler}
>
<Resizer
tag="svg"
initialPosition={{ x1: scaleX()(to), x2: scaleX()(from) }}
initialPosition={{ x1: to, x2: from }}
onEnd={handleOnStopResizing}
parentElement={viewerRef}
key={`${id}_${to}_${from}`}
>
{(x1, x2) => (
<g transform={`translate(0,10)`}>
{({ x1, x2 }, isActive) => (
<g
transform={`translate(0,10)`}
css={
isBlockedByEditing ||
highlightRange.isActive ||
assignmentRange.isActive ||
isActive
? stylesHighlighted
: stylesOnHover
}
>
<rect
data-no-export="true"
x="0"
Expand Down
2 changes: 2 additions & 0 deletions src/component/2d/Chart2D.tsx
Expand Up @@ -3,6 +3,7 @@ import { memo } from 'react';
import { Datum1D } from '../../data/types/data1d';
import { useChartData } from '../context/ChartContext';
import { Margin } from '../reducer/Reducer';
import FloatMoleculeStructures from '../tool/FloatMoleculeStructures';

import Contours from './Contours';
import Left1DChart from './Left1DChart';
Expand Down Expand Up @@ -67,6 +68,7 @@ function chart2DInner({
<XAxis />
<YAxis />
</g>
<FloatMoleculeStructures />
</svg>
);
}
Expand Down
3 changes: 3 additions & 0 deletions src/component/NMRium.tsx
Expand Up @@ -211,6 +211,7 @@ function InnerNMRium({
}: NMRiumProps) {
const rootRef = useRef<HTMLDivElement>(null);
const elementsWrapperRef = useRef<HTMLDivElement>(null);
const viewerRef = useRef<HTMLDivElement>(null);
const [show, toggle] = useToggle(false);

const isFullscreen = useFullscreen(rootRef, show, {
Expand Down Expand Up @@ -295,6 +296,7 @@ function InnerNMRium({
value={{
rootRef: rootRef.current,
elementsWrapperRef: elementsWrapperRef.current,
viewerRef: viewerRef.current,
}}
>
<PreferencesProvider value={preferencesState}>
Expand Down Expand Up @@ -340,6 +342,7 @@ function InnerNMRium({
<KeysListenerTracker />
<div
data-test-id="viewer"
ref={viewerRef}
style={{
width: '100%',
height: '100%',
Expand Down
3 changes: 2 additions & 1 deletion src/component/context/GlobalContext.tsx
Expand Up @@ -3,7 +3,8 @@ import { createContext, useContext } from 'react';
export const GlobalConetxt = createContext<{
rootRef: HTMLDivElement | null;
elementsWrapperRef: HTMLDivElement | null;
}>({ rootRef: null, elementsWrapperRef: null });
viewerRef: HTMLDivElement | null;
}>({ rootRef: null, elementsWrapperRef: null, viewerRef: null });

export const GlobalProvider = GlobalConetxt.Provider;

Expand Down
2 changes: 1 addition & 1 deletion src/component/elements/Button.tsx
Expand Up @@ -208,7 +208,7 @@ const Button = (props: ButtonProps) => {
]}
{...restProps}
>
<span style={{ flex: 1 }}> {props.children}</span>
<span style={{ flex: 1, pointerEvents: 'none' }}> {props.children}</span>
</button>
);
};
Expand Down
82 changes: 82 additions & 0 deletions src/component/elements/draggble/SVGDraggable.tsx
@@ -0,0 +1,82 @@
import { useEffect, ReactFragment } from 'react';

import useDraggable, { Position } from './useDraggable';

type ChildType =
| Array<React.ReactElement>
| React.ReactElement
| ReactFragment
| boolean
| null;

type PositionChangeHandler = (data: Position) => void;

export interface DraggableProps {
children?: ChildType | ((x1: number, x2: number) => ChildType);
initialPosition?: Position;
width: number;
height: number;
onStart?: PositionChangeHandler;
onMove?: PositionChangeHandler;
onEnd?: PositionChangeHandler;
parentElement?: HTMLElement | null;
dragHandleClassName?: string;
}

export default function SVGDraggable(props: DraggableProps) {
const {
children,
initialPosition = { x: 0, y: 0 },
width,
height,
onStart,
onMove,
onEnd,
parentElement,
dragHandleClassName,
} = props;

const {
position: {
value: { x, y },
action,
},
onMouseDown,
} = useDraggable({
position: initialPosition,
parentElement,
dragHandleClassName,
});

useEffect(() => {
const position: Position = {
x,
y,
};

switch (action) {
case 'start':
onStart?.(position);
break;
case 'move':
onMove?.(position);
break;
case 'end':
onEnd?.(position);
break;
default:
break;
}
}, [action, onEnd, onMove, onStart, x, y]);

return (
<g
style={{
transform: `translate(${x}px,${y}px)`,
}}
onMouseDown={onMouseDown}
>
{typeof children === 'function' ? children(width, height) : children}
</g>
);
}

0 comments on commit 843173d

Please sign in to comment.