1use crate::aes::Aesthetic;
7use crate::coord::Coord;
8use crate::data::{DataFrame, Value};
9use crate::position::identity::PositionIdentity;
10use crate::position::Position;
11use crate::render::backend::{DrawBackend, LineStyle, Linetype, PointStyle, RectStyle};
12use crate::render::RenderError;
13use crate::scale::ScaleSet;
14use crate::stat::renko::{StatPointFigure, StatRenko};
15use crate::stat::Stat;
16use crate::theme::Theme;
17
18use super::{Geom, GeomParams};
19
20pub struct GeomRenko {
22 pub box_size: f64,
23 pub reversal: usize,
25 pub point_figure: bool,
26 pub up: (u8, u8, u8),
27 pub down: (u8, u8, u8),
28}
29
30impl GeomRenko {
31 pub fn renko(box_size: f64) -> Self {
32 GeomRenko {
33 box_size,
34 reversal: 3,
35 point_figure: false,
36 up: (0x0c, 0xa6, 0x78),
37 down: (0xe0, 0x31, 0x31),
38 }
39 }
40
41 pub fn point_figure(box_size: f64, reversal: usize) -> Self {
42 GeomRenko {
43 reversal,
44 point_figure: true,
45 ..Self::renko(box_size)
46 }
47 }
48}
49
50impl Geom for GeomRenko {
51 fn draw(
52 &self,
53 data: &DataFrame,
54 coord: &dyn Coord,
55 scales: &ScaleSet,
56 _theme: &Theme,
57 backend: &mut dyn DrawBackend,
58 ) -> Result<(), RenderError> {
59 let col = |n: &str| {
60 data.column(n)
61 .ok_or_else(|| RenderError::MissingAesthetic(n.into()))
62 };
63 let (xc, y0c, y1c) = (col("x")?, col("ymin")?, col("ymax")?);
64 let kind = if self.point_figure {
65 col("mark")?
66 } else {
67 col("direction")?
68 };
69 let area = backend.plot_area();
70 let m = |aes: Aesthetic, v: &Value| scales.get(&aes).map(|s| s.map(v)).unwrap_or(0.0);
71 let half = scales
72 .get(&Aesthetic::X)
73 .map(|s| {
74 let d = s.map(&Value::Float(2.0)) - s.map(&Value::Float(1.0));
75 0.42 * d.abs()
76 })
77 .unwrap_or(0.01);
78 for i in 0..data.nrows() {
79 let nx = m(Aesthetic::X, &xc[i]);
80 let up = matches!(kind[i].key_str().as_ref(), "up" | "X");
81 let color = if up { self.up } else { self.down };
82 let a = coord.transform((nx - half, m(Aesthetic::Y, &y1c[i])), &area);
83 let b = coord.transform((nx + half, m(Aesthetic::Y, &y0c[i])), &area);
84 let (l, t, r, btm) = (a.0.min(b.0), a.1.min(b.1), a.0.max(b.0), a.1.max(b.1));
85 super::set_mark(
86 backend,
87 Some(format!(
88 "{} {}–{}",
89 kind[i].key_str(),
90 super::tip_value(&y0c[i]),
91 super::tip_value(&y1c[i])
92 )),
93 Some(super::tip_value(&xc[i])),
94 Some(kind[i].key_str().into_owned()),
95 super::raw_value(&y1c[i]),
96 );
97 if !self.point_figure {
98 backend.draw_rect(
99 (l, t),
100 (r, btm),
101 &RectStyle {
102 fill: Some(color),
103 stroke: Some((255, 255, 255)),
104 stroke_width: 0.5,
105 alpha: 1.0,
106 clip: true,
107 corner_radius: 0.0,
108 },
109 )?;
110 } else if up {
111 let s = LineStyle {
112 color,
113 alpha: 1.0,
114 width: 1.5,
115 linetype: Linetype::Solid,
116 ..Default::default()
117 };
118 let inset = 0.15 * (r - l).min(btm - t);
119 backend.draw_line(&[(l + inset, t + inset), (r - inset, btm - inset)], &s)?;
120 backend.draw_line(&[(l + inset, btm - inset), (r - inset, t + inset)], &s)?;
121 } else {
122 backend.draw_circle(
123 ((l + r) / 2.0, (t + btm) / 2.0),
124 0.38 * (r - l).min(btm - t),
125 &PointStyle {
126 color,
127 alpha: 1.0,
128 filled: false,
129 ..Default::default()
130 },
131 )?;
132 }
133 }
134 super::clear_mark(backend);
135 Ok(())
136 }
137
138 fn required_aes(&self) -> Vec<Aesthetic> {
139 vec![Aesthetic::Y]
140 }
141 fn default_stat(&self) -> Box<dyn Stat> {
142 if self.point_figure {
143 Box::new(StatPointFigure {
144 box_size: self.box_size,
145 reversal: self.reversal,
146 })
147 } else {
148 Box::new(StatRenko {
149 box_size: self.box_size,
150 })
151 }
152 }
153 fn default_position(&self) -> Box<dyn Position> {
154 Box::new(PositionIdentity)
155 }
156 fn default_params(&self) -> GeomParams {
157 GeomParams::default()
158 }
159 fn name(&self) -> &str {
160 if self.point_figure {
161 "point_figure"
162 } else {
163 "renko"
164 }
165 }
166}