keep hidden events hidden across date-range changes

Clicking a legend item on the events chart toggled hidden via
chart.current.getDatasetMeta(idx).hidden, which lives on Chart.js's
per-dataset meta object. Each time chartData changed (date-range
switch, refetch, focusLabel update) the second useEffect in Chart.tsx
replaces datasets wholesale, Chart.js regenerates the meta, and the
hidden flags vanish, so previously-toggled-off events came back on
their own.

Lift the hidden state into React. EventsChart owns a Set<string> of
hidden labels and passes it down via a new optional hiddenLabels prop
on Chart, plus an onLegendClick callback for controlled toggling.
Chart re-applies hidden after the existing focusLabel pass so the set
survives every data refresh, and falls back to the original
meta-based behaviour when no callback is provided so other charts
(website overview, revenue) keep their existing semantics.

Verified with the seeded Demo SaaS data: hide signup_started in the
Last 24 hours view, switch to Last 7 days then Last 30 days, the
label stays greyed in the legend and absent from the bars; clicking
again restores it. State is component-scoped, so it intentionally
resets on reload or navigation away from the events page.
This commit is contained in:
Yan
2026-05-08 08:38:15 -04:00
parent a9508e7aae
commit 4f72d17faf
2 changed files with 37 additions and 1 deletions
+25 -1
View File
@@ -18,6 +18,8 @@ export interface ChartProps extends BoxProps {
updateMode?: UpdateMode;
animationDuration?: number;
onTooltip?: (model: any) => void;
hiddenLabels?: Set<string>;
onLegendClick?: (label: string, willBeHidden: boolean) => void;
}
export function Chart({
@@ -27,6 +29,8 @@ export function Chart({
updateMode,
onTooltip,
chartOptions,
hiddenLabels,
onLegendClick,
...props
}: ChartProps) {
const canvas = useRef(null);
@@ -61,6 +65,15 @@ export function Chart({
}, [chartOptions]);
const handleLegendClick = (item: LegendItem) => {
if (onLegendClick && type === 'bar') {
// Controlled mode: caller owns the hidden state. We report the click
// and let the parent push a new hiddenLabels set on the next render.
const { datasetIndex } = item;
const ds = chart.current.data.datasets[datasetIndex];
onLegendClick(ds.label, !ds.hidden);
return;
}
if (type === 'bar') {
const { datasetIndex } = item;
const meta = chart.current.getDatasetMeta(datasetIndex);
@@ -111,13 +124,24 @@ export function Chart({
});
}
// Re-apply caller-driven hidden flags after focusLabel handling so a
// dataset stays hidden across data changes (e.g. date-range switches)
// even though Chart.js regenerates dataset meta on every replace.
if (hiddenLabels) {
chart.current.data.datasets.forEach((ds: { hidden: boolean; label: any }) => {
if (hiddenLabels.has(ds.label)) {
ds.hidden = true;
}
});
}
chart.current.options = options;
chart.current.update(updateMode);
setLegendItems(chart.current.legend.legendItems);
}
}, [chartData, options, updateMode]);
}, [chartData, options, updateMode, hiddenLabels]);
return (
<Column gap="6">
+12
View File
@@ -26,6 +26,16 @@ export function EventsChart({ websiteId, focusLabel, limit }: EventsChartProps)
const { locale, dateLocale } = useLocale();
const { data, isLoading, error } = useWebsiteEventsSeriesQuery(websiteId, { limit });
const [label, setLabel] = useState<string>(focusLabel);
const [hiddenLabels, setHiddenLabels] = useState<Set<string>>(() => new Set());
const handleLegendClick = useCallback((legendLabel: string, willBeHidden: boolean) => {
setHiddenLabels(prev => {
const next = new Set(prev);
if (willBeHidden) next.add(legendLabel);
else next.delete(legendLabel);
return next;
});
}, []);
const chartData: any = useMemo(() => {
if (!data) return;
@@ -87,6 +97,8 @@ export function EventsChart({ websiteId, focusLabel, limit }: EventsChartProps)
stacked={true}
renderXLabel={renderXLabel}
height="400px"
hiddenLabels={hiddenLabels}
onLegendClick={handleLegendClick}
/>
)}
</LoadingPanel>