Skip to content Skip to sidebar Skip to footer

Plotting The Projection Of 3d Plot In Three Planes Using Contours

I have a three columns catalogue of data and I would like to make a 3D plot of them plus the projection of each axis as a projected contour in the the plane of the other two axises

Solution 1:

hmm, indeed, difficult data to display. Maybe creating some slices along one axis and creating certain number 2D plots would be best. However 3D plots are fancy. I played a bit with the data resulting in one 3D plot as you did and a separate plot with the projections.

  • The colors of the points are according the missing axis
  • Added transparency to give an idea of density
  • Kept axes of both plots the same

enter image description hereenter image description here

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

data = np.loadtxt('test.cat', skiprows=1)

X=data[:,0]
Y=data[:,1]
Z=data[:,2]

plt.figure()
ax1 = plt.subplot(111,  projection='3d')

ax1.scatter(X, Y, Z, c='b', marker='.', alpha=0.2)
ax1.set_xlabel('X - axis')
ax1.set_ylabel('Y - axis')
ax1.set_zlabel('Z - axis')

plt.figure()
ax2 = plt.subplot(111,  projection='3d')

plt.hot()

cx = np.ones_like(X) * ax1.get_xlim3d()[0]
cy = np.ones_like(X) * ax1.get_ylim3d()[1]
cz = np.ones_like(Z) * ax1.get_zlim3d()[0]

ax2.scatter(X, Y, cz, c=Z,  marker='.', lw=0, alpha=0.2)
ax2.scatter(X, cy, Z, c=-Y, marker='.', lw=0, alpha=0.2)
ax2.scatter(cx, Y, Z, c=X,  marker='.', lw=0, alpha=0.2)
ax2.set_xlim3d(ax1.get_xlim3d())
ax2.set_ylim3d(ax1.get_ylim3d())
ax2.set_zlim3d(ax1.get_zlim3d())
ax2.set_xlabel('X - axis')
ax2.set_ylabel('Y - axis')
ax2.set_zlabel('Z - axis')

Solution 2:

According to what you want to do you need to use the zdir parameter for the contour and contourf functions. Here an example:

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

fig = plt.figure()
ax = Axes3D(fig)
X = np.arange(-4, 4, 0.25)
Y = np.arange(-4, 4, 0.25)
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X **2+ Y **2)
Z = np.sin(R)


ax.contourf(X, Y, Z, zdir='x', offset=-4, cmap=plt.cm.hot)
ax.contour(X, Y, Z, zdir='x', offset=-4, colors='k')
ax.contourf(X, Y, Z, zdir='y', offset=4, cmap=plt.cm.hot)
ax.contour(X, Y, Z, zdir='y', offset=4, colors='k')
ax.contourf(X, Y, Z, zdir='z', offset=-1, cmap=plt.cm.hot)
ax.contour(X, Y, Z, zdir='z', offset=-1, colors='k')

plt.show()

With result: enter image description here

Solution 3:

Here's hoping I'm not shooting a mosquito with a [very ugly] cannon, I recently made a Binning_Array class for a school project and I think it might be able to help you: ThreeBinners

import numpy as np
import matplotlib.pyplot as plt
import binning_array as ba
from mpl_toolkits.mplot3d import axes3d

data = np.loadtxt('test.cat')
X = data[:,0]
Y = data[:,1]
Z = data[:,2]

n_points = data.shape[0]

X_min = np.round(np.min(data[:,0])-0.5)
X_max = np.round(np.max(data[:,0])+0.5)
Y_min = np.round(np.min(data[:,1])-0.5)
Y_max = np.round(np.max(data[:,1])+0.5)
Z_min = np.round(np.min(data[:,2])-0.5)
Z_max = np.round(np.max(data[:,2])+0.5)
n_min_bins = 25
step = min([(X_max-X_min)/n_min_bins, (Y_max-Y_min)/n_min_bins, (Z_max-Z_min)/n_min_bins])

# Using three Binners
BinnerXY = ba.Binning_Array([[X_min, X_max, step],
                             [Y_min, Y_max, step]])
BinnerYZ = ba.Binning_Array([[Y_min, Y_max, step],
                             [Z_min, Z_max, step]])
BinnerXZ = ba.Binning_Array([[X_min, X_max, step],
                             [Z_min, Z_max, step]])

for point in data:
    BinnerXY.add_value([point[0], point[1]])
    BinnerXZ.add_value([point[0], point[2]])
    BinnerYZ.add_value([point[1], point[2]])

fig = plt.figure()
ax = [fig.add_subplot(221, projection='3d'),
      fig.add_subplot(222),
      fig.add_subplot(223),
      fig.add_subplot(224)]

# Plot 2D projections on the 3D graph
vmin = np.min([BinnerXZ.bin_min(), BinnerYZ.bin_min(), BinnerXY.bin_min()])
vmax = np.max([BinnerXZ.bin_max(), BinnerYZ.bin_max(), BinnerXY.bin_max()])
levels = np.linspace(vmin,vmax,20)

xs_c = np.arange(*BinnerXZ.limits[0])
zs_c = np.arange(*BinnerXZ.limits[1])
ZS_C, XS_C = np.meshgrid(zs_c,xs_c)
ax[0].contourf(X=XS_C, Y=BinnerXZ.bins, Z=ZS_C,
               zdir='y', offset=Y_max,
               vmin=vmin, vmax=vmax,
               cmap=plt.cm.coolwarm, levels=levels,
               alpha=0.5)
xs_c = np.arange(*BinnerXY.limits[0])
ys_c = np.arange(*BinnerXY.limits[1])
YS_C, XS_C = np.meshgrid(ys_c,xs_c)
ax[0].contourf(X=XS_C, Y=YS_C, Z=BinnerXY.bins,
               zdir='z', offset=Z_min,
               vmin=vmin, vmax=vmax,
               cmap=plt.cm.coolwarm, levels=levels,
               alpha=0.5)
ys_c = np.arange(*BinnerYZ.limits[0])
zs_c = np.arange(*BinnerYZ.limits[1])
ZS_C, YS_C = np.meshgrid(zs_c, ys_c)
ax[0].contourf(X=BinnerYZ.bins, Y=YS_C, Z=ZS_C,
               zdir='x', offset=X_min,
               vmin=vmin, vmax=vmax,
               cmap=plt.cm.coolwarm, levels=levels,
               alpha=0.5)

# Plot scatter of all data
ax[0].scatter(X, Y, Z, c='g', marker='.', alpha=0.2)
ax[0].set_xlabel(r"$x$")
ax[0].set_ylabel(r"$y$")
ax[0].set_zlabel(r"$z$")
max_range = max([X_max-X_min, Y_max-Y_min, Z_max-Z_min]) / 2.
pos = [(X_max+X_min)/2., (Y_max+Y_min)/2., (Z_max+Z_min)/2.]
ax[0].set_xlim(pos[0] - max_range, pos[0] + max_range)
ax[0].set_ylim(pos[1] - max_range, pos[1] + max_range)
ax[0].set_zlim(pos[2] - max_range, pos[2] + max_range)

# Plot 2D histograms
BinnerXZ.plot_2d_slice(fig=fig, ax=ax[1], xlabel=r"$x$", ylabel=r'$z$')
BinnerXY.plot_2d_slice(fig=fig, ax=ax[2], xlabel=r"$x$", ylabel=r'$y$')
BinnerYZ.plot_2d_slice(fig=fig, ax=ax[3], xlabel=r"$y$", ylabel=r'$z$')
plt.show()

You can also use only one Binner, but notice that you will get artifacts where the planes intersect: One Binner

# ...# Using three Binners# ...# Using only one Binner (adds a small error! see comments!)
Binner = ba.Binning_Array([[X_min, X_max, step],
                           [Y_min, Y_max, step],
                           [Z_min, Z_max, step]])
for point indata:
    Binner.add_value([point[0], point[1], Z_min])
    Binner.add_value([point[0], Y_max-step, point[2]])
    Binner.add_value([X_min, point[1], point[2]])
fig = plt.figure()
ax = [fig.add_subplot(221, projection='3d'),
      fig.add_subplot(222),
      fig.add_subplot(223),
      fig.add_subplot(224)]
ax[0].scatter(X, Y, Z, c='g', marker='.', alpha=0.2)
Binner.plot_slices(others={0:X_min, 1:Y_max, 2:Z_min}, fig=fig, ax=ax)
plt.show()

The binning_array.py was made for a school project and is not entirely polished, but it's enough for what you want.

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

classBinning_Array:
    def__init__(self, limits=[[-1.,1.,1.],[-1.,1.,1.]]):
        """Create a new binning array.
        The amount of given limits determines the dimension of the array,
        although only 2 and 3D have been tested.
        Each limit must be a list of start, stop and step for the
        axis it represents (x, y or z)."""
        self.limits  = np.array(limits)
        self._shape = []
        for i in xrange(len(self.limits)):
            self._shape.append((self.limits[i][1]-self.limits[i][0]) / \
                               float(self.limits[i][2]))
        self._shape = tuple(self._shape)
        self.dimensions = len(self._shape)
        self.bins = np.zeros(self._shape)
        self.outside = 0.
        self._normalized = 1.def__repr__(self):
        """Representation method. <<REVIEW>>"""return"Binning Array! Hurray!"def__getitem__(self, index):
        """Direct acess to read self.bins (use something like self[index])
        Direct acess to write would be given by __setitem__
        """return self.bins.__getitem__(index)

    defposition2index(self,position,axis=0):
        """Convert a given position to an index in axis.
        If it is outside, it returns -1.
        """if self.limits[axis][0] <= position < self.limits[axis][1]:
            returnint((position - self.limits[axis][0]) / self.limits[axis][2])
        else: return -1defindex2position(self, index, axis=0):
        """Convert a given index to a position in axis.
        If it is outisde, it returns -1.
        """if0 <= index < self._shape[axis]:
            return self.limits[axis][0] + self.limits[axis][2] * index
        else:
            return -1defadd_value(self, position, value=1., verbose=False):
        """Add a given value to a specified position.
        If verbose it returns a list with the axies at which the position
        is outside the scope of this Binning_Array.
        Not very efficient because of that (verbose was for debugging).
        """
        indexs  = []
        outside = Falseif verbose:
            outs = []
        for i in xrange(self.dimensions):
            # using self.dimensions serves as a filter# if position has non valid shape
            index = self.position2index(position[i],i)
            if index == -1:
                if verbose:
                    outside = True
                    outs.append(i)
                else:
                    self.outside += value / self._normalized  
                    returnNone# nothing, as it is not verboseelse:
                indexs.append(index)
        if outside: # the only way to get here is if verbose is True...
            self.outside += value / self._normalized
            return outs # so I can just return outs...else:
            self.bins[tuple(indexs)] += value / self._normalized
            if verbose:
                return outs

    defget_value(self, position, verbose=False):
        """Return the value at the specified position.
        If verbose it alse returns a list with the axies at which the position
        is outside the scope of this Binning_Array.
        """
        indexs = []
        outside = Falseif verbose:
            outs = []
        for i in xrange(self.dimensions):
            index = self.position2index(position[i],i)
            if index == -1:
                if verbose:
                    outside = True
                    outs.append[i]
                else:
                    return self.outside
            else:
                indexs.append(index)
        if outside: # the only way to get here is if verbose is Truereturn self.outside, outs # so I can just return outs...else:
            if verbose:
                return self.bins[tuple(indexs)], outs
            else:
                return self.bins[tuple(indexs)]

    defnormalize(self, total=None):
        """Divide the entire array by the sum of its values (and outside).
        Any value added after this will be normalized by the same factor.
        """if total isNone:
            total = self.n_counts()
        self.bins /= total
        self.outside /= total
        self.normalize *= total

    defn_counts(self):
        """Return the number of counts."""return np.sum(self.bins) + self.outside

    defbin_max(self):
        """Return the value of the largest bin."""return np.max(self.bins)

    defbin_min(self):
        """Return the value of the largest bin."""return np.min(self.bins)

    defplot_2d_slice(self, cuts=[0,1], others={},
                      fig=None, ax=None, show=True, **kwargs):
        """Plot a 2D slice."""
        x = min(cuts)
        y = max(cuts)
        xs = np.arange(self.limits[x][0],
                       self.limits[x][1] + self.limits[x][2],
                       self.limits[x][2])
        ys = np.arange(self.limits[y][0],
                       self.limits[y][1] + self.limits[y][2],
                       self.limits[y][2])
        index = []
        title = ''for i in xrange(self.dimensions):
            if i in cuts:
                appendix = slice(self._shape[i]+1)
            else:
                appendix = others.get(i,(self.limits[i][0]+
                                         self.limits[i][1]) / 2.)
                title += '%d:%.4e\t' % (i,appendix)
                appendix = self.position2index(appendix,i)
            index.append(appendix)
        index = tuple(index)
        if fig isNone:
            fig, ax = plt.subplots(1,1)
        YS,XS = np.meshgrid(ys, xs)
        graph = ax.pcolormesh (XS, YS, self.bins[index], cmap=plt.cm.coolwarm)
        fig.colorbar(graph, ax=ax)
        ax.axis('equal')
        ax.set_xlim(self.limits[x][0], self.limits[x][1])
        ax.set_ylim(self.limits[y][0], self.limits[y][1])
        if'xticks'in kwargs:
            ax.set_xticks(kwargs['xticks'])
        if'yticks'in kwargs.keys():
            ax.set_yticks(kwargs['yticks'])
        if'xlabel'in kwargs:
            ax.set_xlabel(kwargs['xlabel'])
        if'ylabel'in kwargs:
            ax.set_ylabel(kwargs['ylabel'])
        if'xlim'in kwargs:
            ax.set_xlim(*kwargs['xlim'])
        if'ylim'in kwargs:
            ax.set_ylim(*kwargs['ylim'])
        if show:
            fig.tight_layout()
            fig.show()

    defplot_slices(self, others={}, fig=None, ax=None,
                    show=True, projections=True):
        index = []
        pos = []
        title = ''for i in xrange(self.dimensions):
            temp = others.get(i,(self.limits[i][0]+self.limits[i][1])/2.)
            title += '%d:%.4e\t' % (i,temp)
            pos.append(temp)
            index.append(self.position2index(temp,i))
        if self.dimensions == 3:
            if fig isNone:
                fig = plt.figure()
                if projections:
                    ax = [fig.add_subplot(221, projection='3d'),
                          fig.add_subplot(222),
                          fig.add_subplot(223),
                          fig.add_subplot(224)]
                else:
                    ax  = fig.add_subplot(111, projection='3d')
            if projections:
                xs = np.arange(self.limits[0][0],
                               self.limits[0][1] + self.limits[0][2],
                               self.limits[0][2])
                ys = np.arange(self.limits[1][0],
                               self.limits[1][1] + self.limits[1][2],
                               self.limits[1][2])
                zs = np.arange(self.limits[2][0],
                               self.limits[2][1] + self.limits[2][2],
                               self.limits[2][2])
                xs_c = np.arange(*self.limits[0])
                ys_c = np.arange(*self.limits[1])
                zs_c = np.arange(*self.limits[2])
                vmin = np.min(self.bins)
                vmax = np.max(self.bins)
                levels = np.linspace(vmin,vmax,20)
                #graph 0 (3D)
                ax[0].set_xlabel(r"$x$")
                ax[0].set_ylabel(r"$y$")
                ax[0].set_zlabel(r"$z$")
                #ax[0].axis('equal') #not supported in 3D:#http://stackoverflow.com/questions/13685386/\#matplotlib-equal-unit-length-with-equal-aspect-ratio-z-axis-is-not-equal-to
                max_range = max([xs[-1]-xs[0],ys[-1]-ys[0],zs[-1]-zs[0]]) / 2.#                x_mean = (xs[-1] + xs[0])/2.#                y_mean = (ys[-1] + ys[0])/2.#                z_mean = (zs[-1] +zs[0])/2.
                ax[0].set_xlim(pos[0] - max_range, pos[0] + max_range)
                ax[0].set_ylim(pos[1] - max_range, pos[1] + max_range)
                ax[0].set_zlim(pos[2] - max_range, pos[2] + max_range)
                # to understand holes in contour plot:#http://stackoverflow.com/questions/18897950/\#matplotlib-pyplot-contourf-function-introduces-holes-or-gaps-when-plotting-regul# graph 1 (2D)
                ZS, XS = np.meshgrid(zs,xs)
                ZS_C, XS_C = np.meshgrid(zs_c,xs_c)
                ax[1].pcolormesh(XS, ZS, self.bins[:,index[1],:],
                             vmin=vmin, vmax=vmax,
                             cmap=plt.cm.coolwarm)
                ax[0].contourf(X=XS_C, Y=self.bins[:,index[1],:], Z=ZS_C,
                               zdir='y', offset=pos[1],
                               vmin=vmin, vmax=vmax,
                               cmap=plt.cm.coolwarm, levels=levels,
                               alpha=0.5)
                ax[1].set_xlabel(r"$x$")
                ax[1].set_ylabel(r"$z$")
                ax[1].set_xlim(xs[0],xs[-1])
                ax[1].set_ylim(zs[0],zs[-1])
                ax[1].axis('equal')
                # graph 2 (2D)
                YS, XS = np.meshgrid(ys,xs)
                YS_C, XS_C = np.meshgrid(ys_c,xs_c)
                ax[2].pcolormesh(XS, YS, self.bins[:,:,index[2]],
                                 vmin=vmin, vmax=vmax,
                                 cmap=plt.cm.coolwarm)
                ax[0].contourf(X=XS_C, Y=YS_C, Z=self.bins[:,:,index[2]],
                               zdir='z', offset=pos[2],
                               vmin=vmin, vmax=vmax,
                               cmap=plt.cm.coolwarm, levels=levels,
                               alpha=0.5)
                ax[2].set_xlabel(r"$x$")
                ax[2].set_ylabel(r"$y$")
                ax[2].set_xlim(xs[0],xs[-1])
                ax[2].set_ylim(ys[0],ys[-1])
                ax[2].axis('equal')
                # graph 3 (2D)
                ZS, YS = np.meshgrid(zs, ys)
                ZS_C, YS_C = np.meshgrid(zs_c, ys_c)
                ax[3].pcolormesh(YS, ZS, self.bins[index[0],:,:],
                                 vmin=vmin, vmax=vmax,
                                 cmap=plt.cm.coolwarm)
                ax[0].contourf(X=self.bins[index[0],:,:], Y=YS_C, Z=ZS_C,
                               zdir='x', offset=pos[0],
                               vmin=vmin, vmax=vmax,
                               cmap=plt.cm.coolwarm, levels=levels,
                               alpha=0.5)
                ax[3].set_xlabel(r"$y$")
                ax[3].set_ylabel(r"$z$")
                ax[3].set_xlim(ys[0],ys[-1])
                ax[3].set_ylim(zs[0],zs[-1])
                ax[3].axis('equal')
            else:
                # update to draw a given slice, use it to plot eaxh axes above!
                ax.plot(self.XS,self.YS,self.ZS)
                ax.set_zlabel(r"$z$")
                ax.set_xlabel(r"$x$")
                ax.set_ylabel(r"$y$")
                ax.axis('equal')
        else:
            if fig isNone:
                fig, ax = plt.subplots(1)
            xs = np.arange(self.limits[0][0],
                           self.limits[0][1] + self.limits[0][2],
                           self.limits[0][2])
            ys = np.arange(self.limits[1][0],
                           self.limits[1][1] + self.limits[1][2],
                           self.limits[1][2],)
            YS, XS = np.meshgrid(ys, xs)
            graph = ax.pcolormesh(XS, YS, self.bins, cmap=plt.cm.coolwarm)
            fig.colorbar(graph)
            ax.set_xlim(self.limits[0][0], self.limits[0][1])
            ax.set_ylim(self.limits[1][0], self.limits[1][1])
            ax.set_title('Energy Distribution')
            ax.set_xlabel(r"$x$")
            ax.set_ylabel(r"$y$")
            ax.axis('equal')
        if show:
            fig.tight_layout()
            fig.show()
        return fig, ax

If anything on the code is wrong or bugged please say so and I will edit the above (the school project was already graded, so you won't be doing my homework). Also, if anything is less than clear please say so I add comments or explanations as needed.

Post a Comment for "Plotting The Projection Of 3d Plot In Three Planes Using Contours"