use bon::bon;
use polars::frame::DataFrame;
use crate::{
components::{Cell, Header, Text},
ir::layout::LayoutIR,
ir::trace::{TableIR, TraceIR},
};
#[derive(Clone)]
#[allow(dead_code)]
pub struct Table {
traces: Vec<TraceIR>,
layout: LayoutIR,
}
#[bon]
impl Table {
#[builder(on(String, into), on(Text, into))]
pub fn new(
data: &DataFrame,
columns: Vec<&str>,
header: Option<&Header>,
cell: Option<&Cell>,
column_width: Option<f64>,
plot_title: Option<Text>,
) -> Self {
let column_names: Vec<String> = if let Some(h) = header {
if let Some(custom_values) = &h.values {
custom_values.clone()
} else {
columns.iter().map(|&c| c.to_string()).collect()
}
} else {
columns.iter().map(|&c| c.to_string()).collect()
};
let mut column_data: Vec<Vec<String>> = Vec::new();
for column_name in &columns {
let col_data = crate::data::get_string_column(data, column_name);
let col_strings: Vec<String> = col_data
.iter()
.map(|opt| opt.clone().unwrap_or_default())
.collect();
column_data.push(col_strings);
}
let ir_trace = TraceIR::Table(TableIR {
header: header.cloned(),
cell: cell.cloned(),
column_names,
column_data,
column_width,
});
let traces = vec![ir_trace];
let layout = LayoutIR {
title: plot_title.clone(),
x_title: None,
y_title: None,
y2_title: None,
z_title: None,
legend_title: None,
legend: None,
dimensions: None,
bar_mode: None,
box_mode: None,
box_gap: None,
margin_bottom: None,
axes_2d: None,
scene_3d: None,
polar: None,
mapbox: None,
grid: None,
annotations: vec![],
};
Self { traces, layout }
}
}
#[bon]
impl Table {
#[builder(
start_fn = try_builder,
finish_fn = try_build,
builder_type = TableTryBuilder,
on(String, into),
on(Text, into),
)]
pub fn try_new(
data: &DataFrame,
columns: Vec<&str>,
header: Option<&Header>,
cell: Option<&Cell>,
column_width: Option<f64>,
plot_title: Option<Text>,
) -> Result<Self, crate::io::PlotlarsError> {
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
Self::__orig_new(data, columns, header, cell, column_width, plot_title)
}))
.map_err(|panic| {
let msg = panic
.downcast_ref::<String>()
.cloned()
.or_else(|| panic.downcast_ref::<&str>().map(|s| s.to_string()))
.unwrap_or_else(|| "unknown error".to_string());
crate::io::PlotlarsError::PlotBuild { message: msg }
})
}
}
impl crate::Plot for Table {
fn ir_traces(&self) -> &[TraceIR] {
&self.traces
}
fn ir_layout(&self) -> &LayoutIR {
&self.layout
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Plot;
use polars::prelude::*;
#[test]
fn test_basic_one_trace() {
let df = df![
"name" => ["Alice", "Bob"],
"age" => [30, 25]
]
.unwrap();
let plot = Table::builder()
.data(&df)
.columns(vec!["name", "age"])
.build();
assert_eq!(plot.ir_traces().len(), 1);
}
#[test]
fn test_trace_variant() {
let df = df![
"col1" => ["a", "b"],
"col2" => ["c", "d"]
]
.unwrap();
let plot = Table::builder()
.data(&df)
.columns(vec!["col1", "col2"])
.build();
assert!(matches!(plot.ir_traces()[0], TraceIR::Table(_)));
}
#[test]
fn test_layout_no_axes() {
let df = df![
"col1" => ["a"]
]
.unwrap();
let plot = Table::builder().data(&df).columns(vec!["col1"]).build();
let layout = plot.ir_layout();
assert!(layout.axes_2d.is_none());
assert!(layout.scene_3d.is_none());
assert!(layout.polar.is_none());
}
}