1use crate::aes::Aesthetic;
6use crate::coord::Coord;
7use crate::data::{DataFrame, Value};
8use crate::position::identity::PositionIdentity;
9use crate::position::Position;
10use crate::render::backend::{DrawBackend, LineStyle, Linetype, RectStyle};
11use crate::render::RenderError;
12use crate::scale::ScaleSet;
13use crate::stat::identity::StatIdentity;
14use crate::stat::Stat;
15use crate::theme::Theme;
16
17use super::{Geom, GeomParams};
18
19pub struct GeomCandlestick {
22 pub up: (u8, u8, u8),
24 pub down: (u8, u8, u8),
26 pub width: f64,
28 pub line_width: f64,
29 pub alpha: f64,
30}
31
32impl Default for GeomCandlestick {
33 fn default() -> Self {
34 GeomCandlestick {
35 up: (0x0c, 0xa6, 0x78),
36 down: (0xe0, 0x31, 0x31),
37 width: 0.6,
38 line_width: 1.0,
39 alpha: 1.0,
40 }
41 }
42}
43
44pub struct GeomOhlc {
47 pub up: (u8, u8, u8),
48 pub down: (u8, u8, u8),
49 pub width: f64,
51 pub line_width: f64,
52 pub alpha: f64,
53}
54
55impl Default for GeomOhlc {
56 fn default() -> Self {
57 GeomOhlc {
58 up: (0x0c, 0xa6, 0x78),
59 down: (0xe0, 0x31, 0x31),
60 width: 0.6,
61 line_width: 1.4,
62 alpha: 1.0,
63 }
64 }
65}
66
67fn ohlc_required() -> Vec<Aesthetic> {
68 vec![
69 Aesthetic::X,
70 Aesthetic::Open,
71 Aesthetic::High,
72 Aesthetic::Low,
73 Aesthetic::Close,
74 ]
75}
76
77fn ohlc_setup(data: &mut DataFrame) {
79 for (src, dst) in [("low", "ymin"), ("high", "ymax")] {
80 if !data.has_column(dst) {
81 if let Some(c) = data.column(src) {
82 let c = c.to_vec();
83 data.add_column(dst.to_string(), c);
84 }
85 }
86 }
87}
88
89struct Period {
91 nx: f64,
92 open: f64,
93 high: f64,
94 low: f64,
95 close: f64,
96 up: bool,
97}
98
99fn for_each_period(
102 data: &DataFrame,
103 scales: &ScaleSet,
104 width: f64,
105 backend: &mut dyn DrawBackend,
106 mut draw: impl FnMut(&Period, f64, &mut dyn DrawBackend) -> Result<(), RenderError>,
107) -> Result<(), RenderError> {
108 let col = |n: &str| {
109 data.column(n)
110 .ok_or_else(|| RenderError::MissingAesthetic(n.into()))
111 };
112 let (x_col, o_col, h_col, l_col, c_col) = (
113 col("x")?,
114 col("open")?,
115 col("high")?,
116 col("low")?,
117 col("close")?,
118 );
119 let x_scale = scales.get(&Aesthetic::X);
120 let y_scale = scales.get(&Aesthetic::Y);
121 let my = |v: f64| y_scale.map(|s| s.map(&Value::Float(v))).unwrap_or(0.0);
122 let half = if x_scale.map(|s| s.is_discrete()).unwrap_or(false) {
123 let n = x_scale.map(|s| s.breaks().len()).unwrap_or(1).max(1) as f64;
124 width / n / 2.0
125 } else {
126 super::continuous_bar_half_width(
127 x_col.iter().filter_map(|v| x_scale.map(|s| s.map(v))),
128 width,
129 0.01,
130 )
131 };
132 for i in 0..data.nrows() {
133 let (Some(o), Some(h), Some(l), Some(c)) = (
134 o_col[i].as_f64(),
135 h_col[i].as_f64(),
136 l_col[i].as_f64(),
137 c_col[i].as_f64(),
138 ) else {
139 continue;
140 };
141 let p = Period {
142 nx: x_scale.map(|s| s.map(&x_col[i])).unwrap_or(0.5),
143 open: my(o),
144 high: my(h),
145 low: my(l),
146 close: my(c),
147 up: c >= o,
148 };
149 let x = super::tip_value(&x_col[i]);
150 let f = |v: &Value| super::tip_value(v);
151 let tip = format!(
152 "{x} — O {} H {} L {} C {}",
153 f(&o_col[i]),
154 f(&h_col[i]),
155 f(&l_col[i]),
156 f(&c_col[i])
157 );
158 let series =
159 super::series_key(data, i).or_else(|| Some(if p.up { "up" } else { "down" }.into()));
160 super::set_mark(
161 backend,
162 Some(tip),
163 Some(x),
164 series,
165 super::raw_value(&c_col[i]),
166 );
167 draw(&p, half, backend)?;
168 }
169 super::clear_mark(backend);
170 Ok(())
171}
172
173impl Geom for GeomCandlestick {
174 fn draw(
175 &self,
176 data: &DataFrame,
177 coord: &dyn Coord,
178 scales: &ScaleSet,
179 _theme: &Theme,
180 backend: &mut dyn DrawBackend,
181 ) -> Result<(), RenderError> {
182 let area = backend.plot_area();
183 for_each_period(data, scales, self.width, backend, |p, half, b| {
184 let color = if p.up { self.up } else { self.down };
185 let wick = [
186 coord.transform((p.nx, p.low), &area),
187 coord.transform((p.nx, p.high), &area),
188 ];
189 b.draw_line(
190 &wick,
191 &LineStyle {
192 color,
193 alpha: self.alpha,
194 width: self.line_width,
195 linetype: Linetype::Solid,
196 },
197 )?;
198 let (x0, y0) = coord.transform((p.nx - half, p.open.max(p.close)), &area);
199 let (x1, y1) = coord.transform((p.nx + half, p.open.min(p.close)), &area);
200 let (top, bottom) = (y0.min(y1), y0.max(y1).max(y0.min(y1) + 1.0));
202 b.draw_rect(
203 (x0.min(x1), top),
204 (x0.max(x1), bottom),
205 &RectStyle {
206 fill: Some(color),
207 stroke: Some(color),
208 stroke_width: self.line_width * 0.5,
209 alpha: self.alpha,
210 clip: true,
211 },
212 )
213 })
214 }
215
216 fn required_aes(&self) -> Vec<Aesthetic> {
217 ohlc_required()
218 }
219 fn default_stat(&self) -> Box<dyn Stat> {
220 Box::new(StatIdentity)
221 }
222 fn default_position(&self) -> Box<dyn Position> {
223 Box::new(PositionIdentity)
224 }
225 fn default_params(&self) -> GeomParams {
226 GeomParams::default()
227 }
228 fn name(&self) -> &str {
229 "candlestick"
230 }
231 fn setup_data(&self, data: &mut DataFrame) {
232 ohlc_setup(data);
233 }
234}
235
236impl Geom for GeomOhlc {
237 fn draw(
238 &self,
239 data: &DataFrame,
240 coord: &dyn Coord,
241 scales: &ScaleSet,
242 _theme: &Theme,
243 backend: &mut dyn DrawBackend,
244 ) -> Result<(), RenderError> {
245 let area = backend.plot_area();
246 for_each_period(data, scales, self.width, backend, |p, half, b| {
247 let style = LineStyle {
248 color: if p.up { self.up } else { self.down },
249 alpha: self.alpha,
250 width: self.line_width,
251 linetype: Linetype::Solid,
252 };
253 let t = |x: f64, y: f64| coord.transform((x, y), &area);
254 b.draw_line(&[t(p.nx, p.low), t(p.nx, p.high)], &style)?;
255 b.draw_line(&[t(p.nx - half, p.open), t(p.nx, p.open)], &style)?;
256 b.draw_line(&[t(p.nx, p.close), t(p.nx + half, p.close)], &style)
257 })
258 }
259
260 fn required_aes(&self) -> Vec<Aesthetic> {
261 ohlc_required()
262 }
263 fn default_stat(&self) -> Box<dyn Stat> {
264 Box::new(StatIdentity)
265 }
266 fn default_position(&self) -> Box<dyn Position> {
267 Box::new(PositionIdentity)
268 }
269 fn default_params(&self) -> GeomParams {
270 GeomParams::default()
271 }
272 fn name(&self) -> &str {
273 "ohlc"
274 }
275 fn setup_data(&self, data: &mut DataFrame) {
276 ohlc_setup(data);
277 }
278}