Skip to content

data_table: Add collapsible row groups to DataTable#2436

Open
panzhifu wants to merge 2 commits into
longbridge:mainfrom
panzhifu:main
Open

data_table: Add collapsible row groups to DataTable#2436
panzhifu wants to merge 2 commits into
longbridge:mainfrom
panzhifu:main

Conversation

@panzhifu

@panzhifu panzhifu commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Closes #2370

Add collapsible row groups to DataTable

Introduce row grouping with optional expand/collapse toggles via a new leftmost
toggle column. Three new TableDelegate methods (row_depth, row_children_count,
render_row_toggle) let delegates describe tree-structured data stored in a flat
row list.

TableState provides toggle_row_group, expand_all, and automatically computes
visible rows to skip collapsed descendants. Toggle column width is configurable
via DataTable::row_toggle_width().

Description

This PR adds collapsible row groups to DataTable, enabling tree-like
expand/collapse behavior for grouped data rows.

New TableDelegate methods (all with default implementations):

  • row_depth(row_ix) — returns the tree depth of a row
  • row_children_count(row_ix) — returns the total number of descendant rows
    for a group header, or None for leaf rows
  • render_row_toggle(row_ix, is_expanded) — renders the chevron toggle icon,
    customizable by the delegate

New TableState API:

  • row_groupable(bool) — enable/disable the feature, default false
  • toggle_row_group(row_ix) — programmatically expand/collapse a group
  • expand_all() — expand all groups

New DataTable builder:

  • row_toggle_width(px) — configure toggle column width, default 24px

How it works:

  1. Delegate describes tree structure via row_children_count (total descendants
    per group) and row_depth
  2. TableState manages collapsed state in a HashSet and computes visible
    rows via compute_visible_rows()
  3. Collapsed descendants are excluded from the virtual list, so row count
    updates dynamically
  4. A fixed leftmost toggle column renders the chevron icon, aligned with the
    existing row-header column pattern

Screenshot

image

Break Changes

None. All new trait methods have default implementations, and row_groupable
defaults to false.

How to Test

  1. Run cargo run
  2. Navigate to DataTable in the story gallery
  3. Check the "Row Groupable" checkbox
  4. Click the chevron (/) icons in the left toggle column to
    expand/collapse groups
  5. Verify:
    • Toggle column renders correctly with chevrons for group headers
    • Group header rows are styled differently (bold + muted background)
    • Columns remain aligned across all rows
    • Collapsed rows are hidden and visible row count updates
    • Unchecking "Row Groupable" restores the original flat table

Checklist

  • I have read the CONTRIBUTING document and followed
    the guidelines.
  • Reviewed the changes in this PR and confirmed AI generated code (if any)
    is accurate.
  • Passed cargo run for story tests related to the changes.
  • Tested macOS, Windows and Linux platforms performance (if the change is
    platform-specific)

@huacnlee huacnlee changed the title Add collapsible row groups to DataTable data_table: Add collapsible row groups to DataTable Jun 3, 2026
@huacnlee

huacnlee commented Jun 3, 2026

Copy link
Copy Markdown
Member

Thanks for the PR! But this is not a real group design, so we can't merge it yet.

A real group should come from the data. The user gives the group data (for example, group by a field like sector), and the table builds the group rows from it.

This PR does not do that. The group is hardcoded by hand — the demo just splits rows by position (every 6 rows), and the user must build the parent/children relation themselves. The group does not come from the data, so it is very confusing to read (row 0 becomes the parent of the next 5 rows, even though they are not related).

So the group logic needs a redesign to be data-driven. Can you tell me what you want first: group by a field, or tree rows (a parent row that is also real data)?

@panzhifu

panzhifu commented Jun 3, 2026

Copy link
Copy Markdown
Contributor Author

Got it. I'll redesign it to group by a field (e.g., groupBy="sector") so groups come from the data, not hardcoded positions. The table will generate group headers and handle collapse/expand on them. Will update the PR.

Thanks for the PR! But this is not a real group design, so we can't merge it yet.

A real group should come from the data. The user gives the group data (for example, group by a field like sector), and the table builds the group rows from it.

This PR does not do that. The group is hardcoded by hand — the demo just splits rows by position (every 6 rows), and the user must build the parent/children relation themselves. The group does not come from the data, so it is very confusing to read (row 0 becomes the parent of the next 5 rows, even though they are not related).

So the group logic needs a redesign to be data-driven. Can you tell me what you want first: group by a field, or tree rows (a parent row that is also real data)?

@panzhifu panzhifu force-pushed the main branch 4 times, most recently from 1ed5054 to c197d24 Compare June 3, 2026 08:42
TableState::group_by(&[col_ix]) scans delegate.cell_text() to
automatically build a group tree from flat data, rendering group
header rows with expand/collapse toggles on the right side.

- Multi-level grouping via group_by(&[1, 0]) with nested children
- Group tree is cached and only recomputed when row count or columns
  change, avoiding per-frame cell_text() scanning
- Delegate can customize toggle icon (render_row_toggle) and group
  header row (render_group_tr)
- TableEvent::ToggleGroup emitted on expand/collapse
- groups() accessor returns all group info
@panzhifu

panzhifu commented Jun 3, 2026

Copy link
Copy Markdown
Contributor Author

Got it. I'll redesign it to group by a field so groups come from
the data, not hardcoded positions.

Done. The PR has been updated to use data-driven grouping:

How it works now:

  • TableState::group_by(&[1]) groups by column index — the table
    scans delegate.cell_text(row, 1) for each row. Consecutive rows
    with the same value form a group, and group headers are generated
    automatically.
  • Multiple indices (group_by(&[1, 0])) create nested groups.
  • Demo groups by the Market column (column 1), sorting data on toggle
    to ensure groups are contiguous.

Key changes:

  • Removed row_children_count / row_depth — groups are now computed
    automatically from cell_text(), not hand-built by the delegate.
  • Added GroupInfo with key, label, depth, count, start, and optional
    children for nested groups.
  • Group tree is cached (recomputed only when row count or group columns
    change) and flattened into VisibleRow on toggle.
  • render_group_tr(group) lets the delegate customize the group row.
  • render_row_toggle(is_expanded) customizes the chevron icon.
  • TableEvent::ToggleGroup(key, expanded) emitted on expand/collapse.
  • groups() accessor returns group info for external use.

The demo now shows a "Group by Market" checkbox that groups the random
stocks into "US" and "HK" sections with expandable group headers.
image

@huacnlee huacnlee left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm sorry, but after reviewing the overall API design, I still don't think it's a good solution.

I can't point out every single error in the current PR implementation, but I think the underlying approach is flawed.

It appears that group already exists, but it's actually built on a specific implementation, which isn't good API design. For example, why is group handled internally? This makes DataTable manage too many things, which is very limiting.

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.

Enhancement : Opt-in Collapsible Row groups for DataTable

2 participants