use egui::{Align, DragValue, Grid, Layout, Ui, Vec2};
use polars::prelude::*;
use std::{collections::HashMap, fmt::Debug, sync::LazyLock};
pub static DEFAULT_ALIGNMENTS: LazyLock<HashMap<DataType, Align>> = LazyLock::new(|| {
HashMap::from([
(DataType::Float32, Align::RIGHT),
(DataType::Float64, Align::RIGHT),
(DataType::Int8, Align::Center),
(DataType::Int16, Align::Center),
(DataType::Int32, Align::Center),
(DataType::Int64, Align::Center),
(DataType::UInt8, Align::Center),
(DataType::UInt16, Align::Center),
(DataType::UInt32, Align::Center),
(DataType::UInt64, Align::Center),
(DataType::Date, Align::Center),
(DataType::Time, Align::Center),
(
DataType::Datetime(TimeUnit::Milliseconds, None),
Align::Center,
),
(
DataType::Datetime(TimeUnit::Nanoseconds, None),
Align::Center,
),
(DataType::Duration(TimeUnit::Milliseconds), Align::Center),
(DataType::Duration(TimeUnit::Nanoseconds), Align::Center),
(DataType::Boolean, Align::Center),
(DataType::String, Align::LEFT),
(DataType::Binary, Align::LEFT),
])
});
#[derive(Debug, Clone, PartialEq)]
pub struct DataFormat {
pub alignments: HashMap<DataType, Align>,
pub auto_col_width: bool,
pub decimal: usize,
pub header_padding: f32,
pub use_enhanced_header: bool,
}
impl Default for DataFormat {
fn default() -> Self {
DataFormat {
alignments: DEFAULT_ALIGNMENTS.clone(), auto_col_width: true, decimal: 2, header_padding: 5.0, use_enhanced_header: true, }
}
}
impl DataFormat {
pub fn get_default_padding(&self) -> f32 {
Self::default().header_padding
}
pub fn render_format(&mut self, ui: &mut Ui) -> Option<DataFormat> {
let format_former = self.clone();
let mut result = None;
let width_max = ui.available_width();
let width_min = 200.0; let grid = Grid::new("data_format_grid")
.num_columns(2) .spacing([10.0, 20.0])
.striped(true);
ui.allocate_ui_with_layout(
Vec2::new(width_max, ui.available_height()),
Layout::top_down(Align::LEFT),
|ui| {
grid.show(ui, |ui| {
ui.set_min_width(width_min);
self.render_alignment_panel(ui); self.render_decimal_input(ui); self.render_auto_col(ui); self.render_header(ui);
if self.use_enhanced_header {
self.render_header_padding_input(ui); }
if *self != format_former {
result = Some(self.clone()); tracing::debug!(
"Format change detected in render_format. New state: {:#?}",
self
);
}
});
},
);
result
}
fn render_alignment_panel(&mut self, ui: &mut Ui) {
ui.label("Alignment:");
ui.collapsing("Data Types", |ui| {
Grid::new("align_grid")
.num_columns(4)
.spacing([10.0, 10.0])
.striped(true)
.show(ui, |ui| {
self.show_alignment_row(ui, &DataType::Float64);
self.show_alignment_row(ui, &DataType::Float32);
self.show_alignment_row(ui, &DataType::Int64);
self.show_alignment_row(ui, &DataType::Int32);
self.show_alignment_row(ui, &DataType::Int16);
self.show_alignment_row(ui, &DataType::Int8);
self.show_alignment_row(ui, &DataType::UInt64);
self.show_alignment_row(ui, &DataType::UInt32);
self.show_alignment_row(ui, &DataType::UInt16);
self.show_alignment_row(ui, &DataType::UInt8);
self.show_alignment_row(ui, &DataType::Date);
self.show_alignment_row(ui, &DataType::Time);
self.show_alignment_row(ui, &DataType::Boolean);
self.show_alignment_row(ui, &DataType::Binary);
self.show_alignment_row(ui, &DataType::String);
});
});
ui.end_row(); }
fn show_alignment_row(&mut self, ui: &mut Ui, data_type: &DataType) {
let current_align: &mut Align = self
.alignments
.entry(data_type.clone())
.or_insert(Align::LEFT);
ui.label(format!("{data_type:?}"));
ui.radio_value(current_align, Align::LEFT, "Left")
.on_hover_text("Align column content to the left.");
ui.radio_value(current_align, Align::Center, "Center")
.on_hover_text("Align column content to the center.");
ui.radio_value(current_align, Align::RIGHT, "Right")
.on_hover_text("Align column content to the right.");
ui.end_row(); }
fn render_decimal_input(&mut self, ui: &mut Ui) {
let decimal_max = 10;
ui.label("Decimals:");
ui.add(
DragValue::new(&mut self.decimal)
.speed(1) .range(0..=decimal_max), )
.on_hover_text(format!(
"Number of decimal places for floating-point numbers.\n\
Maximum decimal places: {decimal_max}"
));
ui.end_row();
}
fn render_auto_col(&mut self, ui: &mut Ui) {
ui.label("Auto Col Width:");
ui.checkbox(&mut self.auto_col_width, "").on_hover_text(
"Enable: Size columns based on content (slower).\n\
Disable: Use uniform initial widths (faster), allows manual resize.",
);
ui.end_row();
}
fn render_header(&mut self, ui: &mut Ui) {
ui.label("Enhanced Header:");
ui.checkbox(&mut self.use_enhanced_header, "")
.on_hover_text(
"Enable: Styled, wrapping text with icon-only sort click.\n\
Disable: Simpler button header.",
);
ui.end_row();
}
fn render_header_padding_input(&mut self, ui: &mut Ui) {
let heigth_max = 800.0;
ui.label("Header Padding:");
ui.add(
DragValue::new(&mut self.header_padding)
.speed(0.5)
.range(0.0..=heigth_max) .suffix(" px"), )
.on_hover_text(format!(
"Additional vertical padding for the enhanced table header.\n\
Maximum header padding: {:.*} px",
1, heigth_max
));
ui.end_row();
}
}
#[cfg(test)]
mod tests_format {
use polars::prelude::*;
#[test]
fn test_quoted_bool_ints() -> PolarsResult<()> {
let csv = r#"
foo,bar,baz
1,"4","false"
3,"5","false"
5,"6","true"
"#;
let file = std::io::Cursor::new(csv); let df = CsvReader::new(file).finish()?; println!("df = {df}");
let expected = df![
"foo" => [1, 3, 5],
"bar" => [4, 5, 6],
"baz" => [false, false, true],
]?;
assert!(df.equals_missing(&expected));
Ok(())
}
}