use std::collections::HashMap;
use super::{PriceRange, PriceScale, PriceScaleMode, PriceScaleOptions};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum PriceScaleId {
Left,
#[default]
Right,
}
impl PriceScaleId {
pub fn opposite(&self) -> Self {
match self {
Self::Left => Self::Right,
Self::Right => Self::Left,
}
}
}
impl std::fmt::Display for PriceScaleId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Left => write!(f, "left"),
Self::Right => write!(f, "right"),
}
}
}
#[derive(Debug, Clone)]
pub struct PosedPriceScaleOptions {
pub options: PriceScaleOptions,
pub width: f32,
pub show_border: bool,
}
impl Default for PosedPriceScaleOptions {
fn default() -> Self {
Self {
options: PriceScaleOptions::default(),
width: 70.0,
show_border: true,
}
}
}
#[derive(Debug, Clone)]
pub struct SeriesScaleAssignment {
pub series_id: String,
pub scale_id: PriceScaleId,
}
pub struct DualPriceScaleManager {
left_scale: PriceScale,
right_scale: PriceScale,
left_options: PosedPriceScaleOptions,
right_options: PosedPriceScaleOptions,
assignments: HashMap<String, PriceScaleId>,
left_visible: bool,
right_visible: bool,
}
impl Default for DualPriceScaleManager {
fn default() -> Self {
Self::new(100.0)
}
}
impl DualPriceScaleManager {
pub fn new(height: f32) -> Self {
Self {
left_scale: PriceScale::new(height),
right_scale: PriceScale::new(height),
left_options: PosedPriceScaleOptions {
options: PriceScaleOptions {
visible: false, ..Default::default()
},
..Default::default()
},
right_options: PosedPriceScaleOptions::default(),
assignments: HashMap::new(),
left_visible: false,
right_visible: true,
}
}
pub fn with_options(
height: f32,
left_options: PosedPriceScaleOptions,
right_options: PosedPriceScaleOptions,
) -> Self {
let left_visible = left_options.options.visible;
let right_visible = right_options.options.visible;
Self {
left_scale: PriceScale::with_options(height, left_options.options),
right_scale: PriceScale::with_options(height, right_options.options),
left_options,
right_options,
assignments: HashMap::new(),
left_visible,
right_visible,
}
}
pub fn set_height(&mut self, height: f32) {
self.left_scale.set_height(height);
self.right_scale.set_height(height);
}
pub fn get_scale(&self, id: PriceScaleId) -> &PriceScale {
match id {
PriceScaleId::Left => &self.left_scale,
PriceScaleId::Right => &self.right_scale,
}
}
pub fn get_scale_mut(&mut self, id: PriceScaleId) -> &mut PriceScale {
match id {
PriceScaleId::Left => &mut self.left_scale,
PriceScaleId::Right => &mut self.right_scale,
}
}
pub fn get_options(&self, id: PriceScaleId) -> &PosedPriceScaleOptions {
match id {
PriceScaleId::Left => &self.left_options,
PriceScaleId::Right => &self.right_options,
}
}
pub fn set_options(&mut self, id: PriceScaleId, options: PosedPriceScaleOptions) {
let visible = options.options.visible;
match id {
PriceScaleId::Left => {
self.left_scale.set_options(options.options);
self.left_options = options;
self.left_visible = visible;
}
PriceScaleId::Right => {
self.right_scale.set_options(options.options);
self.right_options = options;
self.right_visible = visible;
}
}
}
pub fn is_visible(&self, id: PriceScaleId) -> bool {
match id {
PriceScaleId::Left => self.left_visible,
PriceScaleId::Right => self.right_visible,
}
}
pub fn set_visible(&mut self, id: PriceScaleId, visible: bool) {
match id {
PriceScaleId::Left => self.left_visible = visible,
PriceScaleId::Right => self.right_visible = visible,
}
}
pub fn get_width(&self, id: PriceScaleId) -> f32 {
if !self.is_visible(id) {
return 0.0;
}
match id {
PriceScaleId::Left => self.left_options.width,
PriceScaleId::Right => self.right_options.width,
}
}
pub fn left_padding(&self) -> f32 {
if self.left_visible {
self.left_options.width
} else {
0.0
}
}
pub fn right_padding(&self) -> f32 {
if self.right_visible {
self.right_options.width
} else {
0.0
}
}
pub fn assign_series(&mut self, series_id: impl Into<String>, scale_id: PriceScaleId) {
self.assignments.insert(series_id.into(), scale_id);
}
pub fn unassign_series(&mut self, series_id: &str) {
self.assignments.remove(series_id);
}
pub fn get_series_scale(&self, series_id: &str) -> PriceScaleId {
self.assignments
.get(series_id)
.copied()
.unwrap_or(PriceScaleId::Right)
}
pub fn get_assigned_series(&self, scale_id: PriceScaleId) -> Vec<&str> {
self.assignments
.iter()
.filter_map(|(id, &assigned)| {
if assigned == scale_id {
Some(id.as_str())
} else {
None
}
})
.collect()
}
pub fn auto_scale(&mut self, id: PriceScaleId, data_min: f64, data_max: f64) {
self.get_scale_mut(id).auto_scale(data_min, data_max);
}
pub fn set_first_val(&mut self, id: PriceScaleId, value: f64) {
self.get_scale_mut(id).set_first_val(value);
}
pub fn price_to_coord(&self, series_id: &str, price: f64) -> f32 {
let scale_id = self.get_series_scale(series_id);
self.get_scale(scale_id).price_to_coord(price)
}
pub fn coord_to_price(&self, series_id: &str, y: f32) -> f64 {
let scale_id = self.get_series_scale(series_id);
self.get_scale(scale_id).coord_to_price(y)
}
pub fn price_range(&self, id: PriceScaleId) -> PriceRange {
self.get_scale(id).price_range()
}
pub fn get_mode(&self, id: PriceScaleId) -> PriceScaleMode {
match id {
PriceScaleId::Left => self.left_options.options.mode,
PriceScaleId::Right => self.right_options.options.mode,
}
}
pub fn set_mode(&mut self, id: PriceScaleId, mode: PriceScaleMode) {
match id {
PriceScaleId::Left => {
self.left_options.options.mode = mode;
self.left_scale.set_options(self.left_options.options);
}
PriceScaleId::Right => {
self.right_options.options.mode = mode;
self.right_scale.set_options(self.right_options.options);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_configuration() {
let manager = DualPriceScaleManager::new(400.0);
assert!(manager.is_visible(PriceScaleId::Right));
assert!(!manager.is_visible(PriceScaleId::Left));
assert!(manager.right_padding() > 0.0);
assert_eq!(manager.left_padding(), 0.0);
}
#[test]
fn test_series_assignment() {
let mut manager = DualPriceScaleManager::new(400.0);
assert_eq!(manager.get_series_scale("BTCUSDT"), PriceScaleId::Right);
manager.assign_series("RSI", PriceScaleId::Left);
assert_eq!(manager.get_series_scale("RSI"), PriceScaleId::Left);
let left_series = manager.get_assigned_series(PriceScaleId::Left);
assert!(left_series.contains(&"RSI"));
}
#[test]
fn test_dual_scale_modes() {
let mut manager = DualPriceScaleManager::new(400.0);
manager.set_mode(PriceScaleId::Right, PriceScaleMode::Normal);
manager.set_mode(PriceScaleId::Left, PriceScaleMode::Percentage);
assert_eq!(
manager.get_mode(PriceScaleId::Right),
PriceScaleMode::Normal
);
assert_eq!(
manager.get_mode(PriceScaleId::Left),
PriceScaleMode::Percentage
);
}
#[test]
fn test_independent_auto_scale() {
let mut manager = DualPriceScaleManager::new(400.0);
manager.auto_scale(PriceScaleId::Right, 100.0, 200.0);
manager.auto_scale(PriceScaleId::Left, 0.0, 100.0);
let right_range = manager.price_range(PriceScaleId::Right);
let left_range = manager.price_range(PriceScaleId::Left);
assert!(right_range.min > left_range.min);
}
}