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