1use super::canvas;
14use super::color::*;
15use super::common::*;
16use super::component::*;
17use super::params::*;
18use super::theme::{get_default_theme_name, get_theme, Theme, DEFAULT_Y_AXIS_WIDTH};
19use super::util::*;
20use super::Canvas;
21use crate::charts::measure_text_width_family;
22use charts_rs_derive::Chart;
23use serde::{Deserialize, Serialize};
24use std::sync::Arc;
25
26#[derive(Serialize, Deserialize, Clone, Debug, Default, Chart)]
27pub struct CandlestickChart {
28 pub width: f32,
29 pub height: f32,
30 pub x: f32,
31 pub y: f32,
32 pub margin: Box,
33 pub series_list: Vec<Series>,
35 pub font_family: String,
36 pub background_color: Color,
37 pub is_light: bool,
38
39 pub title_text: String,
41 pub title_font_size: f32,
42 pub title_font_color: Color,
43 pub title_font_weight: Option<String>,
44 pub title_margin: Option<Box>,
45 pub title_align: Align,
46 pub title_height: f32,
47
48 pub sub_title_text: String,
50 pub sub_title_font_size: f32,
51 pub sub_title_font_color: Color,
52 pub sub_title_font_weight: Option<String>,
53 pub sub_title_margin: Option<Box>,
54 pub sub_title_align: Align,
55 pub sub_title_height: f32,
56
57 pub legend_font_size: f32,
59 pub legend_font_color: Color,
60 pub legend_font_weight: Option<String>,
61 pub legend_align: Align,
62 pub legend_margin: Option<Box>,
63 pub legend_category: LegendCategory,
64 pub legend_show: Option<bool>,
65
66 pub x_axis_data: Vec<String>,
68 pub x_axis_height: f32,
69 pub x_axis_stroke_color: Color,
70 pub x_axis_font_size: f32,
71 pub x_axis_font_color: Color,
72 pub x_axis_font_weight: Option<String>,
73 pub x_axis_name_gap: f32,
74 pub x_axis_name_rotate: f32,
75 pub x_axis_margin: Option<Box>,
76 pub x_axis_hidden: bool,
77 pub x_boundary_gap: Option<bool>,
78
79 pub y_axis_hidden: bool,
81 pub y_axis_configs: Vec<YAxisConfig>,
82
83 pub grid_stroke_color: Color,
85 pub grid_stroke_width: f32,
86
87 pub series_stroke_width: f32,
89 pub series_label_font_color: Color,
90 pub series_label_font_size: f32,
91 pub series_label_font_weight: Option<String>,
92 pub series_label_formatter: String,
93 pub series_colors: Vec<Color>,
94 pub series_symbol: Option<Symbol>,
95 pub series_smooth: bool,
96 pub series_fill: bool,
97
98 pub candlestick_up_color: Color,
99 pub candlestick_up_border_color: Color,
100 pub candlestick_down_color: Color,
101 pub candlestick_down_border_color: Color,
102}
103
104impl CandlestickChart {
105 fn fill_default(&mut self) {
106 if self.candlestick_up_color.is_zero() {
107 self.candlestick_up_color = (236, 0, 0).into();
108 }
109 if self.candlestick_up_border_color.is_zero() {
110 self.candlestick_up_border_color = (138, 0, 0).into();
111 }
112 if self.candlestick_down_color.is_zero() {
113 self.candlestick_down_color = (0, 218, 60).into();
114 }
115 if self.candlestick_down_border_color.is_zero() {
116 self.candlestick_down_border_color = (0, 143, 40).into();
117 }
118 }
119 pub fn from_json(data: &str) -> canvas::Result<CandlestickChart> {
121 let mut c = CandlestickChart {
122 ..Default::default()
123 };
124 let value = c.fill_option(data)?;
125 if let Some(value) = get_color_from_value(&value, "candlestick_up_color") {
126 c.candlestick_up_color = value;
127 }
128 if let Some(value) = get_color_from_value(&value, "candlestick_up_border_color") {
129 c.candlestick_up_border_color = value;
130 }
131 if let Some(value) = get_color_from_value(&value, "candlestick_down_color") {
132 c.candlestick_down_color = value;
133 }
134 if let Some(value) = get_color_from_value(&value, "candlestick_down_border_color") {
135 c.candlestick_down_border_color = value;
136 }
137 if let Some(x_axis_hidden) = get_bool_from_value(&value, "x_axis_hidden") {
138 c.x_axis_hidden = x_axis_hidden;
139 }
140 if let Some(y_axis_hidden) = get_bool_from_value(&value, "y_axis_hidden") {
141 c.y_axis_hidden = y_axis_hidden;
142 }
143 c.fill_default();
144 Ok(c)
145 }
146 pub fn new_with_theme(
148 mut series_list: Vec<Series>,
149 x_axis_data: Vec<String>,
150 theme: &str,
151 ) -> CandlestickChart {
152 series_list
154 .iter_mut()
155 .enumerate()
156 .for_each(|(index, item)| {
157 item.index = Some(index);
158 });
159 let mut c = CandlestickChart {
160 series_list,
161 x_axis_data,
162 ..Default::default()
163 };
164 let theme = get_theme(theme);
165 c.fill_theme(theme);
166 c.fill_default();
167 c
168 }
169 pub fn new(series_list: Vec<Series>, x_axis_data: Vec<String>) -> CandlestickChart {
171 CandlestickChart::new_with_theme(series_list, x_axis_data, &get_default_theme_name())
172 }
173 pub fn svg(&self) -> canvas::Result<String> {
175 let mut c = Canvas::new_width_xy(self.width, self.height, self.x, self.y);
176
177 self.render_background(c.child(Box::default()));
178 let mut x_axis_height = self.x_axis_height;
179 if self.x_axis_hidden {
180 x_axis_height = 0.0;
181 }
182 c.margin = self.margin.clone();
183
184 let title_height = self.render_title(c.child(Box::default()));
185
186 let legend_height = self.render_legend(c.child(Box::default()));
187 let axis_top = if legend_height > title_height {
189 legend_height
190 } else {
191 title_height
192 };
193
194 let (left_y_axis_values, mut left_y_axis_width) = self.get_y_axis_values(0);
195 if self.y_axis_hidden {
196 left_y_axis_width = 0.0;
197 }
198
199 let axis_height = c.height() - x_axis_height - axis_top;
200 let axis_width = c.width() - left_y_axis_width;
201 if axis_top > 0.0 {
203 c = c.child(Box {
204 top: axis_top,
205 ..Default::default()
206 });
207 }
208
209 self.render_grid(
210 c.child(Box {
211 left: left_y_axis_width,
212 ..Default::default()
213 }),
214 axis_width,
215 axis_height,
216 );
217
218 if !self.y_axis_hidden {
220 self.render_y_axis(
221 c.child(Box::default()),
222 left_y_axis_values.data.clone(),
223 axis_height,
224 left_y_axis_width,
225 0,
226 );
227 }
228
229 if !self.x_axis_hidden {
231 self.render_x_axis(
232 c.child(Box {
233 top: c.height() - x_axis_height,
234 left: left_y_axis_width,
235 ..Default::default()
236 }),
237 self.x_axis_data.clone(),
238 axis_width,
239 );
240 }
241 let chunk_width = axis_width / self.x_axis_data.len() as f32;
242 let half_chunk_width = chunk_width / 2.0;
243 for series in self.series_list.iter() {
244 if series.category.is_some() {
245 continue;
246 }
247 let chunks = series.data.chunks(4);
250
251 for (index, chunk) in chunks.enumerate() {
252 if chunk.len() != 4 {
253 continue;
254 }
255
256 let open = left_y_axis_values.get_offset_height(chunk[0], axis_height);
257 let close = left_y_axis_values.get_offset_height(chunk[1], axis_height);
258 let lowest = left_y_axis_values.get_offset_height(chunk[2], axis_height);
259 let highest = left_y_axis_values.get_offset_height(chunk[3], axis_height);
260 let mut fill = self.candlestick_up_color;
261 let mut border_color = self.candlestick_up_border_color;
262 if chunk[0] > chunk[1] {
264 fill = self.candlestick_down_color;
265 border_color = self.candlestick_down_border_color;
266 } else if chunk[0] == chunk[1] {
267 border_color = Color::transparent();
268 }
269
270 let line_left = half_chunk_width + chunk_width * index as f32 - 1.0;
271 c.child(Box {
272 left: left_y_axis_width,
273 ..Default::default()
274 })
275 .line(Line {
276 color: Some(fill),
277 stroke_width: 1.0,
278 left: line_left,
279 top: lowest.min(highest),
280 right: line_left,
281 bottom: lowest.max(highest),
282 ..Default::default()
283 });
284
285 c.child(Box {
286 left: left_y_axis_width,
287 ..Default::default()
288 })
289 .rect(Rect {
290 color: Some(border_color),
291 fill: Some(fill),
292 left: half_chunk_width / 2.0 + chunk_width * index as f32 - 1.0,
293 top: open.min(close),
294 width: half_chunk_width,
295 height: (open.max(close) - open.min(close)).max(1.0),
296 ..Default::default()
297 });
298 }
299 }
300 let mut line_series_list = vec![];
301 self.series_list.iter().for_each(|item| {
302 if let Some(ref cat) = item.category {
303 if *cat == SeriesCategory::Line {
304 line_series_list.push(item);
305 }
306 }
307 });
308
309 let y_axis_values_list = vec![&left_y_axis_values];
310 let max_height = c.height() - x_axis_height;
311 let line_series_labels_list = self.render_line(
312 c.child(Box {
313 left: left_y_axis_width,
314 ..Default::default()
315 }),
316 &line_series_list,
317 &y_axis_values_list,
318 max_height,
319 axis_height,
320 self.x_axis_data.len(),
321 );
322
323 self.render_series_label(
324 c.child(Box {
325 left: left_y_axis_width,
326 ..Default::default()
327 }),
328 line_series_labels_list,
329 );
330
331 c.svg()
332 }
333}
334
335#[cfg(test)]
336mod tests {
337 use super::CandlestickChart;
338 use crate::SeriesCategory;
339 use pretty_assertions::assert_eq;
340 #[test]
341 fn candlestick_chart_basic() {
342 let candlestick_chart = CandlestickChart::new(
343 vec![(
344 "",
345 vec![
346 20.0, 34.0, 10.0, 38.0, 40.0, 35.0, 30.0, 50.0, 31.0, 38.0, 33.0, 44.0, 38.0,
347 15.0, 5.0, 42.0,
348 ],
349 )
350 .into()],
351 vec![
352 "2017-10-24".to_string(),
353 "2017-10-25".to_string(),
354 "2017-10-26".to_string(),
355 "2017-10-27".to_string(),
356 ],
357 );
358 assert_eq!(
359 include_str!("../../asset/candlestick_chart/basic.svg"),
360 candlestick_chart.svg().unwrap()
361 );
362 }
363
364 #[test]
365 fn candlestick_chart_no_axis() {
366 let mut candlestick_chart = CandlestickChart::new(
367 vec![(
368 "",
369 vec![
370 20.0, 34.0, 10.0, 38.0, 40.0, 35.0, 30.0, 50.0, 31.0, 38.0, 33.0, 44.0, 38.0,
371 15.0, 5.0, 42.0,
372 ],
373 )
374 .into()],
375 vec![
376 "2017-10-24".to_string(),
377 "2017-10-25".to_string(),
378 "2017-10-26".to_string(),
379 "2017-10-27".to_string(),
380 ],
381 );
382 candlestick_chart.x_axis_hidden = true;
383 candlestick_chart.y_axis_hidden = true;
384 assert_eq!(
385 include_str!("../../asset/candlestick_chart/no_axis.svg"),
386 candlestick_chart.svg().unwrap()
387 );
388 }
389
390 #[test]
391 fn candlestick_chart_sh() {
392 let mut candlestick_chart = CandlestickChart::new(
393 vec![
394 (
396 "MA5",
397 vec![
398 2352.93, 2378.48, 2394.81, 2409.64, 2420.04, 2426.66, 2429.33, 2428.01,
399 2417.97, 2410.51, 2391.99, 2368.35, 2349.20, 2331.29, 2314.49, 2322.42,
400 2331.49, 2321.01, 2327.60, 2334.39, 2326.13, 2317.95, 2325.39, 2317.45,
401 2300.81, 2290.01, 2281.96, 2267.85, 2262.02, 2272.7, 2283.49, 2293.46,
402 2310.80, 2318.85, 2315.63, 2298.04, 2279.71, 2261.25, 2247.26, 2232.06,
403 2227.12, 2224.95, 2223.30, 2221.66, 2217.96, 2212.03, 2205.85, 2199.38,
404 2194.99, 2202.56, 2214.61, 2212.55, 2217.45, 2217.79, 2204.45,
405 ],
406 )
407 .into(),
408 (
409 "日K",
410 vec![
411 2320.26, 2320.26, 2287.3, 2362.94, 2300.0, 2291.3, 2288.26, 2308.38,
412 2295.35, 2346.5, 2295.35, 2346.92, 2347.22, 2358.98, 2337.35, 2363.8,
413 2360.75, 2382.48, 2347.89, 2383.76, 2383.43, 2385.42, 2371.23, 2391.82,
414 2377.41, 2419.02, 2369.57, 2421.15, 2425.92, 2428.15, 2417.58, 2440.38,
415 2411.0, 2433.13, 2403.3, 2437.42, 2432.68, 2434.48, 2427.7, 2441.73,
416 2430.69, 2418.53, 2394.22, 2433.89, 2416.62, 2432.4, 2414.4, 2443.03,
417 2441.91, 2421.56, 2415.43, 2444.8, 2420.26, 2382.91, 2373.53, 2427.07,
418 2383.49, 2397.18, 2370.61, 2397.94, 2378.82, 2325.95, 2309.17, 2378.82,
419 2322.94, 2314.16, 2308.76, 2330.88, 2320.62, 2325.82, 2315.01, 2338.78,
420 2313.74, 2293.34, 2289.89, 2340.71, 2297.77, 2313.22, 2292.03, 2324.63,
421 2322.32, 2365.59, 2308.92, 2366.16, 2364.54, 2359.51, 2330.86, 2369.65,
422 2332.08, 2273.4, 2259.25, 2333.54, 2274.81, 2326.31, 2270.1, 2328.14,
423 2333.61, 2347.18, 2321.6, 2351.44, 2340.44, 2324.29, 2304.27, 2352.02,
424 2326.42, 2318.61, 2314.59, 2333.67, 2314.68, 2310.59, 2296.58, 2320.96,
425 2309.16, 2286.6, 2264.83, 2333.29, 2282.17, 2263.97, 2253.25, 2286.33,
426 2255.77, 2270.28, 2253.31, 2276.22, 2269.31, 2278.4, 2250.0, 2312.08,
427 2267.29, 2240.02, 2239.21, 2276.05, 2244.26, 2257.43, 2232.02, 2261.31,
428 2257.74, 2317.37, 2257.42, 2317.86, 2318.21, 2324.24, 2311.6, 2330.81,
429 2321.4, 2328.28, 2314.97, 2332.0, 2334.74, 2326.72, 2319.91, 2344.89,
430 2318.58, 2297.67, 2281.12, 2319.99, 2299.38, 2301.26, 2289.0, 2323.48,
431 2273.55, 2236.3, 2232.91, 2273.55, 2238.49, 2236.62, 2228.81, 2246.87,
432 2229.46, 2234.4, 2227.31, 2243.95, 2234.9, 2227.74, 2220.44, 2253.42,
433 2232.69, 2225.29, 2217.25, 2241.34, 2196.24, 2211.59, 2180.67, 2212.59,
434 2215.47, 2225.77, 2215.47, 2234.73, 2224.93, 2226.13, 2212.56, 2233.04,
435 2236.98, 2219.55, 2217.26, 2242.48, 2218.09, 2206.78, 2204.44, 2226.26,
436 2199.91, 2181.94, 2177.39, 2204.99, 2169.63, 2194.85, 2165.78, 2196.43,
437 2195.03, 2193.8, 2178.47, 2197.51, 2181.82, 2197.6, 2175.44, 2206.03,
438 2201.12, 2244.64, 2200.58, 2250.11, 2236.4, 2242.17, 2232.26, 2245.12,
439 2242.62, 2184.54, 2182.81, 2242.62, 2187.35, 2218.32, 2184.11, 2226.12,
440 2213.19, 2199.31, 2191.85, 2224.63, 2203.89, 2177.91, 2173.86, 2210.58,
441 ],
442 )
443 .into(),
444 ],
445 vec![
446 "2013/1/24".to_string(),
447 "2013/1/25".to_string(),
448 "2013/1/28".to_string(),
449 "2013/1/29".to_string(),
450 "2013/1/30".to_string(),
451 "2013/1/31".to_string(),
452 "2013/2/1".to_string(),
453 "2013/2/4".to_string(),
454 "2013/2/5".to_string(),
455 "2013/2/6".to_string(),
456 "2013/2/7".to_string(),
457 "2013/2/8".to_string(),
458 "2013/2/18".to_string(),
459 "2013/2/19".to_string(),
460 "2013/2/20".to_string(),
461 "2013/2/21".to_string(),
462 "2013/2/22".to_string(),
463 "2013/2/25".to_string(),
464 "2013/2/26".to_string(),
465 "2013/2/27".to_string(),
466 "2013/2/28".to_string(),
467 "2013/3/1".to_string(),
468 "2013/3/4".to_string(),
469 "2013/3/5".to_string(),
470 "2013/3/6".to_string(),
471 "2013/3/7".to_string(),
472 "2013/3/8".to_string(),
473 "2013/3/11".to_string(),
474 "2013/3/12".to_string(),
475 "2013/3/13".to_string(),
476 "2013/3/14".to_string(),
477 "2013/3/15".to_string(),
478 "2013/3/18".to_string(),
479 "2013/3/18".to_string(),
480 "2013/3/20".to_string(),
481 "2013/3/21".to_string(),
482 "2013/3/22".to_string(),
483 "2013/3/25".to_string(),
484 "2013/3/26".to_string(),
485 "2013/3/27".to_string(),
486 "2013/3/28".to_string(),
487 "2013/3/29".to_string(),
488 "2013/4/1".to_string(),
489 "2013/4/2".to_string(),
490 "2013/4/3".to_string(),
491 "2013/4/8".to_string(),
492 "2013/4/9".to_string(),
493 "2013/4/10".to_string(),
494 "2013/4/11".to_string(),
495 "2013/4/12".to_string(),
496 "2013/4/15".to_string(),
497 "2013/4/16".to_string(),
498 "2013/4/17".to_string(),
499 "2013/4/18".to_string(),
500 "2013/4/19".to_string(),
501 "2013/4/22".to_string(),
502 "2013/4/23".to_string(),
503 "2013/4/24".to_string(),
504 "2013/4/25".to_string(),
505 "2013/4/26".to_string(),
506 ],
507 );
508 candlestick_chart.series_list[0].category = Some(SeriesCategory::Line);
509 candlestick_chart.series_list[0].start_index = 5;
510 candlestick_chart.y_axis_configs[0].axis_min = Some(2100.0);
511 candlestick_chart.y_axis_configs[0].axis_max = Some(2460.0);
512 candlestick_chart.y_axis_configs[0].axis_formatter = Some("{t}".to_string());
513 assert_eq!(
514 include_str!("../../asset/candlestick_chart/sh.svg"),
515 candlestick_chart.svg().unwrap()
516 );
517 }
518}