#![doc(html_root_url = "https://docs.rs/egui-dataframe/0.1.1")]
use eframe::{self, egui::*};
use egui_grid::GridBuilder;
use egui_extras::{Size, TableBuilder, Column};
use polars::prelude::{DataFrame, AnyValue, Schema, Field, DataType};
#[macro_export]
macro_rules! to_any {
($v: expr, DataType::Null) => { AnyValue::Null };
($v: expr, DataType:: $t: ident) => { AnyValue::$t($v) }
}
pub fn row_schema(row: Vec<AnyValue<'_>>) -> polars::frame::row::Row {
polars::frame::row::Row::new(row)
}
pub fn row_fields(desc: Vec<(&str, DataType)>) -> Vec<Field> {
desc.into_iter().map(|(s, t)| Field::new(s, t)).collect()
}
pub fn named_fields(df: &DataFrame, n: Vec<&str>) -> Vec<Field> {
let t = df.dtypes();
row_fields(n.into_iter().enumerate().map(|(i, s)|
(s, t[i].clone())).collect())
}
pub fn named_schema(df: &DataFrame, n: Vec<&str>) -> Schema {
Schema::from_iter(named_fields(&df, n))
}
#[derive(Debug, Clone)]
pub struct Decorator {
pub sz: Vec2,
pub sense: Sense,
pub cols: Vec<Option<Color32>>,
pub align: Align2,
pub ofs: Vec2,
pub fontid: FontId
}
impl Decorator {
pub fn new(sz: Vec2, sense: Sense, cols: Vec<Option<Color32>>,
align: Align2, ofs: Vec2, fontid: FontId) -> Self {
Decorator{sz, sense, cols, align, ofs, fontid}
}
pub fn disp(&self, ui: &mut Ui, txt: &str) -> Option<(Response, Painter)> {
let (bgr, bgc, fgc) = (self.cols[0], self.cols[1], self.cols[2]);
if let Some(fgc) = fgc {
let (resp, painter) = ui.allocate_painter(self.sz, self.sense);
let rc = resp.rect;
if let Some(bgr) = bgr {
if let Some(bgc) = bgc {
painter.rect(rc, 0.0, bgc, Stroke{width: 1.0, color: bgr});
}
}
painter.text(rc.min + self.ofs, self.align, txt,
self.fontid.clone(), fgc);
Some((resp, painter))
}else{
ui.label(RichText::new(txt)
.size(self.fontid.size).family(self.fontid.family.clone()));
None
}
}
}
#[derive(Debug, Clone)]
pub struct DFDesc {
pub default_deco: (Decorator, Decorator),
pub deco: Vec<(Option<Decorator>, Option<Decorator>)>,
pub schema: Schema
}
impl DFDesc {
pub fn new(default_deco: (Decorator, Decorator), schema: Schema) -> Self {
let n = schema.len();
DFDesc{default_deco,
deco: Vec::<(Option<Decorator>, Option<Decorator>)>::with_capacity(n),
schema}
}
pub fn all_from(mut self,
deco: Vec<(Option<Decorator>, Option<Decorator>)>) -> Self {
self.deco = deco; self
}
pub fn all_default(mut self) -> Self {
for _i in 0..self.schema.len() { self.push((None, None)) }
self
}
pub fn push(&mut self, deco: (Option<Decorator>, Option<Decorator>)) {
self.deco.push(deco);
}
pub fn disp<'a>(&'a self, ui: &'a mut Ui, df: &DataFrame,
height_head: f32, height_body: f32, resizable: bool, striped: bool,
vscroll: bool) {
let (nrows, ncols) = (df.height(), df.width());
let cols = df.get_column_names();
TableBuilder::new(ui).columns(Column::auto().resizable(resizable), ncols)
.resizable(resizable)
.striped(striped)
.vscroll(vscroll)
.header(height_head, |mut header| {
for (i, head) in cols.iter().enumerate() {
header.col(|ui| {
let tx = format!("{}", head);
let _r_p = (match &self.deco[i] {
(None, _) => &self.default_deco.0,
(Some(deco_head), _) => deco_head
}).disp(ui, &tx);
});
}
})
.body(|body| {
body.rows(height_body, nrows, |ri, mut row| {
for (i, col) in cols.iter().enumerate() {
row.col(|ui| {
if let Ok(column) = &df.column(col) {
let value = column.get(ri);
let tx = format!("{}", value);
let _r_p = (match &self.deco[i] {
(_, None) => &self.default_deco.1,
(_, Some(deco_body)) => deco_body
}).disp(ui, &tx);
}
});
}
});
});
}
pub fn grid<'a>(&'a self, ui: &'a mut Ui, df: &DataFrame,
width: f32, height: f32, ts: &TextStyle,
sp: &(f32, f32), ma: &style::Margin) {
let (nrows, ncols) = (df.height(), df.width());
ui.style_mut().override_text_style = Some(ts.clone());
let mut gb = GridBuilder::new().spacing(sp.0, sp.1).clip(true);
gb = (0..nrows).into_iter().fold(gb, |gb, _i|
(0..ncols).into_iter().fold(gb.new_row(Size::exact(height)), |gb, _j|
gb.cell(Size::exact(width)).with_margin(ma.clone())
)
);
gb.show(ui, |mut grid| {
let cols = df.get_column_names();
for j in 0..nrows {
for (i, col) in cols.iter().enumerate() {
grid.cell(|ui| {
if let Ok(column) = &df.column(col) {
let value = column.get(j);
let tx = format!("{}", value);
let _r_p = (match &self.deco[i] {
(_, None) => &self.default_deco.1,
(_, Some(deco_body)) => deco_body
}).disp(ui, &tx);
}
});
}
}
});
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dataframe() {
let a = to_any!(3, DataType::UInt64);
assert_eq!(a, AnyValue::UInt64(3));
assert_eq!(a.dtype(), DataType::UInt64);
let b = to_any!("A", DataType::Utf8);
assert_eq!(b, AnyValue::Utf8("A"));
assert_eq!(b.dtype(), DataType::Utf8);
let c = to_any!(4, DataType::Int8);
assert_eq!(c, AnyValue::Int8(4));
assert_eq!(c.dtype(), DataType::Int8);
let d = to_any!(1.5, DataType::Float64);
assert_eq!(d, AnyValue::Float64(1.5));
assert_eq!(d.dtype(), DataType::Float64);
let e = to_any!(true, DataType::Boolean);
assert_eq!(e, AnyValue::Boolean(true));
assert_eq!(e.dtype(), DataType::Boolean);
let f = to_any!(&[255, 0], DataType::Binary);
assert_eq!(f, AnyValue::Binary(&[255, 0]));
assert_eq!(f.dtype(), DataType::Binary);
}
}