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 ;
}