use super::data::PlotText;
use crate::core::config::PlotConfig;
use crate::render::Theme;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TextEngineMode {
Plain,
#[cfg(feature = "typst-math")]
#[cfg_attr(docsrs, doc(cfg(feature = "typst-math")))]
Typst,
}
impl TextEngineMode {
pub(crate) const fn uses_typst(self) -> bool {
#[cfg(feature = "typst-math")]
{
matches!(self, Self::Typst)
}
#[cfg(not(feature = "typst-math"))]
{
false
}
}
}
#[derive(Debug, Clone)]
pub struct PlotConfiguration {
pub(crate) title: Option<PlotText>,
pub(crate) xlabel: Option<PlotText>,
pub(crate) ylabel: Option<PlotText>,
pub(crate) dimensions: (u32, u32),
pub(crate) dpi: u32,
pub(crate) theme: Theme,
pub(crate) text_engine: TextEngineMode,
pub(crate) config: PlotConfig,
}
impl Default for PlotConfiguration {
fn default() -> Self {
Self::new()
}
}
impl PlotConfiguration {
pub fn new() -> Self {
Self {
title: None,
xlabel: None,
ylabel: None,
dimensions: (800, 600),
dpi: 100,
theme: Theme::default(),
text_engine: TextEngineMode::Plain,
config: PlotConfig::default(),
}
}
pub fn with_title<S: Into<String>>(mut self, title: S) -> Self {
self.title = Some(PlotText::Static(title.into()));
self
}
pub fn with_xlabel<S: Into<String>>(mut self, label: S) -> Self {
self.xlabel = Some(PlotText::Static(label.into()));
self
}
pub fn with_ylabel<S: Into<String>>(mut self, label: S) -> Self {
self.ylabel = Some(PlotText::Static(label.into()));
self
}
pub fn with_title_reactive(mut self, title: impl Into<PlotText>) -> Self {
self.title = Some(title.into());
self
}
pub fn with_xlabel_reactive(mut self, label: impl Into<PlotText>) -> Self {
self.xlabel = Some(label.into());
self
}
pub fn with_ylabel_reactive(mut self, label: impl Into<PlotText>) -> Self {
self.ylabel = Some(label.into());
self
}
#[deprecated(
since = "0.8.0",
note = "Use with_config() and PlotConfig for DPI-independent sizing"
)]
pub fn with_dimensions(mut self, width: u32, height: u32) -> Self {
self.dimensions = (width, height);
self
}
#[deprecated(
since = "0.8.0",
note = "Use with_config() and PlotConfig for DPI-independent sizing"
)]
pub fn with_dpi(mut self, dpi: u32) -> Self {
self.dpi = dpi;
self
}
pub fn with_theme(mut self, theme: Theme) -> Self {
self.theme = theme;
self
}
pub fn with_config(mut self, config: PlotConfig) -> Self {
self.config = config;
self
}
pub fn title(&self) -> Option<&str> {
self.title.as_ref().and_then(|t| t.as_static_str())
}
pub fn title_text(&self) -> Option<&PlotText> {
self.title.as_ref()
}
pub fn resolve_title(&self, time: f64) -> Option<String> {
self.title.as_ref().map(|t| t.resolve(time))
}
pub fn xlabel(&self) -> Option<&str> {
self.xlabel.as_ref().and_then(|t| t.as_static_str())
}
pub fn xlabel_text(&self) -> Option<&PlotText> {
self.xlabel.as_ref()
}
pub fn resolve_xlabel(&self, time: f64) -> Option<String> {
self.xlabel.as_ref().map(|t| t.resolve(time))
}
pub fn ylabel(&self) -> Option<&str> {
self.ylabel.as_ref().and_then(|t| t.as_static_str())
}
pub fn ylabel_text(&self) -> Option<&PlotText> {
self.ylabel.as_ref()
}
pub fn resolve_ylabel(&self, time: f64) -> Option<String> {
self.ylabel.as_ref().map(|t| t.resolve(time))
}
pub fn dimensions(&self) -> (u32, u32) {
self.dimensions
}
pub fn dpi(&self) -> u32 {
self.dpi
}
pub fn theme(&self) -> &Theme {
&self.theme
}
pub fn text_engine(&self) -> TextEngineMode {
self.text_engine
}
pub fn config(&self) -> &PlotConfig {
&self.config
}
pub fn config_mut(&mut self) -> &mut PlotConfig {
&mut self.config
}
pub(crate) fn set_title<S: Into<String>>(&mut self, title: S) {
self.title = Some(PlotText::Static(title.into()));
}
pub(crate) fn set_title_reactive(&mut self, title: impl Into<PlotText>) {
self.title = Some(title.into());
}
pub(crate) fn set_xlabel<S: Into<String>>(&mut self, label: S) {
self.xlabel = Some(PlotText::Static(label.into()));
}
pub(crate) fn set_xlabel_reactive(&mut self, label: impl Into<PlotText>) {
self.xlabel = Some(label.into());
}
pub(crate) fn set_ylabel<S: Into<String>>(&mut self, label: S) {
self.ylabel = Some(PlotText::Static(label.into()));
}
pub(crate) fn set_ylabel_reactive(&mut self, label: impl Into<PlotText>) {
self.ylabel = Some(label.into());
}
pub(crate) fn set_dimensions(&mut self, width: u32, height: u32) {
self.dimensions = (width, height);
}
pub(crate) fn set_dpi(&mut self, dpi: u32) {
self.dpi = dpi;
}
pub(crate) fn set_theme(&mut self, theme: Theme) {
self.theme = theme;
}
pub(crate) fn set_text_engine(&mut self, mode: TextEngineMode) {
self.text_engine = mode;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_configuration() {
let config = PlotConfiguration::new();
assert!(config.title().is_none());
assert!(config.xlabel().is_none());
assert!(config.ylabel().is_none());
assert_eq!(config.dimensions(), (800, 600));
assert_eq!(config.dpi(), 100);
assert_eq!(config.text_engine(), TextEngineMode::Plain);
}
#[test]
fn test_builder_pattern() {
let config = PlotConfiguration::new()
.with_title("Test Title")
.with_xlabel("X Label")
.with_ylabel("Y Label");
assert_eq!(config.title(), Some("Test Title"));
assert_eq!(config.xlabel(), Some("X Label"));
assert_eq!(config.ylabel(), Some("Y Label"));
}
#[test]
fn test_theme_configuration() {
let config = PlotConfiguration::new().with_theme(Theme::dark());
assert!(config.theme().background != Theme::default().background);
}
#[test]
#[allow(deprecated)]
fn test_deprecated_dimensions() {
let config = PlotConfiguration::new()
.with_dimensions(1920, 1080)
.with_dpi(300);
assert_eq!(config.dimensions(), (1920, 1080));
assert_eq!(config.dpi(), 300);
}
#[test]
fn test_mutable_setters() {
let mut config = PlotConfiguration::new();
config.set_title("New Title");
config.set_xlabel("New X");
config.set_ylabel("New Y");
config.set_dimensions(1024, 768);
config.set_dpi(150);
assert_eq!(config.title(), Some("New Title"));
assert_eq!(config.xlabel(), Some("New X"));
assert_eq!(config.ylabel(), Some("New Y"));
assert_eq!(config.dimensions(), (1024, 768));
assert_eq!(config.dpi(), 150);
assert_eq!(config.text_engine(), TextEngineMode::Plain);
}
#[cfg(feature = "typst-math")]
#[test]
fn test_mutable_typst_setter() {
let mut config = PlotConfiguration::new();
config.set_text_engine(TextEngineMode::Typst);
assert_eq!(config.text_engine(), TextEngineMode::Typst);
}
#[test]
fn test_reactive_title() {
use crate::data::signal;
let title_signal = signal::of(|t| format!("t = {:.2}s", t));
let config = PlotConfiguration::new().with_title_reactive(title_signal);
assert!(config.title().is_none());
assert!(config.title_text().is_some());
assert_eq!(config.resolve_title(1.5), Some("t = 1.50s".to_string()));
}
#[test]
fn test_reactive_labels() {
use crate::data::signal;
let xlabel_signal = signal::of(|t| format!("x @ {:.1}s", t));
let ylabel_signal = signal::of(|t| format!("y @ {:.1}s", t));
let config = PlotConfiguration::new()
.with_xlabel_reactive(xlabel_signal)
.with_ylabel_reactive(ylabel_signal);
assert_eq!(config.resolve_xlabel(2.0), Some("x @ 2.0s".to_string()));
assert_eq!(config.resolve_ylabel(2.0), Some("y @ 2.0s".to_string()));
}
}