//! a category for each dimension to a value, which is commonly a number but
//! could also be a variable name or an arbitrary text string.
//!
-//! Creating a pivot table usually consists of the following steps:
-//!
-//! 1. Create the table with pivot_table_create(), passing in the title.
-//!
-//! 2. Optionally, set the format to use for "count" values with
-//! pivot_table_set_weight_var() or pivot_table_set_weight_format().
-//!
-//! 3. Create each dimension with pivot_dimension_create() and populate it with
-//! categories and, possibly, with groups that contain the categories. This
-//! call also assigns the dimension to an axis.
-//!
-//! In simple cases, only a call to pivot_dimension_create() is needed.
-//! Other functions such as pivot_category_create_group() can be used for
-//! hierarchies of categories.
-//!
-//! Sometimes it's easier to create categories in tandem with inserting data,
-//! for example by adding a category for a variable just before inserting the
-//! first cell for that variable. In that case, creating categories and
-//! inserting data can be interleaved.
-//!
-//! 4. Insert data. For each cell, supply the category indexes, which are
-//! assigned starting from 0 in the order in which the categories were
-//! created in step 2, and the value to go in the cell. If the table has a
-//! small, fixed number of dimensions, functions like, e.g.
-//! pivot_table_put3() for 3 dimensions, can be used. The general function
-//! pivot_table_put() works for other cases.
-//!
-//! 5. Output the table for user consumption. Use pivot_table_submit().
+//! Use [PivotTable::builder] to create a pivot table.
use std::{
collections::HashMap,
}
impl PivotTable {
- fn builder(title: impl Into<Value>, dimensions: &[DimensionBuilder]) -> PivotTableBuilder {
+ fn builder(title: impl Into<Value>, dimensions: Vec<DimensionBuilder>) -> PivotTableBuilder {
PivotTableBuilder::new(title, dimensions)
}
fn axis_values(&self, axis: Axis3) -> AxisIterator {
#[derive(Clone)]
pub struct DimensionBuilder {
axis: Axis3,
- root: GroupBuilder,
- len: usize,
- hide_all_labels: bool,
+ dimension: Dimension,
}
impl DimensionBuilder {
pub fn new(axis: Axis3, root: GroupBuilder) -> Self {
- let len = root.len();
+ let mut leaves = Vec::with_capacity(root.len());
+ let root = root.build(None, &mut leaves);
Self {
axis,
- root,
- len,
- hide_all_labels: false,
+ dimension: Dimension {
+ root,
+ presentation_order: (0..leaves.len()).collect(),
+ data_leaves: leaves,
+ hide_all_labels: false,
+ },
}
}
pub fn with_all_labels_hidden(mut self) -> Self {
- self.hide_all_labels = true;
+ self.dimension.hide_all_labels = true;
self
}
fn build(self) -> Dimension {
- let mut leaves = Vec::with_capacity(self.len);
- let root = self.root.build(None, &mut leaves);
- Dimension {
- root,
- presentation_order: (0..leaves.len()).collect(),
- data_leaves: leaves,
- hide_all_labels: self.hide_all_labels,
- }
+ self.dimension
}
}
pub struct PivotTableBuilder {
look: Arc<Look>,
title: Box<Value>,
- dimensions: Vec<DimensionBuilder>,
+ axes: EnumMap<Axis3, Axis>,
+ dimensions: Vec<Dimension>,
cells: HashMap<usize, Value>,
}
impl PivotTableBuilder {
- pub fn new(title: impl Into<Value>, dimensions: &[DimensionBuilder]) -> Self {
+ pub fn new(title: impl Into<Value>, dimension_builders: Vec<DimensionBuilder>) -> Self {
+ let mut dimensions = Vec::with_capacity(dimension_builders.len());
+ let mut axes = EnumMap::from_fn(|_axis| Axis {
+ dimensions: Vec::with_capacity(dimension_builders.len()),
+ });
+ for d in dimension_builders {
+ axes[d.axis].dimensions.push(dimensions.len());
+ dimensions.push(d.build());
+ }
Self {
look: Settings::global().look.clone(),
title: Box::new(title.into()),
- dimensions: dimensions.to_vec(),
+ axes,
+ dimensions,
cells: HashMap::new(),
}
}
self.look = look;
self
}
+ pub fn insert_number(&mut self, data_indexes: &[usize], number: Option<f64>, class: Class) {
+ let format = match class {
+ Class::Other => Settings::global().default_format,
+ Class::Integer => Format::F40,
+ Class::Correlations => Format::F40_3,
+ Class::Significance => Format::F40_3,
+ Class::Percent => Format::PCT40_1,
+ Class::Residual => Format::F40_2,
+ Class::Count => Format::F40, // XXX
+ };
+ let value = Value::new(ValueInner::Number {
+ show: None,
+ format,
+ honor_small: class == Class::Other,
+ value: number,
+ var_name: None,
+ value_label: None,
+ });
+ self.insert(data_indexes, value);
+ }
pub fn insert(&mut self, data_indexes: &[usize], value: Value) {
self.cells.insert(
- cell_index(data_indexes, self.dimensions.iter().map(|d| d.len)),
+ cell_index(data_indexes, self.dimensions.iter().map(|d| d.len())),
value,
);
}
pub fn build(self) -> PivotTable {
let mut table = PivotTable::new(self.title, self.look.clone());
- let mut dimensions = Vec::with_capacity(self.dimensions.len());
- let mut axes = EnumMap::from_fn(|_axis| Axis {
- dimensions: Vec::with_capacity(self.dimensions.len()),
- });
- for d in self.dimensions {
- axes[d.axis].dimensions.push(dimensions.len());
- dimensions.push(d.build());
- }
- table.dimensions = dimensions;
- table.axes = axes;
+ table.dimensions = self.dimensions;
+ table.axes = self.axes;
table.cells = self.cells;
table.current_layer = repeat_n(0, table.axes[Axis3::Z].dimensions.len()).collect();
table
use enum_map::EnumMap;
use crate::output::pivot::{
- Area, Axis2, Border, BorderStyle, Color, Dimension, Group, HeadingRegion, LabelPosition, Look,
- PivotTable, RowColBorder, Stroke,
+ Area, Axis2, Border, BorderStyle, Class, Color, Dimension, Group, HeadingRegion, LabelPosition,
+ Look, PivotTable, RowColBorder, Stroke,
};
use super::{Axis3, DimensionBuilder, GroupBuilder, PivotTableBuilder, Value};
group.push(Value::new_text(name));
}
let dimension = DimensionBuilder::new(axis, group);
- let mut pt = PivotTableBuilder::new(Value::new_text(title), &[dimension]);
+ let mut pt = PivotTableBuilder::new(Value::new_text(title), vec![dimension]);
for i in 0..3 {
pt.insert(&[i], Value::new_integer(Some(i as f64)));
}
}
let d2 = DimensionBuilder::new(axes[1], b);
- let mut pt = PivotTableBuilder::new(Value::new_text(title), &[d1, d2]);
+ let mut pt = PivotTableBuilder::new(Value::new_text(title), vec![d1, d2]);
let mut i = 0;
for b in 0..3 {
for a in 0..3 {
#[test]
fn no_dimension() {
- let pivot_table = PivotTableBuilder::new(Value::new_text("No Dimensions"), &[])
+ let pivot_table = PivotTableBuilder::new(Value::new_text("No Dimensions"), vec![])
.with_look(Arc::new(test_look()))
.build();
assert_rendering(
let look = Arc::new(test_look().with_omit_empty(false));
let d1 = DimensionBuilder::new(Axis3::X, GroupBuilder::new(Value::new_text("a")));
- let pivot_table = PivotTableBuilder::new(Value::new_text("One Empty Dimension"), &[d1])
+ let pivot_table = PivotTableBuilder::new(Value::new_text("One Empty Dimension"), vec![d1])
.with_look(look.clone())
.build();
assert_rendering(&pivot_table, "One Empty Dimension\n");
Axis3::X,
GroupBuilder::new(Value::new_text("b")).with_label_shown(),
);
- let pivot_table = PivotTableBuilder::new(Value::new_text("Two Empty Dimensions"), &[d1, d2])
+ let pivot_table = PivotTableBuilder::new(Value::new_text("Two Empty Dimensions"), vec![d1, d2])
.with_look(look.clone())
.build();
assert_rendering(&pivot_table, "Two Empty Dimensions\n");
);
let pivot_table = PivotTableBuilder::new(
Value::new_text("Three Dimensions, Two Empty"),
- &[d1, d2, d3],
+ vec![d1, d2, d3],
)
.with_look(look.clone())
.build();
.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 pt = PivotTableBuilder::new(Value::new_text("Empty Groups"), vec![d1, d2]);
let mut i = 0;
for b in 0..2 {
for a in 0..2 {
.with(Group::builder("dg1").with("d1").with("d2"))
.with("d3"),
);
- let mut pivot_table = PivotTable::builder(title, &[a, b, c, d])
+ let mut pivot_table = PivotTable::builder(title, vec![a, b, c, d])
.with_look(Arc::new(test_look().with_borders(borders)));
let mut i = 0;
for d in 0..3 {
",
);
}
+
+#[test]
+fn small_numbers() {
+ let exponent = Dimension::builder(
+ Axis3::Y,
+ Group::builder("exponent")
+ .with("0")
+ .with("-1")
+ .with("-2")
+ .with("-3")
+ .with("-4")
+ .with("-5")
+ .with("-6")
+ .with("-7")
+ .with("-8")
+ .with("-9")
+ .with_label_shown(),
+ );
+ let sign = Dimension::builder(
+ Axis3::X,
+ Group::builder("sign")
+ .with("positive")
+ .with("negative")
+ .with_label_shown(),
+ );
+ let rc = Dimension::builder(
+ Axis3::X,
+ Group::builder("result class")
+ .with((Value::new_text("general"), Class::Other))
+ .with((Value::new_text("specific"), Class::Residual))
+ .with_label_shown(),
+ );
+ let mut pt = PivotTable::builder("small numbers", vec![exponent, sign, rc]);
+ pt.insert_number(&[0, 0, 0], Some(1.0), Class::Other);
+ pt.insert_number(&[1, 0, 0], Some(0.1), Class::Other);
+ pt.insert_number(&[2, 0, 0], Some(0.01), Class::Other);
+ pt.insert_number(&[3, 0, 0], Some(0.001), Class::Other);
+ pt.insert_number(&[4, 0, 0], Some(0.0001), Class::Other);
+ pt.insert_number(&[5, 0, 0], Some(0.00001), Class::Other);
+ pt.insert_number(&[6, 0, 0], Some(0.000001), Class::Other);
+ pt.insert_number(&[7, 0, 0], Some(0.0000001), Class::Other);
+ pt.insert_number(&[8, 0, 0], Some(0.00000001), Class::Other);
+ pt.insert_number(&[9, 0, 0], Some(0.000000001), Class::Other);
+ pt.insert_number(&[0, 0, 1], Some(-1.0), Class::Residual);
+ pt.insert_number(&[1, 0, 1], Some(-0.1), Class::Residual);
+ pt.insert_number(&[2, 0, 1], Some(-0.01), Class::Residual);
+ pt.insert_number(&[3, 0, 1], Some(-0.001), Class::Residual);
+ pt.insert_number(&[4, 0, 1], Some(-0.0001), Class::Residual);
+ pt.insert_number(&[5, 0, 1], Some(-0.00001), Class::Residual);
+ pt.insert_number(&[6, 0, 1], Some(-0.000001), Class::Residual);
+ pt.insert_number(&[7, 0, 1], Some(-0.0000001), Class::Residual);
+ pt.insert_number(&[8, 0, 1], Some(-0.00000001), Class::Residual);
+ pt.insert_number(&[9, 0, 1], Some(-0.000000001), Class::Residual);
+ pt.insert_number(&[0, 1, 0], Some(1.0), Class::Other);
+ pt.insert_number(&[1, 1, 0], Some(0.1), Class::Other);
+ pt.insert_number(&[2, 1, 0], Some(0.01), Class::Other);
+ pt.insert_number(&[3, 1, 0], Some(0.001), Class::Other);
+ pt.insert_number(&[4, 1, 0], Some(0.0001), Class::Other);
+ pt.insert_number(&[5, 1, 0], Some(0.00001), Class::Other);
+ pt.insert_number(&[6, 1, 0], Some(0.000001), Class::Other);
+ pt.insert_number(&[7, 1, 0], Some(0.0000001), Class::Other);
+ pt.insert_number(&[8, 1, 0], Some(0.00000001), Class::Other);
+ pt.insert_number(&[9, 1, 0], Some(0.000000001), Class::Other);
+ pt.insert_number(&[0, 1, 1], Some(-1.0), Class::Residual);
+ pt.insert_number(&[1, 1, 1], Some(-0.1), Class::Residual);
+ pt.insert_number(&[2, 1, 1], Some(-0.01), Class::Residual);
+ pt.insert_number(&[3, 1, 1], Some(-0.001), Class::Residual);
+ pt.insert_number(&[4, 1, 1], Some(-0.0001), Class::Residual);
+ pt.insert_number(&[5, 1, 1], Some(-0.00001), Class::Residual);
+ pt.insert_number(&[6, 1, 1], Some(-0.000001), Class::Residual);
+ pt.insert_number(&[7, 1, 1], Some(-0.0000001), Class::Residual);
+ pt.insert_number(&[8, 1, 1], Some(-0.00000001), Class::Residual);
+ pt.insert_number(&[9, 1, 1], Some(-0.000000001), Class::Residual);
+ let pivot_table = pt.with_look(Arc::new(test_look())).build();
+ assert_rendering(
+ &pivot_table,
+ "\
+small numbers
+╭────────┬─────────────────────────────────────╮
+│ │ result class │
+│ ├───────────────────┬─────────────────┤
+│ │ general │ specific │
+│ ├───────────────────┼─────────────────┤
+│ │ sign │ sign │
+│ ├─────────┬─────────┼────────┬────────┤
+│exponent│ positive│ negative│positive│negative│
+├────────┼─────────┼─────────┼────────┼────────┤
+│0 │ 1.00│ 1.00│ -1.00│ -1.00│
+│-1 │ .10│ .10│ -.10│ -.10│
+│-2 │ .01│ .01│ -.01│ -.01│
+│-3 │ .00│ .00│ .00│ .00│
+│-4 │ .00│ .00│ .00│ .00│
+│-5 │1.00E-005│1.00E-005│ .00│ .00│
+│-6 │1.00E-006│1.00E-006│ .00│ .00│
+│-7 │1.00E-007│1.00E-007│ .00│ .00│
+│-8 │1.00E-008│1.00E-008│ .00│ .00│
+│-9 │1.00E-009│1.00E-009│ .00│ .00│
+╰────────┴─────────┴─────────┴────────┴────────╯
+",
+ );
+}