1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
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,
}