0

We are working on a project in which we would like to construct 3D plots. Python is our main language, and therefore naturally chose to use matplotlib as our plotting library. Various tutorials (here, here and here) have teached us how to perform 3D plotting using the mplot3d functionality of matplotlib. Consequently, various StackOverflow answers helped us to move the origin of each of the axes to different locations (here and here).

After searching for a couple of hours we have a hard time finding an answer to our next question, however. We would like to have a positive and negative side for our Z-axis (see the picture below, orange part). This would mean that data points with Z>0 are above origin, and with Z<0 are below origin. We tried several things, but our Z-axis origin always ends up at the most negative value of our dataset.

Example

With great help of the community here, we've come to a minimal example showcasing what I want. The code I used is:

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

fig = plt.figure(figsize=(10, 10))
ax = fig.gca(projection='3d')

# Some settings
sn = 2   #limits in x,y,z
n = 50   #number of sample points
x1, x2 = 0, sn
y1, y2 = 0, sn
z1, z2 = -sn, sn

# Data for points
xs = (x2 - x1)*np.random.rand(n) + x1
ys = (y2 - y1)*np.random.rand(n) + y1
zs = (z2 - z1)*np.random.rand(n) + z1

# Points with z >= 0, plotted in green
ax.scatter(xs[zs>=0], ys[zs>=0], zs[zs>=0], color='green')
# Points with z < 0, plotted in red
ax.scatter(xs[zs<0], ys[zs<0], zs[zs<0], color='red')

# Data for plotting plane x|y|z=0 within the domain
tmp = np.linspace(0, sn, 8)
x, y = np.meshgrid(tmp, tmp)
z = 0*x

# Plot grid lines
ax.plot([0, sn], [0, 0], [0, 0], color='black')
ax.plot([0, 0], [0, sn], [0, 0], color='black')
ax.plot([0, 0], [0, 0], [-sn, sn], color='black')

# Maximum tick labels for X, Y, and Z (x3)
ax.plot([sn, sn], [0, 0], [-.05, .02], color='black')
ax.plot([0, 0], [sn, sn], [-.05, .02], color='black')
ax.plot([-.05, .02], [-.05, .02], [sn, sn], color='black')
ax.plot([-.05, .02], [-.05, .02], [-sn, -sn], color='black')
ax.plot([-.05, .02], [-.05, .02], [0, 0], color='black')

# Label texts
ax.text(sn/2, 0, -.2*sn, 'xlabel', 'x', ha='center')
ax.text(0, sn/2, -.2*sn, 'ylabel', 'y', ha='center')
ax.text(-.1*sn, 0, 0, 'zlabel', 'z', ha='center')

# Maximum limit text for X, Y and Z (x3)
ax.text(sn, 0, -.1*sn, f'{sn}', 'x', ha='center')
ax.text(0, sn, -.1*sn, f'{sn}', 'y', ha='center')
ax.text(-.05*sn, -.05*sn, 0, '0', 'x', ha='center')
ax.text(-.05*sn, -.05*sn, sn, f'{sn}', 'x', ha='right')
ax.text(-.05*sn, -.05*sn, -sn, f'{-sn}', 'x', ha='center')

# Set limits of the 3D display
ax.set_xlim3d([-sn, sn])
ax.set_ylim3d([-sn, sn])
ax.set_zlim3d([-sn, sn])

ax.set_axis_off()

plt.show()

This results in the graph below:

enter image description here

Although I am very happy with the outcome, this is still kind of 'hacky' solution with manually drawing the axis, ticks and labels. If anybody would have a solution in which we can re-design the axis from the mplot3d API that would be very helpful.

wptmdoorn
  • 160
  • 1
  • 12
  • 1
    You mean the 3D equivalent to [this problem](https://stackoverflow.com/q/31556446/8881141)? – Mr. T Jan 27 '21 at 12:46
  • 1
    From the image it looks like the Z axis, why is it the Y axis? – r-beginners Jan 27 '21 at 12:52
  • Are you looking for `ax.set_zlim3d(low_z, high_z)`. – swatchai Jan 27 '21 at 12:54
  • @Mr.T, yes but I cannot use the spines functionality as it is only relevant in a 2D rendering setting – wptmdoorn Jan 27 '21 at 20:43
  • @r-beginners, I am sorry, I mean the Z-axis, I also editted this into the post now. – wptmdoorn Jan 27 '21 at 20:44
  • @swatchai, that does not make Z=0 the origin, it only adjusts the minimum and maximum limits of the Z-axis – wptmdoorn Jan 27 '21 at 20:44
  • I didn't say this is the solution. We are still trying to establish the desired outcome. So, you want to remove the outer panes with the grid but have instead semitransparent planes as indicators of the axes? – Mr. T Jan 27 '21 at 20:51
  • @Mr.T, thank you for the answer and the help. Yes, so basically I want the origin of all axis to go through (0,0,0) [x,y,z]; this would mean that e.g. point (0, 0, -10) would lie directly below the origin 'underneath' the graph. In the current situation however, the origin will go through the lowest Z value possible (e.g. -100) and therefore the point (0,0,-10) will lie above the origin. – wptmdoorn Jan 27 '21 at 21:13
  • I don't think I have seen this implemented yet. You cannot remove the outer panes but you can [make them transparent](https://stackoverflow.com/a/44002650/8881141). Then, you would have to [create semitransparent planes](https://stackoverflow.com/a/53116010/8881141). Not trivial but doable. Maybe somebody else has a better idea. – Mr. T Jan 27 '21 at 21:42
  • Your requirement is quite specific to a rare use-case. Do not expect it as parts of `matplotlib`. However, for your own use, you can refactor the parts you need as a python's `def` to be reused later. That should be a good move. – swatchai Jan 28 '21 at 07:30

1 Answers1

1

(Swatchai creates this as a community wiki):

Sometime, discussion without some runnable code to play/experiment with is not the best approach to get a solution. Here I propose this code to use for further discussion.

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

fig = plt.figure(figsize=(10, 10))
ax = fig.gca(projection='3d')

# Quivers for axes x,y,z from (0,0,0)
quiver1 = ax.quiver([0],[0],[0],[2],[0],[0], colors='r')
quiver2 = ax.quiver([0],[0],[0],[0],[2],[0], colors='g')
quiver3 = ax.quiver([0],[0],[0],[0],[0],[2], colors='b')

# Some settings
sn = 2   #limits in x,y,z
n = 50   #number of sample points
x1, x2 = -sn, sn
y1, y2 = -sn, sn    
z1, z2 = -sn, sn

# Data for points
xs = (x2 - x1)*np.random.rand(n) + x1
ys = (y2 - y1)*np.random.rand(n) + y1
zs = (z2 - z1)*np.random.rand(n) + z1

# Points with z >= 0, plotted in green
ax.scatter(xs[zs>=0], ys[zs>=0], zs[zs>=0], color='green')
# Points with z < 0, plotted in red
ax.scatter(xs[zs<0], ys[zs<0], zs[zs<0], color='red')

# Data for plotting plane x|y|z=0 within the domain
tmp = np.linspace(0, sn, 8)
x,y = np.meshgrid(tmp,tmp)
z = 0*x

ax.plot_surface(z,x,y, alpha=0.15, color='red')    # plot the plane x=0
ax.plot_surface(x,z,y, alpha=0.15, color='green')  # plot the plane y=0
ax.plot_surface(x,y,z, alpha=0.15, color='blue')   # plot the plane z=0

# Set limits of the 3D display
ax.set_xlim3d([-sn, sn])
ax.set_ylim3d([-sn, sn])
ax.set_zlim3d([-sn, sn])

# Set labels at the 3d box/frame
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')

plt.show()

Output plot:

quiverasaxes

swatchai
  • 17,400
  • 3
  • 39
  • 58
  • Hi, thank you for the answer. This generally shares the idea I want to create, I updated it a bit to match my figure: https://gist.github.com/wptmdoorn/f588230cbd1d07a585c23aaec186569d - this is very nice, the only unfortunate thing is that you kind of "hack" your axis onto the figure, same will hold true for axis labels and so on. I hoped that the native mplot3d API would have functionality to do this for the Z-axis inheritly. – wptmdoorn Jan 28 '21 at 05:51
  • @wptmdoorn You may put the modified code and the output plot in your question, also identify things that do not meet your requirements. This will be useful to get further help. – swatchai Jan 28 '21 at 06:05
  • thank you, I just edited the main post with code and an example figure! – wptmdoorn Jan 28 '21 at 06:11