Skip to content

Commit c38e758

Browse files
committed
Only render points on charts when there is data
1 parent fed0f8c commit c38e758

File tree

3 files changed

+52
-29
lines changed

3 files changed

+52
-29
lines changed

apps/webapp/app/components/code/QueryResultsChart.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -244,17 +244,18 @@ function fillTimeGaps(
244244
}
245245
filledData.push(point);
246246
} else {
247-
// Create a zero-filled data point
248-
const zeroPoint: Record<string, unknown> = {
247+
// Create a null-filled data point so gaps appear in line/bar charts
248+
// and legend aggregations (avg/min/max) skip these slots
249+
const gapPoint: Record<string, unknown> = {
249250
[xDataKey]: t,
250251
__rawDate: new Date(t),
251252
__granularity: granularity,
252253
__originalX: new Date(t).toISOString(),
253254
};
254255
for (const s of series) {
255-
zeroPoint[s] = 0;
256+
gapPoint[s] = null;
256257
}
257-
filledData.push(zeroPoint);
258+
filledData.push(gapPoint);
258259
}
259260
}
260261

apps/webapp/app/components/primitives/charts/ChartLegendCompound.tsx

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -68,16 +68,22 @@ export function ChartLegendCompound({
6868
return aggregateValues(values, aggregation);
6969
}, [totals, dataKeys, aggregation]);
7070

71-
// Calculate current total based on hover state
72-
const currentTotal = useMemo(() => {
71+
// Calculate current total based on hover state (null when hovering a gap-filled point)
72+
const currentTotal = useMemo((): number | null => {
7373
if (!highlight.activePayload?.length) return grandTotal;
7474

75-
// Collect all series values from the hovered data point
76-
const values = highlight.activePayload
75+
// Collect all series values from the hovered data point, preserving nulls
76+
const rawValues = highlight.activePayload
7777
.filter((item) => item.value !== undefined && dataKeys.includes(item.dataKey as string))
78-
.map((item) => Number(item.value) || 0);
78+
.map((item) => item.value);
7979

80-
if (values.length === 0) return 0;
80+
// Filter to non-null values only
81+
const values = rawValues
82+
.filter((v): v is number => v != null)
83+
.map((v) => Number(v) || 0);
84+
85+
// All null → gap-filled point, return null to show dash
86+
if (values.length === 0) return null;
8187

8288
if (!aggregation) {
8389
// Default: sum
@@ -101,17 +107,21 @@ export function ChartLegendCompound({
101107
return labelFormatter ? labelFormatter(stringValue) : stringValue;
102108
}, [highlight.activePayload, dataKey, effectiveTotalLabel, labelFormatter]);
103109

104-
// Get current data for the legend based on hover state
105-
const currentData = useMemo(() => {
110+
// Get current data for the legend based on hover state (values may be null for gap-filled points)
111+
const currentData = useMemo((): Record<string, number | null> => {
106112
if (!highlight.activePayload?.length) return totals;
107113

108-
// If we have activePayload data from hovering over a bar
109-
const hoverData = highlight.activePayload.reduce((acc, item) => {
110-
if (item.dataKey && item.value !== undefined) {
111-
acc[item.dataKey] = Number(item.value) || 0;
112-
}
113-
return acc;
114-
}, {} as Record<string, number>);
114+
// If we have activePayload data from hovering over a bar/line
115+
const hoverData = highlight.activePayload.reduce(
116+
(acc, item) => {
117+
if (item.dataKey && item.value !== undefined) {
118+
// Preserve null for gap-filled points instead of coercing to 0
119+
acc[item.dataKey] = item.value != null ? Number(item.value) || 0 : null;
120+
}
121+
return acc;
122+
},
123+
{} as Record<string, number | null>
124+
);
115125

116126
// Return a merged object - totals for keys not in the hover data
117127
return {
@@ -167,7 +177,11 @@ export function ChartLegendCompound({
167177
>
168178
<span className="font-medium">{currentTotalLabel}</span>
169179
<span className="font-medium tabular-nums">
170-
<AnimatedNumber value={currentTotal} duration={0.25} />
180+
{currentTotal != null ? (
181+
<AnimatedNumber value={currentTotal} duration={0.25} />
182+
) : (
183+
"\u2013"
184+
)}
171185
</span>
172186
</div>
173187

@@ -183,15 +197,15 @@ export function ChartLegendCompound({
183197
)}
184198
>
185199
{legendItems.visible.map((item) => {
186-
const total = currentData[item.dataKey] ?? 0;
200+
const total = currentData[item.dataKey] ?? null;
187201
const isActive = highlight.activeBarKey === item.dataKey;
188202

189203
return (
190204
<div
191205
key={item.dataKey}
192206
className={cn(
193207
"relative flex w-full cursor-pointer items-center justify-between gap-2 rounded px-2 py-1 transition",
194-
total === 0 && "opacity-50"
208+
(total == null || total === 0) && "opacity-50"
195209
)}
196210
onMouseEnter={() => highlight.setHoveredLegendItem(item.dataKey)}
197211
onMouseLeave={() => highlight.reset()}
@@ -221,7 +235,11 @@ export function ChartLegendCompound({
221235
isActive ? "text-text-bright" : "text-text-dimmed"
222236
)}
223237
>
224-
<AnimatedNumber value={total} duration={0.25} />
238+
{total != null ? (
239+
<AnimatedNumber value={total} duration={0.25} />
240+
) : (
241+
"\u2013"
242+
)}
225243
</span>
226244
</div>
227245
</div>
@@ -233,7 +251,7 @@ export function ChartLegendCompound({
233251
(legendItems.hoveredHiddenItem ? (
234252
<HoveredHiddenItemRow
235253
item={legendItems.hoveredHiddenItem}
236-
value={currentData[legendItems.hoveredHiddenItem.dataKey] ?? 0}
254+
value={currentData[legendItems.hoveredHiddenItem.dataKey] ?? null}
237255
remainingCount={legendItems.remaining - 1}
238256
/>
239257
) : (
@@ -279,7 +297,7 @@ function ViewAllDataRow({ remainingCount, onViewAll }: ViewAllDataRowProps) {
279297

280298
type HoveredHiddenItemRowProps = {
281299
item: { dataKey: string; color?: string; label: React.ReactNode };
282-
value: number;
300+
value: number | null;
283301
remainingCount: number;
284302
};
285303

@@ -305,7 +323,7 @@ function HoveredHiddenItemRow({ item, value, remainingCount }: HoveredHiddenItem
305323
{remainingCount > 0 && <span className="text-text-dimmed">+{remainingCount} more</span>}
306324
</div>
307325
<span className="tabular-nums text-text-bright">
308-
<AnimatedNumber value={value} duration={0.25} />
326+
{value != null ? <AnimatedNumber value={value} duration={0.25} /> : "\u2013"}
309327
</span>
310328
</div>
311329
</div>

apps/webapp/app/components/primitives/charts/ChartRoot.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,9 @@ export function useSeriesTotal(aggregation?: AggregationType): Record<string, nu
228228
const counts: Record<string, number> = {};
229229
for (const item of data) {
230230
for (const seriesKey of dataKeys) {
231-
const val = Number(item[seriesKey] || 0);
231+
const rawVal = item[seriesKey];
232+
if (rawVal == null) continue; // skip gap-filled nulls
233+
const val = Number(rawVal);
232234
sums[seriesKey] = (sums[seriesKey] || 0) + val;
233235
counts[seriesKey] = (counts[seriesKey] || 0) + 1;
234236
}
@@ -244,7 +246,8 @@ export function useSeriesTotal(aggregation?: AggregationType): Record<string, nu
244246
const result: Record<string, number> = {};
245247
for (const item of data) {
246248
for (const seriesKey of dataKeys) {
247-
const val = Number(item[seriesKey] || 0);
249+
if (item[seriesKey] == null) continue; // skip gap-filled nulls
250+
const val = Number(item[seriesKey]);
248251
if (result[seriesKey] === undefined || val < result[seriesKey]) {
249252
result[seriesKey] = val;
250253
}
@@ -261,7 +264,8 @@ export function useSeriesTotal(aggregation?: AggregationType): Record<string, nu
261264
const result: Record<string, number> = {};
262265
for (const item of data) {
263266
for (const seriesKey of dataKeys) {
264-
const val = Number(item[seriesKey] || 0);
267+
if (item[seriesKey] == null) continue; // skip gap-filled nulls
268+
const val = Number(item[seriesKey]);
265269
if (result[seriesKey] === undefined || val > result[seriesKey]) {
266270
result[seriesKey] = val;
267271
}

0 commit comments

Comments
 (0)