use super::{matrix_to_array, AsMatrix, GraphMaker, StrError};
use crate::quote_marker;
use std::fmt::Write;
pub struct Surface {
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, }
impl Surface {
pub fn new() -> Self {
Surface {
row_stride: 0,
col_stride: 0,
with_surface: true,
with_wireframe: false,
with_points: false,
colormap_name: "bwr".to_string(),
with_colorbar: false,
colorbar_label: String::new(),
number_format_cb: String::new(),
surf_color: String::new(),
surf_line_color: String::new(),
surf_line_style: String::new(),
surf_line_width: 0.0,
wire_line_color: "black".to_string(),
wire_line_style: String::new(),
wire_line_width: 0.0,
point_color: String::new(),
point_void: false,
point_line_color: String::new(),
point_line_width: 0.0,
point_size: 0.0,
point_style: String::new(),
buffer: String::new(),
}
}
pub fn draw<'a, T, U>(&mut self, x: &'a T, y: &'a T, z: &'a T)
where
T: AsMatrix<'a, U>,
U: 'a + std::fmt::Display,
{
matrix_to_array(&mut self.buffer, "x", x);
matrix_to_array(&mut self.buffer, "y", y);
matrix_to_array(&mut self.buffer, "z", z);
if self.with_surface {
let opt_surface = self.options_surface();
write!(&mut self.buffer, "sf=ax3d().plot_surface(x,y,z{})\n", &opt_surface).unwrap();
}
if self.with_wireframe {
let opt_wireframe = self.options_wireframe();
write!(&mut self.buffer, "ax3d().plot_wireframe(x,y,z{})\n", &opt_wireframe).unwrap();
}
if self.with_points {
let opt_points = self.options_points();
write!(&mut self.buffer, "ax3d().scatter(x,y,z{})\n", &opt_points).unwrap();
}
if self.with_colorbar {
let opt_colorbar = self.options_colorbar();
write!(&mut self.buffer, "cb=plt.colorbar(sf{})\n", &opt_colorbar).unwrap();
if self.colorbar_label != "" {
write!(&mut self.buffer, "cb.ax.set_ylabel(r'{}')\n", self.colorbar_label).unwrap();
}
}
}
pub fn set_row_stride(&mut self, value: usize) -> &mut Self {
self.row_stride = value;
self
}
pub fn set_col_stride(&mut self, value: usize) -> &mut Self {
self.col_stride = value;
self
}
pub fn set_with_surface(&mut self, flag: bool) -> &mut Self {
self.with_surface = flag;
self
}
pub fn set_with_wireframe(&mut self, flag: bool) -> &mut Self {
self.with_wireframe = flag;
self
}
pub fn set_with_points(&mut self, flag: bool) -> &mut Self {
self.with_points = flag;
self
}
pub fn set_colormap_index(&mut self, index: usize) -> &mut Self {
const CMAP: [&str; 7] = ["bwr", "RdBu", "hsv", "jet", "terrain", "pink", "Greys"];
self.colormap_name = CMAP[index % 7].to_string();
self
}
pub fn set_colormap_name(&mut self, name: &str) -> &mut Self {
self.colormap_name = String::from(name);
self
}
pub fn set_with_colorbar(&mut self, flag: bool) -> &mut Self {
self.with_colorbar = flag;
self
}
pub fn set_colorbar_label(&mut self, label: &str) -> &mut Self {
self.colorbar_label = String::from(label);
self
}
pub fn set_number_format_cb(&mut self, format: &str) -> &mut Self {
self.number_format_cb = String::from(format);
self
}
pub fn set_surf_color(&mut self, color: &str) -> &mut Self {
self.surf_color = String::from(color);
self
}
pub fn set_surf_line_color(&mut self, color: &str) -> &mut Self {
self.surf_line_color = String::from(color);
self
}
pub fn set_surf_line_style(&mut self, style: &str) -> &mut Self {
self.surf_line_style = String::from(style);
self
}
pub fn set_surf_line_width(&mut self, width: f64) -> &mut Self {
self.surf_line_width = width;
self
}
pub fn set_wire_line_color(&mut self, color: &str) -> &mut Self {
self.wire_line_color = String::from(color);
self
}
pub fn set_wire_line_style(&mut self, style: &str) -> &mut Self {
self.wire_line_style = String::from(style);
self
}
pub fn set_wire_line_width(&mut self, width: f64) -> &mut Self {
self.wire_line_width = width;
self
}
pub fn set_point_color(&mut self, color: &str) -> &mut Self {
self.point_color = String::from(color);
self.point_void = false;
self
}
pub fn set_point_void(&mut self, flag: bool) -> &mut Self {
self.point_void = flag;
self
}
pub fn set_point_line_color(&mut self, color: &str) -> &mut Self {
self.point_line_color = String::from(color);
self
}
pub fn set_point_line_width(&mut self, width: f64) -> &mut Self {
self.point_line_width = width;
self
}
pub fn set_point_size(&mut self, size: f64) -> &mut Self {
self.point_size = size;
self
}
pub fn set_point_style(&mut self, style: &str) -> &mut Self {
self.point_style = String::from(style);
self
}
fn options_surface(&self) -> String {
let mut opt = String::new();
if self.row_stride > 0 {
write!(&mut opt, ",rstride={}", self.row_stride).unwrap();
}
if self.col_stride > 0 {
write!(&mut opt, ",cstride={}", self.col_stride).unwrap();
}
if self.surf_color != "" {
write!(&mut opt, ",color='{}'", self.surf_color).unwrap();
} else {
if self.colormap_name != "" {
write!(&mut opt, ",cmap=plt.get_cmap('{}')", self.colormap_name).unwrap();
}
}
if self.surf_line_color != "" {
write!(&mut opt, ",edgecolors='{}'", self.surf_line_color).unwrap();
}
if self.surf_line_style != "" {
write!(&mut opt, ",linestyle='{}'", self.surf_line_style).unwrap();
}
if self.surf_line_width > 0.0 {
write!(&mut opt, ",linewidth={}", self.surf_line_width).unwrap();
}
opt
}
fn options_wireframe(&self) -> String {
let mut opt = String::new();
if self.row_stride > 0 {
write!(&mut opt, ",rstride={}", self.row_stride).unwrap();
}
if self.col_stride > 0 {
write!(&mut opt, ",cstride={}", self.col_stride).unwrap();
}
if self.wire_line_color != "" {
write!(&mut opt, ",color='{}'", self.wire_line_color).unwrap();
}
if self.wire_line_style != "" {
write!(&mut opt, ",linestyle='{}'", self.wire_line_style).unwrap();
}
if self.wire_line_width > 0.0 {
write!(&mut opt, ",linewidth={}", self.wire_line_width).unwrap();
}
opt
}
fn options_points(&self) -> String {
let mut opt = String::new();
if self.row_stride > 0 {
write!(&mut opt, ",rstride={}", self.row_stride).unwrap();
}
if self.col_stride > 0 {
write!(&mut opt, ",cstride={}", self.col_stride).unwrap();
}
if self.point_line_width > 0.0 {
write!(&mut opt, ",linewidths={}", self.point_line_width).unwrap();
}
if self.point_size > 0.0 {
write!(&mut opt, ",s={}", self.point_size).unwrap();
}
if self.point_style != "" {
write!(&mut opt, ",marker={}", quote_marker(&self.point_style)).unwrap();
}
if self.point_void {
let lc = if self.point_line_color == "" {
"black"
} else {
self.point_line_color.as_str()
};
write!(&mut opt, ",color='none',edgecolor='{}'", lc).unwrap();
} else if self.point_color != "" {
write!(&mut opt, ",color='{}'", self.point_color).unwrap();
if self.point_line_color != "" {
write!(&mut opt, ",edgecolor='{}'", self.point_line_color).unwrap();
}
} else if self.colormap_name != "" {
write!(&mut opt, ",c=z,cmap=plt.get_cmap('{}')", self.colormap_name).unwrap();
}
opt
}
fn options_colorbar(&self) -> String {
let mut opt = String::new();
if self.number_format_cb != "" {
write!(&mut opt, ",format='{}'", self.number_format_cb).unwrap();
}
opt
}
pub(super) fn aligned_system(a: &[f64], b: &[f64]) -> Result<(Vec<f64>, Vec<f64>, Vec<f64>), StrError> {
let n = vec![b[0] - a[0], b[1] - a[1], b[2] - a[2]];
let n_dot_n = n[0] * n[0] + n[1] * n[1] + n[2] * n[2];
if n_dot_n <= f64::EPSILON {
return Err("a-to-b segment is too short");
}
let x = if f64::abs(n[1]) <= f64::EPSILON && f64::abs(n[2]) <= f64::EPSILON {
vec![n[0], n[1] + 1.0, n[2]] } else {
vec![n[0] + 1.0, n[1], n[2]] };
let x_dot_n = x[0] * n[0] + x[1] * n[1] + x[2] * n[2];
let q = vec![
x[0] - n[0] * x_dot_n / n_dot_n,
x[1] - n[1] * x_dot_n / n_dot_n,
x[2] - n[2] * x_dot_n / n_dot_n,
];
let norm_n = f64::sqrt(n_dot_n);
let norm_q = f64::sqrt(q[0] * q[0] + q[1] * q[1] + q[2] * q[2]);
let e0 = vec![n[0] / norm_n, n[1] / norm_n, n[2] / norm_n];
let e1 = vec![q[0] / norm_q, q[1] / norm_q, q[2] / norm_q];
let e2 = vec![
e0[1] * e1[2] - e0[2] * e1[1],
e0[2] * e1[0] - e0[0] * e1[2],
e0[0] * e1[1] - e0[1] * e1[0],
];
Ok((e0, e1, e2))
}
}
impl GraphMaker for Surface {
fn get_buffer<'a>(&'a self) -> &'a String {
&self.buffer
}
fn clear_buffer(&mut self) {
self.buffer.clear();
}
}
#[cfg(test)]
mod tests {
use super::Surface;
use crate::GraphMaker;
#[test]
fn new_works() {
let surface = Surface::new();
assert_eq!(surface.row_stride, 0);
assert_eq!(surface.col_stride, 0);
assert_eq!(surface.with_surface, true);
assert_eq!(surface.with_wireframe, false);
assert_eq!(surface.colormap_name, "bwr".to_string());
assert_eq!(surface.with_colorbar, false);
assert_eq!(surface.colorbar_label.len(), 0);
assert_eq!(surface.number_format_cb.len(), 0);
assert_eq!(surface.wire_line_color, "black".to_string());
assert_eq!(surface.wire_line_style.len(), 0);
assert_eq!(surface.wire_line_width, 0.0);
assert_eq!(surface.buffer.len(), 0);
}
#[test]
fn options_surface_works() {
let mut surface = Surface::new();
surface.set_row_stride(3).set_col_stride(4);
let opt = surface.options_surface();
assert_eq!(opt, ",rstride=3,cstride=4,cmap=plt.get_cmap('bwr')");
surface.set_colormap_name("Pastel1");
let opt = surface.options_surface();
assert_eq!(opt, ",rstride=3,cstride=4,cmap=plt.get_cmap('Pastel1')");
surface.set_colormap_index(3);
let opt = surface.options_surface();
assert_eq!(opt, ",rstride=3,cstride=4,cmap=plt.get_cmap('jet')");
surface.set_colormap_name("turbo");
let opt = surface.options_surface();
assert_eq!(opt, ",rstride=3,cstride=4,cmap=plt.get_cmap('turbo')");
surface.set_surf_color("blue");
let opt = surface.options_surface();
assert_eq!(opt, ",rstride=3,cstride=4,color='blue'");
let mut surface = Surface::new();
surface
.set_surf_line_color("red")
.set_surf_line_style("--")
.set_surf_line_width(2.5);
let opt = surface.options_surface();
assert_eq!(
opt,
",cmap=plt.get_cmap('bwr'),edgecolors='red',linestyle='--',linewidth=2.5"
);
}
#[test]
fn options_wireframe_works() {
let mut surface = Surface::new();
surface
.set_row_stride(3)
.set_col_stride(4)
.set_wire_line_color("red")
.set_wire_line_style("--")
.set_wire_line_width(2.5);
let opt = surface.options_wireframe();
assert_eq!(opt, ",rstride=3,cstride=4,color='red',linestyle='--',linewidth=2.5");
}
#[test]
fn options_points_works() {
let mut surface = Surface::new();
surface.set_row_stride(3).set_col_stride(4);
let opt = surface.options_points();
assert_eq!(opt, ",rstride=3,cstride=4,c=z,cmap=plt.get_cmap('bwr')");
surface.set_colormap_name("Pastel1");
let opt = surface.options_points();
assert_eq!(opt, ",rstride=3,cstride=4,c=z,cmap=plt.get_cmap('Pastel1')");
surface.set_colormap_index(3);
let opt = surface.options_points();
assert_eq!(opt, ",rstride=3,cstride=4,c=z,cmap=plt.get_cmap('jet')");
surface.set_colormap_name("turbo");
let opt = surface.options_points();
assert_eq!(opt, ",rstride=3,cstride=4,c=z,cmap=plt.get_cmap('turbo')");
let mut surface = Surface::new();
surface
.set_point_color("blue")
.set_point_line_color("red")
.set_point_size(100.0)
.set_point_style("*")
.set_point_line_width(3.0);
let opt = surface.options_points();
assert_eq!(opt, ",linewidths=3,s=100,marker='*',color='blue',edgecolor='red'");
surface.set_point_void(true);
let opt = surface.options_points();
assert_eq!(opt, ",linewidths=3,s=100,marker='*',color='none',edgecolor='red'");
surface.set_point_void(true).set_point_line_color("");
let opt = surface.options_points();
assert_eq!(opt, ",linewidths=3,s=100,marker='*',color='none',edgecolor='black'");
}
#[test]
fn options_colorbar_works() {
let mut surface = Surface::new();
surface.set_number_format_cb("%.3f");
let opt = surface.options_colorbar();
assert_eq!(opt, ",format='%.3f'");
}
#[test]
fn draw_works() {
let mut surface = Surface::new();
surface
.set_with_wireframe(true)
.set_with_colorbar(true)
.set_colorbar_label("temperature");
let x = vec![vec![-0.5, 0.0, 0.5], vec![-0.5, 0.0, 0.5], vec![-0.5, 0.0, 0.5]];
let y = vec![vec![-0.5, -0.5, -0.5], vec![0.0, 0.0, 0.0], vec![0.5, 0.5, 0.5]];
let z = vec![vec![0.50, 0.25, 0.50], vec![0.25, 0.00, 0.25], vec![0.50, 0.25, 0.50]];
surface.draw(&x, &y, &z);
let b: &str = "x=np.array([[-0.5,0,0.5,],[-0.5,0,0.5,],[-0.5,0,0.5,],],dtype=float)\n\
y=np.array([[-0.5,-0.5,-0.5,],[0,0,0,],[0.5,0.5,0.5,],],dtype=float)\n\
z=np.array([[0.5,0.25,0.5,],[0.25,0,0.25,],[0.5,0.25,0.5,],],dtype=float)\n\
sf=ax3d().plot_surface(x,y,z,cmap=plt.get_cmap('bwr'))\n\
ax3d().plot_wireframe(x,y,z,color='black')\n\
cb=plt.colorbar(sf)\n\
cb.ax.set_ylabel(r'temperature')\n";
assert_eq!(surface.buffer, b);
surface.clear_buffer();
assert_eq!(surface.buffer, "");
}
#[test]
fn aligned_system_fails_on_wrong_input() {
let res = Surface::aligned_system(&[0.0, 0.0, 0.0], &[0.0, 0.0, 0.0]);
assert_eq!(res.err(), Some("a-to-b segment is too short"));
}
fn approx_eq(a: f64, b: f64, tol: f64) {
let diff = f64::abs(a - b);
if diff > tol {
panic!("numbers are not approximately equal. diff = {:?}", diff);
}
}
#[test]
#[should_panic(expected = "numbers are not approximately equal. diff = 1.0")]
fn approx_eq_captures_errors() {
approx_eq(1.0, 2.0, 1e-15);
}
#[test]
fn aligned_system_works() {
let (e0, e1, e2) = Surface::aligned_system(&[-1.0, 0.0, 0.0], &[8.0, 0.0, 0.0]).unwrap();
assert_eq!(e0, &[1.0, 0.0, 0.0]);
assert_eq!(e1, &[0.0, 1.0, 0.0]);
assert_eq!(e2, &[0.0, 0.0, 1.0]);
let (e0, e1, e2) = Surface::aligned_system(&[0.0, -3.0, 0.0], &[0.0, 3.0, 0.0]).unwrap();
assert_eq!(e0, &[0.0, 1.0, 0.0]);
assert_eq!(e1, &[1.0, 0.0, 0.0]);
assert_eq!(e2, &[0.0, 0.0, -1.0]);
let (e0, e1, e2) = Surface::aligned_system(&[0.0, 10.0, 0.0], &[0.0, 3.0, 0.0]).unwrap();
assert_eq!(e0, &[0.0, -1.0, 0.0]);
assert_eq!(e1, &[1.0, 0.0, 0.0]);
assert_eq!(e2, &[0.0, 0.0, 1.0]);
let (e0, e1, e2) = Surface::aligned_system(&[0.0, 0.0, 80.0], &[0.0, 0.0, 7770.0]).unwrap();
assert_eq!(e0, &[0.0, 0.0, 1.0]);
assert_eq!(e1, &[1.0, 0.0, 0.0]);
assert_eq!(e2, &[0.0, 1.0, 0.0]);
let (m, n, l) = (3.0, 4.0, 5.0);
let (e0, e1, e2) = Surface::aligned_system(&[2.0, -7.0, 5.0], &[2.0 + m, -7.0 + n, 5.0]).unwrap();
let correct0 = &[m / l, n / l, 0.0];
let correct1 = &[n / l, -m / l, 0.0];
let correct2 = &[0.0, 0.0, -1.0];
for i in 0..3 {
approx_eq(e0[i], correct0[i], 1e-15);
approx_eq(e1[i], correct1[i], 1e-15);
approx_eq(e2[i], correct2[i], 1e-15);
}
let s = f64::sqrt(2.0) / 2.0;
let (e0, e1, e2) = Surface::aligned_system(&[0.0, 0.0, 1.0], &[1.0, 0.0, 2.0]).unwrap();
let correct0 = &[s, 0.0, s];
let correct1 = &[s, 0.0, -s];
let correct2 = &[0.0, 1.0, 0.0];
for i in 0..3 {
approx_eq(e0[i], correct0[i], 1e-15);
approx_eq(e1[i], correct1[i], 1e-15);
approx_eq(e2[i], correct2[i], 1e-15);
}
let (c, d, e) = (1.0 / f64::sqrt(3.0), 1.0 / f64::sqrt(6.0), 1.0 / f64::sqrt(2.0));
let (e0, e1, e2) = Surface::aligned_system(&[3.0, 4.0, 5.0], &[13.0, 14.0, 15.0]).unwrap();
let correct0 = &[c, c, c];
let correct1 = &[2.0 * d, -d, -d];
let correct2 = &[0.0, e, -e];
for i in 0..3 {
approx_eq(e0[i], correct0[i], 1e-15);
approx_eq(e1[i], correct1[i], 1e-15);
approx_eq(e2[i], correct2[i], 1e-15);
}
}
}