diff --git a/src/component/1d/Chart1D.tsx b/src/component/1d/Chart1D.tsx index b434b7604a..fedbff82d3 100644 --- a/src/component/1d/Chart1D.tsx +++ b/src/component/1d/Chart1D.tsx @@ -10,6 +10,7 @@ import XAxis from './XAxis'; import DatabaseElements from './database/DatabaseElements'; import IntegralsSeries from './integral/IntegralsSeries'; import JGraph from './jCouplingGraph/JGraph'; +import { Stocsy } from './matrix/Stocsy'; import MultiAnalysisRanges from './multiAnalysis/MultiAnalysisRanges'; import MultiplicityTrees from './multiplicityTree/MultiplicityTrees'; import { PeakEditionProvider } from './peaks/PeakEditionManager'; @@ -61,8 +62,10 @@ function Chart1D({ mode, width, height, margin, displayerKey }) { + + diff --git a/src/component/1d/matrix/Stocsy.tsx b/src/component/1d/matrix/Stocsy.tsx new file mode 100644 index 0000000000..c3b8f1a7f5 --- /dev/null +++ b/src/component/1d/matrix/Stocsy.tsx @@ -0,0 +1,146 @@ +import { extent } from 'd3'; +import { matrixToStocsy } from 'nmr-processing'; +import { useEffect, useRef } from 'react'; + +import { useChartData } from '../../context/ChartContext'; +import { + withExport, + withExportRegister, +} from '../../context/ExportPrepareContext'; +import { useScaleChecked } from '../../context/ScaleContext'; +import { getYScaleWithRation } from '../utilities/scale'; + +import { useMatrix } from './useMatrix'; + +interface StocsyProps { + x: Float64Array | never[]; + y: Float64Array; + color: string[]; + yDomain: number[]; +} + +const componentId = 'stocsy'; + +export function Stocsy() { + const matrix = useMatrix(); + if (!matrix) return null; + const { x, matrixY } = matrix; + const { color, y } = matrixToStocsy(matrixY, 0); + const yDomain = extent(y) as number[]; + + return ( + + + + + ); +} + +const RenderStocsyAsCanvas = withExportRegister((props: StocsyProps) => { + const { x, y, color, yDomain } = props; + + const { width, margin, height } = useChartData(); + const canvasRef = useRef(null); + const { scaleX } = useScaleChecked(); + + const scaleY = getYScaleWithRation({ + height, + yDomain, + scaleRatio: 1, + margin, + }); + + const { right, bottom } = margin; + const canvasWidth = width - right; + const canvasHeight = height - bottom; + + useEffect(() => { + const canvas = canvasRef.current; + const ctx = canvas?.getContext('2d'); + + if (ctx) { + ctx.fillStyle = 'background-color: transparent'; + } + + // Function to draw lines in a rectangle + function drawLinesInRect(x1, x2, y1, y2, color) { + if (!ctx) return; + ctx.strokeStyle = color; + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.moveTo(x1, x2); + ctx.lineTo(y1, y2); + ctx.stroke(); + } + + let index = 0; + for (const val of x) { + if (index === x.length - 1) { + break; + } + const nextIndex = index + 1; + + const x1 = scaleX()(val); + const y1 = scaleY(y[index]); + const x2 = scaleX()(x[nextIndex]); + const y2 = scaleY(y[nextIndex]); + + drawLinesInRect(x1, y1, x2, y2, color[index]); + index++; + } + + return () => { + if (ctx && canvas) { + ctx.clearRect(0, 0, canvas.width, canvas.height); + } + }; + }, [color, height, scaleX, scaleY, width, x, y]); + + return ( + + + + ); +}, componentId); + +const RenderStocsyAsSVG = withExport((props: StocsyProps) => { + const { x, y, color, yDomain } = props; + const { margin, height } = useChartData(); + const { scaleX } = useScaleChecked(); + + const scaleY = getYScaleWithRation({ + height, + yDomain, + scaleRatio: 1, + margin, + }); + + return Array.from(x).map((val, index) => { + if (index === x.length - 1 || typeof val !== 'number') { + return null; + } + const nextIndex = index + 1; + const x1 = scaleX()(val); + const y1 = scaleY(y[index]); + const x2 = scaleX()(x[nextIndex]); + const y2 = scaleY(y[nextIndex]); + + return ( + + ); + }); +}, componentId); diff --git a/src/component/1d/matrix/useMatrix.ts b/src/component/1d/matrix/useMatrix.ts new file mode 100644 index 0000000000..134f5e9232 --- /dev/null +++ b/src/component/1d/matrix/useMatrix.ts @@ -0,0 +1,58 @@ +import { Spectrum } from 'nmr-load-save'; + +import { isSpectrum1D } from '../../../data/data1d/Spectrum1D'; +import { useChartData } from '../../context/ChartContext'; +import useSpectraByActiveNucleus from '../../hooks/useSpectraPerNucleus'; + +interface MatrixColor { + color: string; + start: number; + end: number; +} + +export function interpolateNumber( + inputRange: [number, number], + outputRange: [number, number], +) { + return (value) => + outputRange[0] + + ((value - inputRange[0]) * (outputRange[1] - outputRange[0])) / + (inputRange[1] - inputRange[0]); +} + +export function mapMatrixColors(colors) { + if (colors.length === 0) return []; + + const result: MatrixColor[] = []; + let start = 0; + + for (let i = 1; i <= colors.length; i++) { + if (colors[i] !== colors[start]) { + result.push({ color: colors[start], start, end: i - 1 }); + start = i; + } + } + + return result; +} + +function getX(spectra: Spectrum[]) { + const spectrum = spectra?.[0]; + + if (!spectrum || !isSpectrum1D(spectrum)) return []; + return spectrum.data.x; +} + +export function useMatrix() { + const { displayerMode } = useChartData(); + const spectra = useSpectraByActiveNucleus(); + + if (displayerMode !== '1D') return null; + + return { + x: getX(spectra) || [], + matrixY: spectra.map((spectrum) => + isSpectrum1D(spectrum) ? spectrum.data.re : [], + ), + }; +} diff --git a/src/component/1d/utilities/scale.ts b/src/component/1d/utilities/scale.ts index 2cddefa67e..bcd2856f85 100644 --- a/src/component/1d/utilities/scale.ts +++ b/src/component/1d/utilities/scale.ts @@ -78,6 +78,14 @@ function getIntegralYScale(options: IntegralYScaleOptions) { [height * 0.3, margin.top + height * 0.1], ); } +function getYScaleWithRation(options: IntegralYScaleOptions) { + const { height, margin, yDomain, scaleRatio } = options; + const [min, max] = yDomain; + return scaleLinear( + [min * scaleRatio, max * scaleRatio], + [height - margin.bottom, margin.top], + ); +} function reScaleY(scale: number, { domain, height, margin }) { const _scale = scaleLinear(domain, [height - margin.bottom, margin.top]); @@ -119,4 +127,5 @@ export { getYScale, getIntegralYScale, reScaleY, + getYScaleWithRation, };