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}