datavzrd 2.64.0

A tool to create visual HTML reports from collections of CSV/TSV tables
use anyhow::Result;
use datavzrd::spec::{
    default_page_size, default_single_page_threshold, AuxDomainColumns, Color, ColorRange,
    DatasetSpecs, Heatmap, ItemSpecs, ItemsSpec, PlotSpec, RenderColumnSpec, RenderTableSpecs,
    ScaleType,
};
use datavzrd::utils::column_type::ColumnType;
use std::collections::HashMap;
use std::path::PathBuf;

pub(crate) fn suggest(files: Vec<PathBuf>, separator: Vec<char>, name: String) -> Result<String> {
    let mut items_spec = ItemsSpec {
        report_name: name,
        datasets: HashMap::new(),
        default_view: None,
        max_in_memory_rows: default_single_page_threshold(),
        views: HashMap::new(),
        aux_libraries: None,
        webview_controls: false,
    };
    for (file, sep) in files.iter().zip(separator.iter()) {
        let dataset = DatasetSpecs {
            path: file.to_owned(),
            separator: sep.to_owned(),
            header_rows: 1,
            links: None,
            offer_excel: false,
        };
        let dataset_name = file.file_stem().unwrap().to_str().unwrap().to_string();
        items_spec
            .datasets
            .insert(dataset_name.to_string(), dataset.clone());
        let column_types = datavzrd::utils::column_type::classify_table(&dataset, true)?;
        let mut columns = HashMap::new();
        for (column_name, column_type) in column_types.iter() {
            let render_column_spec = match column_type {
                ColumnType::None => RenderColumnSpec::default(),
                ColumnType::String => RenderColumnSpec {
                    plot: Some(PlotSpec {
                        tick_plot: None,
                        heatmap: Some(Heatmap {
                            vega_type: None,
                            scale_type: ScaleType::Ordinal,
                            clamp: false,
                            color_scheme: "category20".to_string(),
                            color_range: Default::default(),
                            domain: None,
                            domain_mid: None,
                            aux_domain_columns: AuxDomainColumns(None),
                            custom_content: None,
                            legend: None,
                        }),
                        bar_plot: None,
                        pills: None,
                        bubble_plot: None,
                    }),
                    ..RenderColumnSpec::default()
                },
                ColumnType::Integer | ColumnType::Float => RenderColumnSpec {
                    plot: Some(PlotSpec {
                        tick_plot: None,
                        heatmap: Some(Heatmap {
                            vega_type: None,
                            scale_type: ScaleType::Linear,
                            clamp: false,
                            color_scheme: "".to_string(),
                            color_range: ColorRange(vec![
                                Color("white".to_string()),
                                Color("blue".to_string()),
                            ]),
                            domain: None,
                            domain_mid: None,
                            aux_domain_columns: AuxDomainColumns(None),
                            custom_content: None,
                            legend: None,
                        }),
                        bar_plot: None,
                        pills: None,
                        bubble_plot: None,
                    }),
                    ..RenderColumnSpec::default()
                },
            };
            columns.insert(column_name.to_string(), render_column_spec);
        }
        let render_table = RenderTableSpecs {
            columns,
            additional_columns: None,
            headers: None,
        };
        let item_spec = ItemSpecs {
            hidden: false,
            dataset: Some(dataset_name.to_string()),
            datasets: None,
            page_size: default_page_size(),
            single_page_page_size: default_page_size(),
            description: None,
            render_table: Some(render_table),
            render_plot: None,
            render_html: None,
            render_img: None,
            max_in_memory_rows: None,
            spell: None,
        };
        items_spec.views.insert(dataset_name.to_string(), item_spec);
    }
    Ok(serde_yaml::to_string(&items_spec)?)
}

#[cfg(test)]
mod tests {
    use super::*;
    use anyhow::Result;
    use std::io::Write;
    use std::path::PathBuf;

    #[test]
    fn test_suggest() -> Result<()> {
        let files = vec![
            PathBuf::from(".examples/data/movies.csv"),
            PathBuf::from(".examples/data/oscars.csv"),
        ];
        let separators = vec![',', ','];
        let name = "Test Report".to_string();
        let result = suggest(files, separators, name);
        assert!(result.is_ok());
        let tmp = tempfile::NamedTempFile::new()?;
        tmp.as_file().write_all(result.unwrap().as_bytes())?;
        let config = ItemsSpec::from_file(tmp.path())?;
        assert!(config.validate().is_ok());
        Ok(())
    }
}