use encoding_rs::UTF_8;
use enum_iterator::Sequence;
use enum_map::{enum_map, Enum, EnumMap};
+use look_xml::TableProperties;
use quick_xml::{de::from_str, DeError};
use serde::{de::Visitor, Deserialize};
use smallstr::SmallString;
pub mod output;
mod look_xml;
+#[cfg(test)]
+mod test;
mod tlo;
-pub use look_xml::TableProperties;
/// Areas of a pivot table for styling purposes.
#[derive(Copy, Clone, Debug, Default, Enum, PartialEq, Eq)]
keeps: Vec<Range<usize>>,
}
-#[derive(Copy, Clone, Debug, Enum, Sequence)]
+#[derive(Copy, Clone, Debug, Enum, PartialEq, Eq, Sequence)]
pub enum Axis3 {
X,
Y,
/// data.)
#[derive(Clone, Debug)]
pub struct Dimension {
- axis_type: Axis3,
+ /// Our axis within the pivot table.
+ axis: Axis3,
+
+ /// The index for this dimension within its axis.
+ ///
+ /// This [Dimension] is `pivot_table.axes[axis].dimensions[level]`.
level: usize,
+ /// The index within the dimension.
+ ///
+ /// This [Dimension] is `pivot_table.dimensions[top_index]`.
top_index: usize,
/// Hierarchy of categories within the dimension. The groups and categories
///
/// The root must always be a group, although it is allowed to have no
/// subcategories.
- root: Group,
+ pub root: Arc<Group>,
/// All of the leaves reachable via the root.
///
children: Vec<Category>,
/// Display a label for the group itself?
- show_label: bool,
+ pub show_label: bool,
show_label_in_corner: bool,
}
+#[derive(Clone)]
+pub struct DimensionBuilder {
+ axis: Axis3,
+ root: GroupBuilder,
+ len: usize,
+ hide_all_labels: bool,
+}
+
+impl DimensionBuilder {
+ pub fn new(axis: Axis3, root: GroupBuilder) -> Self {
+ let len = root.len();
+ Self {
+ axis,
+ root,
+ len,
+ hide_all_labels: false,
+ }
+ }
+ pub fn with_all_labels_hidden(mut self) -> Self {
+ self.hide_all_labels = true;
+ self
+ }
+ fn build(self, level: usize, top_index: usize, dimension_labels_in_corner: bool) -> Dimension {
+ let mut leaves = Vec::with_capacity(self.len);
+ let root = self
+ .root
+ .build(dimension_labels_in_corner, None, &mut leaves);
+ Dimension {
+ axis: self.axis,
+ level,
+ top_index,
+ root,
+ data_leaves: leaves.clone(),
+ presentation_leaves: leaves,
+ hide_all_labels: self.hide_all_labels,
+ label_depth: 0,
+ }
+ }
+}
+
+#[derive(Clone)]
+pub struct GroupBuilder {
+ name: Box<Value>,
+ children: Vec<CategoryBuilder>,
+ show_label: bool,
+}
+
+impl GroupBuilder {
+ pub fn new(name: Value) -> Self {
+ Self {
+ name: Box::new(name),
+ children: Vec::new(),
+ show_label: true,
+ }
+ }
+ pub fn push<T>(&mut self, value: T)
+ where
+ T: Into<CategoryBuilder>,
+ {
+ self.children.push(value.into());
+ }
+ pub fn with<T>(mut self, value: T) -> Self
+ where
+ T: Into<CategoryBuilder>,
+ {
+ self.push(value);
+ self
+ }
+ pub fn with_label_hidden(mut self) -> Self {
+ self.show_label = false;
+ self
+ }
+ fn len(&self) -> usize {
+ self.children.iter().map(|category| category.len()).sum()
+ }
+ fn build(
+ self,
+ dimension_labels_in_corner: bool,
+ parent: Option<Weak<Group>>,
+ leaves: &mut Vec<Arc<Leaf>>,
+ ) -> Arc<Group> {
+ Arc::new_cyclic(|weak| Group {
+ parent,
+ name: self.name,
+ label_depth: 0,
+ extra_depth: 0,
+ children: self
+ .children
+ .into_iter()
+ .enumerate()
+ .map(|(group_index, c)| {
+ c.build(
+ dimension_labels_in_corner,
+ weak.clone(),
+ group_index,
+ leaves,
+ )
+ })
+ .collect(),
+ show_label: self.show_label,
+ show_label_in_corner: self.show_label && dimension_labels_in_corner,
+ })
+ }
+}
+
+#[derive(Clone)]
+pub enum CategoryBuilder {
+ Group(Box<GroupBuilder>),
+ Leaf {
+ name: Box<Value>,
+ class: Option<Class>,
+ },
+}
+
+impl CategoryBuilder {
+ fn len(&self) -> usize {
+ match self {
+ CategoryBuilder::Group(group) => group.len(),
+ CategoryBuilder::Leaf { .. } => 1,
+ }
+ }
+ fn build(
+ self,
+ dimension_labels_in_corner: bool,
+ parent: Weak<Group>,
+ group_index: usize,
+ leaves: &mut Vec<Arc<Leaf>>,
+ ) -> Category {
+ match self {
+ Self::Group(group) => {
+ Category::Group(group.build(dimension_labels_in_corner, Some(parent), leaves))
+ }
+ Self::Leaf { name, class } => {
+ let leaf = Arc::new(Leaf {
+ parent,
+ name,
+ label_depth: 0,
+ extra_depth: 0,
+ group_index,
+ data_index: leaves.len(),
+ presentation_index: leaves.len(),
+ class,
+ });
+ leaves.push(leaf.clone());
+ Category::Leaf(leaf)
+ }
+ }
+ }
+}
+
+impl From<Value> for CategoryBuilder {
+ fn from(name: Value) -> Self {
+ Self::Leaf {
+ name: Box::new(name),
+ class: None,
+ }
+ }
+}
+
+impl From<(Value, Class)> for CategoryBuilder {
+ fn from((name, class): (Value, Class)) -> Self {
+ Self::Leaf {
+ name: Box::new(name),
+ class: Some(class),
+ }
+ }
+}
+
+pub struct PivotTableBuilder {
+ look: Arc<Look>,
+ title: Box<Value>,
+ dimensions: Vec<DimensionBuilder>,
+ cells: HashMap<usize, Value>,
+}
+
+impl PivotTableBuilder {
+ pub fn new(title: Value, dimensions: &[DimensionBuilder]) -> Self {
+ Self {
+ look: Settings::global().look.clone(),
+ title: Box::new(title),
+ dimensions: dimensions.to_vec(),
+ cells: HashMap::new(),
+ }
+ }
+ pub fn with_look(mut self, look: Arc<Look>) -> Self {
+ self.look = look;
+ self
+ }
+ pub fn insert(&mut self, data_indexes: &[usize], value: Value) {
+ self.cells.insert(
+ cell_index(data_indexes, self.dimensions.iter().map(|d| d.len)),
+ value,
+ );
+ }
+ pub fn build(self) -> PivotTable {
+ let row_label_position = self.look.row_label_position;
+ let corner_text = false;
+ 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(|_key| Vec::with_capacity(self.dimensions.len()));
+ for (top_index, d) in self.dimensions.into_iter().enumerate() {
+ let axis = d.axis;
+ let d = Arc::new(d.build(
+ axes[axis].len(),
+ top_index,
+ axis == Axis3::Y && row_label_position == RowLabelPosition::Corner && !corner_text,
+ ));
+ axes[d.axis].push(d.clone());
+ dimensions.push(d);
+ }
+ table.dimensions = dimensions;
+ table.axes = axes.map(|_axis, dimensions| Axis {
+ dimensions,
+ extent: 0,
+ label_depth: 0,
+ });
+ table.cells = self.cells;
+ table
+ }
+}
+
#[derive(Clone, Debug)]
pub struct Leaf {
parent: Weak<Group>,
label_depth: usize,
extra_depth: usize,
+ /// `parent.children[group_index]` is this.
group_index: usize,
data_index: usize,
presentation_index: usize,
/// Default format for values in this category.
- format: Format,
+ class: Option<Class>,
+}
+
+impl Leaf {
+ pub fn new(name: Value) -> Self {
+ Self {
+ parent: Weak::new(),
+ name: Box::new(name),
+ label_depth: 0,
+ extra_depth: 0,
+ group_index: 0,
+ data_index: 0,
+ presentation_index: 0,
+ class: None,
+ }
+ }
+ pub fn with_class(self, class: Class) -> Self {
+ Self {
+ class: Some(class),
+ ..self
+ }
+ }
+}
- /// Honor [Table]'s `small` setting?
- honor_small: bool,
+/// Pivot result classes.
+///
+/// These are used to mark [Leaf] categories as having particular types of data,
+/// to set their numeric formats.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum Class {
+ Other,
+ Integer,
+ Correlations,
+ Significance,
+ Percent,
+ Residual,
+ Count,
}
/// A pivot_category is a leaf (a category) or a group.
Nested,
#[default]
- InCorner,
+ Corner,
}
/// The heading region of a rendered pivot table:
}
}
-#[cfg(test)]
-mod test {
- use crate::output::pivot::Color;
-
- #[test]
- fn color() {
- assert_eq!("#112233".parse(), Ok(Color::new(0x11, 0x22, 0x33)));
- assert_eq!("112233".parse(), Ok(Color::new(0x11, 0x22, 0x33)));
- assert_eq!("rgb(11,22,33)".parse(), Ok(Color::new(11, 22, 33)));
- assert_eq!(
- "rgba(11,22,33, 0.25)".parse(),
- Ok(Color::new(11, 22, 33).with_alpha(64))
- );
- assert_eq!("lavender".parse(), Ok(Color::new(230, 230, 250)));
- assert_eq!("transparent".parse(), Ok(Color::new(0, 0, 0).with_alpha(0)));
- }
-}
-
impl<'de> Deserialize<'de> for Color {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
pub cells: HashMap<usize, Value>,
}
-impl PivotTable {
- fn new() -> Self {
+impl Default for PivotTable {
+ fn default() -> Self {
Self {
look: Look::shared_default(),
rotate_inner_column_labels: false,
cells: HashMap::new(),
}
}
+}
+
+fn cell_index<I>(data_indexes: &[usize], dimensions: I) -> usize
+where
+ I: ExactSizeIterator<Item = usize>,
+{
+ debug_assert_eq!(data_indexes.len(), dimensions.len());
+ let mut index = 0;
+ for (dimension, data_index) in dimensions.zip(data_indexes.iter()) {
+ debug_assert!(*data_index < dimension);
+ index = dimension * index + data_index;
+ }
+ index
+}
+impl PivotTable {
+ fn new(title: Box<Value>, look: Arc<Look>) -> Self {
+ let mut this = Self::default();
+ this.title = Some(title);
+ this.look = look;
+ this
+ }
fn cell_index(&self, data_indexes: &[usize]) -> usize {
- debug_assert_eq!(data_indexes.len(), self.dimensions.len());
- let mut index = 0;
- for (dimension, data_index) in self.dimensions.iter().zip(data_indexes.iter()) {
- debug_assert!(*data_index < dimension.len());
- index = dimension.len() * index + data_index;
- }
- index
+ cell_index(data_indexes, self.dimensions.iter().map(|d| d.len()))
}
fn insert(&mut self, data_indexes: &[usize], value: Value) {
}
impl Value {
+ pub fn new_text(s: impl Into<String>) -> Self {
+ Self::new_user_text(s)
+ }
pub fn new_user_text(s: impl Into<String>) -> Self {
let s: String = s.into();
Self {