diff --git a/src/canvas.rs b/src/canvas.rs index 512781e..bbe7064 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -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 } @@ -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 } @@ -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; @@ -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(); @@ -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(); @@ -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], @@ -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, ""); @@ -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" ); } @@ -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" ); } @@ -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); } @@ -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] @@ -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); } @@ -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); } } diff --git a/src/constants.rs b/src/constants.rs index 76139c0..a6feb08 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -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 @@ -28,20 +31,27 @@ 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) @@ -49,11 +59,13 @@ 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) @@ -107,6 +119,6 @@ mod tests { #[test] fn constants_are_correct() { - assert_eq!(PYTHON_HEADER.len(), 1601); + assert_eq!(PYTHON_HEADER.len(), 1946); } } diff --git a/src/curve.rs b/src/curve.rs index 60cf343..cd0ed90 100644 --- a/src/curve.rs +++ b/src/curve.rs @@ -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 } @@ -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 } @@ -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 @@ -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); } @@ -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); } diff --git a/src/plot.rs b/src/plot.rs index f1982e5..cb4fda9 100644 --- a/src/plot.rs +++ b/src/plot.rs @@ -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(); @@ -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\ diff --git a/src/surface.rs b/src/surface.rs index 578d0ed..3e75c3d 100644 --- a/src/surface.rs +++ b/src/surface.rs @@ -120,18 +120,17 @@ impl Surface { matrix_to_array(&mut self.buffer, "x", x); matrix_to_array(&mut self.buffer, "y", y); matrix_to_array(&mut self.buffer, "z", z); - write!(&mut self.buffer, "maybe_create_ax3d()\n").unwrap(); 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(); + 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(); + 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(); + write!(&mut self.buffer, "ax3d().scatter(x,y,z{})\n", &opt_points).unwrap(); } if self.with_colorbar { let opt_colorbar = self.options_colorbar(); @@ -558,9 +557,8 @@ mod tests { 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\ - maybe_create_ax3d()\n\ - sf=AX3D.plot_surface(x,y,z,cmap=plt.get_cmap('bwr'))\n\ - AX3D.plot_wireframe(x,y,z,color='black')\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); diff --git a/src/text.rs b/src/text.rs index c4cfb98..d5ac528 100644 --- a/src/text.rs +++ b/src/text.rs @@ -89,8 +89,7 @@ impl Text { let opt = self.options(); write!( &mut self.buffer, - "maybe_create_ax3d()\n\ - t=AX3D.text({},{},{},'{}'{})\n", + "t=ax3d().text({},{},{},'{}'{})\n", x, y, z, message, &opt ) .unwrap(); @@ -295,8 +294,7 @@ mod tests { fn draw_3d_works() { let mut text = Text::new(); text.draw_3d(1.2, 3.4, 5.6, &"message".to_string()); - let b: &str = "maybe_create_ax3d()\n\ - t=AX3D.text(1.2,3.4,5.6,'message')\n"; + let b: &str = "t=ax3d().text(1.2,3.4,5.6,'message')\n"; assert_eq!(text.buffer, b); } }