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