plotly_fork/traces/
surface.rs

1//! Surface trace
2
3use 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/// Construct a surface trace.
97///
98/// # Examples
99///
100/// ```
101/// use plotly::Surface;
102///
103/// let trace = Surface::new(vec![vec![0, 1]]).x(vec![1, 2]).y(vec![2, 3]);
104/// let expected = serde_json::json!({
105///     "type": "surface",
106///     "x": [1, 2],
107///     "y": [2, 3],
108///     "z": [[0, 1]],
109/// });
110///
111/// assert_eq!(serde_json::to_value(trace).unwrap(), expected);
112/// ```
113#[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}