Work on tests (new one fails)
authorBen Pfaff <blp@cs.stanford.edu>
Fri, 11 Apr 2025 03:49:25 +0000 (20:49 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Fri, 11 Apr 2025 03:49:25 +0000 (20:49 -0700)
rust/pspp/src/output/pivot/mod.rs
rust/pspp/src/output/pivot/test.rs

index 64e806b8ce98d005bdd009cc8cd5001f04d02729..f00cdd77f69e58d13500dcc529ae63ac1ffe4b90 100644 (file)
@@ -298,6 +298,9 @@ impl Iterator for AxisIterator {
 }
 
 impl PivotTable {
+    fn builder(title: impl Into<Value>, dimensions: &[DimensionBuilder]) -> PivotTableBuilder {
+        PivotTableBuilder::new(title, dimensions)
+    }
     fn axis_values(&self, axis: Axis3) -> AxisIterator {
         AxisIterator {
             indexes: repeat_n(0, self.axes[axis].dimensions.len()).collect(),
@@ -358,6 +361,10 @@ impl Dimension {
     pub fn len(&self) -> usize {
         self.data_leaves.len()
     }
+
+    pub fn builder(axis: Axis3, root: GroupBuilder) -> DimensionBuilder {
+        DimensionBuilder::new(axis, root)
+    }
 }
 
 #[derive(Clone, Debug)]
@@ -376,6 +383,10 @@ pub struct Group {
 }
 
 impl Group {
+    pub fn builder(name: impl Into<Value>) -> GroupBuilder {
+        GroupBuilder::new(name)
+    }
+
     pub fn parent(&self) -> Option<Arc<Group>> {
         self.parent.as_ref().map(|parent| parent.upgrade().unwrap())
     }
@@ -423,9 +434,9 @@ pub struct GroupBuilder {
 }
 
 impl GroupBuilder {
-    pub fn new(name: Value) -> Self {
+    pub fn new(name: impl Into<Value>) -> Self {
         Self {
-            name: Box::new(name),
+            name: Box::new(name.into()),
             children: Vec::new(),
             show_label: None,
         }
@@ -529,6 +540,14 @@ impl From<(Value, Class)> for CategoryBuilder {
     }
 }
 
+impl From<&str> for CategoryBuilder {
+    fn from(name: &str) -> Self {
+        Self::Leaf {
+            name: Box::new(Value::new_text(name)),
+            class: None,
+        }
+    }
+}
 pub struct PivotTableBuilder {
     look: Arc<Look>,
     title: Box<Value>,
@@ -537,10 +556,10 @@ pub struct PivotTableBuilder {
 }
 
 impl PivotTableBuilder {
-    pub fn new(title: Value, dimensions: &[DimensionBuilder]) -> Self {
+    pub fn new(title: impl Into<Value>, dimensions: &[DimensionBuilder]) -> Self {
         Self {
             look: Settings::global().look.clone(),
-            title: Box::new(title),
+            title: Box::new(title.into()),
             dimensions: dimensions.to_vec(),
             cells: HashMap::new(),
         }
@@ -748,6 +767,10 @@ impl Look {
         self.row_label_position = row_label_position;
         self
     }
+    pub fn with_borders(mut self, borders: EnumMap<Border, BorderStyle>) -> Self {
+        self.borders = borders;
+        self
+    }
 }
 
 impl Default for Look {
@@ -1746,6 +1769,12 @@ impl Value {
     }
 }
 
+impl From<&str> for Value {
+    fn from(value: &str) -> Self {
+        Self::new_text(value)
+    }
+}
+
 pub struct DisplayValue<'a> {
     inner: &'a ValueInner,
     markup: bool,
index 92318a2b74b6d9a53723acfa38470af7ebf5ce8d..c0667f1049ff3cfbdb1bd4b0f8ec0f9140dc1b68 100644 (file)
@@ -1,6 +1,11 @@
 use std::sync::Arc;
 
-use crate::output::pivot::{Area, Color, LabelPosition, Look, PivotTable};
+use enum_map::EnumMap;
+
+use crate::output::pivot::{
+    Area, Axis2, Border, BorderStyle, Color, Dimension, Group, HeadingRegion, LabelPosition, Look,
+    PivotTable, RowColBorder, Stroke,
+};
 
 use super::{Axis3, DimensionBuilder, GroupBuilder, PivotTableBuilder, Value};
 
@@ -582,9 +587,135 @@ fn empty_dimensions() {
             .with(Value::new_text("c1"))
             .with(Value::new_text("c2")),
     );
-    let pivot_table =
-        PivotTableBuilder::new(Value::new_text("Three Dimensions, Two Empty"), &[d1, d2, d3])
-            .with_look(look.clone())
-            .build();
+    let pivot_table = PivotTableBuilder::new(
+        Value::new_text("Three Dimensions, Two Empty"),
+        &[d1, d2, d3],
+    )
+    .with_look(look.clone())
+    .build();
     assert_rendering(&pivot_table, "Three Dimensions, Two Empty\n");
 }
+
+#[test]
+fn empty_groups() {
+    let mut a = GroupBuilder::new(Value::new_text("a"))
+        .with(Value::new_text("a1"))
+        .with(GroupBuilder::new(Value::new_text("a2")))
+        .with(Value::new_text("a3"));
+    let d1 = DimensionBuilder::new(Axis3::X, a);
+
+    let mut b = GroupBuilder::new(Value::new_text("b"))
+        .with(GroupBuilder::new(Value::new_text("b1")))
+        .with(Value::new_text("b2"))
+        .with(Value::new_text("b3"));
+    let d2 = DimensionBuilder::new(Axis3::Y, b);
+
+    let mut pt = PivotTableBuilder::new(Value::new_text("Empty Groups"), &[d1, d2]);
+    let mut i = 0;
+    for b in 0..2 {
+        for a in 0..2 {
+            pt.insert(&[a, b], Value::new_integer(Some(i as f64)));
+            i += 1;
+        }
+    }
+    let pivot_table = pt
+        .with_look(Arc::new(test_look().with_omit_empty(false)))
+        .build();
+    assert_rendering(
+        &pivot_table,
+        "\
+Empty Groups
+╭──┬──┬──╮
+│  │a1│a3│
+├──┼──┼──┤
+│b2│ 0│ 1│
+│b3│ 2│ 3│
+╰──┴──┴──╯
+",
+    );
+}
+
+#[test]
+fn borders() {
+    let a = Dimension::builder(
+        Axis3::X,
+        Group::builder("a")
+            .with_label_shown()
+            .with("a1")
+            .with(Group::builder("ag1").with("a2").with("a3")),
+    );
+    let b = Dimension::builder(
+        Axis3::X,
+        Group::builder("b")
+            .with_label_shown()
+            .with(Group::builder("bg1").with("b1").with("b2"))
+            .with("b3"),
+    );
+    let c = Dimension::builder(
+        Axis3::Y,
+        Group::builder("c")
+            .with_label_shown()
+            .with("c1")
+            .with(Group::builder("cg1").with("c2").with("c3")),
+    );
+    let d = Dimension::builder(
+        Axis3::Y,
+        Group::builder("d")
+            .with_label_shown()
+            .with(Group::builder("dg1").with("d1").with("d2"))
+            .with("d3"),
+    );
+    let borders = EnumMap::from_fn(|border| match border {
+        Border::Dimension(RowColBorder(HeadingRegion::Rows, Axis2::X))
+        | Border::Dimension(RowColBorder(HeadingRegion::Columns, Axis2::Y)) => BorderStyle {
+            stroke: Stroke::Solid,
+            color: Color {
+                alpha: 255,
+                r: 0,
+                g: 0,
+                b: 255,
+            },
+        },
+        _ => BorderStyle::none(),
+    });
+    let mut pivot_table = PivotTable::builder("Dimension Borders 1", &[a, b, c, d])
+        .with_look(Arc::new(test_look().with_borders(borders)));
+    let mut i = 0;
+    for d in 0..3 {
+        for c in 0..3 {
+            for b in 0..3 {
+                for a in 0..3 {
+                    pivot_table.insert(&[a, b, c, d], Value::new_integer(Some(i as f64)));
+                    i += 1;
+                }
+            }
+        }
+    }
+    let pivot_table = pivot_table.build();
+    assert_rendering(
+        &pivot_table,
+        "\
+Dimension Borders 1
+                           b
+                     bg1       │
+                 b1   │   b2   │   b3
+                  a   │    a   │    a
+                │ ag1 │  │ ag1 │  │ ag1
+d      c      a1│a2 a3│a1│a2 a3│a1│a2 a3
+dg1 d1 c1      0│ 1  2│ 3│ 4  5│ 6│ 7  8
+      ╶─────────┼─────┼──┼─────┼──┼─────
+       cg1 c2  9│10 11│12│13 14│15│16 17
+           c3 18│19 20│21│22 23│24│25 26
+   ╶────────────┼─────┼──┼─────┼──┼─────
+    d2 c1     27│28 29│30│31 32│33│34 35
+      ╶─────────┼─────┼──┼─────┼──┼─────
+       cg1 c2 36│37 38│39│40 41│42│43 44
+           c3 45│46 47│48│49 50│51│52 53
+────────────────┼─────┼──┼─────┼──┼─────
+d3     c1     54│55 56│57│58 59│60│61 62
+      ╶─────────┼─────┼──┼─────┼──┼─────
+       cg1 c2 63│64 65│66│67 68│69│70 71
+           c3 72│73 74│75│76 77│78│79 80
+",
+    );
+}