1use super::{matrix_to_array, AsMatrix, GraphMaker, StrError};
2use crate::quote_marker;
3use num_traits::Num;
4use std::fmt::Write;
5
6pub struct Surface {
50 row_stride: usize, col_stride: usize, with_surface: bool, with_wireframe: bool, with_points: bool, colormap_name: String, with_colorbar: bool, colorbar_label: String, number_format_cb: String, surf_color: String, surf_line_color: String, surf_line_style: String, surf_line_width: f64, wire_line_color: String, wire_line_style: String, wire_line_width: f64, point_color: String, point_void: bool, point_line_color: String, point_line_width: f64, point_size: f64, point_style: String, buffer: String, }
74
75impl Surface {
76 pub fn new() -> Self {
78 Surface {
79 row_stride: 0,
80 col_stride: 0,
81 with_surface: true,
82 with_wireframe: false,
83 with_points: false,
84 colormap_name: "bwr".to_string(),
85 with_colorbar: false,
86 colorbar_label: String::new(),
87 number_format_cb: String::new(),
88 surf_color: String::new(),
89 surf_line_color: String::new(),
90 surf_line_style: String::new(),
91 surf_line_width: 0.0,
92 wire_line_color: "black".to_string(),
93 wire_line_style: String::new(),
94 wire_line_width: 0.0,
95 point_color: String::new(),
96 point_void: false,
97 point_line_color: String::new(),
98 point_line_width: 0.0,
99 point_size: 0.0,
100 point_style: String::new(),
101 buffer: String::new(),
102 }
103 }
104
105 pub fn draw<'a, T, U>(&mut self, x: &'a T, y: &'a T, z: &'a T)
120 where
121 T: AsMatrix<'a, U>,
122 U: 'a + std::fmt::Display + Num,
123 {
124 matrix_to_array(&mut self.buffer, "x", x);
125 matrix_to_array(&mut self.buffer, "y", y);
126 matrix_to_array(&mut self.buffer, "z", z);
127 if self.with_surface {
128 let opt_surface = self.options_surface();
129 write!(&mut self.buffer, "sf=ax3d().plot_surface(x,y,z{})\n", &opt_surface).unwrap();
130 }
131 if self.with_wireframe {
132 let opt_wireframe = self.options_wireframe();
133 write!(&mut self.buffer, "ax3d().plot_wireframe(x,y,z{})\n", &opt_wireframe).unwrap();
134 }
135 if self.with_points {
136 let opt_points = self.options_points();
137 write!(&mut self.buffer, "ax3d().scatter(x,y,z{})\n", &opt_points).unwrap();
138 }
139 if self.with_colorbar {
140 let opt_colorbar = self.options_colorbar();
141 write!(&mut self.buffer, "cb=plt.colorbar(sf{})\n", &opt_colorbar).unwrap();
142 if self.colorbar_label != "" {
143 write!(&mut self.buffer, "cb.ax.set_ylabel(r'{}')\n", self.colorbar_label).unwrap();
144 }
145 }
146 }
147
148 pub fn set_row_stride(&mut self, value: usize) -> &mut Self {
150 self.row_stride = value;
151 self
152 }
153
154 pub fn set_col_stride(&mut self, value: usize) -> &mut Self {
156 self.col_stride = value;
157 self
158 }
159
160 pub fn set_with_surface(&mut self, flag: bool) -> &mut Self {
162 self.with_surface = flag;
163 self
164 }
165
166 pub fn set_with_wireframe(&mut self, flag: bool) -> &mut Self {
168 self.with_wireframe = flag;
169 self
170 }
171
172 pub fn set_with_points(&mut self, flag: bool) -> &mut Self {
174 self.with_points = flag;
175 self
176 }
177
178 pub fn set_colormap_index(&mut self, index: usize) -> &mut Self {
193 const CMAP: [&str; 7] = ["bwr", "RdBu", "hsv", "jet", "terrain", "pink", "Greys"];
194 self.colormap_name = CMAP[index % 7].to_string();
195 self
196 }
197
198 pub fn set_colormap_name(&mut self, name: &str) -> &mut Self {
211 self.colormap_name = String::from(name);
212 self
213 }
214
215 pub fn set_with_colorbar(&mut self, flag: bool) -> &mut Self {
217 self.with_colorbar = flag;
218 self
219 }
220
221 pub fn set_colorbar_label(&mut self, label: &str) -> &mut Self {
223 self.colorbar_label = String::from(label);
224 self
225 }
226
227 pub fn set_number_format_cb(&mut self, format: &str) -> &mut Self {
229 self.number_format_cb = String::from(format);
230 self
231 }
232
233 pub fn set_surf_color(&mut self, color: &str) -> &mut Self {
235 self.surf_color = String::from(color);
236 self
237 }
238
239 pub fn set_surf_line_color(&mut self, color: &str) -> &mut Self {
241 self.surf_line_color = String::from(color);
242 self
243 }
244
245 pub fn set_surf_line_style(&mut self, style: &str) -> &mut Self {
251 self.surf_line_style = String::from(style);
252 self
253 }
254
255 pub fn set_surf_line_width(&mut self, width: f64) -> &mut Self {
257 self.surf_line_width = width;
258 self
259 }
260
261 pub fn set_wire_line_color(&mut self, color: &str) -> &mut Self {
265 self.wire_line_color = String::from(color);
266 self
267 }
268
269 pub fn set_wire_line_style(&mut self, style: &str) -> &mut Self {
275 self.wire_line_style = String::from(style);
276 self
277 }
278
279 pub fn set_wire_line_width(&mut self, width: f64) -> &mut Self {
281 self.wire_line_width = width;
282 self
283 }
284
285 pub fn set_point_color(&mut self, color: &str) -> &mut Self {
289 self.point_color = String::from(color);
290 self.point_void = false;
291 self
292 }
293
294 pub fn set_point_void(&mut self, flag: bool) -> &mut Self {
296 self.point_void = flag;
297 self
298 }
299
300 pub fn set_point_line_color(&mut self, color: &str) -> &mut Self {
302 self.point_line_color = String::from(color);
303 self
304 }
305
306 pub fn set_point_line_width(&mut self, width: f64) -> &mut Self {
308 self.point_line_width = width;
309 self
310 }
311
312 pub fn set_point_size(&mut self, size: f64) -> &mut Self {
314 self.point_size = size;
315 self
316 }
317
318 pub fn set_point_style(&mut self, style: &str) -> &mut Self {
325 self.point_style = String::from(style);
326 self
327 }
328
329 fn options_surface(&self) -> String {
333 let mut opt = String::new();
334 if self.row_stride > 0 {
335 write!(&mut opt, ",rstride={}", self.row_stride).unwrap();
336 }
337 if self.col_stride > 0 {
338 write!(&mut opt, ",cstride={}", self.col_stride).unwrap();
339 }
340 if self.surf_color != "" {
341 write!(&mut opt, ",color='{}'", self.surf_color).unwrap();
342 } else {
343 if self.colormap_name != "" {
344 write!(&mut opt, ",cmap=plt.get_cmap('{}')", self.colormap_name).unwrap();
345 }
346 }
347 if self.surf_line_color != "" {
348 write!(&mut opt, ",edgecolors='{}'", self.surf_line_color).unwrap();
349 }
350 if self.surf_line_style != "" {
351 write!(&mut opt, ",linestyle='{}'", self.surf_line_style).unwrap();
352 }
353 if self.surf_line_width > 0.0 {
354 write!(&mut opt, ",linewidth={}", self.surf_line_width).unwrap();
355 }
356 opt
357 }
358
359 fn options_wireframe(&self) -> String {
361 let mut opt = String::new();
362 if self.row_stride > 0 {
363 write!(&mut opt, ",rstride={}", self.row_stride).unwrap();
364 }
365 if self.col_stride > 0 {
366 write!(&mut opt, ",cstride={}", self.col_stride).unwrap();
367 }
368 if self.wire_line_color != "" {
369 write!(&mut opt, ",color='{}'", self.wire_line_color).unwrap();
370 }
371 if self.wire_line_style != "" {
372 write!(&mut opt, ",linestyle='{}'", self.wire_line_style).unwrap();
373 }
374 if self.wire_line_width > 0.0 {
375 write!(&mut opt, ",linewidth={}", self.wire_line_width).unwrap();
376 }
377 opt
378 }
379
380 fn options_points(&self) -> String {
382 let mut opt = String::new();
383 if self.row_stride > 0 {
384 write!(&mut opt, ",rstride={}", self.row_stride).unwrap();
385 }
386 if self.col_stride > 0 {
387 write!(&mut opt, ",cstride={}", self.col_stride).unwrap();
388 }
389 if self.point_line_width > 0.0 {
390 write!(&mut opt, ",linewidths={}", self.point_line_width).unwrap();
391 }
392 if self.point_size > 0.0 {
393 write!(&mut opt, ",s={}", self.point_size).unwrap();
394 }
395 if self.point_style != "" {
396 write!(&mut opt, ",marker={}", quote_marker(&self.point_style)).unwrap();
397 }
398 if self.point_void {
400 let lc = if self.point_line_color == "" {
401 "black"
402 } else {
403 self.point_line_color.as_str()
404 };
405 write!(&mut opt, ",color='none',edgecolor='{}'", lc).unwrap();
406 } else if self.point_color != "" {
407 write!(&mut opt, ",color='{}'", self.point_color).unwrap();
408 if self.point_line_color != "" {
409 write!(&mut opt, ",edgecolor='{}'", self.point_line_color).unwrap();
410 }
411 } else if self.colormap_name != "" {
412 write!(&mut opt, ",c=z,cmap=plt.get_cmap('{}')", self.colormap_name).unwrap();
413 }
414 opt
415 }
416
417 fn options_colorbar(&self) -> String {
419 let mut opt = String::new();
420 if self.number_format_cb != "" {
421 write!(&mut opt, ",format='{}'", self.number_format_cb).unwrap();
422 }
423 opt
424 }
425
426 pub(super) fn aligned_system(a: &[f64], b: &[f64]) -> Result<(Vec<f64>, Vec<f64>, Vec<f64>), StrError> {
428 let n = vec![b[0] - a[0], b[1] - a[1], b[2] - a[2]];
430 let n_dot_n = n[0] * n[0] + n[1] * n[1] + n[2] * n[2];
431 if n_dot_n <= f64::EPSILON {
432 return Err("a-to-b segment is too short");
433 }
434
435 let x = if f64::abs(n[1]) <= f64::EPSILON && f64::abs(n[2]) <= f64::EPSILON {
437 vec![n[0], n[1] + 1.0, n[2]] } else {
439 vec![n[0] + 1.0, n[1], n[2]] };
441
442 let x_dot_n = x[0] * n[0] + x[1] * n[1] + x[2] * n[2];
445 let q = vec![
446 x[0] - n[0] * x_dot_n / n_dot_n,
447 x[1] - n[1] * x_dot_n / n_dot_n,
448 x[2] - n[2] * x_dot_n / n_dot_n,
449 ];
450
451 let norm_n = f64::sqrt(n_dot_n);
453 let norm_q = f64::sqrt(q[0] * q[0] + q[1] * q[1] + q[2] * q[2]);
454 let e0 = vec![n[0] / norm_n, n[1] / norm_n, n[2] / norm_n];
455 let e1 = vec![q[0] / norm_q, q[1] / norm_q, q[2] / norm_q];
456 let e2 = vec![
457 e0[1] * e1[2] - e0[2] * e1[1],
458 e0[2] * e1[0] - e0[0] * e1[2],
459 e0[0] * e1[1] - e0[1] * e1[0],
460 ];
461 Ok((e0, e1, e2))
462 }
463}
464
465impl GraphMaker for Surface {
466 fn get_buffer<'a>(&'a self) -> &'a String {
467 &self.buffer
468 }
469 fn clear_buffer(&mut self) {
470 self.buffer.clear();
471 }
472}
473
474#[cfg(test)]
477mod tests {
478 use super::Surface;
479 use crate::GraphMaker;
480
481 #[test]
482 fn new_works() {
483 let surface = Surface::new();
484 assert_eq!(surface.row_stride, 0);
485 assert_eq!(surface.col_stride, 0);
486 assert_eq!(surface.with_surface, true);
487 assert_eq!(surface.with_wireframe, false);
488 assert_eq!(surface.colormap_name, "bwr".to_string());
489 assert_eq!(surface.with_colorbar, false);
490 assert_eq!(surface.colorbar_label.len(), 0);
491 assert_eq!(surface.number_format_cb.len(), 0);
492 assert_eq!(surface.wire_line_color, "black".to_string());
493 assert_eq!(surface.wire_line_style.len(), 0);
494 assert_eq!(surface.wire_line_width, 0.0);
495 assert_eq!(surface.buffer.len(), 0);
496 }
497
498 #[test]
499 fn options_surface_works() {
500 let mut surface = Surface::new();
501 surface.set_row_stride(3).set_col_stride(4);
502 let opt = surface.options_surface();
503 assert_eq!(opt, ",rstride=3,cstride=4,cmap=plt.get_cmap('bwr')");
504
505 surface.set_colormap_name("Pastel1");
506 let opt = surface.options_surface();
507 assert_eq!(opt, ",rstride=3,cstride=4,cmap=plt.get_cmap('Pastel1')");
508
509 surface.set_colormap_index(3);
510 let opt = surface.options_surface();
511 assert_eq!(opt, ",rstride=3,cstride=4,cmap=plt.get_cmap('jet')");
512
513 surface.set_colormap_name("turbo");
514 let opt = surface.options_surface();
515 assert_eq!(opt, ",rstride=3,cstride=4,cmap=plt.get_cmap('turbo')");
516
517 surface.set_surf_color("blue");
518 let opt = surface.options_surface();
519 assert_eq!(opt, ",rstride=3,cstride=4,color='blue'");
520
521 let mut surface = Surface::new();
522 surface
523 .set_surf_line_color("red")
524 .set_surf_line_style("--")
525 .set_surf_line_width(2.5);
526 let opt = surface.options_surface();
527 assert_eq!(
528 opt,
529 ",cmap=plt.get_cmap('bwr'),edgecolors='red',linestyle='--',linewidth=2.5"
530 );
531 }
532
533 #[test]
534 fn options_wireframe_works() {
535 let mut surface = Surface::new();
536 surface
537 .set_row_stride(3)
538 .set_col_stride(4)
539 .set_wire_line_color("red")
540 .set_wire_line_style("--")
541 .set_wire_line_width(2.5);
542 let opt = surface.options_wireframe();
543 assert_eq!(opt, ",rstride=3,cstride=4,color='red',linestyle='--',linewidth=2.5");
544 }
545
546 #[test]
547 fn options_points_works() {
548 let mut surface = Surface::new();
549 surface.set_row_stride(3).set_col_stride(4);
550 let opt = surface.options_points();
551 assert_eq!(opt, ",rstride=3,cstride=4,c=z,cmap=plt.get_cmap('bwr')");
552
553 surface.set_colormap_name("Pastel1");
554 let opt = surface.options_points();
555 assert_eq!(opt, ",rstride=3,cstride=4,c=z,cmap=plt.get_cmap('Pastel1')");
556
557 surface.set_colormap_index(3);
558 let opt = surface.options_points();
559 assert_eq!(opt, ",rstride=3,cstride=4,c=z,cmap=plt.get_cmap('jet')");
560
561 surface.set_colormap_name("turbo");
562 let opt = surface.options_points();
563 assert_eq!(opt, ",rstride=3,cstride=4,c=z,cmap=plt.get_cmap('turbo')");
564
565 let mut surface = Surface::new();
566 surface
567 .set_point_color("blue")
568 .set_point_line_color("red")
569 .set_point_size(100.0)
570 .set_point_style("*")
571 .set_point_line_width(3.0);
572 let opt = surface.options_points();
573 assert_eq!(opt, ",linewidths=3,s=100,marker='*',color='blue',edgecolor='red'");
574
575 surface.set_point_void(true);
576 let opt = surface.options_points();
577 assert_eq!(opt, ",linewidths=3,s=100,marker='*',color='none',edgecolor='red'");
578
579 surface.set_point_void(true).set_point_line_color("");
580 let opt = surface.options_points();
581 assert_eq!(opt, ",linewidths=3,s=100,marker='*',color='none',edgecolor='black'");
582 }
583
584 #[test]
585 fn options_colorbar_works() {
586 let mut surface = Surface::new();
587 surface.set_number_format_cb("%.3f");
588 let opt = surface.options_colorbar();
589 assert_eq!(opt, ",format='%.3f'");
590 }
591
592 #[test]
593 fn draw_works() {
594 let mut surface = Surface::new();
595 surface
596 .set_with_wireframe(true)
597 .set_with_colorbar(true)
598 .set_colorbar_label("temperature");
599 let x = vec![vec![-0.5, 0.0, 0.5], vec![-0.5, 0.0, 0.5], vec![-0.5, 0.0, 0.5]];
600 let y = vec![vec![-0.5, -0.5, -0.5], vec![0.0, 0.0, 0.0], vec![0.5, 0.5, 0.5]];
601 let z = vec![vec![0.50, 0.25, 0.50], vec![0.25, 0.00, 0.25], vec![0.50, 0.25, 0.50]];
602 surface.draw(&x, &y, &z);
603 let b: &str = "x=np.array([[-0.5,0,0.5,],[-0.5,0,0.5,],[-0.5,0,0.5,],],dtype=float)\n\
604 y=np.array([[-0.5,-0.5,-0.5,],[0,0,0,],[0.5,0.5,0.5,],],dtype=float)\n\
605 z=np.array([[0.5,0.25,0.5,],[0.25,0,0.25,],[0.5,0.25,0.5,],],dtype=float)\n\
606 sf=ax3d().plot_surface(x,y,z,cmap=plt.get_cmap('bwr'))\n\
607 ax3d().plot_wireframe(x,y,z,color='black')\n\
608 cb=plt.colorbar(sf)\n\
609 cb.ax.set_ylabel(r'temperature')\n";
610 assert_eq!(surface.buffer, b);
611 surface.clear_buffer();
612 assert_eq!(surface.buffer, "");
613 }
614
615 #[test]
616 fn aligned_system_fails_on_wrong_input() {
617 let res = Surface::aligned_system(&[0.0, 0.0, 0.0], &[0.0, 0.0, 0.0]);
618 assert_eq!(res.err(), Some("a-to-b segment is too short"));
619 }
620
621 fn approx_eq(a: f64, b: f64, tol: f64) {
622 let diff = f64::abs(a - b);
623 if diff > tol {
624 panic!("numbers are not approximately equal. diff = {:?}", diff);
625 }
626 }
627
628 #[test]
629 #[should_panic(expected = "numbers are not approximately equal. diff = 1.0")]
630 fn approx_eq_captures_errors() {
631 approx_eq(1.0, 2.0, 1e-15);
632 }
633
634 #[test]
635 fn aligned_system_works() {
636 let (e0, e1, e2) = Surface::aligned_system(&[-1.0, 0.0, 0.0], &[8.0, 0.0, 0.0]).unwrap();
637 assert_eq!(e0, &[1.0, 0.0, 0.0]);
638 assert_eq!(e1, &[0.0, 1.0, 0.0]);
639 assert_eq!(e2, &[0.0, 0.0, 1.0]);
640
641 let (e0, e1, e2) = Surface::aligned_system(&[0.0, -3.0, 0.0], &[0.0, 3.0, 0.0]).unwrap();
642 assert_eq!(e0, &[0.0, 1.0, 0.0]);
643 assert_eq!(e1, &[1.0, 0.0, 0.0]);
644 assert_eq!(e2, &[0.0, 0.0, -1.0]);
645
646 let (e0, e1, e2) = Surface::aligned_system(&[0.0, 10.0, 0.0], &[0.0, 3.0, 0.0]).unwrap();
647 assert_eq!(e0, &[0.0, -1.0, 0.0]);
648 assert_eq!(e1, &[1.0, 0.0, 0.0]);
649 assert_eq!(e2, &[0.0, 0.0, 1.0]);
650
651 let (e0, e1, e2) = Surface::aligned_system(&[0.0, 0.0, 80.0], &[0.0, 0.0, 7770.0]).unwrap();
652 assert_eq!(e0, &[0.0, 0.0, 1.0]);
653 assert_eq!(e1, &[1.0, 0.0, 0.0]);
654 assert_eq!(e2, &[0.0, 1.0, 0.0]);
655
656 let (m, n, l) = (3.0, 4.0, 5.0);
657 let (e0, e1, e2) = Surface::aligned_system(&[2.0, -7.0, 5.0], &[2.0 + m, -7.0 + n, 5.0]).unwrap();
658 let correct0 = &[m / l, n / l, 0.0];
659 let correct1 = &[n / l, -m / l, 0.0];
660 let correct2 = &[0.0, 0.0, -1.0];
661 for i in 0..3 {
662 approx_eq(e0[i], correct0[i], 1e-15);
663 approx_eq(e1[i], correct1[i], 1e-15);
664 approx_eq(e2[i], correct2[i], 1e-15);
665 }
666
667 let s = f64::sqrt(2.0) / 2.0;
668 let (e0, e1, e2) = Surface::aligned_system(&[0.0, 0.0, 1.0], &[1.0, 0.0, 2.0]).unwrap();
669 let correct0 = &[s, 0.0, s];
670 let correct1 = &[s, 0.0, -s];
671 let correct2 = &[0.0, 1.0, 0.0];
672 for i in 0..3 {
673 approx_eq(e0[i], correct0[i], 1e-15);
674 approx_eq(e1[i], correct1[i], 1e-15);
675 approx_eq(e2[i], correct2[i], 1e-15);
676 }
677
678 let (c, d, e) = (1.0 / f64::sqrt(3.0), 1.0 / f64::sqrt(6.0), 1.0 / f64::sqrt(2.0));
679 let (e0, e1, e2) = Surface::aligned_system(&[3.0, 4.0, 5.0], &[13.0, 14.0, 15.0]).unwrap();
680 let correct0 = &[c, c, c];
681 let correct1 = &[2.0 * d, -d, -d];
682 let correct2 = &[0.0, e, -e];
683 for i in 0..3 {
684 approx_eq(e0[i], correct0[i], 1e-15);
685 approx_eq(e1[i], correct1[i], 1e-15);
686 approx_eq(e2[i], correct2[i], 1e-15);
687 }
688 }
689}