use super::{GraphMaker, StrError};
use crate::conversions::{matrix_to_array, vector_to_array};
use crate::{AsMatrix, AsVector};
use num_traits::Num;
use std::fmt::Write;
#[derive(Clone, Copy, Debug)]
pub enum PolyCode {
MoveTo,
LineTo,
Curve3,
Curve4,
}
pub struct Canvas {
edge_color: String, face_color: String, line_width: f64, line_style: String, arrow_scale: f64, arrow_style: String,
text_color: String, text_align_horizontal: String, text_align_vertical: String, text_fontsize: f64, text_rotation: f64,
alt_text_color: String, alt_text_align_horizontal: String, alt_text_align_vertical: String, alt_text_fontsize: f64, alt_text_rotation: f64,
stop_clip: bool, shading: bool,
glyph_line_width: f64, glyph_size: f64, glyph_color_x: String, glyph_color_y: String, glyph_color_z: String, glyph_label_x: String, glyph_label_y: String, glyph_label_z: String, glyph_label_color: String, glyph_bbox_opt: String,
buffer: String, }
impl Canvas {
pub fn new() -> Self {
Canvas {
edge_color: "#427ce5".to_string(),
face_color: String::new(),
line_width: 0.0,
line_style: String::new(),
arrow_scale: 0.0,
arrow_style: String::new(),
text_color: "#343434".to_string(),
text_align_horizontal: "center".to_string(),
text_align_vertical: "center".to_string(),
text_fontsize: 10.0,
text_rotation: 0.0,
alt_text_color: "#a81414".to_string(),
alt_text_align_horizontal: String::new(),
alt_text_align_vertical: String::new(),
alt_text_fontsize: 8.0,
alt_text_rotation: 45.0,
stop_clip: false,
shading: true,
glyph_line_width: 2.0,
glyph_size: 1.0,
glyph_color_x: "red".to_string(),
glyph_color_y: "green".to_string(),
glyph_color_z: "blue".to_string(),
glyph_label_x: "X".to_string(),
glyph_label_y: "Y".to_string(),
glyph_label_z: "Z".to_string(),
glyph_label_color: String::new(),
glyph_bbox_opt: "boxstyle='circle,pad=0.1',facecolor='white',edgecolor='None'".to_string(),
buffer: String::new(),
}
}
pub fn draw_arc<T>(&mut self, xc: T, yc: T, r: T, ini_angle: T, fin_angle: T)
where
T: std::fmt::Display + Num,
{
let opt = self.options_shared();
write!(
&mut self.buffer,
"p=pat.Arc(({},{}),2*{},2*{},theta1={},theta2={},angle=0{})\n\
plt.gca().add_patch(p)\n",
xc, yc, r, r, ini_angle, fin_angle, &opt
)
.unwrap();
}
pub fn draw_arrow<T>(&mut self, xi: T, yi: T, xf: T, yf: T)
where
T: std::fmt::Display + Num,
{
let opt_shared = self.options_shared();
let opt_arrow = self.options_arrow();
write!(
&mut self.buffer,
"p=pat.FancyArrowPatch(({},{}),({},{})\
,shrinkA=0,shrinkB=0\
,path_effects=[pff.Stroke(joinstyle='miter')]\
{}{})\n\
plt.gca().add_patch(p)\n",
xi, yi, xf, yf, &opt_shared, &&opt_arrow,
)
.unwrap();
}
pub fn draw_circle<T>(&mut self, xc: T, yc: T, r: T)
where
T: std::fmt::Display + Num,
{
let opt = self.options_shared();
write!(
&mut self.buffer,
"p=pat.Circle(({},{}),{}{})\n\
plt.gca().add_patch(p)\n",
xc, yc, r, &opt
)
.unwrap();
}
pub fn draw_triangles<'a, T, U, C>(&mut self, xx: &'a T, yy: &'a T, connectivity: &'a C) -> &mut Self
where
T: AsVector<'a, U>,
U: 'a + std::fmt::Display + Num,
C: AsMatrix<'a, usize>,
{
vector_to_array(&mut self.buffer, "xx", xx);
vector_to_array(&mut self.buffer, "yy", yy);
matrix_to_array(&mut self.buffer, "triangles", connectivity);
let opt = self.options_triangles();
write!(&mut self.buffer, "plt.triplot(xx,yy,triangles{})\n", &opt).unwrap();
self
}
pub fn draw_triangles_3d<'a, T, U, C>(&mut self, xx: &'a T, yy: &'a T, zz: &'a T, connectivity: &'a C) -> &mut Self
where
T: AsVector<'a, U>,
U: 'a + std::fmt::Display + Num,
C: AsMatrix<'a, usize>,
{
vector_to_array(&mut self.buffer, "xx", xx);
vector_to_array(&mut self.buffer, "yy", yy);
vector_to_array(&mut self.buffer, "zz", zz);
matrix_to_array(&mut self.buffer, "triangles", connectivity);
let opt = self.options_triangles_3d();
let shade = if self.shading { "True" } else { "False" };
write!(
&mut self.buffer,
"poly_collection=ax3d().plot_trisurf(xx,yy,zz,triangles=triangles,shade={}{})\n",
shade, &opt
)
.unwrap();
if self.face_color != "" {
write!(
&mut self.buffer,
"colors=np.array(['{}']*len(triangles))\n\
poly_collection.set_facecolor(colors)\n",
self.face_color
)
.unwrap();
}
self
}
pub fn polycurve_begin(&mut self) -> &mut Self {
write!(&mut self.buffer, "dat=[",).unwrap();
self
}
pub fn polycurve_add<T>(&mut self, x: T, y: T, code: PolyCode) -> &mut Self
where
T: std::fmt::Display + Num,
{
let keyword = match code {
PolyCode::MoveTo => "MOVETO",
PolyCode::LineTo => "LINETO",
PolyCode::Curve3 => "CURVE3",
PolyCode::Curve4 => "CURVE4",
};
write!(&mut self.buffer, "[pth.Path.{},({},{})],", keyword, x, y).unwrap();
self
}
pub fn polycurve_end(&mut self, closed: bool) -> &mut Self {
if closed {
write!(&mut self.buffer, "[pth.Path.CLOSEPOLY,(None,None)]").unwrap();
}
let opt = self.options_shared();
write!(
&mut self.buffer,
"]\n\
cmd,pts=zip(*dat)\n\
h=pth.Path(pts,cmd)\n\
p=pat.PathPatch(h{})\n\
plt.gca().add_patch(p)\n",
&opt
)
.unwrap();
self
}
pub fn draw_polycurve<'a, T, U>(&mut self, points: &'a T, codes: &[PolyCode], closed: bool) -> Result<(), StrError>
where
T: AsMatrix<'a, U>,
U: 'a + std::fmt::Display,
{
let (npoint, ndim) = points.size();
if npoint < 3 {
return Err("npoint must be ≥ 3");
}
if ndim != 2 {
return Err("ndim must be equal to 2");
}
if codes.len() != npoint {
return Err("codes.len() must be equal to npoint");
}
write!(
&mut self.buffer,
"dat=[[pth.Path.MOVETO,({},{})]",
points.at(0, 0),
points.at(0, 1)
)
.unwrap();
for i in 1..npoint {
let keyword = match codes[i] {
PolyCode::MoveTo => "MOVETO",
PolyCode::LineTo => "LINETO",
PolyCode::Curve3 => "CURVE3",
PolyCode::Curve4 => "CURVE4",
};
write!(
&mut self.buffer,
",[pth.Path.{},({},{})]",
keyword,
points.at(i, 0),
points.at(i, 1)
)
.unwrap();
}
if closed {
write!(&mut self.buffer, ",[pth.Path.CLOSEPOLY,(None,None)]").unwrap();
}
let opt = self.options_shared();
write!(
&mut self.buffer,
"]\n\
cmd,pts=zip(*dat)\n\
h=pth.Path(pts,cmd)\n\
p=pat.PathPatch(h{})\n\
plt.gca().add_patch(p)\n",
&opt
)
.unwrap();
Ok(())
}
pub fn polyline_3d_begin(&mut self) -> &mut Self {
write!(&mut self.buffer, "xyz=np.array([").unwrap();
self
}
pub fn polyline_3d_add<T>(&mut self, x: T, y: T, z: T) -> &mut Self
where
T: std::fmt::Display + Num,
{
write!(&mut self.buffer, "[{},{},{}],", x, y, z).unwrap();
self
}
pub fn polyline_3d_end(&mut self) -> &mut Self {
let opt = self.options_line_3d();
write!(
&mut self.buffer,
"])\nax3d().plot(xyz[:,0],xyz[:,1],xyz[:,2]{})\n",
&opt
)
.unwrap();
self
}
pub fn draw_polyline<'a, T, U>(&mut self, points: &'a T, closed: bool)
where
T: AsMatrix<'a, U>,
U: 'a + std::fmt::Display + Num,
{
let (npoint, ndim) = points.size();
if npoint < 2 {
return;
}
if ndim == 2 {
write!(
&mut self.buffer,
"dat=[[pth.Path.MOVETO,({},{})]",
points.at(0, 0),
points.at(0, 1)
)
.unwrap();
for i in 1..npoint {
write!(
&mut self.buffer,
",[pth.Path.LINETO,({},{})]",
points.at(i, 0),
points.at(i, 1)
)
.unwrap();
}
if closed {
write!(&mut self.buffer, ",[pth.Path.CLOSEPOLY,(None,None)]").unwrap();
}
let opt = self.options_shared();
write!(
&mut self.buffer,
"]\n\
cmd,pts=zip(*dat)\n\
h=pth.Path(pts,cmd)\n\
p=pat.PathPatch(h{})\n\
plt.gca().add_patch(p)\n",
&opt
)
.unwrap();
}
if ndim == 3 {
self.polyline_3d_begin();
for i in 0..npoint {
self.polyline_3d_add(points.at(i, 0), points.at(i, 1), points.at(i, 2));
}
if closed && npoint > 2 {
self.polyline_3d_add(points.at(0, 0), points.at(0, 1), points.at(0, 2));
}
self.polyline_3d_end();
}
}
pub fn draw_rectangle<T>(&mut self, x: T, y: T, width: T, height: T) -> &mut Self
where
T: std::fmt::Display + Num,
{
let opt = self.options_shared();
write!(
&mut self.buffer,
"p=pat.Rectangle(({},{}),{},{}{})\n\
plt.gca().add_patch(p)\n",
x, y, width, height, &opt
)
.unwrap();
self
}
pub fn draw_text<T>(&mut self, x: T, y: T, label: &str) -> &mut Self
where
T: std::fmt::Display + Num,
{
self.text(2, &[x, y, T::zero()], label, false);
self
}
pub fn draw_alt_text<T>(&mut self, x: T, y: T, label: &str) -> &mut Self
where
T: std::fmt::Display + Num,
{
self.text(2, &[x, y, T::zero()], label, true);
self
}
pub fn draw_glyph_3d<T>(&mut self, x: T, y: T, z: T) -> &mut Self
where
T: std::fmt::Display + Num,
{
let size = self.glyph_size;
let lx = &self.glyph_label_x;
let ly = &self.glyph_label_y;
let lz = &self.glyph_label_z;
let lw = self.glyph_line_width;
let r = &self.glyph_color_x;
let g = &self.glyph_color_y;
let b = &self.glyph_color_z;
let tr = if self.glyph_label_color == "" {
&self.glyph_color_x
} else {
&self.glyph_label_color
};
let tg = if self.glyph_label_color == "" {
&self.glyph_color_y
} else {
&self.glyph_label_color
};
let tb = if self.glyph_label_color == "" {
&self.glyph_color_z
} else {
&self.glyph_label_color
};
write!(
&mut self.buffer,
"plt.gca().plot([{x},{x}+{size}],[{y},{y}],[{z},{z}],color='{r}',linewidth={lw})\n\
plt.gca().plot([{x},{x}],[{y},{y}+{size}],[{z},{z}],color='{g}',linewidth={lw})\n\
plt.gca().plot([{x},{x}],[{y},{y}],[{z},{z}+{size}],color='{b}',linewidth={lw})\n\
tx=plt.gca().text({x}+{size},{y},{z},'{lx}',color='{tr}',ha='center',va='center')\n\
ty=plt.gca().text({x},{y}+{size},{z},'{ly}',color='{tg}',ha='center',va='center')\n\
tz=plt.gca().text({x},{y},{z}+{size},'{lz}',color='{tb}',ha='center',va='center')\n"
)
.unwrap();
if self.glyph_bbox_opt != "" {
write!(
&mut self.buffer,
"tx.set_bbox(dict({}))\n\
ty.set_bbox(dict({}))\n\
tz.set_bbox(dict({}))\n",
self.glyph_bbox_opt, self.glyph_bbox_opt, self.glyph_bbox_opt
)
.unwrap();
}
self
}
pub fn draw_grid(
&mut self,
xmin: &[f64],
xmax: &[f64],
ndiv: &[usize],
with_point_ids: bool,
with_cell_ids: bool,
) -> Result<(), StrError> {
let ndim = ndiv.len();
if ndim < 2 || ndim > 3 {
return Err("len(ndiv) == ndim must be 2 or 3");
}
if xmin.len() != ndim {
return Err("size of xmin must equal ndim == len(ndiv)");
}
if xmax.len() != ndim {
return Err("size of xmax must equal ndim == len(ndiv)");
}
let mut npoint = [1; 3];
let mut delta = [0.0; 3];
for i in 0..ndim {
npoint[i] = ndiv[i] + 1;
delta[i] = xmax[i] - xmin[i];
if delta[i] <= 0.0 {
return Err("xmax must be greater than xmin");
}
delta[i] /= ndiv[i] as f64;
}
let mut a = [0.0; 3];
let mut b = [0.0; 3];
if ndim == 2 {
write!(&mut self.buffer, "dat=[\n").unwrap();
}
let opt = self.options_shared();
let mut id_point = 0;
for k in 0..npoint[2] {
if ndim == 3 {
a[2] = xmin[2] + delta[2] * (k as f64);
b[2] = a[2];
}
a[1] = xmin[1];
b[1] = xmax[1];
for i in 0..npoint[0] {
a[0] = xmin[0] + delta[0] * (i as f64);
b[0] = a[0];
self.line(ndim, &a, &b);
}
a[0] = xmin[0];
b[0] = xmax[0];
for j in 0..npoint[1] {
a[1] = xmin[1] + delta[1] * (j as f64);
b[1] = a[1];
self.line(ndim, &a, &b);
}
if ndim == 2 {
write!(
&mut self.buffer,
"]\n\
cmd,pts=zip(*dat)\n\
h=pth.Path(pts,cmd)\n\
p=pat.PathPatch(h{})\n\
plt.gca().add_patch(p)\n",
&opt
)
.unwrap();
}
if with_point_ids {
for j in 0..npoint[1] {
a[1] = xmin[1] + delta[1] * (j as f64);
for i in 0..npoint[0] {
a[0] = xmin[0] + delta[0] * (i as f64);
let txt = format!("{}", id_point);
self.text(ndim, &a, &txt, true);
id_point += 1;
}
}
}
}
if with_cell_ids {
let mut id_cell = 0;
let nz = if ndim == 2 { 1 } else { ndiv[2] };
for k in 0..nz {
if ndim == 3 {
a[2] = xmin[2] + delta[2] * (k as f64);
b[2] = a[2] + delta[2] / 2.0;
}
for j in 0..ndiv[1] {
a[1] = xmin[1] + delta[1] * (j as f64);
b[1] = a[1] + delta[1] / 2.0;
for i in 0..ndiv[0] {
a[0] = xmin[0] + delta[0] * (i as f64);
b[0] = a[0] + delta[0] / 2.0;
let txt = format!("{}", id_cell);
self.text(ndim, &b, &txt, false);
id_cell += 1;
}
}
}
}
if ndim == 3 {
a[2] = xmin[2];
b[2] = xmax[2];
for j in 0..npoint[1] {
a[1] = xmin[1] + delta[1] * (j as f64);
b[1] = a[1];
for i in 0..npoint[0] {
a[0] = xmin[0] + delta[0] * (i as f64);
b[0] = a[0];
self.line(ndim, &a, &b);
}
}
}
self.limits(ndim, xmin, xmax);
Ok(())
}
pub fn set_edge_color(&mut self, color: &str) -> &mut Self {
self.edge_color = String::from(color);
self
}
pub fn set_face_color(&mut self, color: &str) -> &mut Self {
self.face_color = String::from(color);
self
}
pub fn set_line_width(&mut self, width: f64) -> &mut Self {
self.line_width = width;
self
}
pub fn set_line_style(&mut self, style: &str) -> &mut Self {
self.line_style = String::from(style);
self
}
pub fn set_arrow_scale(&mut self, scale: f64) -> &mut Self {
self.arrow_scale = scale;
self
}
pub fn set_arrow_style(&mut self, style: &str) -> &mut Self {
self.arrow_style = String::from(style);
self
}
pub fn set_text_color(&mut self, color: &str) -> &mut Self {
self.text_color = String::from(color);
self
}
pub fn set_text_align_horizontal(&mut self, option: &str) -> &mut Self {
self.text_align_horizontal = String::from(option);
self
}
pub fn set_text_align_vertical(&mut self, option: &str) -> &mut Self {
self.text_align_vertical = String::from(option);
self
}
pub fn set_text_fontsize(&mut self, fontsize: f64) -> &mut Self {
self.text_fontsize = fontsize;
self
}
pub fn set_text_rotation(&mut self, rotation: f64) -> &mut Self {
self.text_rotation = rotation;
self
}
pub fn set_alt_text_color(&mut self, color: &str) -> &mut Self {
self.alt_text_color = String::from(color);
self
}
pub fn set_alt_text_align_horizontal(&mut self, option: &str) -> &mut Self {
self.alt_text_align_horizontal = String::from(option);
self
}
pub fn set_alt_text_align_vertical(&mut self, option: &str) -> &mut Self {
self.alt_text_align_vertical = String::from(option);
self
}
pub fn set_alt_text_fontsize(&mut self, fontsize: f64) -> &mut Self {
self.alt_text_fontsize = fontsize;
self
}
pub fn set_alt_text_rotation(&mut self, rotation: f64) -> &mut Self {
self.alt_text_rotation = rotation;
self
}
pub fn set_stop_clip(&mut self, flag: bool) -> &mut Self {
self.stop_clip = flag;
self
}
pub fn set_shading(&mut self, flag: bool) -> &mut Self {
self.shading = flag;
self
}
pub fn set_glyph_line_width(&mut self, width: f64) -> &mut Self {
self.glyph_line_width = width;
self
}
pub fn set_glyph_size(&mut self, size: f64) -> &mut Self {
self.glyph_size = size;
self
}
pub fn set_glyph_color_x(&mut self, color: &str) -> &mut Self {
self.glyph_color_x = String::from(color);
self
}
pub fn set_glyph_color_y(&mut self, color: &str) -> &mut Self {
self.glyph_color_y = String::from(color);
self
}
pub fn set_glyph_color_z(&mut self, color: &str) -> &mut Self {
self.glyph_color_z = String::from(color);
self
}
pub fn set_glyph_label_x(&mut self, label: &str) -> &mut Self {
self.glyph_label_x = String::from(label);
self
}
pub fn set_glyph_label_y(&mut self, label: &str) -> &mut Self {
self.glyph_label_y = String::from(label);
self
}
pub fn set_glyph_label_z(&mut self, label: &str) -> &mut Self {
self.glyph_label_z = String::from(label);
self
}
pub fn set_glyph_label_color(&mut self, label_clr: &str) -> &mut Self {
self.glyph_label_color = String::from(label_clr);
self
}
pub fn set_glyph_bbox(&mut self, bbox_dict: &str) -> &mut Self {
self.glyph_bbox_opt = String::from(bbox_dict);
self
}
fn options_triangles(&self) -> String {
let mut opt = String::new();
if self.edge_color != "" {
write!(&mut opt, ",color='{}'", self.edge_color).unwrap();
}
if self.line_width > 0.0 {
write!(&mut opt, ",linewidth={}", self.line_width).unwrap();
}
if self.line_style != "" {
write!(&mut opt, ",linestyle='{}'", self.line_style).unwrap();
}
if self.stop_clip {
write!(&mut opt, ",clip_on=False").unwrap();
}
opt
}
fn options_triangles_3d(&self) -> String {
let mut opt = String::new();
if self.edge_color != "" {
write!(&mut opt, ",edgecolor='{}'", self.edge_color).unwrap();
}
if self.line_width > 0.0 {
write!(&mut opt, ",linewidth={}", self.line_width).unwrap();
}
if self.line_style != "" {
write!(&mut opt, ",linestyle='{}'", self.line_style).unwrap();
}
if self.stop_clip {
write!(&mut opt, ",clip_on=False").unwrap();
}
opt
}
fn options_shared(&self) -> String {
let mut opt = String::new();
if self.edge_color != "" {
write!(&mut opt, ",edgecolor='{}'", self.edge_color).unwrap();
}
if self.face_color != "" {
write!(&mut opt, ",facecolor='{}'", self.face_color).unwrap();
}
if self.line_width > 0.0 {
write!(&mut opt, ",linewidth={}", self.line_width).unwrap();
}
if self.line_style != "" {
write!(&mut opt, ",linestyle='{}'", self.line_style).unwrap();
}
if self.stop_clip {
write!(&mut opt, ",clip_on=False").unwrap();
}
opt
}
fn options_arrow(&self) -> String {
let mut opt = String::new();
if self.arrow_scale > 0.0 {
write!(&mut opt, ",mutation_scale={}", self.arrow_scale).unwrap();
}
if self.arrow_style != "" {
write!(&mut opt, ",arrowstyle='{}'", self.arrow_style).unwrap();
}
opt
}
fn options_text(&self) -> String {
let mut opt = String::new();
if self.text_color != "" {
write!(&mut opt, ",color='{}'", self.text_color).unwrap();
}
if self.text_align_horizontal != "" {
write!(&mut opt, ",ha='{}'", self.text_align_horizontal).unwrap();
}
if self.text_align_vertical != "" {
write!(&mut opt, ",va='{}'", self.text_align_vertical).unwrap();
}
if self.text_fontsize > 0.0 {
write!(&mut opt, ",fontsize={}", self.text_fontsize).unwrap();
}
if self.text_rotation > 0.0 {
write!(&mut opt, ",rotation={}", self.text_rotation).unwrap();
}
opt
}
fn options_alt_text(&self) -> String {
let mut opt = String::new();
if self.alt_text_color != "" {
write!(&mut opt, ",color='{}'", self.alt_text_color).unwrap();
}
if self.alt_text_align_horizontal != "" {
write!(&mut opt, ",ha='{}'", self.alt_text_align_horizontal).unwrap();
}
if self.alt_text_align_vertical != "" {
write!(&mut opt, ",va='{}'", self.alt_text_align_vertical).unwrap();
}
if self.alt_text_fontsize > 0.0 {
write!(&mut opt, ",fontsize={}", self.alt_text_fontsize).unwrap();
}
if self.alt_text_rotation > 0.0 {
write!(&mut opt, ",rotation={}", self.alt_text_rotation).unwrap();
}
opt
}
fn options_line_3d(&self) -> String {
let mut opt = String::new();
if self.edge_color != "" {
write!(&mut opt, ",color='{}'", self.edge_color).unwrap();
}
if self.line_width > 0.0 {
write!(&mut opt, ",linewidth={}", self.line_width).unwrap();
}
if self.line_style != "" {
write!(&mut opt, ",linestyle='{}'", self.line_style).unwrap();
}
opt
}
fn line<T>(&mut self, ndim: usize, a: &[T; 3], b: &[T; 3])
where
T: std::fmt::Display,
{
if ndim == 2 {
write!(
&mut self.buffer,
" [pth.Path.MOVETO,({},{})],[pth.Path.LINETO,({},{})],\n",
a[0], a[1], b[0], b[1]
)
.unwrap();
} else {
let opt = self.options_line_3d();
write!(
&mut self.buffer,
"ax3d().plot([{},{}],[{},{}],[{},{}]{})\n",
a[0], b[0], a[1], b[1], a[2], b[2], opt,
)
.unwrap();
}
}
fn text<T>(&mut self, ndim: usize, a: &[T; 3], txt: &str, alternative: bool)
where
T: std::fmt::Display,
{
let opt = if alternative {
self.options_alt_text()
} else {
self.options_text()
};
if ndim == 2 {
write!(&mut self.buffer, "plt.text({},{},'{}'{})\n", a[0], a[1], txt, &opt).unwrap();
} else {
write!(
&mut self.buffer,
"ax3d().text({},{},{},'{}'{})\n",
a[0], a[1], a[2], txt, &opt
)
.unwrap();
}
}
fn limits(&mut self, ndim: usize, xmin: &[f64], xmax: &[f64]) {
const FACTOR: f64 = 0.1;
let mut gap = [0.0; 3];
for i in 0..ndim {
gap[i] = (xmax[i] - xmin[i]) * FACTOR;
}
if ndim == 2 {
write!(
&mut self.buffer,
"plt.axis([{},{},{},{}])\n",
xmin[0] - gap[0],
xmax[0] + gap[0],
xmin[1] - gap[1],
xmax[1] + gap[1]
)
.unwrap();
} else {
write!(
&mut self.buffer,
"ax3d().set_xlim3d({},{})\n\
ax3d().set_ylim3d({},{})\n\
ax3d().set_zlim3d({},{})\n",
xmin[0] - gap[0],
xmax[0] + gap[0],
xmin[1] - gap[1],
xmax[1] + gap[1],
xmin[2] - gap[2],
xmax[2] + gap[2]
)
.unwrap();
}
}
}
impl GraphMaker for Canvas {
fn get_buffer<'a>(&'a self) -> &'a String {
&self.buffer
}
fn clear_buffer(&mut self) {
self.buffer.clear();
}
}
#[cfg(test)]
mod tests {
use super::Canvas;
use crate::{GraphMaker, PolyCode};
#[test]
fn derive_works() {
let code = PolyCode::Curve3;
let clone = code.clone();
let correct = "Curve3";
assert_eq!(format!("{:?}", code), correct);
assert_eq!(format!("{:?}", clone), correct);
}
#[test]
fn new_works() {
let canvas = Canvas::new();
assert_eq!(canvas.edge_color.len(), 7);
assert_eq!(canvas.face_color.len(), 0);
assert_eq!(canvas.line_width, 0.0);
assert_eq!(canvas.line_style.len(), 0);
assert_eq!(canvas.arrow_scale, 0.0);
assert_eq!(canvas.arrow_style.len(), 0);
assert_eq!(canvas.buffer.len(), 0);
}
#[test]
fn options_shared_works() {
let mut canvas = Canvas::new();
canvas
.set_edge_color("red")
.set_face_color("blue")
.set_line_width(2.5)
.set_line_style("--")
.set_stop_clip(true);
let opt = canvas.options_shared();
assert_eq!(
opt,
",edgecolor='red'\
,facecolor='blue'\
,linewidth=2.5\
,linestyle='--'\
,clip_on=False"
);
}
#[test]
fn options_arrow_works() {
let mut canvas = Canvas::new();
canvas.set_arrow_scale(25.0).set_arrow_style("fancy");
let opt = canvas.options_arrow();
assert_eq!(
opt,
",mutation_scale=25\
,arrowstyle='fancy'"
);
}
#[test]
fn options_text_works() {
let mut canvas = Canvas::new();
canvas
.set_text_color("red")
.set_text_align_horizontal("center")
.set_text_align_vertical("center")
.set_text_fontsize(8.0)
.set_text_rotation(45.0);
let opt = canvas.options_text();
assert_eq!(
opt,
",color='red'\
,ha='center'\
,va='center'\
,fontsize=8\
,rotation=45"
);
}
#[test]
fn options_alt_text_works() {
let mut canvas = Canvas::new();
canvas
.set_alt_text_color("blue")
.set_alt_text_align_horizontal("right")
.set_alt_text_align_vertical("bottom")
.set_alt_text_fontsize(10.0)
.set_alt_text_rotation(30.0);
let opt = canvas.options_alt_text();
assert_eq!(
opt,
",color='blue'\
,ha='right'\
,va='bottom'\
,fontsize=10\
,rotation=30"
);
}
#[test]
fn options_line_3d_works() {
let mut canvas = Canvas::new();
canvas.set_edge_color("red");
let opt = canvas.options_line_3d();
assert_eq!(opt, ",color='red'");
let mut canvas = Canvas::new();
canvas.set_edge_color("red").set_line_width(5.0).set_line_style(":");
let opt = canvas.options_line_3d();
assert_eq!(opt, ",color='red',linewidth=5,linestyle=':'");
}
#[test]
fn glyph_setters_work() {
let mut canvas = Canvas::new();
canvas
.set_glyph_line_width(4.5)
.set_glyph_size(2.0)
.set_glyph_color_x("orange")
.set_glyph_color_y("cyan")
.set_glyph_color_z("magenta")
.set_glyph_label_x("Ux")
.set_glyph_label_y("Uy")
.set_glyph_label_z("Uz");
assert_eq!(canvas.glyph_line_width, 4.5);
assert_eq!(canvas.glyph_size, 2.0);
assert_eq!(canvas.glyph_color_x, "orange");
assert_eq!(canvas.glyph_color_y, "cyan");
assert_eq!(canvas.glyph_color_z, "magenta");
assert_eq!(canvas.glyph_label_x, "Ux");
assert_eq!(canvas.glyph_label_y, "Uy");
assert_eq!(canvas.glyph_label_z, "Uz");
}
#[test]
fn line_works() {
let mut canvas = Canvas::new();
let a = [0.0; 3];
let b = [0.0; 3];
canvas.line(2, &a, &b);
canvas.line(3, &a, &b);
assert_eq!(
canvas.buffer,
"\x20\x20\x20\x20[pth.Path.MOVETO,(0,0)],[pth.Path.LINETO,(0,0)],\n\
ax3d().plot([0,0],[0,0],[0,0],color='#427ce5')\n"
);
canvas.clear_buffer();
assert_eq!(canvas.buffer, "");
}
#[test]
fn text_works() {
let mut canvas = Canvas::new();
let a = [0.0; 3];
canvas.text(2, &a, "hello", true);
canvas.text(3, &a, "hello", false);
assert_eq!(
canvas.buffer,
"plt.text(0,0,'hello',color='#a81414',fontsize=8,rotation=45)\n\
ax3d().text(0,0,0,'hello',color='#343434',ha='center',va='center',fontsize=10)\n"
);
}
#[test]
fn limits_works() {
let mut canvas = Canvas::new();
let xmin = [0.0; 3];
let xmax = [0.0; 3];
canvas.limits(2, &xmin, &xmax);
canvas.limits(3, &xmin, &xmax);
assert_eq!(
canvas.buffer,
"plt.axis([0,0,0,0])\n\
ax3d().set_xlim3d(0,0)\n\
ax3d().set_ylim3d(0,0)\n\
ax3d().set_zlim3d(0,0)\n"
);
}
#[test]
fn arc_works() {
let mut canvas = Canvas::new();
canvas.draw_arc(0.0, 0.0, 1.0, 30.0, 60.0);
let b: &str = "p=pat.Arc((0,0),2*1,2*1,theta1=30,theta2=60,angle=0,edgecolor='#427ce5')\n\
plt.gca().add_patch(p)\n";
assert_eq!(canvas.buffer, b);
}
#[test]
fn arrow_woks() {
let mut canvas = Canvas::new();
canvas.draw_arrow(0.0, 0.0, 1.0, 1.0);
let b: &str =
"p=pat.FancyArrowPatch((0,0),(1,1),shrinkA=0,shrinkB=0,path_effects=[pff.Stroke(joinstyle='miter')],edgecolor='#427ce5')\n\
plt.gca().add_patch(p)\n";
assert_eq!(canvas.buffer, b);
}
#[test]
fn circle_works() {
let mut canvas = Canvas::new();
canvas.draw_circle(0.0, 0.0, 1.0);
let b: &str = "p=pat.Circle((0,0),1,edgecolor='#427ce5')\n\
plt.gca().add_patch(p)\n";
assert_eq!(canvas.buffer, b);
}
#[test]
fn polycurve_methods_work() {
let mut canvas = Canvas::new();
canvas.polycurve_begin();
assert_eq!(canvas.buffer, "dat=[");
canvas.polycurve_add(0, 0, PolyCode::MoveTo);
assert_eq!(canvas.buffer, "dat=[[pth.Path.MOVETO,(0,0)],");
canvas.polycurve_add(1, 0, PolyCode::LineTo);
assert_eq!(canvas.buffer, "dat=[[pth.Path.MOVETO,(0,0)],[pth.Path.LINETO,(1,0)],");
canvas.polycurve_add(2, 0, PolyCode::Curve3);
assert_eq!(
canvas.buffer,
"dat=[[pth.Path.MOVETO,(0,0)],[pth.Path.LINETO,(1,0)],[pth.Path.CURVE3,(2,0)],"
);
canvas.polycurve_add(3, 0, PolyCode::Curve4);
assert_eq!(
canvas.buffer,
"dat=[[pth.Path.MOVETO,(0,0)],[pth.Path.LINETO,(1,0)],[pth.Path.CURVE3,(2,0)],[pth.Path.CURVE4,(3,0)],"
);
canvas.polycurve_end(true);
assert_eq!(
canvas.buffer,
"dat=[[pth.Path.MOVETO,(0,0)],[pth.Path.LINETO,(1,0)],[pth.Path.CURVE3,(2,0)],[pth.Path.CURVE4,(3,0)],[pth.Path.CLOSEPOLY,(None,None)]]\n\
cmd,pts=zip(*dat)\n\
h=pth.Path(pts,cmd)\n\
p=pat.PathPatch(h,edgecolor='#427ce5')\n\
plt.gca().add_patch(p)\n"
);
}
#[test]
fn polycurve_capture_errors() {
let mut canvas = Canvas::new();
assert_eq!(
canvas.draw_polycurve(&[[0, 0]], &[PolyCode::MoveTo], true).err(),
Some("npoint must be ≥ 3")
);
assert_eq!(
canvas
.draw_polycurve(
&[[0], [0], [0]],
&[PolyCode::MoveTo, PolyCode::LineTo, PolyCode::LineTo],
true
)
.err(),
Some("ndim must be equal to 2")
);
assert_eq!(
canvas
.draw_polycurve(&[[0, 0], [0, 0], [0, 0]], &[PolyCode::MoveTo], true)
.err(),
Some("codes.len() must be equal to npoint")
);
}
#[test]
fn polycurve_works() {
let mut canvas = Canvas::new();
let points = &[[0, 0], [1, 0], [1, 1]];
let codes = &[PolyCode::MoveTo, PolyCode::Curve3, PolyCode::Curve3];
canvas.draw_polycurve(points, codes, true).unwrap();
let b: &str = "dat=[[pth.Path.MOVETO,(0,0)],[pth.Path.CURVE3,(1,0)],[pth.Path.CURVE3,(1,1)],[pth.Path.CLOSEPOLY,(None,None)]]\n\
cmd,pts=zip(*dat)\n\
h=pth.Path(pts,cmd)\n\
p=pat.PathPatch(h,edgecolor='#427ce5')\n\
plt.gca().add_patch(p)\n";
assert_eq!(canvas.buffer, b);
}
#[test]
fn polyline_works_2d() {
let mut canvas = Canvas::new();
let points = &[[1.0, 1.0], [2.0, 1.0], [1.5, 1.866]];
canvas.draw_polyline(points, true);
let b: &str = "dat=[[pth.Path.MOVETO,(1,1)],[pth.Path.LINETO,(2,1)],[pth.Path.LINETO,(1.5,1.866)],[pth.Path.CLOSEPOLY,(None,None)]]\n\
cmd,pts=zip(*dat)\n\
h=pth.Path(pts,cmd)\n\
p=pat.PathPatch(h,edgecolor='#427ce5')\n\
plt.gca().add_patch(p)\n";
assert_eq!(canvas.buffer, b);
}
#[test]
fn polyline_3d_methods_work() {
let mut canvas = Canvas::new();
canvas
.polyline_3d_begin()
.polyline_3d_add(1, 2, 3)
.polyline_3d_add(4, 5, 6)
.polyline_3d_end();
let b: &str = "\
xyz=np.array([[1,2,3],[4,5,6],])\n\
ax3d().plot(xyz[:,0],xyz[:,1],xyz[:,2],color='#427ce5')\n";
assert_eq!(canvas.buffer, b);
}
#[test]
fn polyline_works_3d() {
let mut nothing = Canvas::new();
nothing.draw_polyline(&[[0.0, 0.0]], true);
assert_eq!(nothing.buffer, "");
#[rustfmt::skip]
let points = &[
[2.0, 1.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 1.0, 3.0],
[2.0, 1.0, 3.0],
];
let mut open = Canvas::new();
open.draw_polyline(points, false);
let b: &str = "\
xyz=np.array([[2,1,0],[0,1,0],[0,1,3],[2,1,3],])\n\
ax3d().plot(xyz[:,0],xyz[:,1],xyz[:,2],color='#427ce5')\n";
assert_eq!(open.buffer, b);
let mut closed = Canvas::new();
closed.draw_polyline(points, true);
let b: &str = "\
xyz=np.array([[2,1,0],[0,1,0],[0,1,3],[2,1,3],[2,1,0],])\n\
ax3d().plot(xyz[:,0],xyz[:,1],xyz[:,2],color='#427ce5')\n";
assert_eq!(closed.buffer, b);
#[rustfmt::skip]
let points = &[
[2.0, 1.0, 0.0],
[0.0, 1.0, 0.0],
];
let mut closed_few_points = Canvas::new();
closed_few_points.draw_polyline(points, true);
let b: &str = "\
xyz=np.array([[2,1,0],[0,1,0],])\n\
ax3d().plot(xyz[:,0],xyz[:,1],xyz[:,2],color='#427ce5')\n";
assert_eq!(closed_few_points.buffer, b);
}
#[test]
fn grid_fails_on_wrong_input() {
let mut canvas = Canvas::new();
let res = canvas.draw_grid(&[0.0, 0.0], &[1.0, 1.0], &[1], true, false);
assert_eq!(res, Err("len(ndiv) == ndim must be 2 or 3"));
let res = canvas.draw_grid(&[0.0], &[1.0, 1.0], &[1, 1], true, false);
assert_eq!(res, Err("size of xmin must equal ndim == len(ndiv)"));
let res = canvas.draw_grid(&[0.0, 0.0], &[1.0], &[1, 1], true, false);
assert_eq!(res, Err("size of xmax must equal ndim == len(ndiv)"));
let res = canvas.draw_grid(&[0.0, 0.0], &[0.0, 1.0], &[1, 1], true, false);
assert_eq!(res, Err("xmax must be greater than xmin"));
}
#[test]
fn grid_no_ids_works() {
let mut canvas = Canvas::new();
canvas
.draw_grid(&[0.0, 0.0], &[1.0, 1.0], &[1, 1], false, false)
.unwrap();
let b: &str = "dat=[\n\
\x20\x20\x20\x20[pth.Path.MOVETO,(0,0)],[pth.Path.LINETO,(0,1)],\n\
\x20\x20\x20\x20[pth.Path.MOVETO,(1,0)],[pth.Path.LINETO,(1,1)],\n\
\x20\x20\x20\x20[pth.Path.MOVETO,(0,0)],[pth.Path.LINETO,(1,0)],\n\
\x20\x20\x20\x20[pth.Path.MOVETO,(0,1)],[pth.Path.LINETO,(1,1)],\n\
]\n\
cmd,pts=zip(*dat)\n\
h=pth.Path(pts,cmd)\n\
p=pat.PathPatch(h,edgecolor='#427ce5')\n\
plt.gca().add_patch(p)\n\
plt.axis([-0.1,1.1,-0.1,1.1])\n";
assert_eq!(canvas.buffer, b);
}
#[test]
fn grid_2d_works() {
let mut canvas = Canvas::new();
canvas.draw_grid(&[0.0, 0.0], &[1.0, 1.0], &[1, 1], true, true).unwrap();
let b: &str = "dat=[\n\
\x20\x20\x20\x20[pth.Path.MOVETO,(0,0)],[pth.Path.LINETO,(0,1)],\n\
\x20\x20\x20\x20[pth.Path.MOVETO,(1,0)],[pth.Path.LINETO,(1,1)],\n\
\x20\x20\x20\x20[pth.Path.MOVETO,(0,0)],[pth.Path.LINETO,(1,0)],\n\
\x20\x20\x20\x20[pth.Path.MOVETO,(0,1)],[pth.Path.LINETO,(1,1)],\n\
]\n\
cmd,pts=zip(*dat)\n\
h=pth.Path(pts,cmd)\n\
p=pat.PathPatch(h,edgecolor='#427ce5')\n\
plt.gca().add_patch(p)\n\
plt.text(0,0,'0',color='#a81414',fontsize=8,rotation=45)\n\
plt.text(1,0,'1',color='#a81414',fontsize=8,rotation=45)\n\
plt.text(0,1,'2',color='#a81414',fontsize=8,rotation=45)\n\
plt.text(1,1,'3',color='#a81414',fontsize=8,rotation=45)\n\
plt.text(0.5,0.5,'0',color='#343434',ha='center',va='center',fontsize=10)\n\
plt.axis([-0.1,1.1,-0.1,1.1])\n";
assert_eq!(canvas.buffer, b);
}
#[test]
fn grid_3d_works() {
let mut canvas = Canvas::new();
canvas
.draw_grid(&[0.0, 0.0, 0.0], &[1.0, 1.0, 1.0], &[1, 1, 1], true, true)
.unwrap();
let b: &str = "\
ax3d().plot([0,0],[0,1],[0,0],color='#427ce5')\n\
ax3d().plot([1,1],[0,1],[0,0],color='#427ce5')\n\
ax3d().plot([0,1],[0,0],[0,0],color='#427ce5')\n\
ax3d().plot([0,1],[1,1],[0,0],color='#427ce5')\n\
ax3d().text(0,0,0,'0',color='#a81414',fontsize=8,rotation=45)\n\
ax3d().text(1,0,0,'1',color='#a81414',fontsize=8,rotation=45)\n\
ax3d().text(0,1,0,'2',color='#a81414',fontsize=8,rotation=45)\n\
ax3d().text(1,1,0,'3',color='#a81414',fontsize=8,rotation=45)\n\
ax3d().plot([0,0],[0,1],[1,1],color='#427ce5')\n\
ax3d().plot([1,1],[0,1],[1,1],color='#427ce5')\n\
ax3d().plot([0,1],[0,0],[1,1],color='#427ce5')\n\
ax3d().plot([0,1],[1,1],[1,1],color='#427ce5')\n\
ax3d().text(0,0,1,'4',color='#a81414',fontsize=8,rotation=45)\n\
ax3d().text(1,0,1,'5',color='#a81414',fontsize=8,rotation=45)\n\
ax3d().text(0,1,1,'6',color='#a81414',fontsize=8,rotation=45)\n\
ax3d().text(1,1,1,'7',color='#a81414',fontsize=8,rotation=45)\n\
ax3d().text(0.5,0.5,0.5,'0',color='#343434',ha='center',va='center',fontsize=10)\n\
ax3d().plot([0,0],[0,0],[0,1],color='#427ce5')\n\
ax3d().plot([1,1],[0,0],[0,1],color='#427ce5')\n\
ax3d().plot([0,0],[1,1],[0,1],color='#427ce5')\n\
ax3d().plot([1,1],[1,1],[0,1],color='#427ce5')\n\
ax3d().set_xlim3d(-0.1,1.1)\n\
ax3d().set_ylim3d(-0.1,1.1)\n\
ax3d().set_zlim3d(-0.1,1.1)\n";
assert_eq!(canvas.buffer, b);
}
}