Skip to content

Commit

Permalink
feat: control the margin top of the peaks label
Browse files Browse the repository at this point in the history
close #3112

feat: interactive positioning of peak labels
  • Loading branch information
hamed-musallam committed Oct 18, 2024
1 parent 2cd4eaf commit e177d56
Show file tree
Hide file tree
Showing 11 changed files with 263 additions and 71 deletions.
44 changes: 22 additions & 22 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
190 changes: 150 additions & 40 deletions src/component/1d/peaks/PeakAnnotationsSpreadMode.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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<number>(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<PeaksAnnotationsProps, 'xDomain'> {
height: number;
margin: Margin;
}

function PeakAnnotationsSpreadMode(props: PeakAnnotationsSpreadModeProps) {
const {
peaks,
peaksSource,
spectrumColor,
xDomain,
displayerKey,
peakFormat,
margin,
height,
} = props;
const [ref, boxSize] = useMeasure<SVGGElement>();
const { marginTop, isDragActive, onPointerDown } = usePeaksPosition();

const decimalsCount = getDecimalsCount(xDomain[1], peakFormat);
const actionsButtons: ActionsButtonsPopoverProps['buttons'] = [
{
icon: <BsArrowsMove />,
onPointerDown: (event) => {
event.stopPropagation();
onPointerDown(event);
},
intent: 'none',
title: 'Move peaks label vertically',
style: { cursor: 'move' },
},
];

const mapPeaks = resolve(peaks, {
key: 'scaleX',
Expand All @@ -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 (
<g className="peaks" clipPath={`url(#${displayerKey}clip-chart-1d)`}>
<g transform={`translate(0,${decimalsCount * 10})`}>
{mapPeaks.map((group) => {
return (
<g
key={group.meta.id}
transform={`translate(${group.meta.groupStartX},0)`}
>
{group.group.map((item, index) => {
const { id, x: value, scaleX, parentKeys } = item;
const startX = index * (notationWidth + notationMargin);
const x = scaleX - group.meta.groupStartX;
return (
<PeakAnnotation
key={id}
startX={startX}
x={x}
id={id}
parentKeys={parentKeys}
value={value}
format={peakFormat}
color={spectrumColor}
peakEditionFieldPosition={{
x: group.meta.groupStartX + startX,
y: decimalsCount * 10,
}}
peaksSource={peaksSource}
/>
);
})}
</g>
);
})}
<ActionsButtonsPopover
targetTagName="g"
buttons={actionsButtons}
positioningStrategy="fixed"
position="top"
direction="row"
{...(isDragActive && { isOpen: true })}
modifiers={{
offset: {
data: { x: 0, y },
},
}}
>
<g className="peaks" clipPath={`url(#${displayerKey}clip-chart-1d)`}>
<g
transform={`translate(0,${y})`}
style={{ visibility: boxSize.height > 0 ? 'visible' : 'hidden' }}
>
<rect
data-no-export="true"
width="100%"
y={-boxHeight / 2}
height={boxHeight}
fill={isDragActive ? 'white' : 'transparent'}
opacity={isDragActive ? 0.9 : 0}
/>
<g ref={ref}>
{mapPeaks.map((group) => {
return (
<g
key={group.meta.id}
transform={`translate(${group.meta.groupStartX},0)`}
>
{group.group.map((item, index) => {
const { id, x: value, scaleX, parentKeys } = item;
const startX = index * (notationWidth + notationMargin);
const x = scaleX - group.meta.groupStartX;
return (
<PeakAnnotation
key={id}
startX={startX}
x={x}
id={id}
parentKeys={parentKeys}
value={value}
format={peakFormat}
color={spectrumColor}
peakEditionFieldPosition={{
x: group.meta.groupStartX + startX,
y,
}}
peaksSource={peaksSource}
/>
);
})}
</g>
);
})}
</g>
</g>
</g>
</g>
</ActionsButtonsPopover>
);
}

Expand Down Expand Up @@ -127,4 +237,4 @@ function PeakAnnotation(props: PeakAnnotationProps) {
);
}

export default memo(PeakAnnotationsTreeStyle);
export default memo(PeakAnnotationsSpreadMode);
31 changes: 27 additions & 4 deletions src/component/1d/peaks/Peaks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
Expand All @@ -124,7 +135,8 @@ function InnerPeaks(props: InnerPeaksProps) {
spectrumColor={spectrum.display.color}
peakFormat={peakFormat}
displayerKey={displayerKey}
xDomain={xDomain}
height={height}
margin={margin}
/>
);
}
Expand Down Expand Up @@ -154,6 +166,8 @@ export default function Peaks(props) {
},
displayerKey,
xDomain,
height,
margin,
} = useChartData();
const spectrum = useSpectrum(emptyData) as Spectrum1D;
const peaksViewState = useActiveSpectrumPeaksViewState();
Expand Down Expand Up @@ -197,7 +211,16 @@ export default function Peaks(props) {

return (
<MemoizedPeaksPanel
{...{ spectrum, mode, peaksSource, displayerKey, xDomain, peakFormat }}
{...{
spectrum,
mode,
peaksSource,
displayerKey,
xDomain,
peakFormat,
margin,
height,
}}
/>
);
}
Loading

0 comments on commit e177d56

Please sign in to comment.