16
votes

I spent last few days trying to find a way to remove tiny margins from axes in a 3D plot. I tried ax.margins(0) and ax.autoscale_view('tight') and other approaches, but these small margins are still there. In particular, I don't like that the bar histograms are elevated, i.e., their bottom is not at the zero level -- see example image.

unwanted margins on all axes

In gnuplot, I would use "set xyplane at 0". In matplotlib, since there are margins on every axis on both sides, it would be great to be able to control each of them.

Edit: HYRY's solution below works well, but the 'X' axis gets a grid line drawn over it at Y=0:

strange axis

2
It would really help if you could add the code you use to make the plot, so we have a starting point. Then it's easier for people to copy-paste the code, and thereafter find a solution for this particular problem. - sodd
Lots of sample code here ("Bar plots" example is similar to my case above). - dolphin

2 Answers

13
votes

There is not property or method that can modify this margins. You need to patch the source code. Here is an example:

from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
###patch start###
from mpl_toolkits.mplot3d.axis3d import Axis
if not hasattr(Axis, "_get_coord_info_old"):
    def _get_coord_info_new(self, renderer):
        mins, maxs, centers, deltas, tc, highs = self._get_coord_info_old(renderer)
        mins += deltas / 4
        maxs -= deltas / 4
        return mins, maxs, centers, deltas, tc, highs
    Axis._get_coord_info_old = Axis._get_coord_info  
    Axis._get_coord_info = _get_coord_info_new
###patch end###

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
for c, z in zip(['r', 'g', 'b', 'y'], [30, 20, 10, 0]):
    xs = np.arange(20)
    ys = np.random.rand(20)

    # You can provide either a single color or an array. To demonstrate this,
    # the first bar of each set will be colored cyan.
    cs = [c] * len(xs)
    cs[0] = 'c'
    ax.bar(xs, ys, zs=z, zdir='y', color=cs, alpha=0.8)

ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')

plt.show()

The result is:

enter image description here

Edit

To change the color of the grid lines:

for axis in (ax.xaxis, ax.yaxis, ax.zaxis):
    axis._axinfo['grid']['color']  = 0.7, 1.0, 0.7, 1.0

Edit2

Set X & Y lim:

ax.set_ylim3d(-1, 31)
ax.set_xlim3d(-1, 21)
0
votes

I had to tweak the accepted solution slightly, because in my case the x and y axes (but not the z) had an additional margin, which, by printing mins, maxs, deltas, turned out to be deltas * 6.0/11. Here is the updated patch that worked well in my case.

###patch start###
from mpl_toolkits.mplot3d.axis3d import Axis
def _get_coord_info_new(self, renderer):
    mins, maxs, cs, deltas, tc, highs = self._get_coord_info_old(renderer)
    correction = deltas * [1.0/4 + 6.0/11,
                           1.0/4 + 6.0/11,
                           1.0/4]
    mins += correction
    maxs -= correction
    return mins, maxs, cs, deltas, tc, highs
if not hasattr(Axis, "_get_coord_info_old"):
    Axis._get_coord_info_old = Axis._get_coord_info  
Axis._get_coord_info = _get_coord_info_new
###patch end###

(I've also changed the patching logic around a bit, so that editing the function and reloading its module now works as expected in Jupyter.)