1use crate::error::ChartError;
4use crate::{
5 DEFAULT_HEIGHT, DEFAULT_TITLE_FONT_SIZE, DEFAULT_WIDTH, TITLE_AREA_HEIGHT, validate_data_array,
6 validate_dimensions, validate_grid_dimensions, validate_monotonic, validate_positive,
7};
8use d3rs::surface3d::{Colormap, Surface3DConfig, Surface3DElement, SurfaceData};
9use d3rs::text::{VectorFontConfig, render_vector_text};
10use gpui::prelude::*;
11use gpui::*;
12
13#[derive(Clone)]
15pub struct Surface3DChart {
16 z: Vec<f64>,
17 grid_width: usize,
18 grid_height: usize,
19 x_values: Option<Vec<f64>>,
20 y_values: Option<Vec<f64>>,
21 title: Option<String>,
22 colormap: Colormap,
23 wireframe: bool,
24 width: f32,
25 height: f32,
26 x_log: bool,
27 y_log: bool,
28 z_min: Option<f64>,
29 z_max: Option<f64>,
30 x_label: Option<String>,
31 y_label: Option<String>,
32 z_label: Option<String>,
33}
34
35impl std::fmt::Debug for Surface3DChart {
36 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 f.debug_struct("Surface3DChart")
38 .field("grid_width", &self.grid_width)
39 .field("grid_height", &self.grid_height)
40 .field("colormap", &self.colormap)
41 .field("title", &self.title)
42 .field("wireframe", &self.wireframe)
43 .field("width", &self.width)
44 .field("height", &self.height)
45 .finish()
46 }
47}
48
49impl Surface3DChart {
50 pub fn x(mut self, values: &[f64]) -> Self {
55 self.x_values = Some(values.to_vec());
56 self
57 }
58
59 pub fn y(mut self, values: &[f64]) -> Self {
64 self.y_values = Some(values.to_vec());
65 self
66 }
67
68 pub fn title(mut self, title: impl Into<String>) -> Self {
70 self.title = Some(title.into());
71 self
72 }
73
74 pub fn colormap(mut self, colormap: Colormap) -> Self {
76 self.colormap = colormap;
77 self
78 }
79
80 pub fn wireframe(mut self, wireframe: bool) -> Self {
82 self.wireframe = wireframe;
83 self
84 }
85
86 pub fn size(mut self, width: f32, height: f32) -> Self {
88 self.width = width;
89 self.height = height;
90 self
91 }
92
93 pub fn x_log(mut self, log: bool) -> Self {
95 self.x_log = log;
96 self
97 }
98
99 pub fn y_log(mut self, log: bool) -> Self {
101 self.y_log = log;
102 self
103 }
104
105 pub fn z_range(mut self, min: f64, max: f64) -> Self {
107 self.z_min = Some(min);
108 self.z_max = Some(max);
109 self
110 }
111
112 pub fn x_label(mut self, label: impl Into<String>) -> Self {
114 self.x_label = Some(label.into());
115 self
116 }
117
118 pub fn y_label(mut self, label: impl Into<String>) -> Self {
120 self.y_label = Some(label.into());
121 self
122 }
123
124 pub fn z_label(mut self, label: impl Into<String>) -> Self {
126 self.z_label = Some(label.into());
127 self
128 }
129
130 pub fn build(self) -> Result<impl IntoElement, ChartError> {
132 validate_data_array(&self.z, "z")?;
134 validate_grid_dimensions(&self.z, self.grid_width, self.grid_height)?;
135 validate_dimensions(self.width, self.height)?;
136
137 let x_values = match self.x_values {
139 Some(ref v) => {
140 if v.len() != self.grid_width {
141 return Err(ChartError::DataLengthMismatch {
142 x_field: "x",
143 y_field: "grid_width",
144 x_len: v.len(),
145 y_len: self.grid_width,
146 });
147 }
148 validate_data_array(v, "x")?;
149 validate_monotonic(v, "x")?;
150 if self.x_log {
151 validate_positive(v, "x")?;
152 }
153 v.clone()
154 }
155 None => (0..self.grid_width).map(|i| i as f64).collect(),
156 };
157
158 let y_values = match self.y_values {
160 Some(ref v) => {
161 if v.len() != self.grid_height {
162 return Err(ChartError::DataLengthMismatch {
163 x_field: "y",
164 y_field: "grid_height",
165 x_len: v.len(),
166 y_len: self.grid_height,
167 });
168 }
169 validate_data_array(v, "y")?;
170 validate_monotonic(v, "y")?;
171 if self.y_log {
172 validate_positive(v, "y")?;
173 }
174 v.clone()
175 }
176 None => (0..self.grid_height).map(|i| i as f64).collect(),
177 };
178
179 let mut z_grid = Vec::with_capacity(self.grid_height);
182 for y_idx in 0..self.grid_height {
183 let start = y_idx * self.grid_width;
184 let end = start + self.grid_width;
185 z_grid.push(self.z[start..end].to_vec());
186 }
187
188 let title_height = if self.title.is_some() {
190 TITLE_AREA_HEIGHT
191 } else {
192 0.0
193 };
194 let plot_height = self.height - title_height;
195
196 let mut data = SurfaceData::from_grid(x_values, y_values, z_grid);
198
199 if let Some(label) = self.x_label {
201 data = data.with_x_label(label);
202 }
203 if let Some(label) = self.y_label {
204 data = data.with_y_label(label);
205 }
206 if let Some(label) = self.z_label {
207 data = data.with_z_label(label);
208 }
209 data = data.with_log_x(self.x_log).with_log_y(self.y_log);
210 if let (Some(min), Some(max)) = (self.z_min, self.z_max) {
211 data = data.with_z_range(min, max);
212 }
213
214 let config = Surface3DConfig::new()
216 .colormap(self.colormap)
217 .wireframe(self.wireframe);
218
219 let mut container = div()
221 .w(px(self.width))
222 .h(px(self.height))
223 .relative()
224 .flex()
225 .flex_col();
226
227 if let Some(title) = &self.title {
229 let font_config =
230 VectorFontConfig::horizontal(DEFAULT_TITLE_FONT_SIZE, hsla(0.0, 0.0, 0.2, 1.0));
231 container = container.child(
232 div()
233 .w_full()
234 .h(px(title_height))
235 .flex()
236 .justify_center()
237 .items_center()
238 .child(render_vector_text(title, &font_config)),
239 );
240 }
241
242 container = container.child(
244 div()
245 .w(px(self.width))
246 .h(px(plot_height))
247 .relative()
248 .child(Surface3DElement::new(data, config)),
249 );
250
251 Ok(container)
252 }
253}
254
255pub fn surface3d(z: &[f64], grid_width: usize, grid_height: usize) -> Surface3DChart {
278 Surface3DChart {
279 z: z.to_vec(),
280 grid_width,
281 grid_height,
282 x_values: None,
283 y_values: None,
284 title: None,
285 colormap: Colormap::Viridis,
286 wireframe: false,
287 width: DEFAULT_WIDTH,
288 height: DEFAULT_HEIGHT,
289 x_log: false,
290 y_log: false,
291 z_min: None,
292 z_max: None,
293 x_label: None,
294 y_label: None,
295 z_label: None,
296 }
297}