's fill prop, so the chart's ~N Area
- // elements are not re-created on every hover enter/leave.
- // `rr-block` tells the HyperDX/rrweb session-replay recorder to capture
- // this chart as a placeholder rather than serializing its (very large)
- // SVG DOM on every mutation — the dominant session-replay cost on
- // high-cardinality dashboards.
- className={cx(
- 'rr-block',
- styles.chartRoot,
- isHovered && styles.chartHovered,
- )}
- // Scopes nearestSeriesStyle to this chart instance.
- data-chart-id={id}
- // Stacked lines are read by their filled bands, so keep the fill on
- // hover; the chartHovered fill-hiding rule opts out via this attribute.
- data-stacked-fill={
- displayType === DisplayType.StackedLine ? 'true' : undefined
- }
- style={{ position: 'relative', width: '100%', height: '100%' }}
- >
- {nearestSeriesStyle}
- {hoveredAnnotation != null && (
-
- )}
-
-
{
- const w = width ?? 1;
- setContainerWidth(prev => (prev === w ? prev : w));
- }}
- className={isLoading ? 'effect-pulse' : ''}
- >
-
-
- {/* Gradient defs cover every hex that any fill may reference.
- `COLORS` (the unified categorical palette) is included up-front
- as a baseline; semantic colors returned by the
- `getChartColor{Info,Success,Warning,Error}` helpers can also
- appear in `lineData[].color` (e.g. info-level log series
- resolve to `--color-chart-info`, chart blue `#437eef`, on both
- brands, which matches categorical slot 0). Union them here so the
- referenced `url(#time-chart-lin-grad-…)` always exists. */}
- {collectMemoChartGradientHexes(lineData).map(c => {
- return (
-
-
-
-
- );
- })}
-
- {isHovered && (
-
- )}
-
-
- {lines}
- {/* HOVER tooltip (also drives cross-chart shadow tooltips via syncId).
- Hidden once a point is clicked, where the pinned tooltip takes over.
- Portaled to body so HDXLineChartTooltip can self-position (see its
- docblock) and escape the chart's bounds near an edge. */}
- {isClickActive == null && (
-
- )}
- {referenceLines}
- {annotationElements}
- {laidOutAnnotations != null && (
-
- }
- />
- )}
- {highlightStart && highlightEnd ? (
-
- ) : null}
- {showLegend && (
-
- }
- offset={-100}
- />
- )}
- {/** Needs to be at the bottom to prevent re-rendering */}
- {isClickActive != null ? (
- // The x-axis is numeric (scale="time"); pass a number so the marker
- // positions without relying on the axis coercing a string.
-
- ) : null}
- {logReferenceTimestamp != null ? (
-
- ) : null}
-
-
-
- );
-});
diff --git a/packages/app/src/HDXMultiSeriesTimeChart/ChartLegend.tsx b/packages/app/src/HDXMultiSeriesTimeChart/ChartLegend.tsx
new file mode 100644
index 0000000000..b7a0e4fb6d
--- /dev/null
+++ b/packages/app/src/HDXMultiSeriesTimeChart/ChartLegend.tsx
@@ -0,0 +1,174 @@
+import { memo, useMemo, useState } from 'react';
+import cx from 'classnames';
+import { Popover } from '@mantine/core';
+
+import { type LineData } from '@/ChartUtils';
+import { truncateMiddle } from '@/utils';
+
+import { hasSeriesSelection } from './chartData';
+import { MAX_LEGEND_ITEMS } from './constants';
+
+import styles from '@styles/HDXLineChart.module.scss';
+
+function ExpandableLegendItem({
+ entry,
+ expanded,
+ isSelected,
+ isDisabled,
+ onToggle,
+}: {
+ entry: any;
+ expanded?: boolean;
+ isSelected?: boolean;
+ isDisabled?: boolean;
+ onToggle?: (isShiftKey: boolean) => void;
+}) {
+ const [_expanded, setExpanded] = useState(false);
+ const isExpanded = _expanded || expanded;
+
+ return (
+