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 std::sync::Arc;
24
25#[derive(Clone, Debug, Default, Chart)]
26pub struct HorizontalBarChart {
27 pub width: f32,
28 pub height: f32,
29 pub x: f32,
30 pub y: f32,
31 pub margin: Box,
32 pub series_list: Vec<Series>,
33 pub font_family: String,
34 pub background_color: Color,
35 pub is_light: bool,
36
37 pub title_text: String,
39 pub title_font_size: f32,
40 pub title_font_color: Color,
41 pub title_font_weight: Option<String>,
42 pub title_margin: Option<Box>,
43 pub title_align: Align,
44 pub title_height: f32,
45
46 pub sub_title_text: String,
48 pub sub_title_font_size: f32,
49 pub sub_title_font_color: Color,
50 pub sub_title_font_weight: Option<String>,
51 pub sub_title_margin: Option<Box>,
52 pub sub_title_align: Align,
53 pub sub_title_height: f32,
54
55 pub legend_font_size: f32,
57 pub legend_font_color: Color,
58 pub legend_font_weight: Option<String>,
59 pub legend_align: Align,
60 pub legend_margin: Option<Box>,
61 pub legend_category: LegendCategory,
62 pub legend_show: Option<bool>,
63
64 pub x_axis_data: Vec<String>,
66 pub x_axis_height: f32,
67 pub x_axis_stroke_color: Color,
68 pub x_axis_font_size: f32,
69 pub x_axis_font_color: Color,
70 pub x_axis_font_weight: Option<String>,
71 pub x_axis_name_gap: f32,
72 pub x_axis_name_rotate: f32,
73 pub x_axis_margin: Option<Box>,
74 pub x_boundary_gap: Option<bool>,
75
76 pub y_axis_configs: Vec<YAxisConfig>,
78
79 pub grid_stroke_color: Color,
81 pub grid_stroke_width: f32,
82
83 pub series_stroke_width: f32,
85 pub series_label_font_color: Color,
86 pub series_label_font_size: f32,
87 pub series_label_font_weight: Option<String>,
88 pub series_label_formatter: String,
89 pub series_label_position: Option<Position>,
90 pub series_colors: Vec<Color>,
91 pub series_symbol: Option<Symbol>,
92 pub series_smooth: bool,
93 pub series_fill: bool,
94}
95
96impl HorizontalBarChart {
97 pub fn from_json(data: &str) -> canvas::Result<HorizontalBarChart> {
99 let mut h = HorizontalBarChart {
100 ..Default::default()
101 };
102 let value = h.fill_option(data)?;
103 if let Some(series_label_position) =
104 get_position_from_value(&value, "series_label_position")
105 {
106 h.series_label_position = Some(series_label_position);
107 }
108 Ok(h)
109 }
110 pub fn new_with_theme(
112 series_list: Vec<Series>,
113 x_axis_data: Vec<String>,
114 theme: &str,
115 ) -> HorizontalBarChart {
116 let mut h = HorizontalBarChart {
117 series_list,
118 x_axis_data,
119 ..Default::default()
120 };
121 let theme = get_theme(theme);
122 h.fill_theme(theme);
123 h
124 }
125 pub fn new(series_list: Vec<Series>, x_axis_data: Vec<String>) -> HorizontalBarChart {
127 HorizontalBarChart::new_with_theme(series_list, x_axis_data, &get_default_theme_name())
128 }
129 pub fn svg(&self) -> canvas::Result<String> {
131 let mut c = Canvas::new_width_xy(self.width, self.height, self.x, self.y);
132
133 self.render_background(c.child(Box::default()));
134 c.margin = self.margin.clone();
135
136 let title_height = self.render_title(c.child(Box::default()));
137
138 let legend_height = self.render_legend(c.child(Box::default()));
139 let axis_top = if legend_height > title_height {
141 legend_height
142 } else {
143 title_height
144 };
145
146 let x_axis_height = 25.0_f32;
147 let axis_height = c.height() - axis_top - x_axis_height;
148 if axis_top > 0.0 {
150 c = c.child(Box {
151 top: axis_top,
152 ..Default::default()
153 });
154 }
155
156 let mut data = self.x_axis_data.clone();
157 data.reverse();
158 let mut max_width = 0.0;
159 for text in data.iter() {
160 if let Ok(b) = measure_text_width_family(&self.font_family, self.x_axis_font_size, text)
161 {
162 if b.width() > max_width {
163 max_width = b.width();
164 }
165 }
166 }
167
168 let y_axis_width = max_width + 5.0;
169
170 c.axis(Axis {
171 position: Position::Left,
172 height: axis_height,
173 width: y_axis_width,
174 split_number: self.x_axis_data.len(),
175 font_family: self.font_family.clone(),
176 stroke_color: Some(self.x_axis_stroke_color),
177 name_align: Align::Center,
178 name_gap: self.x_axis_name_gap,
179 font_color: Some(self.x_axis_font_color),
180 font_size: self.x_axis_font_size,
181 data,
182 ..Default::default()
183 });
184
185 let mut data_list = vec![];
186 for series in self.series_list.iter() {
187 data_list.append(series.data.clone().as_mut());
188 }
189 let x_axis_config = self.get_y_axis_config(0);
190 let x_axis_values = get_axis_values(AxisValueParams {
191 data_list,
192 split_number: x_axis_config.axis_split_number,
193 ..Default::default()
194 });
195
196 let x_axis_width = c.width() - y_axis_width;
197 c.child(Box {
198 left: y_axis_width,
199 top: axis_height,
200 ..Default::default()
201 })
202 .axis(Axis {
203 position: Position::Bottom,
204 height: x_axis_height,
205 width: x_axis_width,
206 split_number: x_axis_config.axis_split_number,
207 font_family: self.font_family.clone(),
208 stroke_color: Some(x_axis_config.axis_stroke_color),
209 name_align: Align::Left,
210 name_gap: x_axis_config.axis_name_gap,
211 font_color: Some(x_axis_config.axis_font_color),
212 font_size: x_axis_config.axis_font_size,
213 data: x_axis_values.data.clone(),
214 ..Default::default()
215 });
216
217 c.child(Box {
218 left: y_axis_width,
219 ..Default::default()
220 })
221 .grid(Grid {
222 right: x_axis_width,
223 bottom: axis_height,
224 color: Some(self.grid_stroke_color),
225 stroke_width: self.grid_stroke_width,
226 verticals: x_axis_config.axis_split_number,
227 hidden_verticals: vec![0],
228 ..Default::default()
229 });
230
231 if !self.series_list.is_empty() {
233 let mut c1 = c.child(Box {
234 left: y_axis_width,
235 bottom: x_axis_height,
236 ..Default::default()
237 });
238 let max_width = c1.width();
239 let unit_height = c1.height() / self.series_list[0].data.len() as f32;
240 let bar_chart_margin = 5.0_f32;
241 let bar_chart_gap = 3.0_f32;
242
243 let bar_chart_margin_height = bar_chart_margin * 2.0;
244 let bar_chart_gap_height = bar_chart_gap * (self.series_list.len() - 1) as f32;
245 let bar_height = (unit_height - bar_chart_margin_height - bar_chart_gap_height)
246 / self.series_list.len() as f32;
247 let half_bar_height = bar_height / 2.0;
248
249 let mut series_labels_list = vec![];
250 for (index, series) in self.series_list.iter().enumerate() {
251 let color = get_color(&self.series_colors, series.index.unwrap_or(index));
252
253 let mut series_labels = vec![];
254 let series_data_count = series.data.len();
255 for (i, p) in series.data.iter().enumerate() {
256 let value = p.to_owned();
257 if value == NIL_VALUE {
258 continue;
259 }
260 let mut top =
261 unit_height * (series_data_count - i - 1) as f32 + bar_chart_margin;
262 top += (bar_height + bar_chart_gap) * index as f32;
263
264 let x = max_width - x_axis_values.get_offset_height(value, max_width);
265 c1.rect(Rect {
266 fill: Some(color),
267 top,
268 width: x,
269 height: bar_height,
270 ..Default::default()
271 });
272 series_labels.push(SeriesLabel {
273 point: (x, top + half_bar_height).into(),
274 text: format_series_value(value, &self.series_label_formatter),
275 })
276 }
277 if series.label_show {
278 series_labels_list.push(series_labels);
279 }
280 }
281
282 let series_label_position = self
283 .series_label_position
284 .clone()
285 .unwrap_or(Position::Right);
286 for series_labels in series_labels_list.iter() {
287 for series_label in series_labels.iter() {
288 let mut dy = None;
289 let mut dx = Some(3.0);
290 let mut x = Some(series_label.point.x);
291 if let Ok(value) = measure_text_width_family(
292 &self.font_family,
293 self.series_label_font_size,
294 &series_label.text,
295 ) {
296 dy = Some(value.height() / 2.0 - 2.0);
297 if series_label_position == Position::Inside {
298 dx = None;
299 let offset = series_label.point.x - value.width();
300 if offset <= 0.0 {
301 x = Some(1.0);
302 } else {
303 x = Some(offset / 2.0);
304 }
305 } else if series_label_position == Position::Left {
306 x = Some(0.0);
307 dx = Some(-value.width());
308 }
309 }
310 c1.text(Text {
311 text: series_label.text.clone(),
312 dx,
313 dy,
314 font_family: Some(self.font_family.clone()),
315 font_color: Some(self.series_label_font_color),
316 font_size: Some(self.series_label_font_size),
317 x,
318 y: Some(series_label.point.y),
319 ..Default::default()
320 });
321 }
322 }
323 }
324
325 c.svg()
326 }
327}
328
329#[cfg(test)]
330mod tests {
331 use super::HorizontalBarChart;
332 use crate::{Align, Position, NIL_VALUE};
333 use pretty_assertions::assert_eq;
334 #[test]
335 fn horizontal_bar_chart_basic() {
336 let mut horizontal_bar_chart = HorizontalBarChart::new(
337 vec![
338 (
339 "2011",
340 vec![18203.0, 23489.0, 29034.0, 104970.0, 131744.0, 630230.0],
341 )
342 .into(),
343 (
344 "2012",
345 vec![19325.0, 23438.0, 31000.0, 121594.0, 134141.0, 681807.0],
346 )
347 .into(),
348 ],
349 vec![
350 "Brazil".to_string(),
351 "Indonesia".to_string(),
352 "USA".to_string(),
353 "India".to_string(),
354 "China".to_string(),
355 "World".to_string(),
356 ],
357 );
358 horizontal_bar_chart.title_text = "World Population".to_string();
359 horizontal_bar_chart.series_label_formatter = "{t}".to_string();
360 horizontal_bar_chart.margin.right = 15.0;
361 horizontal_bar_chart.series_list[0].label_show = true;
362 horizontal_bar_chart.title_align = Align::Left;
363 assert_eq!(
364 include_str!("../../asset/horizontal_bar_chart/basic.svg"),
365 horizontal_bar_chart.svg().unwrap()
366 );
367 }
368
369 #[test]
370 fn horizontal_bar_chart_inside() {
371 let mut horizontal_bar_chart = HorizontalBarChart::new(
372 vec![
373 (
374 "2011",
375 vec![18203.0, 23489.0, 29034.0, 104970.0, 131744.0, 630230.0],
376 )
377 .into(),
378 (
379 "2012",
380 vec![19325.0, 23438.0, 31000.0, 121594.0, 134141.0, 681807.0],
381 )
382 .into(),
383 ],
384 vec![
385 "Brazil".to_string(),
386 "Indonesia".to_string(),
387 "USA".to_string(),
388 "India".to_string(),
389 "China".to_string(),
390 "World".to_string(),
391 ],
392 );
393 horizontal_bar_chart.title_text = "World Population".to_string();
394 horizontal_bar_chart.series_label_formatter = "{t}".to_string();
395 horizontal_bar_chart.margin.right = 15.0;
396 horizontal_bar_chart.series_list[0].label_show = true;
397 horizontal_bar_chart.title_align = Align::Left;
398 horizontal_bar_chart.series_label_position = Some(Position::Inside);
399 assert_eq!(
400 include_str!("../../asset/horizontal_bar_chart/basic_label_inside.svg"),
401 horizontal_bar_chart.svg().unwrap()
402 );
403 }
404
405 #[test]
406 fn horizontal_bar_chart_nil_value() {
407 let mut horizontal_bar_chart = HorizontalBarChart::new(
408 vec![
409 (
410 "2011",
411 vec![18203.0, 23489.0, NIL_VALUE, 104970.0, 131744.0, 630230.0],
412 )
413 .into(),
414 (
415 "2012",
416 vec![19325.0, 23438.0, 31000.0, 121594.0, NIL_VALUE, 681807.0],
417 )
418 .into(),
419 ],
420 vec![
421 "Brazil".to_string(),
422 "Indonesia".to_string(),
423 "USA".to_string(),
424 "India".to_string(),
425 "China".to_string(),
426 "World".to_string(),
427 ],
428 );
429 horizontal_bar_chart.title_text = "World Population".to_string();
430 horizontal_bar_chart.margin.right = 15.0;
431 horizontal_bar_chart.series_list[0].label_show = true;
432 horizontal_bar_chart.title_align = Align::Left;
433 assert_eq!(
434 include_str!("../../asset/horizontal_bar_chart/nil_value.svg"),
435 horizontal_bar_chart.svg().unwrap()
436 );
437 }
438}