Skip to content

Commit

Permalink
Convert AX3D to a function ax3d(). Prepare for subplot3d
Browse files Browse the repository at this point in the history
  • Loading branch information
cpmech committed Mar 21, 2024
1 parent 481d8d5 commit 3bc88ab
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 90 deletions.
97 changes: 50 additions & 47 deletions src/canvas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ impl Canvas {
/// This function must be followed by [Canvas::polyline_3d_add] and [Canvas::polyline_3d_end],
/// otherwise Python/Matplotlib will fail
pub fn polyline_3d_begin(&mut self) -> &mut Self {
write!(&mut self.buffer, "maybe_create_ax3d()\nxyz=np.array([").unwrap();
write!(&mut self.buffer, "xyz=np.array([").unwrap();
self
}

Expand All @@ -375,7 +375,12 @@ impl Canvas {
/// otherwise Python/Matplotlib will fail.
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();
write!(
&mut self.buffer,
"])\nax3d().plot(xyz[:,0],xyz[:,1],xyz[:,2]{})\n",
&opt
)
.unwrap();
self
}

Expand Down Expand Up @@ -478,8 +483,6 @@ impl Canvas {
// loop over lines
if ndim == 2 {
write!(&mut self.buffer, "dat=[\n").unwrap();
} else {
write!(&mut self.buffer, "maybe_create_ax3d()\n").unwrap();
}
let opt = self.options_shared();
let mut id_point = 0;
Expand Down Expand Up @@ -799,7 +802,7 @@ impl Canvas {
let opt = self.options_line_3d();
write!(
&mut self.buffer,
"AX3D.plot([{},{}],[{},{}],[{},{}]{})\n",
"ax3d().plot([{},{}],[{},{}],[{},{}]{})\n",
a[0], b[0], a[1], b[1], a[2], b[2], opt,
)
.unwrap();
Expand All @@ -818,7 +821,7 @@ impl Canvas {
} else {
write!(
&mut self.buffer,
"AX3D.text({},{},{},'{}'{})\n",
"ax3d().text({},{},{},'{}'{})\n",
a[0], a[1], a[2], txt, &opt
)
.unwrap();
Expand All @@ -845,9 +848,9 @@ impl Canvas {
} else {
write!(
&mut self.buffer,
"AX3D.set_xlim3d({},{})\n\
AX3D.set_ylim3d({},{})\n\
AX3D.set_zlim3d({},{})\n",
"ax3d().set_xlim3d({},{})\n\
ax3d().set_ylim3d({},{})\n\
ax3d().set_zlim3d({},{})\n",
xmin[0] - gap[0],
xmax[0] + gap[0],
xmin[1] - gap[1],
Expand Down Expand Up @@ -994,7 +997,7 @@ mod tests {
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"
ax3d().plot([0,0],[0,0],[0,0],color='#427ce5')\n"
);
canvas.clear_buffer();
assert_eq!(canvas.buffer, "");
Expand All @@ -1009,7 +1012,7 @@ mod tests {
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"
ax3d().text(0,0,0,'hello',color='#343434',ha='center',va='center',fontsize=10)\n"
);
}

Expand All @@ -1023,9 +1026,9 @@ mod tests {
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"
ax3d().set_xlim3d(0,0)\n\
ax3d().set_ylim3d(0,0)\n\
ax3d().set_zlim3d(0,0)\n"
);
}

Expand Down Expand Up @@ -1148,9 +1151,9 @@ mod tests {
.polyline_3d_add(1, 2, 3)
.polyline_3d_add(4, 5, 6)
.polyline_3d_end();
let b: &str = "maybe_create_ax3d()\n\
let b: &str = "\
xyz=np.array([[1,2,3],[4,5,6],])\n\
AX3D.plot(xyz[:,0],xyz[:,1],xyz[:,2],color='#427ce5')\n";
ax3d().plot(xyz[:,0],xyz[:,1],xyz[:,2],color='#427ce5')\n";
assert_eq!(canvas.buffer, b);
}

Expand All @@ -1170,16 +1173,16 @@ mod tests {

let mut open = Canvas::new();
open.draw_polyline(points, false);
let b: &str = "maybe_create_ax3d()\n\
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";
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 = "maybe_create_ax3d()\n\
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";
ax3d().plot(xyz[:,0],xyz[:,1],xyz[:,2],color='#427ce5')\n";
assert_eq!(closed.buffer, b);

#[rustfmt::skip]
Expand All @@ -1190,9 +1193,9 @@ mod tests {

let mut closed_few_points = Canvas::new();
closed_few_points.draw_polyline(points, true);
let b: &str = "maybe_create_ax3d()\n\
let b: &str = "\
xyz=np.array([[2,1,0],[0,1,0],])\n\
AX3D.plot(xyz[:,0],xyz[:,1],xyz[:,2],color='#427ce5')\n";
ax3d().plot(xyz[:,0],xyz[:,1],xyz[:,2],color='#427ce5')\n";
assert_eq!(closed_few_points.buffer, b);
}

Expand Down Expand Up @@ -1258,31 +1261,31 @@ mod tests {
canvas
.draw_grid(&[0.0, 0.0, 0.0], &[1.0, 1.0, 1.0], &[1, 1, 1], true, true)
.unwrap();
let b: &str = "maybe_create_ax3d()\n\
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";
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);
}
}
44 changes: 28 additions & 16 deletions src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,22 @@

/// Commands to be added at the beginning of the Python script
///
/// The python functions are:
/// The python definitions are:
///
/// * `EXTRA_ARTISTS` -- List of additional objects that must not be ignored when saving the figure
/// * `add_to_ea` -- Adds an entity to the EXTRA_ARTISTS list to prevent them being ignored
/// when Matplotlib decides to calculate the bounding boxes. The Legend is an example of entity that could
/// be ignored by the savefig command (this is issue is prevented here).
/// * `maybe_create_ax3d` -- If AX3D is None, allocates a new mplot3d (Matplotlib's 3D plotting capability)
/// * `THREE_D` -- Is a dictionary of mplot3d objects (one for each subplot3d)
/// * `THREE_D_ACTIVE` -- Is a tuple holding the key to the current THREE_D object (defines the subplot3d)
/// * `ax3d` -- Creates or returns the mplot3d object with the current subplot3d definition specified by THREE_D_ACTIVE
/// * `subplot3d` -- Specifies the THREE_D_ACTIVE parameters to define a subplot3d
/// * `data_to_axis` -- Transforms data limits to axis limits
/// * `axis_to_data` -- Transforms axis limits to data limits
/// * `set_equal_axes` -- Configures the aspect of axes with a same scaling from data to plot units for x, y and z.
/// For example a circle will show as a circle in the screen and not an ellipse. This function also handles
/// the 3D case which is a little tricky with Matplotlib. In this case (3D), the version of Matplotlib
/// must be greater than 3.3.0.
/// * TODO: find a way to pass down the option `proj_type = 'ortho'` to AX3D
pub const PYTHON_HEADER: &str = "### file generated by plotpy
import numpy as np
import matplotlib.pyplot as plt
Expand All @@ -28,32 +31,41 @@ import matplotlib.path as pth
import matplotlib.patheffects as pff
import matplotlib.lines as lns
import matplotlib.transforms as tra
import mpl_toolkits.mplot3d as m3d
import mpl_toolkits.mplot3d
NaN = np.NaN
EXTRA_ARTISTS = []
def add_to_ea(obj):
if obj!=None: EXTRA_ARTISTS.append(obj)
AX3D = None
def maybe_create_ax3d():
global AX3D
if AX3D == None:
AX3D = plt.gcf().add_subplot(111, projection='3d')
AX3D.set_xlabel('x')
AX3D.set_ylabel('y')
AX3D.set_zlabel('z')
add_to_ea(AX3D)
THREE_D = dict()
THREE_D_ACTIVE = (1,1,1)
def ax3d():
global THREE_D
global THREE_D_ACTIVE
if not THREE_D_ACTIVE in THREE_D:
a, b, c = THREE_D_ACTIVE
THREE_D[THREE_D_ACTIVE] = plt.gcf().add_subplot(a,b,c,projection='3d')
THREE_D[THREE_D_ACTIVE].set_xlabel('x')
THREE_D[THREE_D_ACTIVE].set_ylabel('y')
THREE_D[THREE_D_ACTIVE].set_zlabel('z')
add_to_ea(THREE_D[THREE_D_ACTIVE])
return THREE_D[THREE_D_ACTIVE]
def subplot3d(a,b,c):
global THREE_D_ACTIVE
THREE_D_ACTIVE = (a,b,c)
def data_to_axis(coords):
plt.axis() # must call this first
return plt.gca().transLimits.transform(coords)
def axis_to_data(coords):
plt.axis() # must call this first
return plt.gca().transLimits.inverted().transform(coords)
def set_equal_axes():
ax = plt.gca()
if AX3D == None:
global THREE_D
if len(THREE_D) == 0:
ax = plt.gca()
ax.axes.set_aspect('equal')
return
try:
ax = ax3d()
ax.set_box_aspect([1,1,1])
limits = np.array([ax.get_xlim3d(), ax.get_ylim3d(), ax.get_zlim3d()])
origin = np.mean(limits, axis=1)
Expand Down Expand Up @@ -107,6 +119,6 @@ mod tests {

#[test]
fn constants_are_correct() {
assert_eq!(PYTHON_HEADER.len(), 1601);
assert_eq!(PYTHON_HEADER.len(), 1946);
}
}
19 changes: 11 additions & 8 deletions src/curve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ impl Curve {
/// This function must be followed by [Curve::points_3d_add] and [Curve::points_3d_end],
/// otherwise Python/Matplotlib will fail
pub fn points_3d_begin(&mut self) -> &mut Self {
write!(&mut self.buffer, "maybe_create_ax3d()\nxyz=np.array([").unwrap();
write!(&mut self.buffer, "xyz=np.array([").unwrap();
self
}

Expand All @@ -219,7 +219,12 @@ impl Curve {
/// otherwise Python/Matplotlib will fail.
pub fn points_3d_end(&mut self) -> &mut Self {
let opt = self.options();
write!(&mut self.buffer, "])\nAX3D.plot(xyz[:,0],xyz[:,1],xyz[:,2]{})\n", &opt).unwrap();
write!(
&mut self.buffer,
"])\nax3d().plot(xyz[:,0],xyz[:,1],xyz[:,2]{})\n",
&opt
)
.unwrap();
self
}

Expand Down Expand Up @@ -266,8 +271,7 @@ impl Curve {
vector_to_array(&mut self.buffer, "y", y);
vector_to_array(&mut self.buffer, "z", z);
let opt = self.options();
write!(&mut self.buffer, "maybe_create_ax3d()\n").unwrap();
write!(&mut self.buffer, "AX3D.plot(x,y,z{})\n", &opt).unwrap();
write!(&mut self.buffer, "ax3d().plot(x,y,z{})\n", &opt).unwrap();
}

/// Sets the name of this curve in the legend
Expand Down Expand Up @@ -537,9 +541,9 @@ mod tests {
.points_3d_add(1, 2, 3)
.points_3d_add(4, 5, 6)
.points_3d_end();
let b: &str = "maybe_create_ax3d()\n\
let b: &str = "\
xyz=np.array([[1,2,3],[4,5,6],])\n\
AX3D.plot(xyz[:,0],xyz[:,1],xyz[:,2])\n";
ax3d().plot(xyz[:,0],xyz[:,1],xyz[:,2])\n";
assert_eq!(curve.buffer, b);
}

Expand Down Expand Up @@ -569,8 +573,7 @@ mod tests {
let b: &str = "x=np.array([1,2,3,4,5,],dtype=float)\n\
y=np.array([1,4,9,16,25,],dtype=float)\n\
z=np.array([0,0,0,1,1,],dtype=float)\n\
maybe_create_ax3d()\n\
AX3D.plot(x,y,z,label='the-curve')\n";
ax3d().plot(x,y,z,label='the-curve')\n";
assert_eq!(curve.buffer, b);
}

Expand Down
14 changes: 6 additions & 8 deletions src/plot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,10 +340,9 @@ impl Plot {
pub fn set_range_3d(&mut self, xmin: f64, xmax: f64, ymin: f64, ymax: f64, zmin: f64, zmax: f64) -> &mut Self {
write!(
&mut self.buffer,
"maybe_create_ax3d()\n\
AX3D.set_xlim3d({},{})\n\
AX3D.set_ylim3d({},{})\n\
AX3D.set_zlim3d({},{})\n",
"ax3d().set_xlim3d({},{})\n\
ax3d().set_ylim3d({},{})\n\
ax3d().set_zlim3d({},{})\n",
xmin, xmax, ymin, ymax, zmin, zmax,
)
.unwrap();
Expand Down Expand Up @@ -895,10 +894,9 @@ mod tests {
set_equal_axes()\n\
plt.gca().axes.set_aspect('auto')\n\
plt.axis('off')\n\
maybe_create_ax3d()\n\
AX3D.set_xlim3d(-1,1)\n\
AX3D.set_ylim3d(-1,1)\n\
AX3D.set_zlim3d(-1,1)\n\
ax3d().set_xlim3d(-1,1)\n\
ax3d().set_ylim3d(-1,1)\n\
ax3d().set_zlim3d(-1,1)\n\
plt.axis([-1,1,-1,1])\n\
plt.axis([0,1,0,1])\n\
plt.axis([0,plt.axis()[1],plt.axis()[2],plt.axis()[3]])\n\
Expand Down
Loading

0 comments on commit 3bc88ab

Please sign in to comment.