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(),
},
// Top-left corner.
Corner,
- ColumnLabels,
- RowLabels,
+ /// Labels for columns ([Axis2::X]) and rows ([Axis2::Y]).
+ Labels(Axis2),
+
#[default]
Data,
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]),
};
};
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.
RowColBorder::ColVert,
self.rotate_outer_row_labels,
false,
- Area::ColumnLabels,
);
row_headings.render(
&mut body,
RowColBorder::RowHorz,
false,
self.rotate_inner_column_labels,
- Area::RowLabels,
);
for (y, row_indexes) in row_enumeration.iter().enumerate() {
pub footnotes: Option<Table>,
}
-fn find_category<'a>(
- d: &'a Dimension,
- dim_index: usize,
- indexes: &[usize],
- mut row_ofs: usize,
-) -> Option<Category> {
- 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<Group>; 4]>,
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;
(rotate_inner_labels && is_inner_row)
|| (rotate_outer_labels && is_outer_row)
},
- area,
+ area: Area::Labels(h),
value: Box::new(name.clone()),
},
);
// +-----+-----+-----+-----+-----+-----+-----+-----+-----+
// |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,
col_vert: RowColBorder,
rotate_inner_labels: bool,
rotate_outer_labels: bool,
- area: Area,
) {
if self.headings.is_empty() {
return;
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:
//
use crate::output::{
driver::Driver,
- pivot::{Area, Color, Look},
+ pivot::{Area, Color, Look, PivotTable},
text::TextDriver,
Details, Item,
};
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;
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│
+╰──┴──┴──┴──╯
+"
+ );
}
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(),
},
Details, Item,
};
-pub struct TextDriver {
- file: BufWriter<File>,
-
+pub struct TextRenderer {
/// Enable bold and underline in output?
emphasis: bool,
lines: Vec<TextLine>,
}
+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,
}
}
+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<File>,
+ 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<TextLine> {
+ 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> {
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
}