Description
Is your feature request related to a problem?
There is a @_plot2d decorator vith miscellaneous plots methods and a signature if one wants to build its own custom 2D plot method but no method for 3D plot while I believe we can already have 3D scatter and line plots.
Describe the solution you'd like
It would be great to have a voxel method wrapped on the matplotlib.pyplot in a proper _3Dplot method. It should deal with the label names, facetgrids or severals axes, the colorbar and the dimension.variables of the dataset we want to plot. Coding it for a 3D datarray would already be a very good start !
It is particularly useful for 3D Histograms the same way as pcolormesh is useful for 2D ones and is already implemented
I've written my own basic approach, I here are the plots to give you an idea :
As you can see the multiple axis dealing part can be seriously improved ahah
Describe alternatives you've considered
This is my code for the moment, I used some functions that you define the documentation to get the labels for example.
I got rid of most of the args checking bc my code is not intended to be used for others and I dropped the facegrid option bc I create my custom figs by hand
[Edit : colormap and levels working now, so it can be used by others if the voxels method is still not available]
`
def hist3Dplot(darray,bins,levels,ax=None,cmap="viridis", add_labels=True,add_colorbar=True,**kwargs):#colors=None,,cbar_ax=None
#bins could in theory be obtained by darray but [darray[dim].values for dim in darray.dims] give centered values and not interval borns
def bin2coord(bins):
#X,Y,Z are the coords of the bins hence the "+1"
return np.meshgrid(*[np.linspace(bin[0][0],bin[-1][1],len(bin)+1) for bin in bins])
NPArr=darray.to_numpy()
BoolArr=NPArr!=0 #won't draw non occuring voxels from the histogram
palette=mpl.cm.get_cmap("viridis",lut=levels).copy()
palette.set_under((0,0,0,0)) #for the cmap so that vmin is taken into account
CArr = palette(NPArr/np.max(NPArr))
CArr[:,:,:,3]=0.9
X, Y, Z = bin2coord(bins)
if ax==None:
fig=plt.figure()
ax = fig.add_subplot(projection='3d')
else:
fig=plt.gcf()
primitive = ax.voxels(X, Y, Z, BoolArr, facecolors=CArr)#,edgecolor=(236/250, 236/255, 236/255, 1))
dim_names=darray.dims
if add_labels:
#So that the long labels don't stack on the axis ticks
axis_labels=np.array([xr.plot.utils.label_from_attrs(darray[dim_names[i]]) for i in range(3)])
labelpad=np.where(np.char.str_len(axis_labels)>50,20,5)
ax.set_xlabel(axis_labels[0],fontsize=15,labelpad=labelpad[0])
ax.set_ylabel(axis_labels[1],fontsize=15,labelpad=labelpad[1])
ax.set_zlabel(axis_labels[2],fontsize=15,labelpad=labelpad[2])
ax.set_title(darray._title_for_slice())
if add_colorbar:
from matplotlib import cm
vmax=np.max(NPArr)
norm = mpl.colors.Normalize(0,vmax)
m = cm.ScalarMappable(cmap=palette,norm=norm)
m.set_array([])
ticks=np.linspace(1,vmax,levels+1)
fig.colorbar(m,ax=ax,extend="min",ticks=ticks)
return primitive
`
In this example I have a custom cmap to filter the 0 values as transparent (it's easier to read for a histogram) that's why I add a level in the colorbar and I created the axis before calling the function
I believe someone with a better understanding of the API could create a wrapper around matplotlib.pyplot.voxels easily. The _3dplot decorator would make some sense if you keep adding 3d plot methods on requests !
Additional context
No response