import { extent } from 'd3-array'; import { xFindClosestIndex } from 'ml-spectra-processing'; import { matrixToStocsy } from 'nmr-processing'; import { useMemo } from 'react'; import { useChartData } from '../../context/ChartContext.js'; import { useScaleChecked } from '../../context/ScaleContext.js'; import { useActiveNucleusTab } from '../../hooks/useActiveNucleusTab.js'; import { usePanelPreferences } from '../../hooks/usePanelPreferences.js'; import { PathBuilder } from '../../utility/PathBuilder.js'; import { getYScaleWithRation } from '../utilities/scale.js'; import { groupPointsByColor, sliceArrayForDomain, useMatrix, } from './useMatrix.js'; interface StocsyProps { x: Float64Array | number[]; y: number[]; color: string[]; scaleRatio: number; yDomain: number[]; } interface StocsyIndexPointProps { chemicalShift: number | null; } interface StocsyData { x: Float64Array | number[]; y: number[]; color: string[]; yDomain: number[]; } function useStocsy(chemicalShift: number | null): StocsyData | null { const matrix = useMatrix(); return useMemo(() => { if (!matrix) return null; const { x, matrixY } = matrix; const cIndex = xFindClosestIndex(x, chemicalShift ?? x[0]); const { color, y } = matrixToStocsy(matrixY, cIndex); const yDomain = extent(y) as number[]; return { color: color as string[], y, yDomain, x, }; }, [chemicalShift, matrix]); } function useSliceStocsyData(options?: StocsyData | null) { const { xDomain: [from, to], } = useChartData(); return useMemo(() => { if (!options) return null; const { color, y, x } = options; const fromIndex = xFindClosestIndex(x, from); const toIndex = xFindClosestIndex(x, to); return { x: sliceArrayForDomain(x, { fromIndex, toIndex }), y: sliceArrayForDomain(y, { fromIndex, toIndex }), color: sliceArrayForDomain(color, { fromIndex, toIndex }), }; }, [from, options, to]); } export function Stocsy() { const activeTab = useActiveNucleusTab(); const options = usePanelPreferences('matrixGeneration', activeTab); if (!options) return; const { scaleRatio, showStocsy, chemicalShift } = options; if (!showStocsy) return null; return ; } interface InnerStocsyProps { scaleRatio: number; chemicalShift: number | null; } function InnerStocsy(props: InnerStocsyProps) { const { scaleRatio, chemicalShift } = props; const stocsyData = useStocsy(chemicalShift); const data = useSliceStocsyData(stocsyData); if (!data || !stocsyData) return null; const { yDomain } = stocsyData; const { x, y, color } = data; return ( ); } function useYScale(scaleRatio: number, yDomain: number[]) { const { margin, height } = useChartData(); const { spectraBottomMargin } = useScaleChecked(); return getYScaleWithRation({ height, yDomain, scaleRatio, margin, spectraBottomMargin, }); } function StocsyIndexPoint(props: StocsyIndexPointProps) { const { chemicalShift } = props; const { scaleX } = useScaleChecked(); const { xDomain: [from, to], } = useChartData(); if (!chemicalShift || chemicalShift < from || chemicalShift > to) return null; const xPixel = scaleX()(chemicalShift); return ( ); } function RenderStocsyAsSVG(props: StocsyProps) { const { x, y, color, scaleRatio, yDomain } = props; const scaleY = useYScale(scaleRatio, yDomain); const { scaleX } = useScaleChecked(); const colorGroups = groupPointsByColor({ x, y, color }); const xScaler = scaleX(); return Object.keys(colorGroups).map((color) => { const points = colorGroups[color]; const pathBuilder = new PathBuilder(); pathBuilder.moveTo(xScaler(points[0].x), scaleY(points[0].y)); for (let i = 1; i < points.length; i++) { const point = points[i]; pathBuilder.lineTo(xScaler(points[i].x), scaleY(points[i].y)); if (point.endPath && i < points.length - 1) { pathBuilder.moveTo(xScaler(points[i + 1].x), scaleY(points[i + 1].y)); } } return ( ); }); }