WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit a6ede6a

Browse files
committed
add filtered
1 parent 8010026 commit a6ede6a

File tree

5 files changed

+284
-95
lines changed

5 files changed

+284
-95
lines changed

src/components/LightCurvePlot.tsx

Lines changed: 149 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,21 @@ import { xKeyMap } from '@/utils/xKeyMap';
1313
import { createAnnotation, ModalInfo } from '@/utils/annotationUtils';
1414
import { RenderTooltip } from '@/constants/hoverUtils';
1515
// import { digitize, weightedAvg } from '@/libs/math';
16-
import { PlotTrace, PlotDatumWithBBox, PlotLayout, AveragePointCustomData } from '@/types/PlotTypes';
16+
import { PlotTrace, PlotDatumWithBBox, PlotLayout, AveragePointCustomData, RawPoint } from '@/types/PlotTypes';
1717
import { ImageModal } from '@/components/ImageModal';
1818
import RawDataPlotPanel from '@/components/RawDataPlotPanel';
1919
import { Trash2 } from 'lucide-react';
2020
import { flushSync } from 'react-dom';
2121
import { isDarkRGB } from '@/libs/color';
22+
import { filterRawPointsByAverage, filterRawPointsByAverageCurve } from '@/utils/filterRawByAverage';
23+
import { filter } from 'd3';
2224
const Plot = dynamic(() => import('react-plotly.js'), { ssr: false });
2325

2426
export default function LightCurvePlot() {
2527
const {
2628
dataType, dataSelection, xAxis, errorBars, noOfBins, noOfDataPoint,
2729
plotType, pointSize, lineWidth, legendFontSize, labelFontSize, tooltipFontSize,
28-
figure, rawFigure, avgPointRawMap, setSettings
30+
figure, rawFigure, avgPointRawMap, filterPercent, isFiltered, setSettings
2931
} = usePlotSettings();
3032

3133
const [loading, setLoading] = useState(false);
@@ -35,16 +37,10 @@ export default function LightCurvePlot() {
3537
const [modalInfo, setModalInfo] = useState<ModalInfo | null>(null);
3638
const [tooltipContent, setTooltipContent] = useState<React.ReactNode | null>(null);
3739
const [tooltipPosition, setTooltipPosition] = useState<{ left: number; top: number } | null>(null);
38-
const [rawPlot, setRawPlot] = useState<{
39-
x: number; y: number; err?: number; customdata: unknown;
40-
phase?: number;
41-
mjd?: number;
42-
time?: number;
43-
second?: number;
44-
minute?: number;
45-
hour?: number;
46-
day?: number;
47-
}[] | null>(null);
40+
41+
const [rawPlot, setRawPlot] = useState<RawPoint[] | null>(null);
42+
const [filteredRawPlot, setFilteredRawPlot] = useState<{ tracesSW: PlotTrace[]; tracesLW: PlotTrace[] }>({ tracesSW: [], tracesLW: [] });
43+
const [filterBand, setFilterBand] = useState<{ minY: number; maxY: number } | null>(null);
4844
// const [rawColor, setRawColor] = useState<string>('#1f77b4');
4945
const [rawMarkerBorderColor, setRawMarkerBorderColor] = useState<string>('#000000');
5046
const [rawMarkerFillColor, setRawMarkerFillColor] = useState<string>('#1f77b4');
@@ -130,36 +126,48 @@ export default function LightCurvePlot() {
130126
}
131127
const { traces: traceSW, patch: patchSW } = addTrace({ wave: 'SW', json: swJson, axis: xAxis, mode: dType, noOfBins, noOfDataPoint, timeKey, epoch, r_in: r1, r_out: r2, avgPointRawMap, markerFillColor, markerBorderColor, lineColor, plotType: plotType as 'lines' | 'markers' | 'lines+markers', errorBars: errorBars as 'bar' | 'hide' | 'separate' | undefined, pointSize, lineWidth, dataMode });
132128
rawTracesSW.push(...traceSW);
133-
setSettings({
134-
avgPointRawMap: Object.fromEntries(
135-
Object.entries(patchSW).map(([key, arr]) => [
136-
key,
137-
arr.map(p => ({
138-
...p,
139-
x: typeof p.x === 'number' ? p.x : (typeof p.x === 'string' ? Number(p.x) : (p.x instanceof Date ? p.x.getTime() : 0))
140-
}))
141-
])
142-
)
143-
});
129+
setSettings(prev => ({
130+
avgPointRawMap: {
131+
...prev.avgPointRawMap,
132+
...Object.fromEntries(
133+
Object.entries(patchSW).map(([key, arr]) => [
134+
key,
135+
arr.map(p => ({
136+
...p,
137+
x: typeof p.x === 'number'
138+
? p.x
139+
: (typeof p.x === 'string'
140+
? Number(p.x)
141+
: (p.x instanceof Date
142+
? p.x.getTime()
143+
: 0))
144+
}))
145+
])
146+
)
147+
}
148+
}));
144149
const { traces: traceLW, patch: patchLW } = addTrace({ wave: 'LW', json: lwJson, axis: xAxis, mode: dType, noOfBins, noOfDataPoint, timeKey, epoch, r_in: r1, r_out: r2, avgPointRawMap, markerFillColor, markerBorderColor, lineColor, plotType: plotType as 'lines' | 'markers' | 'lines+markers', errorBars: errorBars as 'bar' | 'hide' | 'separate' | undefined, pointSize, lineWidth, dataMode });
145150
rawTracesLW.push(...traceLW);
146-
setSettings({
147-
avgPointRawMap: Object.fromEntries(
148-
Object.entries(patchLW).map(([key, arr]) => [
149-
key,
150-
arr.map(p => ({
151-
...p,
152-
x: typeof p.x === 'number'
153-
? p.x
154-
: (typeof p.x === 'string'
155-
? Number(p.x)
156-
: (p.x instanceof Date
157-
? p.x.getTime()
158-
: 0))
159-
}))
160-
])
161-
)
162-
});
151+
setSettings(prev => ({
152+
avgPointRawMap: {
153+
...prev.avgPointRawMap,
154+
...Object.fromEntries(
155+
Object.entries(patchLW).map(([key, arr]) => [
156+
key,
157+
arr.map(p => ({
158+
...p,
159+
x: typeof p.x === 'number'
160+
? p.x
161+
: (typeof p.x === 'string'
162+
? Number(p.x)
163+
: (p.x instanceof Date
164+
? p.x.getTime()
165+
: 0))
166+
}))
167+
])
168+
)
169+
}
170+
}));
163171

164172
}
165173
} catch (err) {
@@ -230,6 +238,7 @@ export default function LightCurvePlot() {
230238
];
231239

232240
setSettings({ rawFigure: { tracesSW: rawTracesSW, tracesLW: rawTracesLW } });
241+
setFilteredRawPlot({ tracesSW: rawTracesSW, tracesLW: rawTracesLW });
233242
setSettings({ figure: { data: fullData, layout: layout } });
234243
setFig({ data: fullData });
235244
setSettings({ avgPointRawMap });
@@ -240,7 +249,102 @@ export default function LightCurvePlot() {
240249
}, [dataSelection, dataType, xAxis, errorBars, noOfBins, noOfDataPoint]);
241250

242251
useEffect(() => {
243-
if (!rawFigure) return;
252+
if (!rawFigure || !avgPointRawMap) return;
253+
if (!isFiltered) { setFilteredRawPlot(rawFigure) }
254+
else {
255+
const tracesSW = rawFigure.tracesSW.map(t => ({ ...t }));
256+
const tracesLW = rawFigure.tracesLW.map(t => ({ ...t }));
257+
258+
tracesSW.forEach((trace: PlotTrace) => {
259+
const name = trace.name ?? "";
260+
if (!name.includes("_average")) return;
261+
const base_raw_name = name.split(" ")[0].replace("_average", "_raw");
262+
const rawTraceIndex = tracesSW.findIndex(t =>
263+
(t.name ?? "").startsWith(base_raw_name)
264+
);
265+
const newRawTrace = {
266+
...tracesSW[rawTraceIndex],
267+
x: [],
268+
y: [],
269+
err: [],
270+
customdata: []
271+
};
272+
273+
trace.y?.forEach((avgY, index) => {
274+
const cd = trace.customdata?.[index] as AveragePointCustomData | undefined;
275+
if (!cd) return;
276+
277+
const { type, epoch, r_in, r_out, phase } = cd;
278+
279+
const key = `${type}_${epoch}_${r_in}_${r_out}_${Number(phase).toFixed(5)}`;
280+
const rawList = avgPointRawMap[key];
281+
if (!rawList) return;
282+
283+
const { kept } = filterRawPointsByAverage(rawList, avgY, filterPercent);
284+
285+
newRawTrace.x.push(...kept.map(p => p.x));
286+
newRawTrace.y.push(...kept.map(p => p.y));
287+
newRawTrace.err.push(...kept.map(p => p.err));
288+
newRawTrace.customdata.push(...kept.map(p => p.customdata));
289+
});
290+
newRawTrace.name = `${base_raw_name} (${newRawTrace.y.length}/${rawFigure.tracesSW[rawTraceIndex].y.length})`;
291+
if (xAxis === 'phase') {
292+
newRawTrace.x = newRawTrace.x.concat((newRawTrace.x as number[]).map(v => v + 1));
293+
newRawTrace.y = newRawTrace.y.concat(newRawTrace.y);
294+
newRawTrace.err = newRawTrace.err.concat(newRawTrace.err);
295+
newRawTrace.customdata = newRawTrace.customdata.concat(newRawTrace.customdata);
296+
}
297+
tracesSW[rawTraceIndex] = newRawTrace;
298+
});
299+
tracesLW.forEach((trace: PlotTrace) => {
300+
const name = trace.name ?? "";
301+
if (!name.includes("_average")) return;
302+
const base_raw_name = name.split(" ")[0].replace("_average", "_raw");
303+
const rawTraceIndex = tracesLW.findIndex(t =>
304+
(t.name ?? "").startsWith(base_raw_name)
305+
);
306+
const newRawTrace = {
307+
...tracesLW[rawTraceIndex],
308+
x: [],
309+
y: [],
310+
err: [],
311+
customdata: []
312+
};
313+
314+
trace.y?.forEach((avgY, index) => {
315+
const cd = trace.customdata?.[index] as AveragePointCustomData | undefined;
316+
if (!cd) return;
317+
318+
const { type, epoch, r_in, r_out, phase } = cd;
319+
320+
const key = `${type}_${epoch}_${r_in}_${r_out}_${Number(phase).toFixed(5)}`;
321+
const rawList = avgPointRawMap[key];
322+
if (!rawList) return;
323+
324+
const { kept } = filterRawPointsByAverage(rawList, avgY, filterPercent);
325+
326+
newRawTrace.x.push(...kept.map(p => p.x));
327+
newRawTrace.y.push(...kept.map(p => p.y));
328+
newRawTrace.err.push(...kept.map(p => p.err));
329+
newRawTrace.customdata.push(...kept.map(p => p.customdata));
330+
});
331+
newRawTrace.name = `${base_raw_name} (${newRawTrace.y.length}/${rawFigure.tracesLW[rawTraceIndex].y.length})`;
332+
if (xAxis === 'phase') {
333+
newRawTrace.x = newRawTrace.x.concat((newRawTrace.x as number[]).map(v => v + 1));
334+
newRawTrace.y = newRawTrace.y.concat(newRawTrace.y);
335+
newRawTrace.err = newRawTrace.err.concat(newRawTrace.err);
336+
newRawTrace.customdata = newRawTrace.customdata.concat(newRawTrace.customdata);
337+
}
338+
tracesLW[rawTraceIndex] = newRawTrace;
339+
});
340+
341+
setFilteredRawPlot({ tracesSW, tracesLW });
342+
}
343+
}, [avgPointRawMap, dataSelection, rawFigure, filterPercent, isFiltered, xAxis]);
344+
345+
346+
useEffect(() => {
347+
if (!rawFigure || !filteredRawPlot) return;
244348
const applyStyle = (tr: PlotTrace, axisX: string, axisY: string) => ({
245349
...tr,
246350
mode: plotType as 'lines' | 'markers' | 'lines+markers',
@@ -249,19 +353,18 @@ export default function LightCurvePlot() {
249353
xaxis: axisX,
250354
yaxis: axisY,
251355
});
252-
console.log("Updating figure with new styles");
253-
console.log(figure?.layout);
356+
254357
setSettings({
255358
figure: {
256359
data: [
257-
...rawFigure.tracesSW.map(tr => applyStyle(tr, 'x2', 'y')),
258-
...rawFigure.tracesLW.map(tr => applyStyle(tr, 'x2', 'y2')),
360+
...filteredRawPlot.tracesSW.map(tr => applyStyle(tr, 'x2', 'y')),
361+
...filteredRawPlot.tracesLW.map(tr => applyStyle(tr, 'x2', 'y2')),
259362
],
260363
layout: figure?.layout || {}
261364
}
262365
});
263366

264-
}, [plotType, pointSize, lineWidth, rawFigure]);
367+
}, [plotType, pointSize, lineWidth, rawFigure, filteredRawPlot, isFiltered]);
265368

266369
useEffect(() => {
267370
clearAll();

src/components/Sidebar/Sidebar.tsx

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Select from 'react-select';
55
import { ChevronLeft, ChevronRight } from 'lucide-react';
66
import { usePlotSettings } from '@/context/PlotSettingsContext';
77
import { usePathname } from 'next/navigation';
8+
89
const visibilityByPath: Record<string, Partial<Record<string, boolean>>> = {
910
'/': {
1011
dataType: true,
@@ -33,7 +34,7 @@ const visibilityByPath: Record<string, Partial<Record<string, boolean>>> = {
3334

3435
export default function Sidebar() {
3536
const {
36-
dataType, dataSelection, xAxis, errorBars, noOfBins, noOfDataPoint, colorBy, setSettings,
37+
dataType, dataSelection, xAxis, errorBars, noOfBins, noOfDataPoint, colorBy, filterPercent, isFiltered, setSettings,
3738
// focusRangeMax
3839
} = usePlotSettings();
3940

@@ -215,6 +216,50 @@ export default function Sidebar() {
215216
/>
216217
</div>
217218
)}
219+
{/* Raw Filter Slider — only when both average + raw modes are selected */}
220+
{dataType.includes('average') && dataType.includes('raw') && (
221+
<div className="space-y-2 mb-4">
222+
<label className="flex items-center gap-2 font-medium text-sm text-gray-700">
223+
<input
224+
type="checkbox"
225+
checked={isFiltered}
226+
onChange={(e) => setSettings({ isFiltered: e.target.checked })}
227+
className="w-4 h-4"
228+
/>
229+
Enable Raw Filtering
230+
</label>
231+
{isFiltered && (
232+
<>
233+
<label className="font-medium text-sm text-gray-700">
234+
Raw Filter Range: ±{filterPercent}%
235+
</label>
236+
237+
<input
238+
type="range"
239+
min={1}
240+
max={200}
241+
defaultValue={filterPercent}
242+
className="w-full"
243+
onMouseUp={(e) => setSettings({ filterPercent: Number(e.currentTarget.value) })}
244+
onTouchEnd={(e) => setSettings({ filterPercent: Number(e.currentTarget.value) })}
245+
/>
246+
247+
<input
248+
type="number"
249+
min={1}
250+
max={200}
251+
defaultValue={filterPercent}
252+
onBlur={(e) => setSettings({ filterPercent: Number(e.target.value) })}
253+
className="w-full px-2 py-1 border border-gray-300 rounded text-sm"
254+
/>
255+
256+
<p className="text-xs text-gray-500">
257+
Keeps raw points whose Y-values fall within ±{filterPercent}% of the average curve.
258+
</p>
259+
</>)}
260+
</div>
261+
)}
262+
218263
</Section>
219264
)}
220265

src/context/PlotSettingsContext.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ type PlotSettings = {
3333
colorBy: string;
3434
focusRangeMax: number;
3535
focusRangeMaxManuallySet: boolean;
36+
37+
isFiltered: boolean;
38+
filterPercent: number;
3639
setSettings: (updates: Partial<PlotSettings>) => void;
3740
};
3841

@@ -66,6 +69,8 @@ const defaultValue: PlotSettings = {
6669
focusRangeMax: 100,
6770
focusRangeMaxManuallySet: false,
6871

72+
isFiltered: false,
73+
filterPercent: 10,
6974
setSettings: () => { },
7075
};
7176

0 commit comments

Comments
 (0)