Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,12 @@ export function ChartLegendCompound({
const currentTotal = useMemo((): number | null => {
if (!activePayload?.length) return grandTotal;

// Collect all series values from the hovered data point, preserving nulls
const rawValues = activePayload
.filter((item) => item.value !== undefined && dataKeys.includes(item.dataKey as string))
.map((item) => item.value);
// Use the full data row so the total covers ALL dataKeys, not just visibleSeries
const dataRow = activePayload[0]?.payload;
if (!dataRow) return grandTotal;

const rawValues = dataKeys.map((key) => dataRow[key]);

// Filter to non-null values only
const values = rawValues
.filter((v): v is number => v != null)
.map((v) => Number(v) || 0);
Expand All @@ -88,7 +88,6 @@ export function ChartLegendCompound({
if (values.length === 0) return null;

if (!aggregation) {
// Default: sum
return values.reduce((a, b) => a + b, 0);
}
return aggregateValues(values, aggregation);
Expand All @@ -113,24 +112,24 @@ export function ChartLegendCompound({
const currentData = useMemo((): Record<string, number | null> => {
if (!activePayload?.length) return totals;

// If we have activePayload data from hovering over a bar/line
const hoverData = activePayload.reduce(
(acc, item) => {
if (item.dataKey && item.value !== undefined) {
// Preserve null for gap-filled points instead of coercing to 0
acc[item.dataKey] = item.value != null ? Number(item.value) || 0 : null;
}
return acc;
},
{} as Record<string, number | null>
);
// Use the full data row so ALL dataKeys are resolved from the hovered point,
// not just the visibleSeries present in activePayload.
const dataRow = activePayload[0]?.payload;
if (!dataRow) return totals;

const hoverData: Record<string, number | null> = {};
for (const key of dataKeys) {
const value = dataRow[key];
if (value !== undefined) {
hoverData[key] = value != null ? Number(value) || 0 : null;
}
}

// Return a merged object - totals for keys not in the hover data
return {
...totals,
...hoverData,
};
}, [activePayload, totals]);
}, [activePayload, totals, dataKeys]);
Comment on lines +115 to +132
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

One subtle behaviour worth confirming: undefined keys fall back to totals.

Keys absent from dataRow (i.e. dataRow[key] === undefined) are intentionally excluded from hoverData, so the spread on line 129 (...totals, ...hoverData) falls back to the series total for those keys rather than showing a dash. This is distinct from keys that are explicitly null (gap-fill → shown as ).

If a dataKey could be legitimately absent from a data row in a non-gap scenario (e.g., sparse datasets), hovering would silently show the aggregate total for that series instead of , which might mislead users. Please confirm this edge-case is handled as intended or add an explicit mapping to null for absent keys if the dash behaviour is preferred.

💡 Alternative: treat absent keys as null (show dash on hover)
     for (const key of dataKeys) {
       const value = dataRow[key];
-      if (value !== undefined) {
-        hoverData[key] = value != null ? Number(value) || 0 : null;
-      }
+      hoverData[key] = value != null ? Number(value) || 0 : null;
     }

This would make absent and explicit-null keys both render as when hovering, which may be the safer default for sparse data.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/webapp/app/components/primitives/charts/ChartLegendCompound.tsx` around
lines 115 - 132, The current logic in ChartLegendCompound (inside the useMemo
that reads activePayload -> dataRow) skips keys when dataRow[key] === undefined,
causing a fallback to totals; to make absent keys render as a dash instead,
change the loop that builds hoverData (iterating dataKeys) so that instead of
skipping undefined you explicitly set hoverData[key] = null when value ===
undefined, while preserving the existing Number(value) conversion for non-null
values; update the logic around hoverData/dataRow/dataKeys to ensure absent keys
are treated the same as explicit nulls (so spread {...totals, ...hoverData}
yields null for absent keys) and adjust any downstream rendering that expects
null to display the dash.


// Prepare legend items with capped display
const legendItems = useMemo(() => {
Expand Down