use crate::core::Rect;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct IndexAxis {
pub origin_x: i32,
pub slot_width: i32,
pub count: usize,
}
impl IndexAxis {
pub fn new(origin_x: i32, width: i32, count: usize) -> Self {
let slot_width = if count == 0 { 0 } else { (width.max(0) / count as i32).max(1) };
Self { origin_x, slot_width, count }
}
pub fn x_for(&self, index: usize) -> i32 {
self.origin_x + self.slot_width * index as i32
}
pub fn center_for(&self, index: usize) -> i32 {
self.x_for(index) + self.slot_width / 2
}
pub fn body_width(&self) -> i32 {
if self.slot_width <= 2 {
1
} else {
self.slot_width - 2
}
}
pub fn index_at(&self, x: i32) -> Option<usize> {
if self.slot_width <= 0 || self.count == 0 {
return None;
}
let offset = x - self.origin_x;
if offset < 0 {
return None;
}
let index = (offset / self.slot_width) as usize;
if index < self.count {
Some(index)
} else {
None
}
}
pub fn total_width(&self) -> i32 {
self.slot_width * self.count as i32
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PriceAxis {
pub top: i32,
pub bottom: i32,
pub low: f64,
pub high: f64,
}
impl PriceAxis {
pub fn new(top: i32, bottom: i32, low: f64, high: f64) -> Self {
let mut adjusted_low = low;
let mut adjusted_high = high;
if !adjusted_low.is_finite() || !adjusted_high.is_finite() {
adjusted_low = 0.0;
adjusted_high = 1.0;
} else if adjusted_high <= adjusted_low {
let magnitude = adjusted_low.abs().max(1.0) * 0.001;
adjusted_low -= magnitude;
adjusted_high += magnitude;
}
Self { top, bottom, low: adjusted_low, high: adjusted_high }
}
pub fn y_for(&self, price: f64) -> i32 {
if !price.is_finite() {
return self.bottom;
}
let span = self.high - self.low;
if span <= 0.0 {
return self.bottom;
}
let fraction = (price - self.low) / span;
let y = self.bottom as f64 - fraction * (self.bottom - self.top) as f64;
(y.round() as i32).clamp(self.top.min(self.bottom), self.top.max(self.bottom))
}
pub fn height(&self) -> i32 {
(self.bottom - self.top).abs()
}
pub fn price_at(&self, y: i32) -> f64 {
let height = (self.bottom - self.top) as f64;
if height == 0.0 {
return self.low;
}
let fraction = (self.bottom - y) as f64 / height;
self.low + fraction * (self.high - self.low)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PlotArea {
pub rect: Rect,
pub left_margin: i32,
pub right_margin: i32,
pub top_margin: i32,
pub bottom_margin: i32,
}
impl PlotArea {
pub fn of(rect: Rect) -> Self {
Self::with_margins(rect, 48, 8, 8, 20)
}
pub fn with_margins(rect: Rect, left: i32, right: i32, top: i32, bottom: i32) -> Self {
let max_horizontal = (rect.width as i32).saturating_sub(4).max(0) / 2;
let left_margin = left.min(max_horizontal).max(0);
let right_margin = right.min(max_horizontal).max(0);
let max_vertical = (rect.height as i32).saturating_sub(2).max(0) / 2;
let top_margin = top.min(max_vertical).max(0);
let bottom_margin = bottom.min(max_vertical).max(0);
let width = (rect.width as i32 - left_margin - right_margin).max(0) as u32;
let height = (rect.height as i32 - top_margin - bottom_margin).max(0) as u32;
Self {
rect: Rect::new(rect.x + left_margin, rect.y + top_margin, width, height),
left_margin,
right_margin,
top_margin,
bottom_margin,
}
}
pub fn index_axis(&self, count: usize) -> IndexAxis {
IndexAxis::new(self.rect.x, self.rect.width as i32, count)
}
pub fn price_axis(&self, low: f64, high: f64) -> PriceAxis {
PriceAxis::new(self.rect.y, self.rect.y + self.rect.height as i32, low, high)
}
pub fn right(&self) -> i32 {
self.rect.x + self.rect.width as i32
}
pub fn bottom(&self) -> i32 {
self.rect.y + self.rect.height as i32
}
pub fn label_x(&self) -> i32 {
self.rect.x - 4
}
}