From: Ben Pfaff Date: Wed, 9 Apr 2025 22:24:54 +0000 (-0700) Subject: tests pass for pivot tables X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ba2f75152defec46dd209d7b00d0aeefef641aa0;p=pspp tests pass for pivot tables --- diff --git a/rust/pspp/src/output/pivot/look_xml.rs b/rust/pspp/src/output/pivot/look_xml.rs index 72d3f8dfde..58fdeea77e 100644 --- a/rust/pspp/src/output/pivot/look_xml.rs +++ b/rust/pspp/src/output/pivot/look_xml.rs @@ -38,8 +38,8 @@ impl From for Look { Area::Caption => table_properties.cell_format_properties.caption.style.as_area_style(), Area::Footer => table_properties.cell_format_properties.footnotes.style.as_area_style(), Area::Corner => table_properties.cell_format_properties.corner_labels.style.as_area_style(), - Area::ColumnLabels => table_properties.cell_format_properties.column_labels.style.as_area_style(), - Area::RowLabels => table_properties.cell_format_properties.row_labels.style.as_area_style(), + Area::Labels(Axis2::X) => table_properties.cell_format_properties.column_labels.style.as_area_style(), + Area::Labels(Axis2::Y) => table_properties.cell_format_properties.row_labels.style.as_area_style(), Area::Data => table_properties.cell_format_properties.data.style.as_area_style(), Area::Layers => table_properties.cell_format_properties.layers.style.as_area_style(), }, diff --git a/rust/pspp/src/output/pivot/mod.rs b/rust/pspp/src/output/pivot/mod.rs index f582d18376..5d25d0b9ca 100644 --- a/rust/pspp/src/output/pivot/mod.rs +++ b/rust/pspp/src/output/pivot/mod.rs @@ -106,8 +106,9 @@ pub enum Area { // Top-left corner. Corner, - ColumnLabels, - RowLabels, + /// Labels for columns ([Axis2::X]) and rows ([Axis2::Y]). + Labels(Axis2), + #[default] Data, @@ -124,8 +125,8 @@ impl Area { Area::Caption => (Some(Left), Top, [8, 11], [1, 1]), Area::Footer => (Some(Left), Top, [11, 8], [2, 3]), Area::Corner => (Some(Left), Bottom, [8, 11], [1, 1]), - Area::ColumnLabels => (Some(Center), Top, [8, 11], [1, 3]), - Area::RowLabels => (Some(Left), Top, [8, 11], [1, 3]), + Area::Labels(Axis2::X) => (Some(Center), Top, [8, 11], [1, 3]), + Area::Labels(Axis2::Y) => (Some(Left), Top, [8, 11], [1, 3]), Area::Data => (None, Top, [8, 11], [1, 1]), Area::Layers => (Some(Left), Bottom, [8, 11], [1, 3]), }; diff --git a/rust/pspp/src/output/pivot/output.rs b/rust/pspp/src/output/pivot/output.rs index 706e7f46ac..58d5c1f873 100644 --- a/rust/pspp/src/output/pivot/output.rs +++ b/rust/pspp/src/output/pivot/output.rs @@ -10,8 +10,8 @@ use crate::output::{ }; use super::{ - Area, AsValueOptions, Axis2, Axis3, Border, BorderStyle, BoxBorder, Category, CategoryTrait, - Color, Coord2, Dimension, Footnote, PivotTable, Rect2, RowColBorder, Stroke, Value, + Area, AsValueOptions, Axis2, Axis3, Border, BorderStyle, BoxBorder, Color, Coord2, Dimension, + Footnote, PivotTable, Rect2, RowColBorder, Stroke, Value, }; /// All of the combinations of dimensions along an axis. @@ -181,7 +181,6 @@ impl PivotTable { RowColBorder::ColVert, self.rotate_outer_row_labels, false, - Area::ColumnLabels, ); row_headings.render( &mut body, @@ -190,7 +189,6 @@ impl PivotTable { RowColBorder::RowHorz, false, self.rotate_inner_column_labels, - Area::RowLabels, ); for (y, row_indexes) in row_enumeration.iter().enumerate() { @@ -338,21 +336,6 @@ pub struct OutputTables { pub footnotes: Option, } -fn find_category<'a>( - d: &'a Dimension, - dim_index: usize, - indexes: &[usize], - mut row_ofs: usize, -) -> Option { - let index = indexes[dim_index]; - let mut c = Category::Leaf(Arc::clone(&d.data_leaves[d.presentation_order[index]])); - while row_ofs != c.extra_depth() { - row_ofs = row_ofs.checked_sub(1 + c.extra_depth())?; - c = Category::Group(Arc::clone(&c.parent()?)); - } - Some(c) -} - struct HeadingColumn<'a> { leaf: &'a Leaf, groups: SmallVec<[Arc; 4]>, @@ -415,12 +398,11 @@ impl<'a> Heading<'a> { h: Axis2, h_ofs: usize, v_ofs: usize, - column_enumeration: &AxisEnumeration, col_horz: RowColBorder, col_vert: RowColBorder, rotate_inner_labels: bool, rotate_outer_labels: bool, - area: Area, + inner: bool, ) { let v = !h; @@ -453,7 +435,7 @@ impl<'a> Heading<'a> { (rotate_inner_labels && is_inner_row) || (rotate_outer_labels && is_outer_row) }, - area, + area: Area::Labels(h), value: Box::new(name.clone()), }, ); @@ -474,19 +456,17 @@ impl<'a> Heading<'a> { // +-----+-----+-----+-----+-----+-----+-----+-----+-----+ // |aaaa1#aaaa2#aaaa3|aaaa1#aaaa2#aaaa3|aaaa1#aaaa2#aaaa3| // +-----+-----+-----+-----+-----+-----+-----+-----+-----+ - // ``` - let border = if y1 == self.height - 1 { + // ``` + let border = if row == self.height - 1 && inner { Border::Categories(col_vert) } else { Border::Dimensions(col_vert) }; - if !vrules[x2] { - table.draw_line(border, (v, x2 + h_ofs), y1..table.n[v]); - vrules[x2] = true; - } - if !vrules[x1] { - table.draw_line(border, (v, x1 + h_ofs), y1..table.n[v]); - vrules[x1] = true; + for x in [x1, x2] { + if !vrules[x] { + table.draw_line(border, (v, x + h_ofs), y1..table.n[v]); + vrules[x] = true; + } } // Draws the horizontal lines within a dimension, that is, @@ -554,7 +534,6 @@ impl<'a> Headings<'a> { col_vert: RowColBorder, rotate_inner_labels: bool, rotate_outer_labels: bool, - area: Area, ) { if self.headings.is_empty() { return; @@ -562,28 +541,27 @@ impl<'a> Headings<'a> { let h = self.h; let n_columns = self.width(); - let n_rows = self.height(); let mut vrules = vec![false; n_columns + 1]; vrules[0] = true; vrules[n_columns] = true; let mut v_ofs = 0; for (index, heading) in self.headings.iter().enumerate() { + let inner = index == self.headings.len() - 1; heading.render( table, &mut vrules, h, h_ofs, v_ofs, - self.column_enumeration, col_horz, col_vert, rotate_inner_labels, rotate_outer_labels, - area, + inner, ); v_ofs += heading.height; - if index != self.headings.len() - 1 { + if !inner { // Draw the horizontal line between dimensions, e.g. the `=====` // line here: // diff --git a/rust/pspp/src/output/pivot/test.rs b/rust/pspp/src/output/pivot/test.rs index 5905433b94..022487d9f3 100644 --- a/rust/pspp/src/output/pivot/test.rs +++ b/rust/pspp/src/output/pivot/test.rs @@ -2,7 +2,7 @@ use std::{fs::File, sync::Arc}; use crate::output::{ driver::Driver, - pivot::{Area, Color, Look}, + pivot::{Area, Color, Look, PivotTable}, text::TextDriver, Details, Item, }; @@ -45,8 +45,7 @@ fn pivot_table_1d() { driver.write(&Arc::new(Item::new(Details::Table(Box::new(pt))))); } -#[test] -fn pivot_table_2d() { +fn d2() -> PivotTable { let mut look = Look::default(); look.areas[Area::Title].cell_style.horz_align = Some(super::HorzAlign::Left); look.areas[Area::Title].font_style.bold = false; @@ -71,19 +70,91 @@ fn pivot_table_2d() { i += 1; } } - let mut pt = pt.with_look(Arc::new(look)).build(); - let mut driver = TextDriver::new(File::create("/dev/stdout").unwrap()); - driver.write(&Arc::new(Item::new(Details::Table(Box::new(pt.clone()))))); - return; + pt.with_look(Arc::new(look)).build() +} + +#[test] +fn d2_columns() { + assert_eq!( + d2().to_string(), + "\ +Columns +╭────────┬────────┬────────╮ +│ b1 │ b2 │ b3 │ +├──┬──┬──┼──┬──┬──┼──┬──┬──┤ +│a1│a2│a3│a1│a2│a3│a1│a2│a3│ +├──┼──┼──┼──┼──┼──┼──┼──┼──┤ +│ 0│ 1│ 2│ 3│ 4│ 5│ 6│ 7│ 8│ +╰──┴──┴──┴──┴──┴──┴──┴──┴──╯ +" + ); +} + +#[test] +fn d2_rows() { + let mut pt = d2(); pt.transpose(); pt.title = Some(Box::new(Value::new_text("Rows"))); - driver.write(&Arc::new(Item::new(Details::Table(Box::new(pt.clone()))))); + assert_eq!( + pt.to_string(), + "\ +Rows +╭─────┬─╮ +│b1 a1│0│ +│ a2│1│ +│ a3│2│ +├─────┼─┤ +│b2 a1│3│ +│ a2│4│ +│ a3│5│ +├─────┼─┤ +│b3 a1│6│ +│ a2│7│ +│ a3│8│ +╰─────┴─╯ +" + ); +} +#[test] +fn d2_column_row() { + let mut pt = d2(); + pt.transpose(); pt.move_dimension(0, Axis3::X, 0); pt.title = Some(Box::new(Value::new_text("Column x Row"))); - driver.write(&Arc::new(Item::new(Details::Table(Box::new(pt.clone()))))); + assert_eq!( + pt.to_string(), + "\ +Column x Row +╭──┬──┬──┬──╮ +│ │a1│a2│a3│ +├──┼──┼──┼──┤ +│b1│ 0│ 1│ 2│ +│b2│ 3│ 4│ 5│ +│b3│ 6│ 7│ 8│ +╰──┴──┴──┴──╯ +" + ); +} +#[test] +fn d2_row_column() { + let mut pt = d2(); + pt.transpose(); + pt.move_dimension(0, Axis3::X, 0); pt.transpose(); pt.title = Some(Box::new(Value::new_text("Row x Column"))); - driver.write(&Arc::new(Item::new(Details::Table(Box::new(pt.clone()))))); + assert_eq!( + pt.to_string(), + "\ +Row x Column +╭──┬──┬──┬──╮ +│ │b1│b2│b3│ +├──┼──┼──┼──┤ +│a1│ 0│ 3│ 6│ +│a2│ 1│ 4│ 7│ +│a3│ 2│ 5│ 8│ +╰──┴──┴──┴──╯ +" + ); } diff --git a/rust/pspp/src/output/pivot/tlo.rs b/rust/pspp/src/output/pivot/tlo.rs index 74410a2b02..6697d3a6a8 100644 --- a/rust/pspp/src/output/pivot/tlo.rs +++ b/rust/pspp/src/output/pivot/tlo.rs @@ -82,8 +82,8 @@ impl From for Look { Area::Caption => (&look.pv_text_style.caption).into(), Area::Footer => (&look.pv_text_style.footer).into(), Area::Corner => (&look.pv_text_style.corner).into(), - Area::ColumnLabels => (&look.pv_text_style.column_labels).into(), - Area::RowLabels => (&look.pv_text_style.row_labels).into(), + Area::Labels(Axis2::X) => (&look.pv_text_style.column_labels).into(), + Area::Labels(Axis2::Y) => (&look.pv_text_style.row_labels).into(), Area::Data => (&look.pv_text_style.data).into(), Area::Layers => (&look.pv_text_style.layers).into(), }, diff --git a/rust/pspp/src/output/text.rs b/rust/pspp/src/output/text.rs index dd3781efb3..6d6bbcbcc7 100644 --- a/rust/pspp/src/output/text.rs +++ b/rust/pspp/src/output/text.rs @@ -22,9 +22,7 @@ use super::{ Details, Item, }; -pub struct TextDriver { - file: BufWriter, - +pub struct TextRenderer { /// Enable bold and underline in output? emphasis: bool, @@ -41,6 +39,32 @@ pub struct TextDriver { lines: Vec, } +impl TextRenderer { + pub fn new() -> Self { + let width = 80; + Self { + emphasis: true, + width, + min_hbreak: 20, + box_chars: &*&UNICODE_BOX, + n_objects: 0, + params: Params { + size: Coord2::new(width, usize::MAX), + font_size: EnumMap::from_fn(|_| 1), + line_widths: EnumMap::from_fn(|stroke| if stroke == Stroke::None { 0 } else { 1 }), + px_size: None, + min_break: EnumMap::default(), + supports_margins: false, + rtl: false, + printing: true, + can_adjust_break: false, + can_scale: false, + }, + lines: Vec::new(), + } + } +} + #[derive(Copy, Clone, PartialEq, Eq, Enum)] enum Line { None, @@ -241,97 +265,56 @@ impl PivotTable { } } +impl Display for PivotTable { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.display()) + } +} + pub struct DisplayPivotTable<'a> { pt: &'a PivotTable, - - /// Enable bold and underline in output? - emphasis: bool, - - /// Page width. - width: usize, - - /// Minimum cell size to break across pages. - min_hbreak: usize, - - box_chars: &'static BoxChars, - - params: Params, } impl<'a> DisplayPivotTable<'a> { fn new(pt: &'a PivotTable) -> Self { - let width = 80; - Self { - pt, - emphasis: false, - width, - min_hbreak: 20, - box_chars: &*&UNICODE_BOX, - params: Params { - size: Coord2::new(width, usize::MAX), - font_size: EnumMap::from_fn(|_| 1), - line_widths: EnumMap::from_fn(|stroke| if stroke == Stroke::None { 0 } else { 1 }), - px_size: None, - min_break: EnumMap::default(), - supports_margins: false, - rtl: false, - printing: true, - can_adjust_break: false, - can_scale: false, - }, - } + Self { pt } } } impl<'a> Display for DisplayPivotTable<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - todo!() + for line in TextRenderer::new().render(self.pt) { + write!(f, "{}\n", line)?; + } + Ok(()) } } +pub struct TextDriver { + file: BufWriter, + renderer: TextRenderer, +} + impl TextDriver { pub fn new(file: File) -> TextDriver { - let width = 80; Self { file: BufWriter::new(file), - emphasis: true, - width, - min_hbreak: 20, - box_chars: &*&UNICODE_BOX, - n_objects: 0, - params: Params { - size: Coord2::new(width, usize::MAX), - font_size: EnumMap::from_fn(|_| 1), - line_widths: EnumMap::from_fn(|stroke| if stroke == Stroke::None { 0 } else { 1 }), - px_size: None, - min_break: EnumMap::default(), - supports_margins: false, - rtl: false, - printing: true, - can_adjust_break: false, - can_scale: false, - }, - lines: Vec::new(), + renderer: TextRenderer::new(), } } +} - fn output_table(&mut self, table: &PivotTable) { +impl TextRenderer { + fn render(&mut self, table: &PivotTable) -> Vec { + let mut output = Vec::new(); for layer_indexes in table.layers(true) { let mut pager = Pager::new(self, table, Some(layer_indexes.as_slice())); while pager.has_next(self) { - if self.n_objects > 0 { - writeln!(&mut self.file).unwrap(); - } - self.n_objects += 1; - - let h = pager.draw_next(self, usize::MAX); - - for line in self.lines[..h].iter_mut() { - println!("{line}"); - line.clear(); - } + pager.draw_next(self, usize::MAX); + output.append(&mut self.lines); } } + output } fn display_cell<'a>(cell: &DrawCell<'a>) -> DisplayValue<'a> { @@ -503,13 +486,17 @@ impl Driver for TextDriver { Details::Group(_) => todo!(), Details::Message(_diagnostic) => todo!(), Details::PageBreak => (), - Details::Table(pivot_table) => self.output_table(pivot_table), + Details::Table(pivot_table) => { + for line in self.renderer.render(&pivot_table) { + writeln!(self.file, "{}", line.str()).unwrap(); + } + } Details::Text(_text) => todo!(), } } } -impl Device for TextDriver { +impl Device for TextRenderer { fn params(&self) -> &Params { &self.params }