Skip to content

Commit 42e6db2

Browse files
committed
Annotations
1 parent ead4ae6 commit 42e6db2

File tree

4 files changed

+154
-19
lines changed

4 files changed

+154
-19
lines changed

src/components/LightCurvePlot.tsx

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,8 @@ export default function LightCurvePlot() {
378378
const rawPoints = avgPointRawMap[key];
379379

380380
if (rawPoints) {
381-
clearAll()
381+
// clearAll()
382+
clearRawOnly();
382383
setRawPlot(rawPoints);
383384
setFig({
384385
data: [
@@ -528,6 +529,35 @@ export default function LightCurvePlot() {
528529
handleOriginalPointClick(figure, pt);
529530
}
530531
}, [dataType, avgPointRawMap, figure, selectedImages]);
532+
// const handlePointClick = useCallback((e: Plotly.PlotMouseEvent) => {
533+
// const pt = e.points[0];
534+
// if (!figure) return;
535+
536+
// const hasAverage = dataType.includes('average');
537+
// const hasRaw = dataType.includes('raw');
538+
// const hasBoth = hasAverage && hasRaw;
539+
540+
// const dataMode = ((pt.customdata as unknown) as AveragePointCustomData)?.dataMode;
541+
542+
// // BLOCK average clicks when both raw+average selected
543+
// if (hasBoth && dataMode === 'average') {
544+
// console.log("Average click blocked because raw+avg selected");
545+
// return;
546+
// }
547+
548+
// // normal logic
549+
// if (dataMode === '') {
550+
// if (dataType.includes('average')) {
551+
// handleAveragePointClick(figure, pt);
552+
// } else {
553+
// handleOriginalPointClick(figure, pt);
554+
// }
555+
// } else if (dataMode === 'average') {
556+
// handleAveragePointClick(figure, pt);
557+
// } else if (dataMode === 'raw') {
558+
// handleOriginalPointClick(figure, pt);
559+
// }
560+
// }, [dataType, avgPointRawMap, figure, selectedImages]);
531561

532562

533563
function clearAll() {
@@ -545,7 +575,16 @@ export default function LightCurvePlot() {
545575
// setPosX(undefined);
546576
// setPosY(undefined);
547577
}
578+
function clearRawOnly() {
579+
setRawPlot(null);
580+
// setFig({ data: [] });
581+
setUseRawMode(false);
582+
setAnnotations([]);
583+
imageCounter.current = 1; // reset counter
584+
setSelectedImages([]);
585+
setModalInfo(null);
548586

587+
}
549588
// eslint-disable-next-line @typescript-eslint/no-explicit-any
550589
function handleHover(e: any) {
551590
const pt = e.points[0];

src/components/Matrix/PlotlyMatrixPlotWithImg.tsx

Lines changed: 111 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { usePlotSettings } from '@/context/PlotSettingsContext';
77
import { getColorConfig, ColorByMode } from '@/utils/matrix';
88
import dynamic from 'next/dynamic';
99
import { SimpleLinearRegression } from 'ml-regression-simple-linear';
10-
import { useEffect, useMemo, useState, useCallback } from 'react';
10+
import { useEffect, useMemo, useState } from 'react';
1111
import { ImageModal } from '@/components/ImageModal';
1212
// import RawDataPlotPanel from '@/components/RawDataPlotPanel';
1313
import { reducedChiSquared } from '@/libs/math';
@@ -71,6 +71,8 @@ const MatrixPlot = (props: MatrixPlotProps) => {
7171
} | null>(null);
7272
// keep a handle to the GraphDiv
7373
const [gd, setGd] = useState<Plotly.PlotlyHTMLElement | null>(null);
74+
const [userAnnotations, setUserAnnotations] = useState<Partial<Plotly.Annotations>[]>([]);
75+
7476

7577
const { colorBy, focusRangeMax, setSettings, focusRangeMaxManuallySet } = usePlotSettings();
7678
if (!matrixData || matrixData.length === 0) return null;
@@ -90,6 +92,64 @@ const MatrixPlot = (props: MatrixPlotProps) => {
9092
}, [matrixData, xKey, yKey, compute]);
9193
const maxRaw = useMemo(() => (rawValues.length ? Math.max(...rawValues) : 0), [rawValues]);
9294

95+
96+
function makePointAnnotation(pt: Plotly.PlotDatum) {
97+
const xVal = pt.x;
98+
const yVal = pt.y;
99+
100+
const fmt = (v: any) => {
101+
const n = Number(v);
102+
return Number.isFinite(n) ? n.toFixed(2) : String(v);
103+
};
104+
105+
// ---------- CASE 1: 2D scatter ----------
106+
if (!pt.fullData.dimensions) {
107+
return [{
108+
x: xVal,
109+
y: yVal,
110+
xref: "x",
111+
yref: "y",
112+
text: `(${fmt(xVal)}, ${fmt(yVal)})`,
113+
showarrow: true,
114+
arrowhead: 6,
115+
ax: 20,
116+
ay: -30,
117+
bgcolor: 'rgba(0,240,255,0.7)',
118+
bordercolor: 'black',
119+
borderwidth: 1,
120+
font: { color: 'black', size: 12 }
121+
}];
122+
}
123+
124+
// ---------- CASE 2: SPLOM ----------
125+
const n = pt.fullData.dimensions.length;
126+
const anns: any[] = [];
127+
128+
for (let xi = 1; xi <= n; xi++) {
129+
for (let yi = 1; yi <= n; yi++) {
130+
if (yi <= xi) continue;
131+
132+
anns.push({
133+
x: xVal,
134+
y: yVal,
135+
xref: `x${xi}`,
136+
yref: `y${yi}`,
137+
text: `(${fmt(xVal)}, ${fmt(yVal)})`,
138+
showarrow: true,
139+
arrowhead: 6,
140+
ax: 20,
141+
ay: -30,
142+
bgcolor: 'rgba(0,240,255,0.7)',
143+
bordercolor: 'black',
144+
borderwidth: 1,
145+
font: { color: 'black', size: 12 }
146+
});
147+
}
148+
}
149+
150+
return anns;
151+
}
152+
93153
useEffect(() => {
94154
if (!focusRangeMaxManuallySet) {
95155
if (focusRangeMax === 100 || maxRaw > focusRangeMax) {
@@ -188,19 +248,54 @@ const MatrixPlot = (props: MatrixPlotProps) => {
188248
});
189249
}
190250

191-
const handlePointClick = useCallback((e: Plotly.PlotMouseEvent) => {
192-
const pt = e.points[0];
193-
handleOriginalPointClick(pt);
194-
}, [matrixData, dimensions]);
251+
useEffect(() => {
252+
if (!gd) return;
195253

254+
function domClickHandler(ev: MouseEvent) {
255+
// if Plotly captured a click on a point, the 'plotly_click' handler runs first
256+
// so we clear only if NO point was selected
257+
258+
const clickedInsidePlot =
259+
(ev.target as HTMLElement).closest('.plotly') !== null;
260+
261+
if (clickedInsidePlot) {
262+
// check if last event was a point-click
263+
if ((gd as any)._lastPlotlyClickWasPoint) {
264+
(gd as any)._lastPlotlyClickWasPoint = false;
265+
return;
266+
}
267+
268+
// empty-space click → clear
269+
setUserAnnotations([]);
270+
setImageModal(null);
271+
}
272+
}
273+
274+
gd.addEventListener('click', domClickHandler);
275+
276+
return () => gd.removeEventListener('click', domClickHandler);
277+
}, [gd]);
196278
useEffect(() => {
197279
if (!gd) return;
198-
const clickHandler = (ev: any) => handlePointClick(ev);
199-
gd.on?.('plotly_click', clickHandler);
200-
return () => {
201-
try { gd.removeAllListeners?.('plotly_click'); } catch { }
280+
281+
const handler = (ev: any) => {
282+
// mark this click as a point-click so DOM listener won't clear annotations
283+
(gd as any)._lastPlotlyClickWasPoint = true;
284+
285+
const pt = ev.points?.[0];
286+
if (!pt) return;
287+
288+
const anns = makePointAnnotation(pt);
289+
setUserAnnotations(anns);
290+
handleOriginalPointClick(pt);
202291
};
203-
}, [gd, handlePointClick]);
292+
293+
gd.on('plotly_click', handler);
294+
295+
return () => { try { gd.removeAllListeners('plotly_click'); } catch { } };
296+
}, [gd]);
297+
298+
204299

205300
// ===== 2D scatter =====
206301
if (dimensions.length === 2) {
@@ -228,12 +323,13 @@ const MatrixPlot = (props: MatrixPlotProps) => {
228323
xaxis: { title: { text: xKey } },
229324
yaxis: { title: { text: yKey } },
230325
modebar: { orientation: 'v' },
326+
annotations: [...userAnnotations]
231327
}}
232328
config={{ responsive: true }}
233329
onInitialized={(_, graphDiv) => setGd(graphDiv as Plotly.PlotlyHTMLElement)}
234330
onUpdate={(_, graphDiv) => setGd(graphDiv as Plotly.PlotlyHTMLElement)}
235-
// keep onClick too (nice to have)
236-
onClick={(ev) => { console.log('onClick prop', ev); handlePointClick(ev); }}
331+
// keep onClick too (nice to have)
332+
// onClick={(ev) => { console.log('onClick prop', ev); handlePointClick(ev); }}
237333
/>
238334

239335
{imageModal && (
@@ -400,7 +496,7 @@ const MatrixPlot = (props: MatrixPlotProps) => {
400496
modebar: { orientation: 'v' },
401497
height,
402498
font: { size: labelFontSize },
403-
annotations, // your computed annotations
499+
annotations: [...annotations, ...userAnnotations],
404500
updatemenus: [{
405501
type: 'dropdown', direction: 'down', x: 0.75, y: 1, showactive: true,
406502
buttons: [
@@ -413,8 +509,8 @@ const MatrixPlot = (props: MatrixPlotProps) => {
413509
config={{ responsive: true }}
414510
onInitialized={(_, graphDiv) => setGd(graphDiv as Plotly.PlotlyHTMLElement)}
415511
onUpdate={(_, graphDiv) => setGd(graphDiv as Plotly.PlotlyHTMLElement)}
416-
// keep onClick too
417-
onClick={(ev) => { console.log('onClick prop', ev); handlePointClick(ev); }}
512+
// keep onClick too
513+
// onClick={(ev) => { console.log('onClick prop', ev); handlePointClick(ev); }}
418514
/>
419515

420516
{imageModal && (

src/components/Sidebar/Sidebar.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@ const visibilityByPath: Record<string, Partial<Record<string, boolean>>> = {
1717
dataType: false,
1818
dataSelection: true,
1919
averageConfig: true,
20-
matrixSection: true,
20+
matrixSection: false,
2121
},
2222
'/matrix_plotly/': {
2323
dataType: true,
2424
dataSelection: true,
2525
averageConfig: true,
26-
matrixSection: true,
26+
matrixSection: false,
2727
},
2828
'/lite': {
2929
dataType: true,

src/types/PlotTypes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export interface PlotDatumWithBBox extends Plotly.PlotDatum {
7171
y0: number;
7272
y1: number;
7373
};
74-
fullData?: PlotTrace;
74+
fullData: PlotTrace;
7575
}
7676

7777

0 commit comments

Comments
 (0)