Skip to content

Add legends column to XY trace viewer#404

Open
hoathaitran3209 wants to merge 3 commits into
eclipse-cdt-cloud:masterfrom
hoathaitran3209:bug/IDE-92565-add-legends-column
Open

Add legends column to XY trace viewer#404
hoathaitran3209 wants to merge 3 commits into
eclipse-cdt-cloud:masterfrom
hoathaitran3209:bug/IDE-92565-add-legends-column

Conversation

@hoathaitran3209

@hoathaitran3209 hoathaitran3209 commented Jul 9, 2026

Copy link
Copy Markdown

Summary

This pull request adds a legends column to the XY trace viewer to improve readability and make it easier to identify plotted series.

Changes

  • Add a legends column to the filter tree.
  • Update XY output components to support the new column.
  • Adjust table layout and styles.
  • Refactor related utility code where necessary.

Testing

  • Verified the legends column is displayed correctly.
  • Verified existing XY trace viewer functionality continues to work as expected.

Summary by CodeRabbit

  • New Features
    • Added per-series legend color swatches to XY charts and the filter tree/table.
    • Introduced a dedicated “Legend” column to keep legend labeling consistent across views.
  • Bug Fixes
    • Legend colors now track and refresh properly whenever XY data is rebuilt and when tree/table content updates.
    • Updated tree rendering so legend/color changes trigger the correct re-render.
  • Style
    • Added styling for compact legend swatches.

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3dd24ce1-d2d7-4e59-be50-8f954561bcb8

📥 Commits

Reviewing files that changed from the base of the PR and between aabc1fd and 8768784.

📒 Files selected for processing (2)
  • local-libs/traceviewer-libs/react-components/src/components/utils/filter-tree/utils.tsx
  • local-libs/traceviewer-libs/react-components/src/components/utils/xy-shared.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • local-libs/traceviewer-libs/react-components/src/components/utils/filter-tree/utils.tsx

📝 Walkthrough

Walkthrough

This PR adds legend color computation and a dedicated legend column to XY chart tree tables. It threads legendColors and legendColumnIndex through XY output and filter-tree components, adds legend-aware label handling and swatch rendering, and introduces shared helpers for computing and placing legend columns.

Changes

Legend color and column feature

Layer / File(s) Summary
Legend column and color computation utilities
.../filter-tree/utils.tsx, .../utils/xy-shared.ts
Adds legend constants and helpers, aligns tree labels with the legend column, normalizes missing parent IDs, computes series color mappings, and ensures tree-state columns include a legend column.
XY output components compute and propagate legendColors
abstract-xy-output-component.tsx, generic-xy-output-component.tsx, xy-output-component.tsx, xy-output-overview-component.tsx
Adds legend color state, registers series names by ID, recomputes colors during XY data updates, passes colors into EntryTree, and initializes the new state field.
Filter-tree components render legend swatches
.../filter-tree/tree.tsx, table.tsx, table-body.tsx, table-row.tsx, table-cell.tsx, entry-tree.tsx, style/output-components-style.css
Propagates legend properties through the table stack, determines the legend column, renders colored swatches, adjusts control-column handling, and styles swatches.

Estimated code review effort: 3 (Moderate) | ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a legends column to the XY trace viewer.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@hoathaitran3209 hoathaitran3209 changed the title IDE-92565-add-aengends-colummn Add legends column to XY trace viewer Jul 9, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
local-libs/traceviewer-libs/react-components/src/components/utils/xy-shared.ts (1)

23-28: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

ColumnSpec duplicates ColumnHeader.

ColumnSpec (title, sortable?, resizable?, tooltip?) is nearly identical to the existing ColumnHeader interface (which additionally has dataType?). Maintaining two parallel header shapes (with casts like built.columns as any elsewhere) risks drift between them.

Consider reusing/extending ColumnHeader instead of introducing a new type.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@local-libs/traceviewer-libs/react-components/src/components/utils/xy-shared.ts`
around lines 23 - 28, ColumnSpec is duplicating the existing ColumnHeader shape,
which can drift over time; update the shared types in xy-shared.ts so ColumnSpec
reuses or extends ColumnHeader instead of defining a parallel interface. Keep
the ColumnSpec name only if needed for compatibility, but make it derive from
ColumnHeader (including its optional dataType field) and then adjust any
consumers of ColumnSpec to use the unified type rather than casts like
built.columns as any.
local-libs/traceviewer-libs/react-components/src/components/utils/filter-tree/table.tsx (1)

36-37: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

legendColumnIndex prop on TableProps is unused.

No external caller passes legendColumnIndex to TableFilterTree.renderTable() only forwards legendColors. Table always computes it locally via getLegendColumnIndex(this.props.headers) and explicitly passes the computed value to TableBody, overriding any spread value. The declared prop is dead code and could confuse maintainers into thinking they need to supply it.

♻️ Remove unused prop
 interface TableProps {
     nodes: TreeNode[];
     selectedRow?: number;
     multiSelectedRows?: number[];
     collapsedNodes: number[];
     isCheckable: boolean;
     isClosable: boolean;
     isPinnable?: boolean;
     pinnedRows?: number[];
     sortConfig: SortConfig[];
     onRowClick: (id: number) => void;
     onMultipleRowClick?: (id: number, isShiftClicked?: boolean) => void;
     onContextMenu: (event: React.MouseEvent<HTMLDivElement>, id: number) => void;
     getCheckedStatus: (id: number) => number;
     onToggleCollapse: (id: number) => void;
     onToggleCheck: (id: number) => void;
     onClose: (id: number) => void;
     onPin?: (id: number) => void;
     onSort: (sortedNodes: TreeNode[]) => void;
     onSortReset: () => void;
     onSortConfigChange: (sortConfig: SortConfig[]) => void;
     showHeader: boolean;
     headers: ColumnHeader[];
     className: string;
     hideFillers?: boolean;
     emptyNodes: number[];
     hideEmptyNodes: boolean;
-    legendColumnIndex?: number;
     legendColors?: Record<number, string>;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@local-libs/traceviewer-libs/react-components/src/components/utils/filter-tree/table.tsx`
around lines 36 - 37, `legendColumnIndex` is dead on `TableProps` and should be
removed from the `Table` API. Update the `Table` type definition in `table.tsx`
to drop the unused prop, and make sure `Table`, `TableBody`, and any related
prop spreads continue to rely only on the locally computed value from
`getLegendColumnIndex(this.props.headers)`. Also verify
`FilterTree.renderTable()` still passes only the supported props and that no
other callers reference `legendColumnIndex`.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@local-libs/traceviewer-libs/react-components/src/components/utils/xy-shared.ts`:
- Around line 53-74: `buildLegendColors` is using a fallback key that can differ
from the chart’s series color key, causing inconsistent legend colors and extra
allocator entries. Update `buildLegendColors` in `xy-shared.ts` to derive the
legend color key from the same source used by `abstract-xy-output-component.tsx`
for `series.seriesName`, or delay color assignment until `seriesNameById` is
available. Keep the logic aligned around the `buildLegendColors` loop and the
`seriesNameById` lookup so the legend swatch always matches the series color.

---

Nitpick comments:
In
`@local-libs/traceviewer-libs/react-components/src/components/utils/filter-tree/table.tsx`:
- Around line 36-37: `legendColumnIndex` is dead on `TableProps` and should be
removed from the `Table` API. Update the `Table` type definition in `table.tsx`
to drop the unused prop, and make sure `Table`, `TableBody`, and any related
prop spreads continue to rely only on the locally computed value from
`getLegendColumnIndex(this.props.headers)`. Also verify
`FilterTree.renderTable()` still passes only the supported props and that no
other callers reference `legendColumnIndex`.

In
`@local-libs/traceviewer-libs/react-components/src/components/utils/xy-shared.ts`:
- Around line 23-28: ColumnSpec is duplicating the existing ColumnHeader shape,
which can drift over time; update the shared types in xy-shared.ts so ColumnSpec
reuses or extends ColumnHeader instead of defining a parallel interface. Keep
the ColumnSpec name only if needed for compatibility, but make it derive from
ColumnHeader (including its optional dataType field) and then adjust any
consumers of ColumnSpec to use the unified type rather than casts like
built.columns as any.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: feadc0f5-de6c-4771-b43f-604f045adbf2

📥 Commits

Reviewing files that changed from the base of the PR and between bb0b7d1 and 1ba74b0.

📒 Files selected for processing (13)
  • local-libs/traceviewer-libs/react-components/src/components/abstract-xy-output-component.tsx
  • local-libs/traceviewer-libs/react-components/src/components/generic-xy-output-component.tsx
  • local-libs/traceviewer-libs/react-components/src/components/utils/filter-tree/entry-tree.tsx
  • local-libs/traceviewer-libs/react-components/src/components/utils/filter-tree/table-body.tsx
  • local-libs/traceviewer-libs/react-components/src/components/utils/filter-tree/table-cell.tsx
  • local-libs/traceviewer-libs/react-components/src/components/utils/filter-tree/table-row.tsx
  • local-libs/traceviewer-libs/react-components/src/components/utils/filter-tree/table.tsx
  • local-libs/traceviewer-libs/react-components/src/components/utils/filter-tree/tree.tsx
  • local-libs/traceviewer-libs/react-components/src/components/utils/filter-tree/utils.tsx
  • local-libs/traceviewer-libs/react-components/src/components/utils/xy-shared.ts
  • local-libs/traceviewer-libs/react-components/src/components/xy-output-component.tsx
  • local-libs/traceviewer-libs/react-components/src/components/xy-output-overview-component.tsx
  • local-libs/traceviewer-libs/react-components/style/output-components-style.css

@hoathaitran3209 hoathaitran3209 force-pushed the bug/IDE-92565-add-legends-column branch from 1ba74b0 to dab1731 Compare July 9, 2026 08:31
@hoathaitran3209 hoathaitran3209 force-pushed the bug/IDE-92565-add-legends-column branch from dab1731 to f5bbbca Compare July 9, 2026 08:34
@MatthewKhouzam

Copy link
Copy Markdown
Contributor

Hey, I like this patch a lot, it is something I was planning on doing when I had breathing time. Thanks, the only issue is that it does not take the colors given by the tsp. therefore the totals are no longer red. Can you fix that?
Hint from Matthew:
I think you rebuilt the legend instead of using the one supplied first.

Hint from claude:
PR 404 doesn't reuse the colors the chart already assigned to each line — it runs a second, independently-keyed color allocation for the legend, and at the moment it runs the keys don't line up with the chart's keys. So the swatches walk the palette on their own instead of mirroring the existing line colors.

Here's the mechanism, from the cherry-picked code:

Chart lines get their color in buildXYData (abstract-xy-output-component.tsx):

  const color = this.getSeriesColor(series.seriesName); // -> this.colors.get(series.seriesName)

So the ColorAllocator key for a line is the full series.seriesName.

The legend gets its color in buildLegendColors (xy-shared.ts):

const key = seriesNameById.get(entry.id) ?? entry.labels?.[0] ?? String(entry.id);
legendColors[entry.id] = colors.get(key);

ColorAllocator.get(key) caches by the string key and hands out palette slots in first-seen order:

get(key: string): string {
    let idx = this.map.get(key);
    if (idx === undefined) { idx = this.i % this.palette.length; this.map.set(key, idx); this.i++; }
    return this.palette[idx];
}

The problem is timing plus key identity:

  1. buildLegendColors is first called in fetchTree, right when the tree is built:
   legendColors: buildLegendColors(built.xyTree, this.colors, this.seriesNameById)
 At that point this.seriesNameById is still empty — it's only populated later inside buildXYData/buildScatterData via

registerSeriesNames, which run during updateXY() (called after this setState).

  1. With an empty seriesNameById, the legend key falls back to entry.labels?.[0] (the leaf display name) for every entry — and it does
    this for all leaf entries in the tree, not just the plotted ones.
  2. When the chart later colors its lines with colors.get(series.seriesName), the key is the qualified series name, which is frequently
    not identical to the tree leaf's labels[0]. Different key → different palette slot. And because the legend already consumed palette
    slots (advancing this.i) for every leaf, the plotted series land on shifted indices.

Net effect: the legend swatches are derived from a fresh, differently-keyed palette walk rather than from the color the chart actually
drew, so they "ignore" the existing line colors. They'd only coincidentally match when labels[0] === seriesName for every series.

The correct fix is to make the legend use the same identity the chart uses — either look up the color already assigned to each series
(by seriesId, from the chart datasets) instead of re-deriving it, or at minimum defer/rebuild legendColors after registerSeriesNames
has populated seriesNameById and key it identically to getSeriesColor. Want me to implement that?

@hoathaitran3209

Copy link
Copy Markdown
Author

Hi @MatthewKhouzam, about your comment, I updated it.
Thank you so much.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@local-libs/traceviewer-libs/react-components/src/components/abstract-xy-output-component.tsx`:
- Line 162: Clear the seriesNameById map before registering series names in both
buildScatterData and buildXYData. Ensure each updateXY cycle removes stale
unchecked-series entries while preserving ColorAllocator caching so rechecked
series retain their previous colors.

In
`@local-libs/traceviewer-libs/react-components/src/components/generic-xy-output-component.tsx`:
- Around line 353-361: Clear this.seriesNameById before registerSeriesNames in
updateXY so unchecked series cannot retain stale legend colors; keep this.colors
unchanged so cached allocations are preserved. In the empty-series early-return
setState branch, also reset legendColors to {}.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c755c040-785d-46c1-a80c-18196d4ed78a

📥 Commits

Reviewing files that changed from the base of the PR and between dab1731 and aabc1fd.

📒 Files selected for processing (13)
  • local-libs/traceviewer-libs/react-components/src/components/abstract-xy-output-component.tsx
  • local-libs/traceviewer-libs/react-components/src/components/generic-xy-output-component.tsx
  • local-libs/traceviewer-libs/react-components/src/components/utils/filter-tree/entry-tree.tsx
  • local-libs/traceviewer-libs/react-components/src/components/utils/filter-tree/table-body.tsx
  • local-libs/traceviewer-libs/react-components/src/components/utils/filter-tree/table-cell.tsx
  • local-libs/traceviewer-libs/react-components/src/components/utils/filter-tree/table-row.tsx
  • local-libs/traceviewer-libs/react-components/src/components/utils/filter-tree/table.tsx
  • local-libs/traceviewer-libs/react-components/src/components/utils/filter-tree/tree.tsx
  • local-libs/traceviewer-libs/react-components/src/components/utils/filter-tree/utils.tsx
  • local-libs/traceviewer-libs/react-components/src/components/utils/xy-shared.ts
  • local-libs/traceviewer-libs/react-components/src/components/xy-output-component.tsx
  • local-libs/traceviewer-libs/react-components/src/components/xy-output-overview-component.tsx
  • local-libs/traceviewer-libs/react-components/style/output-components-style.css
✅ Files skipped from review due to trivial changes (1)
  • local-libs/traceviewer-libs/react-components/src/components/utils/filter-tree/table-body.tsx
🚧 Files skipped from review as they are similar to previous changes (10)
  • local-libs/traceviewer-libs/react-components/src/components/utils/filter-tree/entry-tree.tsx
  • local-libs/traceviewer-libs/react-components/style/output-components-style.css
  • local-libs/traceviewer-libs/react-components/src/components/xy-output-component.tsx
  • local-libs/traceviewer-libs/react-components/src/components/utils/filter-tree/table-cell.tsx
  • local-libs/traceviewer-libs/react-components/src/components/utils/filter-tree/table.tsx
  • local-libs/traceviewer-libs/react-components/src/components/utils/filter-tree/table-row.tsx
  • local-libs/traceviewer-libs/react-components/src/components/utils/filter-tree/utils.tsx
  • local-libs/traceviewer-libs/react-components/src/components/utils/filter-tree/tree.tsx
  • local-libs/traceviewer-libs/react-components/src/components/xy-output-overview-component.tsx
  • local-libs/traceviewer-libs/react-components/src/components/utils/xy-shared.ts

private _debouncedUpdateXY = debounce(() => this.updateXY(), 500);

private colors = new ColorAllocator();
private seriesNameById = new Map<number, string>();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Clear seriesNameById before re-registering to avoid stale legend colors for unchecked series.

seriesNameById is never cleared between updateXY calls. When a user unchecks a series, its entry persists in the map, so buildLegendColors still assigns it a legend color even though it's absent from the chart. Meanwhile, series that were never checked have no entry and get no color. This creates an inconsistent legend: some unchecked rows show colors, others don't.

Clear the map before each registerSeriesNames call so only the currently-fetched (checked) series receive legend colors:

🛡️ Proposed fix for `buildScatterData` (line 473) and `buildXYData` (line 518)
 private buildScatterData(seriesObj: XYSeries[]) {
+    this.seriesNameById.clear();
     registerSeriesNames(this.seriesNameById, seriesObj);
 private buildXYData(seriesObj: XYSeries[]) {
+    this.seriesNameById.clear();
     registerSeriesNames(this.seriesNameById, seriesObj);

The ColorAllocator (this.colors) retains cached colors by key, so re-checking a previously unchecked series will still receive the same color it had before.

Also applies to: 473-473, 518-518

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@local-libs/traceviewer-libs/react-components/src/components/abstract-xy-output-component.tsx`
at line 162, Clear the seriesNameById map before registering series names in
both buildScatterData and buildXYData. Ensure each updateXY cycle removes stale
unchecked-series entries while preserving ColorAllocator caching so rechecked
series retain their previous colors.

Comment on lines +353 to +361
registerSeriesNames(this.seriesNameById, series);
const xy = this.buildXYData(series, this.mode);
flushSync(() => this.setState({ xyData: xy, outputStatus: model.status ?? ResponseStatus.COMPLETED }));
flushSync(() =>
this.setState({
xyData: xy,
outputStatus: model.status ?? ResponseStatus.COMPLETED,
legendColors: buildLegendColors(this.state.xyTree, this.colors, this.seriesNameById)
})
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Clear seriesNameById before re-registering to avoid stale legend colors for unchecked series.

seriesNameById is never cleared between updateXY calls. When a user unchecks a series, its entry persists, so buildLegendColors still assigns it a legend color even though it's absent from the chart. Meanwhile, never-checked series get no color. This creates an inconsistent legend.

🛡️ Proposed fix
 registerSeriesNames(this.seriesNameById, series);
+// Or clear at the start of updateXY:
+// this.seriesNameById.clear();
+// registerSeriesNames(this.seriesNameById, series);

The simplest approach is to clear before registering:

+this.seriesNameById.clear();
 registerSeriesNames(this.seriesNameById, series);

The ColorAllocator (this.colors) retains cached colors by key, so re-checking a series will still receive the same color.

Additionally, the empty-series early-return branch (lines 335–346) does not reset legendColors, so the legend retains stale colors when all series are unchecked. Add legendColors: {} to that setState call:

🛡️ Proposed fix for empty-series branch
 if (!series.length) {
     flushSync(() =>
         this.setState({
             xyData: { labels: [], datasets: [], labelIndices: [] },
-            outputStatus: model.status ?? ResponseStatus.COMPLETED
+            outputStatus: model.status ?? ResponseStatus.COMPLETED,
+            legendColors: {}
         })
     );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@local-libs/traceviewer-libs/react-components/src/components/generic-xy-output-component.tsx`
around lines 353 - 361, Clear this.seriesNameById before registerSeriesNames in
updateXY so unchecked series cannot retain stale legend colors; keep this.colors
unchanged so cached allocations are preserved. In the empty-series early-return
setState branch, also reset legendColors to {}.

@MatthewKhouzam

Copy link
Copy Markdown
Contributor

Almost there! this looks great! You need to enable the legend for the "total" or default fields.

@hoathaitran3209

Copy link
Copy Markdown
Author

Almost there! this looks great! You need to enable the legend for the "total" or default fields.

Done. I also updated the code Minor "Clear seriesNameById before re-registering to avoid stale legend colors for unchecked series."

@MatthewKhouzam MatthewKhouzam left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Works here! thanks!

@MatthewKhouzam

Copy link
Copy Markdown
Contributor

Fix the issues in linting please also you want to squash the commits and update the commit message? if you used coding assistants such as claude opus, please mention it in the commit message.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants