Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions src/component/1d/HorizontalAxis1D.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
} from '../hooks/use_axis_unit.ts';
import { useGridline1DConfig } from '../hooks/use_gridlines_config.ts';
import { useTicksConfig } from '../hooks/use_ticks_config.ts';
import { getAxisLayout } from '../utility/getAxisLayout.ts';

import { useIsInset } from './inset/InsetProvider.js';

Expand All @@ -35,7 +36,9 @@ export function HorizontalAxis1D() {
'horizontal',
refAxis,
);
const { labelStyle } = useTicksConfig();
const axis = useTicksConfig();
const { labelStyle } = axis;
const { labelText } = getAxisLayout(axis);
const gridConfig = useGridline1DConfig();

if (!width || !height) {
Expand Down Expand Up @@ -65,9 +68,7 @@ export function HorizontalAxis1D() {
<SVGStyledText
fill="black"
x={width - 10}
y="30"
dy="0.70em"
textAnchor="end"
{...labelText}
{...labelStyle}
>
{unitLabel}
Expand Down
16 changes: 6 additions & 10 deletions src/component/2d/DirectAxis2D.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { useCheckExportStatus } from '../hooks/useViewportSize.tsx';
import { axisUnitToLabel, useDirectAxisUnit } from '../hooks/use_axis_unit.ts';
import { useGridline2DConfig } from '../hooks/use_gridlines_config.ts';
import { useTicksConfig } from '../hooks/use_ticks_config.ts';
import { getAxisLayout } from '../utility/getAxisLayout.ts';

import { useScale2DX } from './utilities/scale.js';

Expand Down Expand Up @@ -63,7 +64,7 @@ function DirectAxis2D(props: DirectAxis2DProps) {
primaryGridProps={gridConfig.primary.lineStyle}
secondaryGridProps={gridConfig.secondary.lineStyle}
>
<Unit width={width - 60} axis={axis} />
<Unit width={width - margin.right} axis={axis} />
</D3Axis>
);
}
Expand Down Expand Up @@ -95,16 +96,11 @@ interface UnitLabelProps {
}
function UnitLabel(props: UnitLabelProps) {
const { width, children } = props;
const { labelStyle } = useTicksConfig();
const axis = useTicksConfig();
const { labelStyle } = axis;
const { labelText } = getAxisLayout(axis);
return (
<SVGStyledText
fill="black"
x={width - 60}
y="20"
dy="0.71em"
textAnchor="end"
{...labelStyle}
>
<SVGStyledText fill="black" x={width} {...labelText} {...labelStyle}>
{children}
</SVGStyledText>
);
Expand Down
57 changes: 19 additions & 38 deletions src/component/2d/IndirectAxis2D.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import { memo, useRef } from 'react';
import { useLinearPrimaryTicks } from 'react-d3-utils';
import { memo, useMemo } from 'react';
import { SVGStyledText } from 'react-science/ui';

import { useIsInset } from '../1d/inset/InsetProvider.tsx';
import { AxisUnitPicker } from '../1d-2d/components/axis_unit_picker.tsx';
import { useChartData } from '../context/ChartContext.js';
import { D3Axis } from '../elements/D3Axis.js';
import { useActiveNucleusTab } from '../hooks/useActiveNucleusTab.js';
import { useTextMetrics } from '../hooks/useTextMetrics.js';
import { useCheckExportStatus } from '../hooks/useViewportSize.tsx';
import {
axisUnitToLabel,
useIndirectAxisUnit,
} from '../hooks/use_axis_unit.ts';
import { useGridline2DConfig } from '../hooks/use_gridlines_config.ts';
import { useTicksConfig } from '../hooks/use_ticks_config.ts';
import { getAxisLayout } from '../utility/getAxisLayout.ts';
import { getVerticalAxisTicks } from '../utility/getVerticalAxisTicks.ts';

import { useScale2DY } from './utilities/scale.js';

const defaultMargin = { right: 50, top: 0, bottom: 0, left: 0 };
const defaultMargin = { right: 0, top: 0, bottom: 0, left: 0 };

interface IndirectAxis2DProps {
margin?: {
Expand Down Expand Up @@ -46,12 +46,11 @@ function IndirectAxis2D(props: IndirectAxis2DProps) {
const isInset = useIsInset();
const isExportingProcessStart = useCheckExportStatus();

const refAxis = useRef<SVGGElement>(null);

const { ticks, scale: ticksScale } = useLinearPrimaryTicks(
scaleY,
'vertical',
refAxis,
const axisStyle = useTicksConfig();
const { tickFontSize, rightLabelInset } = getAxisLayout(axisStyle);
const { ticks, scale: ticksScale } = useMemo(
() => getVerticalAxisTicks(scaleY, tickFontSize),
[scaleY, tickFontSize],
);
const gridConfig = useGridline2DConfig();

Expand All @@ -61,8 +60,7 @@ function IndirectAxis2D(props: IndirectAxis2DProps) {

return (
<D3Axis
ref={refAxis}
transform={`translate(${width - marginProps.right})`}
transform={`translate(${width - margin.right - marginProps.right})`}
scale={ticksScale}
axisPosition="right"
gridSize={width - margin.left - margin.right}
Expand All @@ -73,7 +71,9 @@ function IndirectAxis2D(props: IndirectAxis2DProps) {
primaryGridProps={gridConfig.primary.lineStyle}
secondaryGridProps={gridConfig.secondary.lineStyle}
>
<g transform={`translate(${marginProps.right - 20}, ${margin.top})`}>
<g
transform={`translate(${margin.right - rightLabelInset}, ${margin.top})`}
>
<Unit
axis={axis}
matchNucleus={matchNucleus}
Expand Down Expand Up @@ -110,33 +110,14 @@ interface UnitLabelProps {
}
function UnitLabel(props: UnitLabelProps) {
const { children } = props;
const { labelStyle } = useTicksConfig();
const { getTextWidth } = useTextMetrics({ fontSize: labelStyle.fontSize });
const labelHeight = getTextWidth(children);
const axis = useTicksConfig();
const { labelStyle } = axis;
const { rightLabelText } = getAxisLayout(axis);

return (
<>
<rect
fill="white"
rx={5}
ry={5}
width={20}
height={labelHeight + 10}
x={-10}
y={-5}
opacity={0.8}
/>

<SVGStyledText
fill="black"
transform="rotate(-90)"
dominantBaseline="middle"
textAnchor="end"
{...labelStyle}
>
{children}
</SVGStyledText>
</>
<SVGStyledText fill="black" {...rightLabelText} {...labelStyle}>
{children}
</SVGStyledText>
);
}

Expand Down
41 changes: 41 additions & 0 deletions src/component/2d/utilities/getIndirectAxis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { Spectrum } from '@zakodium/nmrium-core';
import {
axisUnits2DFid,
axisUnits2DFt,
defaultAxisUnit2DFid,
defaultAxisUnit2DFt,
isSpectrum2D,
} from '@zakodium/nmrium-core';
import { assertIn } from '@zakodium/utils';
import { scaleLinear } from 'd3-scale';

import type { State } from '../../reducer/Reducer.ts';

// Resolves the indirect-axis unit and converts its domain to that unit.
export function getIndirectAxis(
state: Pick<State, 'view' | 'originDomain' | 'yDomain'>,
spectrum: Spectrum | undefined,
) {
if (!spectrum || !isSpectrum2D(spectrum)) return undefined;

const nucleus = state.view.spectra.activeTab;
const mode: 'ft' | 'fid' = spectrum.info.isFt ? 'ft' : 'fid';
const unit =
state.view.units2D[nucleus]?.indirect[mode] ??
(mode === 'ft' ? defaultAxisUnit2DFt : defaultAxisUnit2DFid);
const allowedUnits = mode === 'ft' ? axisUnits2DFt : axisUnits2DFid;
assertIn(unit, allowedUnits);
let domain = state.yDomain;

if (unit === 'pt') {
const ppmToPoint = scaleLinear(state.originDomain.yDomain, [
0,
spectrum.info.spectrumSize[1],
]);
domain = domain.map((value) => ppmToPoint(value));
} else if (unit === 'hz') {
domain = domain.map((value) => value * spectrum.info.originFrequency[1]);
}

return { nucleus, mode, unit, allowedUnits, domain } as const;
}
47 changes: 47 additions & 0 deletions src/component/2d/utilities/getRightAxisMargin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { scaleLinear } from 'd3-scale';

import { getViewSpectra } from '../../../data/get_view_spectra.ts';
import type { State } from '../../reducer/Reducer.ts';
import { getActiveSpectrum } from '../../reducer/helper/getActiveSpectrum.ts';
import type { AxisStyles } from '../../utility/getAxisLayout.ts';
import { getAxisLayout } from '../../utility/getAxisLayout.ts';
import { getVerticalAxisTicks } from '../../utility/getVerticalAxisTicks.ts';

import { getIndirectAxis } from './getIndirectAxis.ts';

interface TextMetrics {
getTextWidth: (text: string) => number;
getTextHeight: () => number;
}

// Reserves room for the widest right-axis tick and the vertical unit label.
export function getRightAxisMargin(
state: State,
axis: AxisStyles,
metrics: TextMetrics,
) {
// Use the same unit conversion and tick generation as the right axis.
const activeSpectrum = getActiveSpectrum(state);
const spectrum = getViewSpectra(state).find(
(spectrum) => spectrum.id === activeSpectrum?.id,
);
const indirectAxis = getIndirectAxis(state, spectrum);
const [, indirectNucleus] = state.view.spectra.activeTab.split(',', 2);
const matchNucleus = /^[0-9]+[A-Z][a-z]?$/.test(indirectNucleus);
const scale = scaleLinear(
matchNucleus ? (indirectAxis?.domain ?? state.yDomain) : state.yDomain,
[state.margin.top, state.height - state.margin.bottom],
);
const { tickFontSize } = getAxisLayout(axis);
const { ticks } = getVerticalAxisTicks(scale, tickFontSize);

// Reserve space for the widest tick number and the vertical unit label.
const tickLabelWidth = Math.max(
0,
...ticks.map(({ label }) => metrics.getTextWidth(label)),
);
return getAxisLayout(axis, {
tickLabelWidth,
labelHeight: metrics.getTextHeight(),
}).right;
}
60 changes: 49 additions & 11 deletions src/component/context/ChartContext.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,65 @@
import { produce } from 'immer';
import { createContext, use } from 'react';
import { createContext, use, useMemo } from 'react';

import { useIsInset } from '../1d/inset/InsetProvider.js';
import { getRightAxisMargin } from '../2d/utilities/getRightAxisMargin.ts';
import { useTextMetrics } from '../hooks/useTextMetrics.ts';
import { useViewportSize } from '../hooks/useViewportSize.js';
import { useTicksConfig } from '../hooks/use_ticks_config.ts';
import type { State } from '../reducer/Reducer.js';
import { initialState } from '../reducer/Reducer.js';
import { getAxisLayout } from '../utility/getAxisLayout.ts';

export const ChartDataContext = createContext<State>(initialState);

export function useChartData() {
const data = use(ChartDataContext);

const viewportSize = useViewportSize();
const axis = useTicksConfig();
const isInset = useIsInset();
const layout = getAxisLayout(axis);
const { getTextWidth } = useTextMetrics({
...axis.primaryTicks.textStyle,
fontSize: layout.tickFontSize,
});
const { getTextHeight } = useTextMetrics({
...axis.labelStyle,
fontSize: axis.labelStyle.fontSize ?? layout.fontSize,
});

if (!viewportSize) return data;
return useMemo(() => {
return produce(data, (draft) => {
// 1. Apply export, print, or inset dimensions before calculating spacing.
if (viewportSize) {
const { width, height, margin, xDomain, yDomain } = viewportSize;
draft.width = width;
draft.height = height;
draft.margin = margin ?? draft.margin;
draft.xDomain = xDomain ?? draft.xDomain;
draft.yDomain = yDomain ?? draft.yDomain;
}

const { width, height, margin, xDomain, yDomain } = viewportSize;
const updatedData = produce(data, (draft) => {
draft.width = width;
draft.height = height;
draft.margin = margin ?? draft.margin;
draft.xDomain = xDomain ?? draft.xDomain;
draft.yDomain = yDomain ?? draft.yDomain;
});
// 2. Keep inset spacing unchanged; reserve bottom space for other axes.
if (isInset) return;
draft.margin.bottom = Math.max(draft.margin.bottom, layout.bottom);

return updatedData;
// 3. In 2D, leave room for tick numbers and the vertical right-axis unit.
if (draft.displayerMode === '2D') {
const right = getRightAxisMargin(draft, axis, {
getTextWidth,
getTextHeight,
});
draft.margin.right = Math.max(draft.margin.right, right);
}
});
}, [
axis,
data,
getTextHeight,
getTextWidth,
isInset,
layout.bottom,
viewportSize,
]);
}
Loading
Loading