charts_rs/charts/
table_chart.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License at
4//
5//     http://www.apache.org/licenses/LICENSE-2.0
6//
7// Unless required by applicable law or agreed to in writing, software
8// distributed under the License is distributed on an "AS IS" BASIS,
9// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10// See the License for the specific language governing permissions and
11// limitations under the License.
12
13use 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    // title
43    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    // sub title
52    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    /// Creates a table chart from json.
240    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    /// Creates a table chart with custom theme.
246    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    /// Creates a table chart with default theme.
286    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    /// Converts bar chart to svg.
348    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                // 如果值少于1.0的则认为平分剩余宽度
405                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        // reassign the width of the table to 0 and divide the remaining width equally
415        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                // spans[j] can not be nil
450                // minus the padding value
451                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                // spans[j] can not be nil
516                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                // get the table cell's background color
522                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}