1use plotly_derive::FieldSetter;
4use serde::Serialize;
5
6use crate::{
7 color::Color,
8 common::{
9 Calendar, ColorBar, ColorScale, Dim, HoverInfo, Label, LegendGroupTitle, PlotType, Visible,
10 },
11 Trace,
12};
13
14#[serde_with::skip_serializing_none]
15#[derive(Serialize, Debug, Clone, FieldSetter)]
16pub struct Lighting {
17 ambient: Option<f64>,
18 diffuse: Option<f64>,
19 fresnel: Option<f64>,
20 roughness: Option<f64>,
21 specular: Option<f64>,
22}
23
24impl Lighting {
25 pub fn new() -> Self {
26 Default::default()
27 }
28}
29
30#[derive(Serialize, Debug, Clone)]
31pub struct Position {
32 x: i32,
33 y: i32,
34 z: i32,
35}
36
37impl Position {
38 pub fn new(x: i32, y: i32, z: i32) -> Self {
39 Self { x, y, z }
40 }
41}
42
43#[serde_with::skip_serializing_none]
44#[derive(Serialize, Debug, FieldSetter, Clone)]
45pub struct PlaneProject {
46 x: Option<bool>,
47 y: Option<bool>,
48 z: Option<bool>,
49}
50
51impl PlaneProject {
52 pub fn new() -> Self {
53 Default::default()
54 }
55}
56
57#[serde_with::skip_serializing_none]
58#[derive(Serialize, Debug, FieldSetter, Clone)]
59pub struct PlaneContours {
60 color: Option<Box<dyn Color>>,
61 end: Option<f64>,
62 highlight: Option<bool>,
63 #[serde(rename = "highlightwidth")]
64 highlight_width: Option<usize>,
65 #[serde(rename = "highlightcolor")]
66 highlight_color: Option<Box<dyn Color>>,
67 project: Option<PlaneProject>,
68 show: Option<bool>,
69 size: Option<usize>,
70 start: Option<f64>,
71 #[serde(rename = "usecolormap")]
72 use_colormap: Option<bool>,
73 width: Option<usize>,
74}
75
76impl PlaneContours {
77 pub fn new() -> Self {
78 Default::default()
79 }
80}
81
82#[serde_with::skip_serializing_none]
83#[derive(Serialize, Debug, FieldSetter, Clone)]
84pub struct SurfaceContours {
85 x: Option<PlaneContours>,
86 y: Option<PlaneContours>,
87 z: Option<PlaneContours>,
88}
89
90impl SurfaceContours {
91 pub fn new() -> Self {
92 Default::default()
93 }
94}
95
96#[serde_with::skip_serializing_none]
114#[derive(Serialize, Debug, Clone, FieldSetter)]
115#[field_setter(box_self, kind = "trace")]
116pub struct Surface<X, Y, Z>
117where
118 X: Serialize + Clone,
119 Y: Serialize + Clone,
120 Z: Serialize + Clone,
121{
122 #[field_setter(default = "PlotType::Surface")]
123 r#type: PlotType,
124 x: Option<Vec<X>>,
125 y: Option<Vec<Y>>,
126 z: Option<Vec<Vec<Z>>>,
127 #[serde(rename = "autocolorscale")]
128 auto_color_scale: Option<bool>,
129 cauto: Option<bool>,
130 cmax: Option<f64>,
131 cmid: Option<f64>,
132 cmin: Option<f64>,
133 #[serde(rename = "colorbar")]
134 color_bar: Option<ColorBar>,
135 #[serde(rename = "colorscale")]
136 color_scale: Option<ColorScale>,
137 #[serde(rename = "connectgaps")]
138 connect_gaps: Option<bool>,
139 contours: Option<SurfaceContours>,
140 #[serde(rename = "hidesurface")]
141 hide_surface: Option<bool>,
142 #[serde(rename = "hoverinfo")]
143 hover_info: Option<HoverInfo>,
144 #[serde(rename = "hoverlabel")]
145 hover_label: Option<Label>,
146 #[serde(rename = "hovertemplate")]
147 hover_template: Option<Dim<String>>,
148 #[serde(rename = "hovertext")]
149 hover_text: Option<Dim<String>>,
150 #[serde(rename = "legendgroup")]
151 legend_group: Option<String>,
152 #[serde(rename = "legendgrouptitle")]
153 legend_group_title: Option<LegendGroupTitle>,
154 #[serde(rename = "lightposition")]
155 light_position: Option<Position>,
156 lighting: Option<Lighting>,
157 name: Option<String>,
158 opacity: Option<f64>,
159 #[serde(rename = "reversescale")]
160 reverse_scale: Option<bool>,
161 #[serde(rename = "showlegend")]
162 show_legend: Option<bool>,
163 #[serde(rename = "showscale")]
164 show_scale: Option<bool>,
165 #[serde(rename = "surfacecolor")]
166 surface_color: Option<Vec<Box<dyn Color>>>,
167 text: Option<Dim<String>>,
168 visible: Option<Visible>,
169 #[serde(rename = "xcalendar")]
170 x_calendar: Option<Calendar>,
171 #[serde(rename = "ycalendar")]
172 y_calendar: Option<Calendar>,
173 #[serde(rename = "zcalendar")]
174 z_calendar: Option<Calendar>,
175}
176
177impl<X, Y, Z> Surface<X, Y, Z>
178where
179 X: Serialize + Clone,
180 Y: Serialize + Clone,
181 Z: Serialize + Clone,
182{
183 pub fn new(z: Vec<Vec<Z>>) -> Box<Self> {
184 Box::new(Self {
185 z: Some(z),
186 ..Default::default()
187 })
188 }
189}
190
191impl<X, Y, Z> Trace for Surface<X, Y, Z>
192where
193 X: Serialize + Clone,
194 Y: Serialize + Clone,
195 Z: Serialize + Clone,
196{
197 fn to_json(&self) -> String {
198 serde_json::to_string(self).unwrap()
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use serde_json::{json, to_value};
205
206 use super::*;
207 use crate::common::ColorScalePalette;
208
209 #[test]
210 fn test_serialize_lighting() {
211 let lighting = Lighting::new()
212 .ambient(0.0)
213 .diffuse(1.0)
214 .fresnel(2.0)
215 .roughness(3.0)
216 .specular(4.0);
217
218 let expected = json!({
219 "ambient": 0.0,
220 "diffuse": 1.0,
221 "fresnel": 2.0,
222 "roughness": 3.0,
223 "specular": 4.0,
224 });
225
226 assert_eq!(to_value(lighting).unwrap(), expected);
227 }
228
229 #[test]
230 fn test_serialize_position() {
231 let position = Position::new(0, 1, 2);
232 let expected = json!({
233 "x": 0,
234 "y": 1,
235 "z": 2,
236 });
237
238 assert_eq!(to_value(position).unwrap(), expected);
239 }
240
241 #[test]
242 fn test_serialize_plane_project() {
243 let plane_project = PlaneProject::new().x(true).y(false).z(true);
244 let expected = json!({
245 "x": true,
246 "y": false,
247 "z": true,
248 });
249
250 assert_eq!(to_value(plane_project).unwrap(), expected);
251 }
252
253 #[test]
254 fn test_serialize_plane_contours() {
255 let plane_contours = PlaneContours::new()
256 .color("#123456")
257 .highlight(true)
258 .highlight_color("#456789")
259 .highlight_width(5)
260 .end(10.0)
261 .project(PlaneProject::new().x(false).y(true).z(false))
262 .show(false)
263 .size(50)
264 .start(0.0)
265 .use_colormap(true)
266 .width(100);
267
268 let expected = json!({
269 "color": "#123456",
270 "highlight": true,
271 "highlightcolor": "#456789",
272 "highlightwidth": 5,
273 "end": 10.0,
274 "project": {"x": false, "y": true, "z": false},
275 "show": false,
276 "size": 50,
277 "start": 0.0,
278 "usecolormap": true,
279 "width": 100
280 });
281
282 assert_eq!(to_value(plane_contours).unwrap(), expected);
283 }
284
285 #[test]
286 fn test_serialize_surface_contours() {
287 let surface_contours = SurfaceContours::new()
288 .x(PlaneContours::new())
289 .y(PlaneContours::new())
290 .z(PlaneContours::new());
291
292 let expected = json!({
293 "x": {},
294 "y": {},
295 "z": {},
296 });
297
298 assert_eq!(to_value(surface_contours).unwrap(), expected);
299 }
300
301 #[test]
302 fn test_serialize_default_surface() {
303 let trace = Surface::<i32, i32, i32>::default();
304 let expected = json!({"type": "surface"});
305
306 assert_eq!(to_value(trace).unwrap(), expected);
307 }
308
309 #[test]
310 fn test_serialize_surface() {
311 let trace = Surface::new(vec![vec![0, 1]])
312 .x(vec![2, 3])
313 .y(vec![4, 5])
314 .auto_color_scale(true)
315 .cauto(false)
316 .cmax(5.0)
317 .cmid(2.5)
318 .cmin(0.0)
319 .color_bar(ColorBar::new())
320 .color_scale(ColorScale::Palette(ColorScalePalette::Blues))
321 .connect_gaps(true)
322 .contours(SurfaceContours::new())
323 .hide_surface(false)
324 .hover_info(HoverInfo::All)
325 .hover_label(Label::new())
326 .hover_template("hover_template")
327 .hover_template_array(vec!["hover_template_1"])
328 .hover_text("hover_text")
329 .hover_text_array(vec!["hover_text_1"])
330 .legend_group("legend_group")
331 .legend_group_title(LegendGroupTitle::new("Legend Group Title"))
332 .lighting(Lighting::new())
333 .light_position(Position::new(0, 0, 0))
334 .name("surface_trace")
335 .opacity(0.5)
336 .reverse_scale(true)
337 .surface_color(vec!["#123456"])
338 .show_legend(true)
339 .show_scale(false)
340 .text("text")
341 .text_array(vec!["text1", "text2"])
342 .visible(Visible::False)
343 .x_calendar(Calendar::Chinese)
344 .y_calendar(Calendar::Coptic)
345 .z_calendar(Calendar::DiscWorld);
346
347 let expected = json!({
348 "type": "surface",
349 "x": [2, 3],
350 "y": [4, 5],
351 "z": [[0, 1]],
352 "autocolorscale": true,
353 "cauto": false,
354 "cmax": 5.0,
355 "cmid": 2.5,
356 "cmin": 0.0,
357 "colorbar": {},
358 "colorscale": "Blues",
359 "connectgaps": true,
360 "contours": {},
361 "hidesurface": false,
362 "hoverinfo": "all",
363 "hoverlabel": {},
364 "hovertemplate": ["hover_template_1"],
365 "hovertext": ["hover_text_1"],
366 "legendgroup": "legend_group",
367 "legendgrouptitle": {"text": "Legend Group Title"},
368 "lighting": {},
369 "lightposition": {"x": 0, "y": 0, "z": 0},
370 "name": "surface_trace",
371 "opacity": 0.5,
372 "reversescale": true,
373 "surfacecolor": ["#123456"],
374 "showlegend": true,
375 "showscale": false,
376 "text": ["text1", "text2"],
377 "visible": false,
378 "xcalendar": "chinese",
379 "ycalendar": "coptic",
380 "zcalendar": "discworld",
381 });
382
383 assert_eq!(to_value(trace).unwrap(), expected);
384 }
385}