Skip to content

Feature request : native support of 3D voxel plots #6537

Open
@Luluser

Description

@Luluser

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 :

simple_ax_example

multiple_axes_example

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions