pub mod config;
pub mod data;
pub mod error;
pub mod render;
pub use config::{
Aggregate, ChannelType, ChartMeta, ChartSpec, ChartType, DataFormat, DataSource, Encoding,
Mark, Style,
};
pub use data::{Dataset, Record, Value};
pub use error::{OpsisError, Result};
#[cfg(feature = "egui-backend")]
pub fn show(spec: ChartSpec) -> Result<()> {
render::egui_backend::show_window(spec)
}
#[cfg(feature = "egui-backend")]
pub fn show_path(path: impl AsRef<std::path::Path>) -> Result<()> {
show(ChartSpec::from_toml_path(path)?)
}
#[cfg(feature = "egui-backend")]
pub fn show_str(toml: &str) -> Result<()> {
show(ChartSpec::from_toml_str(toml)?)
}
#[cfg(feature = "ratatui-backend")]
pub fn show_terminal(spec: ChartSpec) -> Result<()> {
render::ratatui_backend::run_terminal(spec)
}
#[cfg(feature = "ratatui-backend")]
pub fn show_terminal_path(path: impl AsRef<std::path::Path>) -> Result<()> {
show_terminal(ChartSpec::from_toml_path(path)?)
}
#[cfg(feature = "ratatui-backend")]
pub fn show_terminal_str(toml: &str) -> Result<()> {
show_terminal(ChartSpec::from_toml_str(toml)?)
}
#[cfg(test)]
mod tests {
use super::*;
const SAMPLE: &str = r#"
[chart]
type = "bar"
title = "Demo"
[data]
values = [
{ category = "A", value = 10 },
{ category = "B", value = 7 },
{ category = "C", value = 14 },
]
[encoding.x]
field = "category"
type = "categorical"
[encoding.y]
field = "value"
type = "quantitative"
"#;
#[test]
fn parses_minimal_spec() {
let spec = ChartSpec::from_toml_str(SAMPLE).expect("parse");
assert_eq!(spec.chart.r#type, ChartType::Bar);
spec.validate().unwrap();
let data = spec.load_data().unwrap();
assert_eq!(data.len(), 3);
}
#[test]
fn aggregate_categorical_sums() {
let spec = ChartSpec::from_toml_str(SAMPLE).unwrap();
let data = spec.load_data().unwrap();
let bars = render::bar_data(&spec, &data).unwrap();
assert_eq!(bars.len(), 3);
assert!((bars.iter().map(|b| b.value).sum::<f64>() - 31.0).abs() < 1e-9);
}
#[test]
fn histogram_default_binning() {
let xs: Vec<f64> = (0..100).map(|i| i as f64).collect();
let bins = render::histogram(&xs, None);
assert!(bins.len() >= 2);
let total: f64 = bins.iter().map(|b| b.value).sum();
assert_eq!(total as usize, 100);
}
#[test]
fn unknown_field_errors_helpfully() {
let data = Dataset::from_json_str(r#"[{"a":1},{"a":2}]"#).unwrap();
match data.column_f64("missing") {
Err(OpsisError::UnknownField { field, available }) => {
assert_eq!(field, "missing");
assert_eq!(available, vec!["a".to_string()]);
}
other => panic!("expected UnknownField, got {other:?}"),
}
}
}