0

I'm trying to scale a figure with two subplots (see image below), so that the z-axis of the 3D plot becomes the same size as the y-axis of the 2D plot. How can i do this?

enter image description here

I've already tried gridspec and aspect, but this doesn't work...

Mr. T
  • 11,960
  • 10
  • 32
  • 54
Len01
  • 31
  • 5
  • The 3D view is interactive. You can move the plot. What do you mean when saying that the axes should be the same size? For a specific viewing angle? Interactively their size should be adapted? – Mr. T Feb 11 '21 at 13:00
  • I've edited my question. Hopefully the new picture is better for showing my describted problem – Len01 Feb 11 '21 at 13:17
  • This is understood. But do you define a specific viewing angle here? I can plot this, move the 3D figure, and the axes will not have the same length anymore. The 3D plot is not static. – Mr. T Feb 11 '21 at 13:23
  • Yes, for the 3d plot i use ax.view_init(elev=10, azim=135). I played around with aspect in combination with gridspec and get almost the same axis lengths. But i think, this is no perfect solution. – Len01 Feb 13 '21 at 16:18

1 Answers1

2

To determine the exact position and length of the 3D axis and replicate it by the 2D graph is rather difficult (although not impossible - I will upvote any solution that does exactly that). But if you can't make it - fake it. Here, we use two 3D plots and convert one that it looks like a 2D plot:

from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d.axes3d import Axes3D

def plot_3d_pseudo2d(arr):
    #one figure, two 3D subplots
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(20, 10), subplot_kw={'projection': "3d"})

    #ax1 - normal isometric 3D projection
    ax1.view_init(elev=10, azim=-45) 
    ax1.set_xlabel("X")
    ax1.set_ylabel("Y")
    ax1.set_zlabel("Z")
    ax1.plot(arr[:, 0], arr[:, 1], arr[:, 2], marker="o")
    
    #pseudo-2D projection
    ax2.view_init(elev=0, azim=-90.1)
    #shrink y-axis projection length
    ax2.get_proj = lambda: np.dot(Axes3D.get_proj(ax2), np.diag([1, 0.01, 1, 1]))
    ax2.set_xlabel("X")
    ax2.set_zlabel("Z")
    #plot x-z pairs for y=0
    ax2.plot(arr[:, 0], np.zeros(arr[:, 1].size), arr[:, 2], marker="o")
    #remove y-ticks
    ax2.set_yticks([])
    #increase label distance 
    ax2.xaxis.labelpad = 10
    ax2.zaxis.labelpad = 10
        
    plt.subplots_adjust(wspace=0.1)     
    plt.show()
    

import numpy as np
plot_3d_pseudo2d(np.asarray([[1,   4,   5], 
                             [12,  23,  89], 
                             [123, 234, 789]]).T)

Sample output:

enter image description here

It is not perfect - 3D projections create a lot of whitespace around them (to have space for the rotation), and the perspective distortion hides the x-axis ticks and sets the z-axis labels slightly off.

Disclaimer: The y-axis projection was shrunk with the help of this answer.

Mr. T
  • 11,960
  • 10
  • 32
  • 54
  • wow thank you very much. I also just discovered [this explanation](https://stackoverflow.com/questions/30223161/matplotlib-mplot3d-how-to-increase-the-size-of-an-axis-stretch-in-a-3d-plo) on stackoverlow – Len01 Feb 15 '21 at 14:23
  • Yes, I had to use it because matplotlib never displays the 3D view as a 2D view even for elev=0. Usually a good idea, but in this case, it was unwanted. I assume you have noticed that the viewing angle differs. If you want your viewing angle as indicated by your image in the question, you have to change some angles in the 2D view, and you have to plot the y-axis instead of the x-axis. However, I have not done this here because in this case, the horizontal axis in the pseudo-2D graph would be displayed from right to left. This may or may not be your intention - I could not bring myself to do it – Mr. T Feb 15 '21 at 14:32
  • Another possibility would be to project the points on the panes, so you would have the 2D view integrated into the 3D view. – Mr. T Feb 15 '21 at 14:37
  • "Yes, I had to use it because matplotlib never displays the 3D view as a 2D view even for elev=0.". That was exactly my first thought, because this works in matlab, No problem, you helped me a lot with this example code. may I ask you how exactly lambda works in your code? In examples, the lambda function is always assigned an argument. But in your example there is no argument. So what is the reason for using lambda? – Len01 Feb 15 '21 at 17:02
  • As I said in the Disclaimer - this line is lifted from [here](https://stackoverflow.com/a/30419243/8881141). So, I have only a vague understanding of the process. Maybe [this explanation](https://stackoverflow.com/a/17671477/8881141) is of help? – Mr. T Feb 15 '21 at 21:26
  • Thank you. I'll take a look at this – Len01 Feb 18 '21 at 13:25