Skip to main content

fluent_plots/
lib.rs

1use polars::prelude::*;
2use plotters::prelude::*;
3use std::error::Error;
4
5#[cfg(feature = "interactive")]
6use charming::{
7    component::{Axis, Title},
8    element::AxisType,
9    series::{Bar, Line, Scatter, Series},
10    Chart as CharmingChart,
11};
12
13#[derive(Clone)]
14pub enum ChartType {
15    Bar,
16    Line,
17    Scatter,
18}
19
20#[derive(Clone)]
21pub struct Chart {
22    chart_type: ChartType,
23    dataframe: DataFrame,
24    x_column: String,
25    y_column: String,
26}
27
28pub fn barchart(dataframe: DataFrame) -> Chart {
29    Chart {
30        chart_type: ChartType::Bar,
31        dataframe,
32        x_column: String::new(),
33        y_column: String::new(),
34    }
35}
36
37pub fn linechart(dataframe: DataFrame) -> Chart {
38    Chart {
39        chart_type: ChartType::Line,
40        dataframe,
41        x_column: String::new(),
42        y_column: String::new(),
43    }
44}
45
46pub fn scatterplot(dataframe: DataFrame) -> Chart {
47    Chart {
48        chart_type: ChartType::Scatter,
49        dataframe,
50        x_column: String::new(),
51        y_column: String::new(),
52    }
53}
54
55impl Chart {
56    pub fn x(mut self, column_name: &str) -> Self {
57        self.x_column = column_name.to_string();
58        self
59    }
60
61    pub fn y(mut self, column_name: &str) -> Self {
62        self.y_column = column_name.to_string();
63        self
64    }
65
66    pub fn draw(&self, path: &str) -> std::result::Result<(), Box<dyn Error>> {
67        let root = BitMapBackend::new(path, (640, 480)).into_drawing_area();
68        root.fill(&WHITE)?;
69
70        println!("Drawing static chart with x='{}' and y='{}'", self.x_column, self.y_column);
71
72        root.present()?;
73        Ok(())
74    }
75}
76
77#[cfg(feature = "interactive")]
78impl Chart {
79    pub fn to_interactive_html(&self) -> std::result::Result<String, Box<dyn Error>> {
80        let x_data: Vec<String> = self
81            .dataframe
82            .column(&self.x_column)?
83            .str()?
84            .into_iter()
85            .map(|opt_val| opt_val.unwrap_or("").to_string())
86            .collect();
87
88        let y_data: Vec<f64> = self
89            .dataframe
90            .column(&self.y_column)?
91            .cast(&DataType::Float64)?
92            .f64()?
93            .into_iter()
94            .map(|opt_val| opt_val.unwrap_or(0.0))
95            .collect();
96
97        let series: Series = match self.chart_type {
98            ChartType::Bar => Bar::new().data(y_data).into(),
99            ChartType::Line => Line::new().data(y_data).into(),
100            ChartType::Scatter => Scatter::new().data(y_data).into(),
101        };
102
103        let chart = CharmingChart::new()
104            .title(Title::new().text("fluent-plots chart"))
105            .x_axis(Axis::new().type_(AxisType::Category).data(x_data))
106            .y_axis(Axis::new().type_(AxisType::Value))
107            .series(series);
108
109        let html = format!(
110            r#"
111            <!DOCTYPE html>
112            <html>
113            <head>
114                <meta charset="utf-8" />
115                <title>Fluent-Plots Chart</title>
116                <script src="https://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js"></script>
117            </head>
118            <body>
119                <div id="main" style="width: 800px;height:600px;"></div>
120                <script type="text/javascript">
121                    var myChart = echarts.init(document.getElementById('main'));
122                    var option = {json_config};
123                    myChart.setOption(option);
124                </script>
125            </body>
126            </html>
127            "#,
128            json_config = chart.to_string()
129        );
130
131        Ok(html)
132    }
133}