refactor: use data_col_to_visual via group_for helpers, add column group toggle
Add row_group_for/col_group_for to GridLayout, replacing inline backward-search logic. Refactor grid renderer to use col_group_for instead of pre-filtering col_items. Add gz keybinding for column group collapse toggle, symmetric with z for rows. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -188,6 +188,40 @@ impl GridLayout {
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Find the group containing the Nth data row.
|
||||
/// Returns `(cat_name, group_name)` of the nearest preceding GroupHeader.
|
||||
pub fn row_group_for(&self, data_row: usize) -> Option<(String, String)> {
|
||||
let vi = self.data_row_to_visual(data_row)?;
|
||||
self.row_items[..vi].iter().rev().find_map(|e| {
|
||||
if let AxisEntry::GroupHeader {
|
||||
cat_name,
|
||||
group_name,
|
||||
} = e
|
||||
{
|
||||
Some((cat_name.clone(), group_name.clone()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Find the group containing the Nth data column.
|
||||
/// Returns `(cat_name, group_name)` of the nearest preceding GroupHeader.
|
||||
pub fn col_group_for(&self, data_col: usize) -> Option<(String, String)> {
|
||||
let vi = self.data_col_to_visual(data_col)?;
|
||||
self.col_items[..vi].iter().rev().find_map(|e| {
|
||||
if let AxisEntry::GroupHeader {
|
||||
cat_name,
|
||||
group_name,
|
||||
} = e
|
||||
{
|
||||
Some((cat_name.clone(), group_name.clone()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Expand a single category into `AxisEntry` values, given a coordinate prefix.
|
||||
@ -483,4 +517,91 @@ mod tests {
|
||||
assert_eq!(layout.data_row_to_visual(1), Some(3)); // Apr is at visual index 3
|
||||
assert_eq!(layout.data_row_to_visual(2), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn data_col_to_visual_skips_headers() {
|
||||
let mut m = Model::new("T");
|
||||
m.add_category("Type").unwrap(); // Row
|
||||
m.add_category("Month").unwrap(); // Column
|
||||
m.category_mut("Type").unwrap().add_item("Food");
|
||||
m.category_mut("Month")
|
||||
.unwrap()
|
||||
.add_item_in_group("Jan", "Q1");
|
||||
m.category_mut("Month")
|
||||
.unwrap()
|
||||
.add_item_in_group("Apr", "Q2");
|
||||
let layout = GridLayout::new(&m, m.active_view());
|
||||
// col_items: [GroupHeader(Q1), DataItem(Jan), GroupHeader(Q2), DataItem(Apr)]
|
||||
assert_eq!(layout.data_col_to_visual(0), Some(1));
|
||||
assert_eq!(layout.data_col_to_visual(1), Some(3));
|
||||
assert_eq!(layout.data_col_to_visual(2), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn row_group_for_finds_enclosing_group() {
|
||||
let mut m = Model::new("T");
|
||||
m.add_category("Month").unwrap();
|
||||
m.add_category("Type").unwrap();
|
||||
m.category_mut("Month")
|
||||
.unwrap()
|
||||
.add_item_in_group("Jan", "Q1");
|
||||
m.category_mut("Month")
|
||||
.unwrap()
|
||||
.add_item_in_group("Apr", "Q2");
|
||||
m.category_mut("Type").unwrap().add_item("Food");
|
||||
let layout = GridLayout::new(&m, m.active_view());
|
||||
assert_eq!(
|
||||
layout.row_group_for(0),
|
||||
Some(("Month".to_string(), "Q1".to_string()))
|
||||
);
|
||||
assert_eq!(
|
||||
layout.row_group_for(1),
|
||||
Some(("Month".to_string(), "Q2".to_string()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn row_group_for_returns_none_for_ungrouped() {
|
||||
let mut m = Model::new("T");
|
||||
m.add_category("Type").unwrap();
|
||||
m.add_category("Month").unwrap();
|
||||
m.category_mut("Type").unwrap().add_item("Food");
|
||||
m.category_mut("Month").unwrap().add_item("Jan");
|
||||
let layout = GridLayout::new(&m, m.active_view());
|
||||
assert_eq!(layout.row_group_for(0), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn col_group_for_finds_enclosing_group() {
|
||||
let mut m = Model::new("T");
|
||||
m.add_category("Type").unwrap(); // Row
|
||||
m.add_category("Month").unwrap(); // Column
|
||||
m.category_mut("Type").unwrap().add_item("Food");
|
||||
m.category_mut("Month")
|
||||
.unwrap()
|
||||
.add_item_in_group("Jan", "Q1");
|
||||
m.category_mut("Month")
|
||||
.unwrap()
|
||||
.add_item_in_group("Apr", "Q2");
|
||||
let layout = GridLayout::new(&m, m.active_view());
|
||||
assert_eq!(
|
||||
layout.col_group_for(0),
|
||||
Some(("Month".to_string(), "Q1".to_string()))
|
||||
);
|
||||
assert_eq!(
|
||||
layout.col_group_for(1),
|
||||
Some(("Month".to_string(), "Q2".to_string()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn col_group_for_returns_none_for_ungrouped() {
|
||||
let mut m = Model::new("T");
|
||||
m.add_category("Type").unwrap();
|
||||
m.add_category("Month").unwrap();
|
||||
m.category_mut("Type").unwrap().add_item("Food");
|
||||
m.category_mut("Month").unwrap().add_item("Jan");
|
||||
let layout = GridLayout::new(&m, m.active_view());
|
||||
assert_eq!(layout.col_group_for(0), None);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user