charton 0.5.0

A high-performance, layered charting system for Rust, featuring a flexible data core and multi-backend rendering.
Documentation
pub mod cartesian;
pub mod polar;

use crate::error::ChartonError;
use crate::scale::{ExplicitTick, ScaleTrait};
use crate::theme::Theme;
use crate::visual::color::SingleColor;
use std::sync::Arc;

/// A simple rectangle representing a physical area on the canvas.
/// This defines where the coordinate system is allowed to draw.
#[derive(Debug, Clone, Copy)]
pub struct Rect {
    pub x: f64,
    pub y: f64,
    pub width: f64,
    pub height: f64,
}

impl Rect {
    pub fn new(x: f64, y: f64, width: f64, height: f64) -> Self {
        Self {
            x,
            y,
            width,
            height,
        }
    }
}

/// Describes the layout preferences and geometric behaviors of a coordinate system.
///
/// Different coordinate systems (Cartesian, Polar, Geo) require different
/// default settings for marks like bars to look "correct" out of the box.
pub struct CoordLayout {
    /// Default stroke color for bars in this coordinate system.
    pub default_bar_stroke: SingleColor,

    /// Default stroke width for bars in this coordinate system.
    pub default_bar_stroke_width: f64,

    /// Suggested relative width for a single bar (0.0 to 1.0).
    pub default_bar_width: f64,

    /// Suggested relative spacing between bars in a group (0.0 to 1.0).
    /// Typically 0.1 for Cartesian dodge, 0.0 for Polar.
    pub default_bar_spacing: f64,

    /// Suggested total span of a bar group (0.0 to 1.0).
    /// Defines the maximum coverage within a discrete category step.
    pub default_bar_span: f64,

    /// Whether the coordinate system distorts straight lines into curves.
    /// When true, the renderer should prefer path-based drawing over simple primitives.
    pub needs_interpolation: bool,
}

/// The core interface for all coordinate systems in Charton.
///
/// Following the ggplot2 philosophy, a Coordinate System is responsible for:
/// 1. Mapping normalized data [0, 1] into screen pixels.
/// 2. Defining the shape of the plotting area (Cartesian, Polar, etc.).
/// 3. Providing metadata for rendering axes and grids.
pub trait CoordinateTrait: Send + Sync {
    /// Orchestrates the visual rendering of axes, grid lines, and titles.
    ///
    /// Different coordinate systems implement this to reflect their geometry:
    /// - **Cartesian**: Renders straight horizontal and vertical lines.
    /// - **Polar**: Renders concentric circles (radius) and radial lines (angle).
    #[allow(clippy::too_many_arguments)]
    fn render_axes(
        &self,
        svg: &mut String,
        theme: &Theme,
        panel: &Rect,
        x_label: &str,
        x_explicit: Option<&[ExplicitTick]>,
        y_label: &str,
        y_explicit: Option<&[ExplicitTick]>,
    ) -> Result<(), ChartonError>;

    /// Transforms normalized data values into absolute pixel coordinates.
    ///
    /// # Arguments
    /// * `x_norm` - A value from 0.0 to 1.0 (usually from x_scale.normalize).
    /// * `y_norm` - A value from 0.0 to 1.0 (usually from y_scale.normalize).
    /// * `panel` - The physical rectangular area available for drawing.
    ///
    /// # Returns
    /// A tuple of (x_pixel, y_pixel).
    fn transform(&self, x_norm: f64, y_norm: f64, panel: &Rect) -> (f64, f64);

    /// Transforms a sequence of normalized points into pixel coordinates.
    ///
    /// The default implementation performs a simple point-by-point linear mapping.
    /// Non-linear coordinate systems (like Polar) should override this method
    /// to perform **adaptive interpolation**, ensuring that logical straight lines
    /// (e.g., the top edge of a bar) appear correctly curved in the final output.
    ///
    /// This is the primary interface for Renderers to generate geometry.
    fn transform_path(
        &self,
        points: &[(f64, f64)],
        is_closed: bool,
        panel: &Rect,
    ) -> Vec<(f64, f64)> {
        let _ = is_closed;
        // Default behavior: Simple point-to-point mapping without interpolation.
        // This is efficient for Cartesian systems where straight lines remain straight.
        points
            .iter()
            .map(|(x, y)| self.transform(*x, *y, panel))
            .collect()
    }

    /// Returns a shared pointer (Arc) to the X scale.
    /// Essential for "injecting" the scale into layers.
    fn get_x_arc(&self) -> Arc<dyn ScaleTrait>;

    /// Returns a shared pointer (Arc) to the Y scale.
    fn get_y_arc(&self) -> Arc<dyn ScaleTrait>;

    /// Borrowed version for quick access during rendering.
    /// Get the scale for the first dimension (e.g., X).
    fn get_x_scale(&self) -> &dyn ScaleTrait;

    /// Borrowed version for quick access during rendering.
    /// Get the scale for the second dimension (e.g., Y).
    fn get_y_scale(&self) -> &dyn ScaleTrait;

    /// Returns the label for the X axis.
    fn get_x_label(&self) -> &str;

    /// Returns the label for the Y axis.
    fn get_y_label(&self) -> &str;

    /// Returns whether the X and Y logical axes are swapped.
    /// Default is false for systems that don't support flipping.
    fn is_flipped(&self) -> bool {
        false
    }

    /// If true, the renderer should clip shapes extending beyond panel boundaries.
    fn is_clipped(&self) -> bool {
        true
    }

    /// Returns the layout hints for this coordinate system.
    /// Default implementation provides Cartesian-friendly defaults.
    fn layout_hints(&self) -> CoordLayout;
}

/// Supported coordinate systems for the chart.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum CoordSystem {
    /// Standard 2D Cartesian coordinates (X and Y axes).
    #[default]
    Cartesian2D,
    /// Polar coordinates (Radius and Angle).
    Polar,
}