import type { NumberArray } from 'cheminfo-types'; import { extent } from 'd3-array'; import { xFindClosestIndex } from 'ml-spectra-processing'; import { matrixToBoxPlot } from 'nmr-processing'; import type { CSSProperties } from 'react'; 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 { sliceArrayForDomain, useMatrix } from './useMatrix.js'; interface InnerBoxplotProps { scaleRatio: number; } interface BaseRenderProps extends InnerBoxplotProps { x: Float64Array | number[]; color: CSSProperties['color']; yDomain: number[]; } interface RenderPathProps extends BaseRenderProps { y: NumberArray; } interface RenderAreaProps extends BaseRenderProps { y1: NumberArray; y2: NumberArray; } interface UsePathLinePoints { x: Float64Array | number[]; y: NumberArray; } interface UsePathAreaPoints { x: Float64Array | number[]; y1: NumberArray; y2: NumberArray; } function useYScale(scaleRatio: number, yDomain: number[]) { const { margin, height } = useChartData(); const { spectraBottomMargin } = useScaleChecked(); return getYScaleWithRation({ height, yDomain, scaleRatio, margin, spectraBottomMargin, }); } function usePath( pathPoints: UsePathLinePoints, scaleRatio: number, yDomain: number[], ) { const { scaleX } = useScaleChecked(); const scaleY = useYScale(scaleRatio, yDomain); const pathBuilder = new PathBuilder(); const xScaler = scaleX(); pathBuilder.moveTo(xScaler(pathPoints.x[0]), scaleY(pathPoints.y[0])); for (let i = 1; i < pathPoints.x.length; i++) { pathBuilder.lineTo(xScaler(pathPoints.x[i]), scaleY(pathPoints.y[i])); } return pathBuilder.toString(); } function useAreaPath( pathPoints: UsePathAreaPoints, scaleRatio: number, yDomain: number[], ) { const { scaleX } = useScaleChecked(); const scaleY = useYScale(scaleRatio, yDomain); const pathBuilder = new PathBuilder(); const pathBuilder2 = new PathBuilder(); const xScaler = scaleX(); pathBuilder.moveTo(xScaler(pathPoints.x[0]), scaleY(pathPoints.y1[0])); for (let i = 1; i < pathPoints.x.length; i++) { pathBuilder.lineTo(xScaler(pathPoints.x[i]), scaleY(pathPoints.y1[i])); } for (let i = pathPoints.x.length - 1; i >= 0; i--) { pathBuilder2.lineTo(xScaler(pathPoints.x[i]), scaleY(pathPoints.y2[i])); } return pathBuilder.concatPath(pathBuilder2); } function useBoxPlot() { const matrix = useMatrix(); const { xDomain: [from, to], } = useChartData(); return useMemo(() => { if (!matrix) return null; const { x, matrixY } = matrix; const { max, min, median, q1, q3 } = matrixToBoxPlot(matrixY); const fromIndex = xFindClosestIndex(x, from); const toIndex = xFindClosestIndex(x, to); const yDomain = extent(median) as number[]; return { x: sliceArrayForDomain(x, { fromIndex, toIndex }), max: sliceArrayForDomain(max, { fromIndex, toIndex }), min: sliceArrayForDomain(min, { fromIndex, toIndex }), median: sliceArrayForDomain(median, { fromIndex, toIndex }), q1: sliceArrayForDomain(q1, { fromIndex, toIndex }), q3: sliceArrayForDomain(q3, { fromIndex, toIndex }), yDomain, }; }, [from, matrix, to]); } export function Boxplot() { const activeTab = useActiveNucleusTab(); const options = usePanelPreferences('matrixGeneration', activeTab); if (!options) return; const { scaleRatio, showBoxPlot } = options; if (!showBoxPlot) return null; return ; } function InnerBoxplot(props: InnerBoxplotProps) { const { scaleRatio } = props; const data = useBoxPlot(); if (!data) return null; const { x, max, min, median, q1, q3, yDomain } = data; return ( ); } function RenderAreaPath(props: RenderAreaProps) { const { x, y1, y2, scaleRatio, color, yDomain } = props; const areaPath = useAreaPath({ x, y1, y2 }, scaleRatio, yDomain); return ; } function RenderPath(props: RenderPathProps) { const { x, y, scaleRatio, color, yDomain } = props; const areaPath = usePath({ x, y }, scaleRatio, yDomain); return ; }