1use bon::bon;
2
3use polars::frame::DataFrame;
4
5use crate::{
6 components::{Axis, Direction, Text},
7 ir::data::ColumnData,
8 ir::layout::LayoutIR,
9 ir::trace::{CandlestickPlotIR, TraceIR},
10};
11
12#[derive(Clone)]
86#[allow(dead_code)]
87pub struct CandlestickPlot {
88 traces: Vec<TraceIR>,
89 layout: LayoutIR,
90}
91
92#[bon]
93impl CandlestickPlot {
94 #[builder(on(String, into), on(Text, into))]
95 pub fn new(
96 data: &DataFrame,
97 dates: &str,
98 open: &str,
99 high: &str,
100 low: &str,
101 close: &str,
102 increasing: Option<&Direction>,
103 decreasing: Option<&Direction>,
104 whisker_width: Option<f64>,
105 plot_title: Option<Text>,
106 x_title: Option<Text>,
107 y_title: Option<Text>,
108 x_axis: Option<&Axis>,
109 y_axis: Option<&Axis>,
110 ) -> Self {
111 let ir_trace = TraceIR::CandlestickPlot(CandlestickPlotIR {
113 dates: ColumnData::String(crate::data::get_string_column(data, dates)),
114 open: ColumnData::Numeric(crate::data::get_numeric_column(data, open)),
115 high: ColumnData::Numeric(crate::data::get_numeric_column(data, high)),
116 low: ColumnData::Numeric(crate::data::get_numeric_column(data, low)),
117 close: ColumnData::Numeric(crate::data::get_numeric_column(data, close)),
118 increasing: increasing.cloned(),
119 decreasing: decreasing.cloned(),
120 whisker_width,
121 });
122 let traces = vec![ir_trace];
123 let layout = LayoutIR {
124 title: plot_title.clone(),
125 x_title: x_title.clone(),
126 y_title: y_title.clone(),
127 y2_title: None,
128 z_title: None,
129 legend_title: None,
130 legend: None,
131 dimensions: None,
132 bar_mode: None,
133 box_mode: None,
134 box_gap: None,
135 margin_bottom: None,
136 axes_2d: Some(crate::ir::layout::Axes2dIR {
137 x_axis: x_axis.cloned(),
138 y_axis: y_axis.cloned(),
139 y2_axis: None,
140 }),
141 scene_3d: None,
142 polar: None,
143 mapbox: None,
144 grid: None,
145 annotations: vec![],
146 };
147 Self { traces, layout }
148 }
149}
150
151#[bon]
152impl CandlestickPlot {
153 #[builder(
154 start_fn = try_builder,
155 finish_fn = try_build,
156 builder_type = CandlestickPlotTryBuilder,
157 on(String, into),
158 on(Text, into),
159 )]
160 pub fn try_new(
161 data: &DataFrame,
162 dates: &str,
163 open: &str,
164 high: &str,
165 low: &str,
166 close: &str,
167 increasing: Option<&Direction>,
168 decreasing: Option<&Direction>,
169 whisker_width: Option<f64>,
170 plot_title: Option<Text>,
171 x_title: Option<Text>,
172 y_title: Option<Text>,
173 x_axis: Option<&Axis>,
174 y_axis: Option<&Axis>,
175 ) -> Result<Self, crate::io::PlotlarsError> {
176 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
177 Self::__orig_new(
178 data,
179 dates,
180 open,
181 high,
182 low,
183 close,
184 increasing,
185 decreasing,
186 whisker_width,
187 plot_title,
188 x_title,
189 y_title,
190 x_axis,
191 y_axis,
192 )
193 }))
194 .map_err(|panic| {
195 let msg = panic
196 .downcast_ref::<String>()
197 .cloned()
198 .or_else(|| panic.downcast_ref::<&str>().map(|s| s.to_string()))
199 .unwrap_or_else(|| "unknown error".to_string());
200 crate::io::PlotlarsError::PlotBuild { message: msg }
201 })
202 }
203}
204
205impl crate::Plot for CandlestickPlot {
206 fn ir_traces(&self) -> &[TraceIR] {
207 &self.traces
208 }
209
210 fn ir_layout(&self) -> &LayoutIR {
211 &self.layout
212 }
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218 use crate::Plot;
219 use polars::prelude::*;
220
221 fn sample_df() -> DataFrame {
222 df![
223 "date" => ["2024-01-01", "2024-01-02", "2024-01-03"],
224 "open" => [100.0, 102.0, 101.0],
225 "high" => [105.0, 106.0, 104.0],
226 "low" => [98.0, 100.0, 99.0],
227 "close" => [103.0, 101.0, 102.0]
228 ]
229 .unwrap()
230 }
231
232 #[test]
233 fn test_basic_one_trace() {
234 let df = sample_df();
235 let plot = CandlestickPlot::builder()
236 .data(&df)
237 .dates("date")
238 .open("open")
239 .high("high")
240 .low("low")
241 .close("close")
242 .build();
243 assert_eq!(plot.ir_traces().len(), 1);
244 }
245
246 #[test]
247 fn test_trace_variant() {
248 let df = sample_df();
249 let plot = CandlestickPlot::builder()
250 .data(&df)
251 .dates("date")
252 .open("open")
253 .high("high")
254 .low("low")
255 .close("close")
256 .build();
257 assert!(matches!(plot.ir_traces()[0], TraceIR::CandlestickPlot(_)));
258 }
259
260 #[test]
261 fn test_layout_has_axes() {
262 let df = sample_df();
263 let plot = CandlestickPlot::builder()
264 .data(&df)
265 .dates("date")
266 .open("open")
267 .high("high")
268 .low("low")
269 .close("close")
270 .build();
271 assert!(plot.ir_layout().axes_2d.is_some());
272 }
273
274 #[test]
275 fn test_layout_title() {
276 let df = sample_df();
277 let plot = CandlestickPlot::builder()
278 .data(&df)
279 .dates("date")
280 .open("open")
281 .high("high")
282 .low("low")
283 .close("close")
284 .plot_title("Candlestick")
285 .build();
286 assert!(plot.ir_layout().title.is_some());
287 }
288}