1use std::ops::Range;
2
3use crate::Axis;
4
5pub trait Points {
12 fn len(&self) -> usize;
14
15 fn is_empty(&self) -> bool {
17 self.len() == 0
18 }
19
20 fn x(&self, index: usize) -> f64;
22
23 fn y(&self, index: usize) -> f64;
25}
26
27impl Points for [(f64, f64)] {
28 fn len(&self) -> usize {
29 <[_]>::len(self)
30 }
31
32 fn x(&self, index: usize) -> f64 {
33 self[index].0
34 }
35
36 fn y(&self, index: usize) -> f64 {
37 self[index].1
38 }
39}
40
41impl Points for (&[f64], &[f64]) {
43 fn len(&self) -> usize {
44 self.0.len().min(self.1.len())
45 }
46
47 fn x(&self, index: usize) -> f64 {
48 self.0[index]
49 }
50
51 fn y(&self, index: usize) -> f64 {
52 self.1[index]
53 }
54}
55
56#[derive(Debug, Clone, Copy, PartialEq)]
58pub struct Candle {
59 pub open: f64,
61 pub high: f64,
63 pub low: f64,
65 pub close: f64,
67}
68
69impl Candle {
70 pub const fn new(open: f64, high: f64, low: f64, close: f64) -> Self {
72 Self {
73 open,
74 high,
75 low,
76 close,
77 }
78 }
79
80 pub fn is_finite(&self) -> bool {
82 self.open.is_finite()
83 && self.high.is_finite()
84 && self.low.is_finite()
85 && self.close.is_finite()
86 }
87}
88
89pub trait Candles {
92 fn len(&self) -> usize;
94
95 fn is_empty(&self) -> bool {
97 self.len() == 0
98 }
99
100 fn x(&self, index: usize) -> f64;
102
103 fn candle(&self, index: usize) -> Candle;
105}
106
107impl Candles for [(f64, Candle)] {
108 fn len(&self) -> usize {
109 <[_]>::len(self)
110 }
111
112 fn x(&self, index: usize) -> f64 {
113 self[index].0
114 }
115
116 fn candle(&self, index: usize) -> Candle {
117 self[index].1
118 }
119}
120
121pub fn points_within<P: Points + ?Sized>(points: &P, x: &Axis) -> Range<usize> {
123 index_range(points.len(), |index| points.x(index), x)
124}
125
126pub fn candles_within<C: Candles + ?Sized>(candles: &C, x: &Axis) -> Range<usize> {
128 index_range(candles.len(), |index| candles.x(index), x)
129}
130
131pub fn y_extent<P: Points + ?Sized>(points: &P, range: Range<usize>) -> Option<(f64, f64)> {
134 let range = range.start.min(points.len())..range.end.min(points.len());
135 extent(range.map(|index| points.y(index)).map(|y| (y, y)))
136}
137
138pub fn candle_extent<C: Candles + ?Sized>(candles: &C, range: Range<usize>) -> Option<(f64, f64)> {
141 let range = range.start.min(candles.len())..range.end.min(candles.len());
142 extent(
143 range
144 .map(|index| candles.candle(index))
145 .filter(Candle::is_finite)
146 .map(|candle| (candle.low, candle.high)),
147 )
148}
149
150fn extent(values: impl Iterator<Item = (f64, f64)>) -> Option<(f64, f64)> {
151 values
152 .filter(|(low, high)| low.is_finite() && high.is_finite())
153 .fold(None, |seen, (low, high)| match seen {
154 None => Some((low, high)),
155 Some((min, max)) => Some((low.min(min), high.max(max))),
156 })
157}
158
159fn index_range(len: usize, x: impl Fn(usize) -> f64, axis: &Axis) -> Range<usize> {
161 let start = partition_point(len, |index| x(index) < axis.start());
162 let end = partition_point(len, |index| x(index) <= axis.end());
163 start..end.max(start)
164}
165
166fn partition_point(len: usize, before: impl Fn(usize) -> bool) -> usize {
168 let (mut low, mut high) = (0, len);
169 while low < high {
170 let middle = low + (high - low) / 2;
171 if before(middle) {
172 low = middle + 1;
173 } else {
174 high = middle;
175 }
176 }
177 low
178}
179
180pub(crate) fn with_neighbours(range: Range<usize>, len: usize) -> Range<usize> {
183 range.start.saturating_sub(1)..range.end.saturating_add(1).min(len)
184}