diff --git a/src/components/AladinViewer.tsx b/src/components/AladinViewer.tsx
index 29bf0ff..f35f973 100644
--- a/src/components/AladinViewer.tsx
+++ b/src/components/AladinViewer.tsx
@@ -84,7 +84,7 @@ export function AladinViewer({ source, nearbySources }: AladinViewerProps) {
return () => {
aladinInstanceRef.current = null;
};
- }, [source.source_id, source.ra, source.dec, nearbySources]);
+ }, [source.source_id, source.name, source.ra, source.dec, nearbySources]);
return
;
}
diff --git a/src/components/FluxFilterMenu.tsx b/src/components/FluxFilterMenu.tsx
index 8756be1..311fbae 100644
--- a/src/components/FluxFilterMenu.tsx
+++ b/src/components/FluxFilterMenu.tsx
@@ -89,7 +89,7 @@ export default function FluxFilterMenu({
))}
-
+
('unbinned');
const [binnedStartTime, setBinnedStartTime] = useState('');
@@ -419,7 +429,13 @@ export function Lightcurve({
}
data.module[idx] = module;
- populatePoint(data, lightcurve, lightcurveKey, idx, isFlagged);
+ populatePoint(
+ data,
+ lightcurve,
+ lightcurveKey,
+ idx,
+ isFlagged && showFlaggedMarkerStyling
+ );
});
for (const data of tracesByModule.values()) {
@@ -434,7 +450,7 @@ export function Lightcurve({
const data = {
// Used for the marker click tooltip's header (see handleMarkerClick) -
// showlegend is false below since the dual legend explains color/shape instead.
- name: `${lightcurveKey}, f${lightcurve.frequency}`,
+ name: `${instrumentModule}, f${lightcurve.frequency}`,
showlegend: false,
visible: isGroupHidden(lightcurve.frequency, instrumentModule)
? 'legendonly'
@@ -475,7 +491,13 @@ export function Lightcurve({
return;
}
- populatePoint(data, lightcurve, lightcurveKey, idx, isFlagged);
+ populatePoint(
+ data,
+ lightcurve,
+ lightcurveKey,
+ idx,
+ isFlagged && showFlaggedMarkerStyling
+ );
});
finalData.push(data);
@@ -507,6 +529,7 @@ export function Lightcurve({
lightcurveData,
hideFlaggedData,
hiddenLegendGroups,
+ showFlaggedMarkerStyling,
]);
/**
@@ -519,7 +542,7 @@ export function Lightcurve({
margin: { t: legendMarginTop },
yaxis: {
title: {
- text: 'Flux Density (Jy)',
+ text: 'Flux Density (mJy)',
},
},
xaxis: {
@@ -946,7 +969,7 @@ export function Lightcurve({
Flux Density:
{String(Number(clickedMarkerData.data.y).toFixed(3))} +/-{' '}
{String(Number(clickedMarkerData.data.flux_err).toFixed(3))}{' '}
- Jy
+ mJy
Flags:
diff --git a/src/components/SourceFluxFilter.tsx b/src/components/SourceFluxFilter.tsx
index 7a195c5..ed9e011 100644
--- a/src/components/SourceFluxFilter.tsx
+++ b/src/components/SourceFluxFilter.tsx
@@ -42,7 +42,7 @@ function SourceFluxFilter({
Filtered on {appliedBand} from {appliedRange[0]} to{' '}
- {appliedRange[1]} Jy
+ {appliedRange[1]} mJy
= {
- displaylogo: false,
- responsive: true,
-};
-
-function buildTrace(key: string, group: UnassignedFluxMeasurement[]): Data {
- const [frequency, module] = key.split(':');
- const color = frequencyColor(Number(frequency));
- return {
- name: `${module} ยท f${frequency}`,
- // The frequency/optics-tube legend is built separately from legend-only proxy traces (see
- // buildInstrumentLegendTraces) so it can split color and shape into two legends.
- showlegend: false,
- x: group.map((m) => new Date(m.time)),
- y: group.map((m) => m.flux),
- error_y: {
- type: 'data',
- array: group.map((m) => m.flux_err),
- visible: true,
- color: color,
- thickness: 1.5,
- },
- customdata: group.map((m) => [m.frequency, m.module]),
- mode: 'markers',
- type: 'scatter',
- marker: {
- color,
- symbol: moduleSymbol(module),
- size: 6,
- line: { width: 1, color },
- },
- hovertemplate:
- 'Time: %{x|%Y-%m-%d %H:%M:%S UTC}
' +
- 'Flux: %{y:.3f} mJy
' +
- 'Frequency: %{customdata[0]} GHz
' +
- 'Optic tube: %{customdata[1]}',
- };
-}
-
-/** Interactive flux-versus-time plot for an unassigned source; one marker trace per
- * (frequency, module), colored by frequency and shaped by optics tube. */
+/** Interactive flux-versus-time plot for an unassigned source, rendered through the same
+ * Lightcurve component an assigned source's plot uses. There are no cutout images for
+ * unassigned measurements yet, so a clicked marker's tooltip will show "Cutout Not Found"
+ * for now. */
export function UnassignedLightcurvePlot({
+ sourceId,
measurements,
height = 400,
}: UnassignedLightcurvePlotProps) {
- const plotRef = useRef(null);
- const [isDataReady, setIsDataReady] = useState(false);
-
- const plotData = useMemo(() => {
- const grouped = groupMeasurementsByInstrument(measurements);
- return [
- ...Array.from(grouped.entries()).map(([key, group]) =>
- buildTrace(key, group)
- ),
- ...buildInstrumentLegendTraces(measurements),
- ];
- }, [measurements]);
-
- const plotLayout = useMemo>(() => {
- const maxFlux = measurements.length
- ? Math.max(...measurements.map((m) => m.flux))
- : 1;
- return {
- autosize: true,
- margin: INSTRUMENT_LEGEND_MARGIN,
- hovermode: 'closest',
- showlegend: true,
- ...INSTRUMENT_LEGEND_LAYOUT,
- xaxis: { title: { text: 'Observation time (UTC)' } },
- yaxis: {
- title: { text: 'Flux (mJy)' },
- range: [0, maxFlux * 1.05],
- zeroline: false,
- },
- };
- }, [measurements]);
-
- useEffect(() => {
- const el = plotRef.current;
- if (!el) return;
-
- setIsDataReady(false);
- void Plotly.newPlot(el, plotData, plotLayout, PLOT_CONFIG).then(() =>
- setIsDataReady(true)
- );
-
- return () => {
- Plotly.purge(el);
- };
- }, [plotData, plotLayout]);
+ const lightcurveData = useMemo(
+ () => toInstrumentLightcurveData(sourceId, measurements),
+ [sourceId, measurements]
+ );
if (!measurements.length) {
return No detections to plot.
;
}
return (
-
-
- {!isDataReady && (
-
- Loading...
-
- )}
-
+
);
}
diff --git a/src/components/UnassignedSource.tsx b/src/components/UnassignedSource.tsx
index 66de72c..ce93795 100644
--- a/src/components/UnassignedSource.tsx
+++ b/src/components/UnassignedSource.tsx
@@ -310,7 +310,10 @@ export function UnassignedSource() {
showCard={data?.source.status !== 'merged'}
>
{data ? (
-
+
) : (
'Loading...'
)}
diff --git a/src/configs/stats.ts b/src/configs/stats.ts
index 39fb792..eb3705a 100644
--- a/src/configs/stats.ts
+++ b/src/configs/stats.ts
@@ -15,26 +15,26 @@ export const statsKeysToDisplaySpecs: Record = {
min_flux: {
label: 'Min Flux',
precision: numSigFigs,
- units: 'Jy',
+ units: 'mJy',
},
max_flux: {
label: 'Max Flux',
precision: numSigFigs,
- units: 'Jy',
+ units: 'mJy',
},
mean_flux: {
label: 'Mean Flux',
precision: numSigFigs,
- units: 'Jy',
+ units: 'mJy',
},
stddev_flux: {
label: 'Std. Dev. Flux',
precision: numSigFigs,
- units: 'Jy',
+ units: 'mJy',
},
median_flux: {
label: 'Median Flux',
precision: numSigFigs,
- units: 'Jy',
+ units: 'mJy',
},
};
diff --git a/src/utils/unassignedPlotHelpers.ts b/src/utils/unassignedPlotHelpers.ts
index 49b08f7..94880ac 100644
--- a/src/utils/unassignedPlotHelpers.ts
+++ b/src/utils/unassignedPlotHelpers.ts
@@ -1,5 +1,9 @@
import { Data, Legend } from 'plotly.js-dist-min';
-import { UnassignedFluxMeasurement } from '../types';
+import {
+ InstrumentLightcurveData,
+ InstrumentLightcurveMeasurements,
+ UnassignedFluxMeasurement,
+} from '../types';
import {
buildInstrumentLegendTraces as buildInstrumentLegendTracesForItems,
DEFAULT_INSTRUMENT_LEGEND_LAYOUT as BASE_INSTRUMENT_LEGEND_LAYOUT,
@@ -23,6 +27,47 @@ export function groupMeasurementsByInstrument(
return grouped;
}
+/** Converts a flat list of unassigned flux measurements into the same InstrumentLightcurveData
+ * shape Lightcurve.tsx expects for an assigned source's instrument-strategy lightcurve, grouped
+ * by (frequency, module) via groupMeasurementsByInstrument - each unassigned measurement is
+ * already a fixed (frequency, module) pair, exactly like an instrument-strategy point, just not
+ * yet grouped into a record keyed that way. Lets UnassignedLightcurvePlot.tsx render an
+ * unassigned source's flux history through the same Lightcurve component/interactions (flagging,
+ * marker click, cutouts once those exist for unassigned measurements too) as an assigned source,
+ * rather than maintaining a second plot implementation. */
+export function toInstrumentLightcurveData(
+ sourceId: string,
+ measurements: UnassignedFluxMeasurement[]
+): InstrumentLightcurveData {
+ const lightcurves: Record = {};
+
+ for (const [key, group] of groupMeasurementsByInstrument(measurements)) {
+ lightcurves[key] = {
+ frequency: group[0].frequency,
+ source_id: sourceId,
+ module: group[0].module,
+ measurement_id: group.map((m) => m.measurement_id),
+ time: group.map((m) => m.time),
+ ra: group.map((m) => m.ra),
+ ra_uncertainty: group.map((m) => m.ra_uncertainty ?? 0),
+ dec: group.map((m) => m.dec),
+ dec_uncertainty: group.map((m) => m.dec_uncertainty ?? 0),
+ flux: group.map((m) => m.flux),
+ flux_err: group.map((m) => m.flux_err),
+ extra: group.map((m) => m.extra ?? null),
+ };
+ }
+
+ return {
+ source_id: sourceId,
+ // Unassigned measurements aren't fetched through the binned/unbinned endpoints - "none" is
+ // the same convention BaseLightcurveData uses for an unbinned assigned-source response.
+ binning_strategy: 'none',
+ selection_strategy: 'instrument',
+ lightcurves,
+ };
+}
+
/** Offsets a position from the source position onto the source's tangent plane, in arcminutes.
* Equivalent to astropy's SkyCoord.spherical_offsets_to at the arcminute scales these plots
* cover. */