#![allow(
dead_code,
unused_variables,
clippy::new_without_default,
clippy::derivable_impls
)]
pub mod builder;
pub mod component;
pub mod data_aggregation;
pub mod heatmap;
pub mod kpi_card;
pub mod layout;
pub mod pivot_table;
pub mod scatter_plot;
pub mod templates;
pub mod theme;
pub mod treemap;
pub use builder::{DashboardBuilder, DashboardConfig};
pub use component::{ComponentPosition, ComponentSpan, DashboardComponent};
pub use data_aggregation::{AggregateFunc, DataAggregator, GroupedData};
pub use heatmap::{ColorScale, HeatMap, HeatMapBuilder, HeatMapData, HeatMapOptions};
pub use kpi_card::{KpiCard, KpiCardBuilder, TrendDirection};
pub use layout::{DashboardLayout, GridPosition, LayoutManager};
pub use pivot_table::{AggregateFunction, PivotConfig, PivotTable, PivotTableBuilder};
pub use scatter_plot::{ScatterPlot, ScatterPlotBuilder, ScatterPlotOptions, ScatterPoint};
pub use templates::{
AnalyticsDashboardTemplate, ChartData, FinancialReportTemplate, KpiData, PieSegmentData,
SalesDashboardTemplate, SeriesData, TemplateData,
};
pub use theme::{DashboardTheme, Typography};
pub use treemap::{TreeMap, TreeMapBuilder, TreeMapNode, TreeMapOptions};
use crate::error::PdfError;
use crate::page::Page;
use crate::Font;
#[derive(Debug, Clone)]
pub struct Dashboard {
pub title: String,
pub subtitle: Option<String>,
pub layout: DashboardLayout,
pub theme: DashboardTheme,
pub components: Vec<Box<dyn DashboardComponent>>,
pub metadata: DashboardMetadata,
}
#[derive(Debug, Clone)]
pub struct DashboardMetadata {
pub created_at: chrono::DateTime<chrono::Utc>,
pub version: String,
pub data_sources: Vec<String>,
pub author: Option<String>,
pub tags: Vec<String>,
}
impl Dashboard {
pub fn render_to_page(&self, page: &mut Page) -> Result<(), PdfError> {
let page_bounds = page.content_area();
let content_area = self.layout.calculate_content_area(page_bounds);
self.render_header(page, content_area)?;
let component_positions = self
.layout
.calculate_positions(&self.components, content_area)?;
for (component, position) in self.components.iter().zip(component_positions.iter()) {
component.render(page, *position, &self.theme)?;
}
self.render_footer(page, content_area)?;
Ok(())
}
fn render_header(
&self,
page: &mut Page,
content_area: (f64, f64, f64, f64),
) -> Result<(), PdfError> {
let (x, y, _width, height) = content_area;
let title_y = y + height - 30.0;
page.text()
.set_font(Font::HelveticaBold, self.theme.typography.title_size)
.set_fill_color(self.theme.colors.text_primary)
.at(x + 20.0, title_y)
.write(&self.title)?;
if let Some(subtitle) = &self.subtitle {
let subtitle_y = title_y - 25.0;
page.text()
.set_font(Font::Helvetica, self.theme.typography.body_size)
.set_fill_color(self.theme.colors.text_secondary)
.at(x + 20.0, subtitle_y)
.write(subtitle)?;
}
Ok(())
}
fn render_footer(
&self,
page: &mut Page,
content_area: (f64, f64, f64, f64),
) -> Result<(), PdfError> {
let (x, y, width, _height) = content_area;
let footer_y = y + 15.0;
let date_text = format!(
"Generated: {}",
self.metadata.created_at.format("%Y-%m-%d %H:%M UTC")
);
page.text()
.set_font(Font::Helvetica, self.theme.typography.caption_size)
.set_fill_color(self.theme.colors.text_muted)
.at(x + 20.0, footer_y)
.write(&date_text)?;
if !self.metadata.data_sources.is_empty() {
let sources_text = format!("Data: {}", self.metadata.data_sources.join(", "));
page.text()
.set_font(Font::Helvetica, self.theme.typography.caption_size)
.set_fill_color(self.theme.colors.text_muted)
.at(x + width - 150.0, footer_y) .write(&sources_text)?;
}
Ok(())
}
pub fn get_stats(&self) -> DashboardStats {
DashboardStats {
component_count: self.components.len(),
estimated_render_time: self.estimate_render_time(),
memory_usage_mb: self.estimate_memory_usage(),
complexity_score: self.calculate_complexity_score(),
}
}
fn estimate_render_time(&self) -> std::time::Duration {
use std::time::Duration;
let base_time_ms = 50u64;
let component_time_ms: u64 = self
.components
.iter()
.map(|c| c.estimated_render_time_ms() as u64)
.sum();
Duration::from_millis(base_time_ms + component_time_ms)
}
fn estimate_memory_usage(&self) -> f64 {
0.5 + self
.components
.iter()
.map(|c| c.estimated_memory_mb())
.sum::<f64>()
}
fn calculate_complexity_score(&self) -> u8 {
let component_complexity: u32 = self
.components
.iter()
.map(|c| c.complexity_score() as u32)
.sum();
((component_complexity / self.components.len().max(1) as u32).min(100)) as u8
}
}
#[derive(Debug, Clone)]
pub struct DashboardStats {
pub component_count: usize,
pub estimated_render_time: std::time::Duration,
pub memory_usage_mb: f64,
pub complexity_score: u8,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Document, Page};
#[test]
fn test_dashboard_creation() {
let dashboard = DashboardBuilder::new()
.title("Test Dashboard")
.subtitle("Unit Test")
.build()
.unwrap();
assert_eq!(dashboard.title, "Test Dashboard");
assert_eq!(dashboard.subtitle, Some("Unit Test".to_string()));
}
#[test]
fn test_dashboard_stats() {
let dashboard = DashboardBuilder::new()
.title("Performance Test")
.build()
.unwrap();
let stats = dashboard.get_stats();
assert!(stats.estimated_render_time.as_millis() > 0);
assert!(stats.memory_usage_mb > 0.0);
}
#[test]
fn test_dashboard_render() {
let dashboard = DashboardBuilder::new()
.title("Render Test")
.build()
.unwrap();
let mut document = Document::new();
let mut page = Page::new(595.0, 842.0);
let result = dashboard.render_to_page(&mut page);
document.add_page(page);
assert!(result.is_ok());
}
}