1use super::canvas;
14use super::color::*;
15use super::common::*;
16use super::component::*;
17use super::font::text_wrap_fit;
18use super::params::*;
19use super::theme::{get_default_theme_name, get_theme, Theme};
20use super::util::*;
21use super::Canvas;
22use crate::charts::measure_text_width_family;
23use std::sync::Arc;
24
25#[derive(Clone, Debug, Default)]
26pub struct TableCellStyle {
27 pub font_color: Option<Color>,
28 pub font_weight: Option<String>,
29 pub background_color: Option<Color>,
30 pub indexes: Vec<usize>,
31}
32
33#[derive(Clone, Debug, Default)]
34pub struct TableChart {
35 pub width: f32,
36 pub height: f32,
37 pub x: f32,
38 pub y: f32,
39 pub font_family: String,
40 pub background_color: Color,
41
42 pub title_text: String,
44 pub title_font_size: f32,
45 pub title_font_color: Color,
46 pub title_font_weight: Option<String>,
47 pub title_margin: Option<Box>,
48 pub title_align: Align,
49 pub title_height: f32,
50
51 pub sub_title_text: String,
53 pub sub_title_font_size: f32,
54 pub sub_title_font_color: Color,
55 pub sub_title_font_weight: Option<String>,
56 pub sub_title_margin: Option<Box>,
57 pub sub_title_align: Align,
58 pub sub_title_height: f32,
59
60 pub data: Vec<Vec<String>>,
61 pub spans: Vec<f32>,
62 pub text_aligns: Vec<Align>,
63 pub border_color: Color,
64 pub outlined: bool,
65
66 pub header_row_padding: Box,
67 pub header_row_height: f32,
68 pub header_font_size: f32,
69 pub header_font_weight: Option<String>,
70 pub header_font_color: Color,
71 pub header_background_color: Color,
72
73 pub body_row_padding: Box,
74 pub body_row_height: f32,
75 pub body_font_size: f32,
76 pub body_font_color: Color,
77 pub body_background_colors: Vec<Color>,
78
79 pub cell_styles: Vec<TableCellStyle>,
80}
81
82impl TableChart {
83 fn fill_option(&mut self, data: &str) -> canvas::Result<serde_json::Value> {
84 let data: serde_json::Value = serde_json::from_str(data)?;
85 let theme = get_string_from_value(&data, "theme").unwrap_or_default();
86 self.fill_theme(get_theme(&theme));
87
88 if let Some(width) = get_f32_from_value(&data, "width") {
89 self.width = width;
90 }
91 if let Some(height) = get_f32_from_value(&data, "height") {
92 self.height = height;
93 }
94 if let Some(x) = get_f32_from_value(&data, "x") {
95 self.x = x;
96 }
97 if let Some(y) = get_f32_from_value(&data, "y") {
98 self.y = y;
99 }
100 if let Some(font_family) = get_string_from_value(&data, "font_family") {
101 self.font_family = font_family;
102 }
103 if let Some(title_text) = get_string_from_value(&data, "title_text") {
104 self.title_text = title_text;
105 }
106 if let Some(title_font_size) = get_f32_from_value(&data, "title_font_size") {
107 self.title_font_size = title_font_size;
108 }
109 if let Some(title_font_color) = get_color_from_value(&data, "title_font_color") {
110 self.title_font_color = title_font_color;
111 }
112 if let Some(title_font_weight) = get_string_from_value(&data, "title_font_weight") {
113 self.title_font_weight = Some(title_font_weight);
114 }
115 if let Some(title_margin) = get_margin_from_value(&data, "title_margin") {
116 self.title_margin = Some(title_margin);
117 }
118 if let Some(title_align) = get_align_from_value(&data, "title_align") {
119 self.title_align = title_align;
120 }
121 if let Some(title_height) = get_f32_from_value(&data, "title_height") {
122 self.title_height = title_height;
123 }
124
125 if let Some(sub_title_text) = get_string_from_value(&data, "sub_title_text") {
126 self.sub_title_text = sub_title_text;
127 }
128 if let Some(sub_title_font_size) = get_f32_from_value(&data, "sub_title_font_size") {
129 self.sub_title_font_size = sub_title_font_size;
130 }
131 if let Some(sub_title_font_color) = get_color_from_value(&data, "sub_title_font_color") {
132 self.sub_title_font_color = sub_title_font_color;
133 }
134 if let Some(sub_title_font_weight) = get_string_from_value(&data, "sub_title_font_weight") {
135 self.sub_title_font_weight = Some(sub_title_font_weight);
136 }
137 if let Some(sub_title_margin) = get_margin_from_value(&data, "sub_title_margin") {
138 self.sub_title_margin = Some(sub_title_margin);
139 }
140 if let Some(sub_title_align) = get_align_from_value(&data, "sub_title_align") {
141 self.sub_title_align = sub_title_align;
142 }
143 if let Some(sub_title_height) = get_f32_from_value(&data, "sub_title_height") {
144 self.sub_title_height = sub_title_height;
145 }
146 if let Some(data) = data.get("cell_styles") {
147 if let Some(arr) = data.as_array() {
148 let mut cell_styles = vec![];
149 for item in arr.iter() {
150 let mut style = TableCellStyle {
151 ..Default::default()
152 };
153 if let Some(font_color) = get_color_from_value(item, "font_color") {
154 style.font_color = Some(font_color);
155 }
156 if let Some(background_color) = get_color_from_value(item, "background_color") {
157 style.background_color = Some(background_color);
158 }
159 if let Some(font_weight) = get_string_from_value(item, "font_weight") {
160 style.font_weight = Some(font_weight);
161 }
162 if let Some(indexes) = get_usize_slice_from_value(item, "indexes") {
163 style.indexes = indexes;
164 }
165 cell_styles.push(style);
166 }
167 self.cell_styles = cell_styles;
168 }
169 }
170
171 if let Some(data) = data.get("data") {
172 if let Some(arr) = data.as_array() {
173 let mut data_list = vec![];
174 for items in arr.iter() {
175 let mut list = vec![];
176 if let Some(sub_arr) = items.as_array() {
177 for item in sub_arr.iter() {
178 if let Some(str) = item.as_str() {
179 list.push(str.to_string());
180 }
181 }
182 }
183 data_list.push(list);
184 }
185 self.data = data_list;
186 }
187 }
188 if let Some(spans) = get_f32_slice_from_value(&data, "spans") {
189 self.spans = spans;
190 }
191 if let Some(text_aligns) = get_align_slice_from_value(&data, "text_aligns") {
192 self.text_aligns = text_aligns;
193 }
194 if let Some(header_row_padding) = get_margin_from_value(&data, "header_row_padding") {
195 self.header_row_padding = header_row_padding;
196 }
197 if let Some(header_row_height) = get_f32_from_value(&data, "header_row_height") {
198 self.header_row_height = header_row_height;
199 }
200 if let Some(header_font_size) = get_f32_from_value(&data, "header_font_size") {
201 self.header_font_size = header_font_size;
202 }
203 if let Some(header_font_weight) = get_string_from_value(&data, "header_font_weight") {
204 self.header_font_weight = Some(header_font_weight);
205 }
206 if let Some(header_font_color) = get_color_from_value(&data, "header_font_color") {
207 self.header_font_color = header_font_color;
208 }
209 if let Some(header_background_color) =
210 get_color_from_value(&data, "header_background_color")
211 {
212 self.header_background_color = header_background_color;
213 }
214 if let Some(body_row_padding) = get_margin_from_value(&data, "body_row_padding") {
215 self.body_row_padding = body_row_padding;
216 }
217 if let Some(body_row_height) = get_f32_from_value(&data, "body_row_height") {
218 self.body_row_height = body_row_height;
219 }
220 if let Some(body_font_size) = get_f32_from_value(&data, "body_font_size") {
221 self.body_font_size = body_font_size;
222 }
223 if let Some(body_font_color) = get_color_from_value(&data, "body_font_color") {
224 self.body_font_color = body_font_color;
225 }
226 if let Some(body_background_colors) =
227 get_color_slice_from_value(&data, "body_background_colors")
228 {
229 self.body_background_colors = body_background_colors;
230 }
231 if let Some(border_color) = get_color_from_value(&data, "border_color") {
232 self.border_color = border_color;
233 }
234 if let Some(outlined) = get_bool_from_value(&data, "outlined") {
235 self.outlined = outlined;
236 }
237 Ok(data)
238 }
239 pub fn from_json(data: &str) -> canvas::Result<TableChart> {
241 let mut t = TableChart::new_with_theme(vec![], "");
242 t.fill_option(data)?;
243 Ok(t)
244 }
245 pub fn new_with_theme(data: Vec<Vec<String>>, theme: &str) -> TableChart {
247 let mut table = TableChart {
248 data,
249 header_row_padding: (10.0, 8.0).into(),
250 header_row_height: 30.0,
251 body_row_padding: (10.0, 5.0).into(),
252 body_row_height: 30.0,
253 ..Default::default()
254 };
255 table.fill_theme(get_theme(theme));
256 table
257 }
258 fn fill_theme(&mut self, t: Arc<Theme>) {
259 self.font_family.clone_from(&t.font_family);
260 self.width = t.width;
261 self.background_color = t.background_color;
262
263 self.title_font_color = t.title_font_color;
264 self.title_font_size = t.title_font_size;
265 self.title_font_weight.clone_from(&t.title_font_weight);
266 self.title_margin.clone_from(&t.title_margin);
267 self.title_align = t.title_align.clone();
268 self.title_height = t.title_height * 1.5;
269
270 self.sub_title_font_color = t.sub_title_font_color;
271 self.sub_title_font_size = t.sub_title_font_size;
272 self.sub_title_margin.clone_from(&t.sub_title_margin);
273 self.sub_title_align = t.sub_title_align.clone();
274 self.sub_title_height = t.sub_title_height;
275
276 self.header_font_size = t.sub_title_font_size;
277 self.header_font_color = t.sub_title_font_color;
278 self.header_background_color = t.table_header_color;
279
280 self.body_font_size = t.sub_title_font_size;
281 self.body_font_color = t.sub_title_font_color;
282 self.body_background_colors.clone_from(&t.table_body_colors);
283 self.border_color = t.table_border_color;
284 }
285 pub fn new(data: Vec<Vec<String>>) -> TableChart {
287 TableChart::new_with_theme(data, &get_default_theme_name())
288 }
289 fn render_title(&self, c: Canvas) -> f32 {
290 let mut title_height = 0.0;
291
292 if !self.title_text.is_empty() {
293 let title_margin = self.title_margin.clone().unwrap_or_default();
294 let mut x = 0.0;
295 if let Ok(title_box) =
296 measure_text_width_family(&self.font_family, self.title_font_size, &self.title_text)
297 {
298 x = match self.title_align {
299 Align::Center => (c.width() - title_box.width()) / 2.0,
300 Align::Right => c.width() - title_box.width(),
301 _ => 0.0,
302 }
303 }
304 let title_margin_bottom = title_margin.bottom;
305 let b = c.child(title_margin).text(Text {
306 text: self.title_text.clone(),
307 font_family: Some(self.font_family.clone()),
308 font_size: Some(self.title_font_size),
309 font_weight: self.title_font_weight.clone(),
310 font_color: Some(self.title_font_color),
311 line_height: Some(self.title_height),
312 x: Some(x),
313 ..Default::default()
314 });
315 title_height = b.height() + title_margin_bottom;
316 }
317 if !self.sub_title_text.is_empty() {
318 let mut sub_title_margin = self.sub_title_margin.clone().unwrap_or_default();
319 let mut x = 0.0;
320 if let Ok(sub_title_box) = measure_text_width_family(
321 &self.font_family,
322 self.sub_title_font_size,
323 &self.sub_title_text,
324 ) {
325 x = match self.title_align {
326 Align::Center => (c.width() - sub_title_box.width()) / 2.0,
327 Align::Right => c.width() - sub_title_box.width(),
328 _ => 0.0,
329 }
330 }
331 let sub_title_margin_bottom = sub_title_margin.bottom;
332 sub_title_margin.top += self.title_height;
333 let b = c.child(sub_title_margin).text(Text {
334 text: self.sub_title_text.clone(),
335 font_family: Some(self.font_family.clone()),
336 font_size: Some(self.sub_title_font_size),
337 font_color: Some(self.sub_title_font_color),
338 font_weight: self.sub_title_font_weight.clone(),
339 line_height: Some(self.sub_title_height),
340 x: Some(x),
341 ..Default::default()
342 });
343 title_height = b.outer_height() + sub_title_margin_bottom;
344 }
345 title_height
346 }
347 pub fn svg(&mut self) -> canvas::Result<String> {
349 if self.data.is_empty() {
350 return Err(canvas::Error::Params {
351 message: "data is empty".to_string(),
352 });
353 }
354 let column_count = self.data[0].len();
355 if column_count == 0 {
356 return Err(canvas::Error::Params {
357 message: "table header column is empty".to_string(),
358 });
359 }
360 for item in self.data.iter() {
361 if item.len() != column_count {
362 return Err(canvas::Error::Params {
363 message: "data len is invalid".to_string(),
364 });
365 }
366 }
367
368 let mut c = Canvas::new_width_xy(self.width, self.height, self.x, self.y);
369
370 if !self.title_text.is_empty() {
371 let mut title_height = self.title_height;
372 if let Some(value) = self.title_margin.clone() {
373 title_height += value.top + value.bottom;
374 }
375 if !self.sub_title_text.is_empty() {
376 title_height += self.sub_title_height;
377 }
378 c.rect(Rect {
379 fill: Some(self.background_color),
380 left: 0.0,
381 top: 0.0,
382 width: self.width,
383 height: title_height,
384 ..Default::default()
385 });
386 }
387
388 let title_height = self.render_title(c.child(Box::default()));
389
390 c = c.child(Box {
391 top: title_height,
392 ..Default::default()
393 });
394 let width = c.width();
395 let mut spans = vec![];
396 let mut rest_width = width;
397 let mut rest_count = 0.0_f32;
398 for index in 0..column_count {
399 if let Some(value) = self.spans.get(index) {
400 let mut v = value.to_owned();
401 if v < 1.0 {
402 v *= width;
403 }
404 if v > 1.0 {
406 rest_width -= v;
407 spans.push(v);
408 continue;
409 }
410 }
411 rest_count += 1.0;
412 spans.push(0.0);
413 }
414 if rest_count > 0.0 {
416 let unit_width = rest_width / rest_count;
417 for item in spans.iter_mut() {
418 if *item == 0.0 {
419 *item = unit_width;
420 }
421 }
422 }
423
424 let find_cell_style = |row: usize, column: usize| -> Option<&TableCellStyle> {
425 for cell_style in self.cell_styles.iter() {
426 if cell_style.indexes.len() != 2 {
427 continue;
428 }
429 if cell_style.indexes[0] != row || cell_style.indexes[1] != column {
430 continue;
431 }
432 return Some(cell_style);
433 }
434 None
435 };
436
437 let mut table_content_list = vec![];
438 for (i, items) in self.data.iter().enumerate() {
439 let mut font_size = self.body_font_size;
440 let mut padding = self.body_row_padding.left + self.body_row_padding.right;
441 let is_header = i == 0;
442 if is_header {
443 font_size = self.header_font_size;
444 padding = self.header_row_padding.left + self.header_row_padding.right;
445 }
446
447 let mut row_content_list = vec![];
448 for (j, item) in items.iter().enumerate() {
449 let span_width = spans[j] - padding;
452 if let Ok(result) = text_wrap_fit(&self.font_family, font_size, item, span_width) {
453 row_content_list.push(result);
454 } else {
455 row_content_list.push(vec![item.clone()]);
456 }
457 }
458 table_content_list.push(row_content_list);
459 }
460 let mut top = 0.0;
461 let body_background_color_count = self.body_background_colors.len();
462
463 for (i, items) in table_content_list.iter().enumerate() {
464 let mut left = 0.0;
465 let mut right = 0.0;
466 let mut line_height = self.body_row_height;
467 let mut padding = self.body_row_padding.top + self.body_row_padding.bottom;
468 let mut font_size = self.body_font_size;
469 let mut font_color = self.body_font_color;
470
471 let is_header = i == 0;
472 let mut font_weight = None;
473 let bg_color = if is_header {
474 line_height = self.header_row_height;
475 padding = self.header_row_padding.top + self.header_row_padding.bottom;
476 font_size = self.header_font_size;
477 font_color = self.header_font_color;
478 font_weight.clone_from(&self.header_font_weight);
479 self.header_background_color
480 } else {
481 self.body_background_colors[(i - 1) % body_background_color_count]
482 };
483
484 let row_padding = if is_header {
485 self.header_row_padding.clone()
486 } else {
487 self.body_row_padding.clone()
488 };
489 let mut count = 0;
490 for content_list in items.iter() {
491 if count < content_list.len() {
492 count = content_list.len();
493 }
494 }
495 let row_height = line_height * count as f32 + padding;
496
497 c.rect(Rect {
498 fill: Some(bg_color),
499 top,
500 width: c.width(),
501 height: row_height,
502 ..Default::default()
503 });
504 if !self.border_color.is_transparent() {
505 c.line(Line {
506 color: Some(self.border_color),
507 stroke_width: 1.0,
508 top,
509 right: c.width(),
510 bottom: top,
511 ..Default::default()
512 });
513 }
514 for (j, content_list) in items.iter().enumerate() {
515 let span_width = spans[j];
517
518 let mut cell_font_color = font_color;
519 let mut cell_font_weight = font_weight.clone();
520
521 if let Some(cell_style) = find_cell_style(i, j) {
523 if let Some(value) = cell_style.font_color {
524 cell_font_color = value;
525 }
526 if let Some(ref value) = cell_style.font_weight {
527 cell_font_weight = Some(value.clone());
528 }
529 if let Some(value) = cell_style.background_color {
530 c.rect(Rect {
531 fill: Some(value),
532 left,
533 top: top + 1.0,
534 width: span_width,
535 height: row_height - 1.0,
536 ..Default::default()
537 });
538 }
539 }
540
541 for (index, item) in content_list.iter().enumerate() {
542 let mut dx = None;
543 if let Ok(measurement) =
544 measure_text_width_family(&self.font_family, font_size, item)
545 {
546 let mut align = Align::Left;
547 if let Some(value) = self.text_aligns.get(j) {
548 value.clone_into(&mut align);
549 }
550 let text_width = measurement.width();
551 let text_max_width = span_width - row_padding.left - row_padding.right;
552 dx = match align {
553 Align::Center => Some((text_max_width - text_width) / 2.0),
554 Align::Right => Some(text_max_width - text_width),
555 Align::Left => None,
556 };
557 }
558 c.child(row_padding.clone()).text(Text {
559 text: item.to_string(),
560 font_weight: cell_font_weight.clone(),
561 font_family: Some(self.font_family.clone()),
562 font_size: Some(font_size),
563 font_color: Some(cell_font_color),
564 line_height: Some(line_height),
565 dx,
566 x: Some(left),
567 y: Some(top + line_height * index as f32),
568 ..Default::default()
569 });
570 }
571
572 right += span_width;
573 left = right
574 }
575 top += row_height;
576 }
577 if self.outlined {
578 c.rect(Rect {
579 color: Some(self.border_color),
580 fill: Some(Color::transparent()),
581 left: 0.0,
582 top: 0.0,
583 width: c.width(),
584 height: top,
585 ..Default::default()
586 });
587 }
588 c.height = c.margin.top + top;
589 self.height = c.height;
590
591 c.svg()
592 }
593}
594
595#[cfg(test)]
596mod tests {
597 use super::{TableCellStyle, TableChart};
598 use crate::{Align, THEME_ANT, THEME_DARK, THEME_GRAFANA};
599 use pretty_assertions::assert_eq;
600
601 #[test]
602 fn table_basic() {
603 let mut table_chart = TableChart::new(vec![
604 vec![
605 "Name".to_string(),
606 "Price".to_string(),
607 "Change".to_string(),
608 ],
609 vec![
610 "Datadog Inc".to_string(),
611 "97.32".to_string(),
612 "-7.49%".to_string(),
613 ],
614 vec![
615 "Hashicorp Inc".to_string(),
616 "28.66".to_string(),
617 "-9.25%".to_string(),
618 ],
619 vec![
620 "Gitlab Inc".to_string(),
621 "51.63".to_string(),
622 "+4.32%".to_string(),
623 ],
624 ]);
625 table_chart.title_text = "NASDAQ".to_string();
626 table_chart.cell_styles = vec![TableCellStyle {
627 indexes: vec![1, 2],
628 font_weight: Some("bold".to_string()),
629 background_color: Some("#3bb357".into()),
630 font_color: Some(("#fff").into()),
631 }];
632 table_chart.outlined = true;
633 assert_eq!(
634 include_str!("../../asset/table_chart/basic.svg"),
635 table_chart.svg().unwrap()
636 );
637 }
638
639 #[test]
640 fn table_multi_lines() {
641 let mut table_chart = TableChart::new(vec![
642 vec![
643 "Name".to_string(),
644 "Price".to_string(),
645 "ChangeChangeChangeChangeChangeChange".to_string(),
646 ],
647 vec![
648 "Datadog Inc".to_string(),
649 "97.32".to_string(),
650 "-7.49%".to_string(),
651 ],
652 vec![
653 "Hashicorp Inc".to_string(),
654 "28.66".to_string(),
655 "Hashicorp Inc Hashicorp Inc -9.25%".to_string(),
656 ],
657 vec![
658 "Gitlab Inc".to_string(),
659 "51.63".to_string(),
660 "+4.32%".to_string(),
661 ],
662 ]);
663 table_chart.title_text = "NASDAQ".to_string();
664 table_chart.text_aligns = vec![Align::Left, Align::Center];
665 table_chart.cell_styles = vec![
666 TableCellStyle {
667 indexes: vec![1, 2],
668 font_weight: Some("bold".to_string()),
669 background_color: Some("#3bb357".into()),
670 font_color: Some(("#fff").into()),
671 },
672 TableCellStyle {
673 indexes: vec![2, 1],
674 background_color: Some("#3bb357".into()),
675 font_color: Some(("#fff").into()),
676 ..Default::default()
677 },
678 ];
679 assert_eq!(
680 include_str!("../../asset/table_chart/multi_lines.svg"),
681 table_chart.svg().unwrap()
682 );
683 }
684
685 #[test]
686 fn table_basic_dark() {
687 let mut table_chart = TableChart::new_with_theme(
688 vec![
689 vec![
690 "Name".to_string(),
691 "Price".to_string(),
692 "Change".to_string(),
693 ],
694 vec![
695 "Datadog Inc".to_string(),
696 "97.32".to_string(),
697 "-7.49%".to_string(),
698 ],
699 vec![
700 "Hashicorp Inc".to_string(),
701 "28.66".to_string(),
702 "-9.25%".to_string(),
703 ],
704 vec![
705 "Gitlab Inc".to_string(),
706 "51.63".to_string(),
707 "+4.32%".to_string(),
708 ],
709 ],
710 THEME_DARK,
711 );
712 table_chart.title_text = "NASDAQ".to_string();
713 table_chart.text_aligns = vec![Align::Left, Align::Center, Align::Right];
714 assert_eq!(
715 include_str!("../../asset/table_chart/basic_dark.svg"),
716 table_chart.svg().unwrap()
717 );
718 }
719
720 #[test]
721 fn table_basic_ant() {
722 let mut table_chart = TableChart::new_with_theme(
723 vec![
724 vec![
725 "Name".to_string(),
726 "Price".to_string(),
727 "Change".to_string(),
728 ],
729 vec![
730 "Datadog Inc".to_string(),
731 "97.32".to_string(),
732 "-7.49%".to_string(),
733 ],
734 vec![
735 "Hashicorp Inc".to_string(),
736 "28.66".to_string(),
737 "-9.25%".to_string(),
738 ],
739 vec![
740 "Gitlab Inc".to_string(),
741 "51.63".to_string(),
742 "+4.32%".to_string(),
743 ],
744 ],
745 THEME_ANT,
746 );
747 table_chart.title_text = "NASDAQ".to_string();
748 table_chart.text_aligns = vec![Align::Left, Align::Center, Align::Right];
749 assert_eq!(
750 include_str!("../../asset/table_chart/basic_ant.svg"),
751 table_chart.svg().unwrap()
752 );
753 }
754
755 #[test]
756 fn table_basic_grafana() {
757 let mut table_chart = TableChart::new_with_theme(
758 vec![
759 vec![
760 "Name".to_string(),
761 "Price".to_string(),
762 "Change".to_string(),
763 ],
764 vec![
765 "Datadog Inc".to_string(),
766 "97.32".to_string(),
767 "-7.49%".to_string(),
768 ],
769 vec![
770 "Hashicorp Inc".to_string(),
771 "28.66".to_string(),
772 "-9.25%".to_string(),
773 ],
774 vec![
775 "Gitlab Inc".to_string(),
776 "51.63".to_string(),
777 "+4.32%".to_string(),
778 ],
779 ],
780 THEME_GRAFANA,
781 );
782 table_chart.title_text = "NASDAQ".to_string();
783 table_chart.sub_title_text = "stock".to_string();
784 table_chart.spans = vec![0.5, 0.3, 0.2];
785 let green = "#2d7c2b".into();
786 let red = "#a93b01".into();
787 table_chart.cell_styles = vec![
788 TableCellStyle {
789 indexes: vec![1, 2],
790 font_weight: Some("bold".to_string()),
791 background_color: Some(green),
792 ..Default::default()
793 },
794 TableCellStyle {
795 indexes: vec![2, 2],
796 font_weight: Some("bold".to_string()),
797 background_color: Some(green),
798 ..Default::default()
799 },
800 TableCellStyle {
801 indexes: vec![3, 2],
802 font_weight: Some("bold".to_string()),
803 background_color: Some(red),
804 ..Default::default()
805 },
806 ];
807 table_chart.spans = vec![150.0, 0.4];
808 table_chart.text_aligns = vec![Align::Left, Align::Center, Align::Center];
809 assert_eq!(
810 include_str!("../../asset/table_chart/basic_grafana.svg"),
811 table_chart.svg().unwrap()
812 );
813 }
814}