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
use super::context::{ChartMapping, LinearPriceMap, RenderContext};
use crate::model::Bar;
/// Indicator Renderer
/// Renders indicator lines overlay on the chart
use crate::studies::{Indicator, IndicatorValue};
use crate::tokens::DESIGN_TOKENS;
use egui::{Color32, Pos2, Stroke};
pub struct IndicatorRenderer;
impl IndicatorRenderer {
pub fn render(
context: &RenderContext,
indicators: &[Box<dyn Indicator>],
bars: &[Bar],
price_scale: &LinearPriceMap,
coords: &ChartMapping,
) {
for indicator in indicators {
if !indicator.is_visible() {
continue;
}
// Only render overlay indicators
if !indicator.is_overlay() {
continue;
}
let values = indicator.values();
let colors = indicator.colors();
// Handle multi-line indicators
let line_cnt = indicator.line_cnt();
for line_idx in 0..line_cnt {
let color = colors.get(line_idx).copied().unwrap_or(Color32::WHITE);
let mut points = Vec::new();
// Calculate visible range for indicator values
// values[i] corresponds to bars[i] in the FULL bars array
// coords.start_idx is the index of the first visible bar
let visible_end = coords.start_idx + bars.len();
for (i, value) in values.iter().enumerate() {
// Skip values before visible range
if i < coords.start_idx {
continue;
}
// Stop after visible range
if i >= visible_end {
break;
}
// i is the global bar index (values[i] corresponds to bar i)
let x = coords.idx_to_x(i);
let price = match value {
IndicatorValue::Single(p) => Some(*p),
IndicatorValue::Multiple(prices) => prices.get(line_idx).copied(),
IndicatorValue::None => None,
};
if let Some(price) = price {
// Convert price to screen Y coord using LinearPriceMap helper
let y = price_scale.price_to_y(price, context.rect);
// Only add if within chart bounds
if y >= context.rect.min.y && y <= context.rect.max.y {
points.push(Pos2::new(x, y));
}
} else {
// Break the line if we have a None value
if points.len() > 1 {
Self::draw_line(context.painter, &points, color);
}
points.clear();
}
}
// Draw final segment
if points.len() > 1 {
Self::draw_line(context.painter, &points, color);
}
}
}
}
fn draw_line(painter: &egui::Painter, points: &[Pos2], color: Color32) {
if points.len() < 2 {
return;
}
for i in 0..points.len() - 1 {
painter.line_segment(
[points[i], points[i + 1]],
Stroke::new(DESIGN_TOKENS.stroke.thick, color),
);
}
}
}