Repository navigation
Open
Labels
Difficulty: Mediumhttps://matplotlib.org/devdocs/devel/contribute.html#good-first-issueshttps://matplotlib.org/devdocs/devel/contribute.html#good-first-issuesNew featurekeepItems to be ignored by the “Stale” Github ActionItems to be ignored by the “Stale” Github Actiontopic: mplot3d
Description
Problem
Hi;
I'm working with 3D plots and I use colored voxels to plots a 4th dimension.
When one wants to use a colorbar, one usually uses an image or a contourf in the colorbar creation method like so :
img = ax.imshow(event, **cmap_args)
plt.colorbar(img)
It is very handful to handle all the colormap arguments (such as vmin etc) directly from the image but doesn't work with voxels.
Currently I use this :
`
palette=mpl.cm.get_cmap("viridis",lut=levels).copy() #simulate the level arg of other plot methods
palette.set_under((0,0,0,0)) #So vmin has an effect
if add_colorbar:
vmax = np.max(NPArr) #NPArr is the data being plotted
norm = mpl.colors.Normalize(0,vmax) #Necessary because there is no mappable from voxels, instead of normalizing the data it is easier to normalize the "cmap"
m = cm.ScalarMappable(cmap=palette,norm=norm) #Creates Mappable
m.set_array([]) #Makes it the right type so get() methods exist
ticks = np.linspace(1,vmax,levels+1)
fig.colorbar(m, ax=ax, extend="min", ticks=ticks) #Finally creates the cmap with other cmap arguments that can't be passed to the mappable
`
Voxels give dictionnary of 3d objet so I have tried to use one of its value as mappable which works but doesn't bring all the data info for the colormap.
Proposed solution
No response
Metadata
Metadata
Assignees
Labels
Difficulty: Mediumhttps://matplotlib.org/devdocs/devel/contribute.html#good-first-issueshttps://matplotlib.org/devdocs/devel/contribute.html#good-first-issuesNew featurekeepItems to be ignored by the “Stale” Github ActionItems to be ignored by the “Stale” Github Actiontopic: mplot3d
Activity
jklymak commentedon May 4, 2022
Voxels doesn't do colormapping so how are we to infer the colormap map from the artist? Please provide a complete example, but this doesn't seem possible.
tacaswell commentedon May 4, 2022
It seems reasonable that we "promote" the artists used in Voxel to be a scalar mappable.
timhoffm commentedon May 5, 2022
The API extension would be straight forward:
ax.voxels([x, y, z,] /, filled, ...alternatively to bool, filled could accepts numeric values to be mapped (with NaN meaning unfilled).
The main task however is to create a new
VoxelCollectionArtist. Currently we return a dict ofPoly3DCollection, but we need one container Artist that derives from ScalarMappable and can carry the numeric and colormapping information.Luluser commentedon May 6, 2022
I'm not sure if you need a working exmaple anymore but here it is :
let's say you have 4D data ie a value for every point in 3D and that is is sparse enough not to make voxels in the whole 3D space.
My personal case is a Histogram ie a at every point in the 3D space you have the number of occurence of your three simultaenous axes variables most of it value being 0.
The 3 axes variables binned and the 3D histogram :
To create the BoolArray for the voxels method I use a mask on those the minimal value I want to display (let's say 0 for simplicity):
To color the voxels by the value in the histogram I use a colormap on the normalized data :
The slight transparency will make the plot more readable. I now have a color for every point in space, since the BoolArray will provide the actual voxels that need to be plotted I don't have to mask this ColorArray.
I can now call :
which makes the plot I'm looking for.
However for the cbar it is a bit more complicated,
Here is the whole code adapted from the one I currently use :
`
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
`
So voxels would need a new argument "value" and that we can pass on a cmap so thats it store internally the data to give the right mappable. Or perhaps something less complicated would be that the BoolArray could actually be an array of float and that the arguments of vmin and vmax would control which values to be displayed by masking them out. The Nan values would replace the False values so we could imagine a FloatArray anyway where Nan values would be ignored in the plotting it would makes sense in the plotting of datasets too !
I hope it helps
Luluser commentedon May 6, 2022
Oh ! That's exactly what I had in mind. I have no idea if my request is actually tough though, I don't know much of this part of the API.
greglucas commentedon May 6, 2022
@Luluser, in case it is of interest, we are having a new contributors meeting this next week. We could walk through this section of the API then if that would be helpful at all. https://discourse.matplotlib.org/t/matplotlib-devel-announcing-the-new-contributors-meeting/22808
jklymak commentedon May 6, 2022
I think that needs to be a different axes method?
But regardless, currently you are doing the mapping manually so it makes sense that you need to create the colorbar manually.
github-actions commentedon Dec 5, 2025
This issue has been marked "inactive" because it has been 365 days since the last comment. If this issue is still present in recent Matplotlib releases, or the feature request is still wanted, please leave a comment and this label will be removed. If there are no updates in another 30 days, this issue will be automatically closed, but you are free to re-open or create a new issue if needed. We value issue reports, and this procedure is meant to help us resurface and prioritize issues that have not been addressed yet, not make them disappear. Thanks for your help!
story645 commentedon Dec 5, 2025
@trygvrad would this be easier to implement w/ the colorizer architecture?
trygvrad commentedon Dec 6, 2025
Yes, I believe it is.
From what I can see,
ax.voxels()returns a dictionary ofPoly3DCollection, which inherits:Poly3DCollection ← PolyCollection ← _CollectionWithSizes ← Collection ← ColorizingArtistThis means that there is already a
Colorizerobject associated with each voxel as it is rendered, because the creation of aColorizingArtistinstantiates aColorizerif one is not provided as a keyword argument.If we include a
colorizer=colorizerto the initialization of eachPoly3DCollection, it is trivial to have a singlecolorizerthat is responsible for all voxels.I made a branch that implements the required changes with as single commit
with this you are able to do:
For the implementation I let the data be input via the
facecolorsargument:This makes the most sense to me, but we could alternatively add an additional keyword argument, as has been suggested previously.
The line
is not very elegant. It arises because of the return type of
ax.voxels(), which is a dictionary.We can fix this by creating a new class as the return type for
ax.voxels(), but I am not sure if this is strictly necessary.@story645 what do you think?
If you think this is a reasonable solution I can add tests to my branch and make a PR.
story645 commentedon Dec 8, 2025
Thanks for the quick workup. I'm not sure about passing in via facecolors b/c then in theory folks may also want to do edgecolors - I like @timhoffm's suggestion of using
filled#22969 (comment)?If it doesn't get too cranky, I think a
VoxelCollectionartist would make sense for a couple of reasons, and we've been introducing more semantic artists lately.But that can also all be discussed on the PR.
trygvrad commentedon Dec 9, 2025
hmmm, I can take a crack at seeing what would be required of a
VoxelCollection,Will
VoxelCollectioncontain a dictionary ofPoly3DCollectionobjects, i.e. an artist containing other artists, or do we think that we can make theVoxelCollectionthe only artist?I'm not familiar enough with the 3D pipeline to know what is needed for the objects to be ordered correctly in screen depth, and if this has any impact of single/multiple artists.
(are there other kinds of artists that contain artists?)
WeatherGod commentedon Dec 9, 2025