use super::*;
pub fn calculate_plot_area(canvas_width: u32, canvas_height: u32, margin_fraction: f32) -> Rect {
let margin_x = (canvas_width as f32) * margin_fraction;
let margin_y = (canvas_height as f32) * margin_fraction;
Rect::from_xywh(
margin_x,
margin_y,
(canvas_width as f32) - 2.0 * margin_x,
(canvas_height as f32) - 2.0 * margin_y,
)
.unwrap_or_else(|| {
Rect::from_xywh(
10.0,
10.0,
(canvas_width as f32) - 20.0,
(canvas_height as f32) - 20.0,
)
.unwrap()
})
}
pub fn calculate_plot_area_dpi(canvas_width: u32, canvas_height: u32, dpi_scale: f32) -> Rect {
let render_scale = RenderScale::from_reference_scale(dpi_scale);
let base_margin_left = 100.0; let base_margin_right = 40.0; let base_margin_top = 80.0; let base_margin_bottom = 60.0;
let margin_left = render_scale.logical_pixels_to_pixels(base_margin_left);
let margin_right = render_scale.logical_pixels_to_pixels(base_margin_right);
let margin_top = render_scale.logical_pixels_to_pixels(base_margin_top);
let margin_bottom = render_scale.logical_pixels_to_pixels(base_margin_bottom);
let plot_width = (canvas_width as f32) - margin_left - margin_right;
let plot_height = (canvas_height as f32) - margin_top - margin_bottom;
if plot_width > 100.0 && plot_height > 100.0 {
let plot_x = margin_left;
let plot_y = margin_top;
Rect::from_xywh(plot_x, plot_y, plot_width, plot_height).unwrap_or_else(|| {
Rect::from_xywh(
40.0,
40.0,
(canvas_width as f32) - 80.0,
(canvas_height as f32) - 80.0,
)
.unwrap()
})
} else {
let fallback_margin = (canvas_width.min(canvas_height) as f32) * 0.1;
Rect::from_xywh(
fallback_margin,
fallback_margin,
(canvas_width as f32) - 2.0 * fallback_margin,
(canvas_height as f32) - 2.0 * fallback_margin,
)
.unwrap()
}
}
pub fn calculate_plot_area_config(
canvas_width: u32,
canvas_height: u32,
margins: &ComputedMargins,
dpi: f32,
) -> Rect {
let margin_left = margins.left_px(dpi);
let margin_right = margins.right_px(dpi);
let margin_top = margins.top_px(dpi);
let margin_bottom = margins.bottom_px(dpi);
let plot_width = (canvas_width as f32) - margin_left - margin_right;
let plot_height = (canvas_height as f32) - margin_top - margin_bottom;
if plot_width > 50.0 && plot_height > 50.0 {
let plot_x = margin_left;
let plot_y = margin_top;
Rect::from_xywh(plot_x, plot_y, plot_width, plot_height).unwrap_or_else(|| {
Rect::from_xywh(
40.0,
40.0,
(canvas_width as f32) - 80.0,
(canvas_height as f32) - 80.0,
)
.unwrap()
})
} else {
let fallback_margin = (canvas_width.min(canvas_height) as f32) * 0.1;
Rect::from_xywh(
fallback_margin,
fallback_margin,
(canvas_width as f32) - 2.0 * fallback_margin,
(canvas_height as f32) - 2.0 * fallback_margin,
)
.unwrap()
}
}
pub fn map_data_to_pixels(
data_x: f64,
data_y: f64,
data_x_min: f64,
data_x_max: f64,
data_y_min: f64,
data_y_max: f64,
plot_area: Rect,
) -> (f32, f32) {
let transform = CoordinateTransform::from_plot_area(
plot_area.left(),
plot_area.top(),
plot_area.width(),
plot_area.height(),
data_x_min,
data_x_max,
data_y_min,
data_y_max,
);
transform.data_to_screen(data_x, data_y)
}
pub fn map_data_to_pixels_scaled(
data_x: f64,
data_y: f64,
data_x_min: f64,
data_x_max: f64,
data_y_min: f64,
data_y_max: f64,
plot_area: Rect,
x_scale: &crate::axes::AxisScale,
y_scale: &crate::axes::AxisScale,
) -> (f32, f32) {
use crate::axes::Scale;
let x_scale_obj = x_scale.create_scale(data_x_min, data_x_max);
let y_scale_obj = y_scale.create_scale(data_y_min, data_y_max);
let normalized_x = x_scale_obj.transform(data_x);
let normalized_y = y_scale_obj.transform(data_y);
let transform = CoordinateTransform::from_plot_area(
plot_area.left(),
plot_area.top(),
plot_area.width(),
plot_area.height(),
0.0, 1.0, 0.0, 1.0, );
transform.data_to_screen(normalized_x, normalized_y)
}
pub fn generate_ticks(min: f64, max: f64, target_count: usize) -> Vec<f64> {
if min >= max || target_count == 0 {
return vec![min, max];
}
let max_ticks = target_count.clamp(3, 10);
generate_scientific_ticks(min, max, max_ticks)
}
fn generate_scientific_ticks(min: f64, max: f64, max_ticks: usize) -> Vec<f64> {
let range = max - min;
if range <= 0.0 {
return vec![min];
}
let rough_step = range / (max_ticks - 1) as f64;
if rough_step <= f64::EPSILON {
return vec![min, max];
}
let magnitude = 10.0_f64.powf(rough_step.log10().floor());
let normalized_step = rough_step / magnitude;
let nice_step = if normalized_step <= 1.0 {
1.0
} else if normalized_step <= 2.0 {
2.0
} else if normalized_step <= 5.0 {
5.0
} else {
10.0
};
let step = nice_step * magnitude;
let start = (min / step).floor() * step;
let end = (max / step).ceil() * step;
let mut ticks = Vec::new();
let mut tick = start;
let epsilon = step * 1e-10;
while tick <= end + epsilon {
if tick >= min - epsilon && tick <= max + epsilon {
let clean_tick = clean_tick_value(tick, step);
ticks.push(clean_tick);
}
tick += step;
if ticks.len() > max_ticks * 2 {
break;
}
}
if ticks.len() < 3 {
let range = max - min;
let fallback_step = range / 2.0;
let clean_min = clean_tick_value(min, fallback_step);
let clean_max = clean_tick_value(max, fallback_step);
let clean_middle = clean_tick_value((min + max) / 2.0, fallback_step);
return vec![clean_min, clean_middle, clean_max];
}
if ticks.len() > max_ticks {
ticks.truncate(max_ticks);
}
ticks
}
fn clean_tick_value(value: f64, step: f64) -> f64 {
let decimals = if step >= 1.0 {
0
} else {
(-step.log10().floor()) as i32 + 1
};
let mult = 10.0_f64.powi(decimals);
(value * mult).round() / mult
}
pub fn generate_minor_ticks(major_ticks: &[f64], minor_count: usize) -> Vec<f64> {
if major_ticks.len() < 2 || minor_count == 0 {
return Vec::new();
}
let mut minor_ticks = Vec::new();
for i in 0..major_ticks.len() - 1 {
let start = major_ticks[i];
let end = major_ticks[i + 1];
let step = (end - start) / (minor_count + 1) as f64;
for j in 1..=minor_count {
let minor_tick = start + step * j as f64;
minor_ticks.push(minor_tick);
}
}
minor_ticks
}
pub fn format_tick_label(value: f64) -> String {
static FORMATTER: std::sync::LazyLock<TickFormatter> =
std::sync::LazyLock::new(TickFormatter::default);
FORMATTER.format_tick(value)
}
pub fn format_tick_labels(values: &[f64]) -> Vec<String> {
static FORMATTER: std::sync::LazyLock<TickFormatter> =
std::sync::LazyLock::new(TickFormatter::default);
FORMATTER.format_ticks(values)
}