Skip to content

Commit d8ecad5

Browse files
committed
img with ring
1 parent 07c985a commit d8ecad5

4 files changed

Lines changed: 685 additions & 556 deletions

File tree

src/components/ImageModal.tsx

Lines changed: 126 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client';
22

3-
import { Fragment } from 'react';
3+
import { Fragment, useEffect, useMemo, useState } from 'react';
44
import { Dialog, Transition } from '@headlessui/react';
55

66
/*
@@ -29,6 +29,19 @@ interface ImageModalProps {
2929
/** Tailwind class controlling Dialog width (e.g. max-w-2xl) */
3030
maxWidthClass?: string;
3131
}
32+
function buildAnnulusPath(
33+
original: string,
34+
r_in: number | string,
35+
r_out: number | string,
36+
withLabel = false
37+
) {
38+
const clean = (v: number | string) => String(v).trim().replace(/\.0+$/, '');
39+
const label = withLabel ? '_label' : '';
40+
return original.replace(
41+
/(\.(?:png|jpe?g|webp))(?=($|[?#]))/i,
42+
`_rin${clean(r_in)}_rout${clean(r_out)}${label}$1`
43+
);
44+
}
3245

3346
export function ImageModal({
3447
isOpen,
@@ -38,29 +51,85 @@ export function ImageModal({
3851
title = 'Details',
3952
maxWidthClass = 'max-w-2xl',
4053
}: ImageModalProps) {
54+
const [previewSrc, setPreviewSrc] = useState<string | undefined>(imgSrc);
55+
const [focusedRow, setFocusedRow] = useState<number | null>(null);
56+
const [labelOn, setLabelOn] = useState(false);
57+
const [selectedPair, setSelectedPair] = useState<{ r_in: string; r_out: string } | null>(null);
58+
59+
useEffect(() => {
60+
if (isOpen) {
61+
setPreviewSrc(imgSrc);
62+
setFocusedRow(null);
63+
setSelectedPair(null);
64+
// keep previous labelOn state (user preference)
65+
}
66+
}, [isOpen, imgSrc]);
67+
68+
const baseSrc = useMemo(() => imgSrc ?? '', [imgSrc]);
69+
70+
function showAnnulus(r_in: string, r_out: string, rowIndex: number) {
71+
if (!baseSrc) return;
72+
setSelectedPair({ r_in, r_out });
73+
setPreviewSrc(buildAnnulusPath(baseSrc, r_in, r_out, labelOn));
74+
setFocusedRow(rowIndex);
75+
}
76+
77+
function showOriginal() {
78+
setPreviewSrc(baseSrc);
79+
setFocusedRow(null);
80+
setSelectedPair(null);
81+
}
82+
83+
function toggleLabel() {
84+
setLabelOn(prev => {
85+
const next = !prev;
86+
// Only affects annulus view; original remains unchanged
87+
if (focusedRow !== null && selectedPair && baseSrc) {
88+
setPreviewSrc(buildAnnulusPath(baseSrc, selectedPair.r_in, selectedPair.r_out, next));
89+
}
90+
return next;
91+
});
92+
}
4193
return (
4294
<Transition show={isOpen} as={Fragment}>
4395
<Dialog onClose={onClose} className="relative z-99999">
4496
<div className="fixed inset-0 bg-black/30" aria-hidden="true" />
4597
<div className="fixed inset-0 flex items-center justify-center p-4">
46-
<Dialog.Panel
47-
className={`${maxWidthClass} w-full rounded-xl bg-white p-6 shadow-xl`}
48-
>
49-
{imgSrc && (
50-
<img src={imgSrc} alt="preview" className="w-full mb-4 rounded" />
98+
<Dialog.Panel className={`${maxWidthClass} w-full rounded-xl bg-white p-6 shadow-xl`}>
99+
{/* Label toggle row (separate line, top-right) */}
100+
<div className="mb-2 flex items-center justify-end">
101+
<button
102+
type="button"
103+
onClick={toggleLabel}
104+
className={`px-3 py-1 rounded-full text-sm shadow
105+
${labelOn ? 'bg-indigo-600 text-white' : 'bg-gray-200 text-gray-800 hover:bg-gray-300'}`}
106+
title="Toggle label on annulus images"
107+
>
108+
Label: {labelOn ? 'ON' : 'OFF'}
109+
</button>
110+
</div>
111+
112+
{/* Image below the toggle */}
113+
{previewSrc && (
114+
<img src={previewSrc} alt="preview" className="w-full mb-4 rounded" />
51115
)}
116+
52117
{title && (
53118
<Dialog.Title className="text-lg font-semibold mb-2 text-black">
54119
{title}
55120
</Dialog.Title>
56121
)}
122+
57123
{details && (
58124
<div className="mt-4 text-m space-y-4 text-black">
59-
{/* Metadata Summary */}
60125
<div className="space-y-1">
61126
<p><span className="font-semibold">Label:</span> {details.label}</p>
62-
{details.phase && <p><span className="font-semibold">Phase:</span> {details.phase}</p>}
63-
{details.mjd && <p><span className="font-semibold">MJD:</span> {details.mjd}</p>}
127+
{details.phase !== undefined && (
128+
<p><span className="font-semibold">Phase:</span> {details.phase}</p>
129+
)}
130+
{details.mjd !== undefined && (
131+
<p><span className="font-semibold">MJD:</span> {details.mjd}</p>
132+
)}
64133
{details.filename && (
65134
<p>
66135
<span className="font-semibold">Filename:</span>{' '}
@@ -69,7 +138,6 @@ export function ImageModal({
69138
)}
70139
</div>
71140

72-
{/* Table of Values */}
73141
{Array.isArray(details.rows) && details.rows.length > 0 && (
74142
<div className="border-t pt-3 overflow-x-auto">
75143
<table className="min-w-full text-sm border border-gray-300 rounded overflow-hidden">
@@ -80,37 +148,63 @@ export function ImageModal({
80148
<th className="px-3 py-2 text-left">r_in</th>
81149
<th className="px-3 py-2 text-left">r_out</th>
82150
<th className="px-3 py-2 text-left">y</th>
151+
<th className="px-3 py-2 text-right">Annulus</th>
83152
</tr>
84153
</thead>
85154
<tbody>
86-
{details.rows.map((row, idx) => (
87-
<tr key={idx} className="border-t hover:bg-gray-50">
88-
<td className="px-3 py-1">
89-
{row.color ? (
90-
<div
91-
className="w-4 h-4 rounded-full border border-gray-300"
92-
style={{ backgroundColor: row.color }}
93-
title={row.color}
94-
/>
95-
) : (
96-
<div className="w-4 h-4 rounded-full border border-gray-200 bg-white" title="No color" />
97-
)}
98-
</td>
99-
<td className="px-3 py-1 font-mono text-black">{row.epoch}</td>
100-
<td className="px-3 py-1 font-mono text-black">{row.r_in}</td>
101-
<td className="px-3 py-1 font-mono text-black">{row.r_out}</td>
102-
<td className="px-3 py-1 text-black font-semibold">{row.y.toFixed(2)}</td>
103-
{/* <td className="px-3 py-1 text-blue-600 font-semibold">{row.y.toFixed(2)}</td> */}
104-
</tr>
105-
))}
155+
{details.rows.map((row, idx) => {
156+
const isFocused = focusedRow === idx;
157+
return (
158+
<tr
159+
key={idx}
160+
className={`border-t ${isFocused ? 'bg-indigo-50 ring-1 ring-indigo-300' : 'hover:bg-gray-50'}`}
161+
>
162+
<td className="px-3 py-1">
163+
{row.color ? (
164+
<div
165+
className="w-4 h-4 rounded-full border border-gray-300"
166+
style={{ backgroundColor: row.color }}
167+
title={row.color}
168+
/>
169+
) : (
170+
<div className="w-4 h-4 rounded-full border border-gray-200 bg-white" title="No color" />
171+
)}
172+
</td>
173+
<td className="px-3 py-1 font-mono text-black">{row.epoch}</td>
174+
<td className="px-3 py-1 font-mono text-black">{row.r_in}</td>
175+
<td className="px-3 py-1 font-mono text-black">{row.r_out}</td>
176+
<td className="px-3 py-1 text-black font-semibold">{row.y.toFixed(2)}</td>
177+
<td className="px-3 py-1 text-right">
178+
{isFocused ? (
179+
<button
180+
type="button"
181+
onClick={showOriginal}
182+
className="px-2 py-1 rounded bg-gray-800 text-white hover:bg-gray-700"
183+
title="Back to original image"
184+
>
185+
Original
186+
</button>
187+
) : (
188+
<button
189+
type="button"
190+
onClick={() => showAnnulus(row.r_in, row.r_out, idx)}
191+
className="px-2 py-1 rounded bg-blue-600 text-white hover:bg-blue-700"
192+
title="Show annulus image"
193+
>
194+
Annulus
195+
</button>
196+
)}
197+
</td>
198+
</tr>
199+
);
200+
})}
106201
</tbody>
107202
</table>
108203
</div>
109204
)}
110205
</div>
111206
)}
112207

113-
114208
<button
115209
onClick={onClose}
116210
className="mt-6 px-4 py-2 rounded bg-gray-800 text-white"
@@ -122,4 +216,4 @@ export function ImageModal({
122216
</Dialog>
123217
</Transition>
124218
);
125-
}
219+
}

src/components/LightCurvePlot.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ import dynamic from 'next/dynamic';
55
import { useEffect, useState, useCallback, useRef } from 'react';
66
import { usePlotSettings } from '@/context/PlotSettingsContext';
77
// import { buildTraces } from '@/utils/buildTraces';
8-
import { addTrace, buildTraces } from '@/utils/traces';
8+
import { addTrace } from '@/utils/traces';
9+
// import { addTrace, buildTraces } from '@/utils/traces';
910
import { colorList } from '@/constants/colors';
1011
import { xKeyMap } from '@/utils/xKeyMap';
1112
import { createAnnotation, ModalInfo } from '@/utils/annotationUtils';
1213
import { RenderTooltip } from '@/constants/hoverUtils';
13-
import { digitize, weightedAvg } from '@/libs/math';
14+
// import { digitize, weightedAvg } from '@/libs/math';
1415
import { PlotTrace, PlotDatumWithBBox, PlotLayout, AveragePointCustomData } from '@/types/PlotTypes';
1516
import { ImageModal } from '@/components/ImageModal';
1617
import RawDataPlotPanel from '@/components/RawDataPlotPanel';
@@ -486,12 +487,18 @@ export default function LightCurvePlot() {
486487

487488
function clearAll() {
488489
setRawPlot(null);
489-
setFig({ data: [] });
490+
// setFig({ data: [] });
490491
setUseRawMode(false);
491492
setAnnotations([]);
492493
imageCounter.current = 1; // reset counter
493494
// Reset selected images
494495
setSelectedImages([]);
496+
setModalInfo(null);
497+
setAvgAnnotations([]);
498+
setRawAverageValue(undefined);
499+
setRawAverageError(undefined);
500+
setPosX(undefined);
501+
setPosY(undefined);
495502
}
496503

497504
// eslint-disable-next-line @typescript-eslint/no-explicit-any

0 commit comments

Comments
 (0)