matplotlib / matplotlib

matplotlib: plotting with Python
https://matplotlib.org/stable/
20.16k stars 7.61k forks source link

[Bug]: Incorrect zorder of bar elements in a 3d histogram #25534

Closed mapfiable closed 1 year ago

mapfiable commented 1 year ago

Bug summary

I am trying to create a 3d histogram following the solution suggested here, which is based on [this matplotlib demo](Create 3D histogram of 2D data). In the finished plot however, the zorder of the bar sides are all messed up.

Code for reproduction

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm

# source: https://stackoverflow.com/a/56923189/5472354
def gaus2d(x=0, y=0, mx=0, my=0, sx=1, sy=1):
    return 1. / (2. * np.pi * sx * sy) * np.exp(
        -((x - mx) ** 2. / (2. * sx ** 2.) + (y - my) ** 2. / (2. * sy ** 2.)))

sample_radius = 5
n_samples = 11
x = np.linspace(-sample_radius, sample_radius, num=n_samples)
y = np.linspace(-sample_radius, sample_radius, num=n_samples)
z = gaus2d(*np.meshgrid(x, y))

fig = plt.figure(constrained_layout=True)
ax = fig.add_subplot(111, projection='3d')
ax.axis('off')
ax.margins(0)

hist, xedges, yedges = np.histogram2d(x, y, bins=n_samples)
xpos, ypos = np.meshgrid(xedges[:-1] + xedges[1:], yedges[:-1] + yedges[1:])

xpos = xpos.flatten() / 2.
ypos = ypos.flatten() / 2.
zpos = np.zeros_like(xpos)

dx = xedges[1] - xedges[0]
dy = yedges[1] - yedges[0]
dz = z.flatten()

cmap = cm.get_cmap('viridis')
max_height = np.max(dz)
min_height = np.min(dz)

rgba = [cmap((k-min_height)/max_height) for k in dz]

ax.bar3d(
    xpos, ypos, zpos, dx, dy, dz, color=rgba, zsort='average', shade=True
)

plt.show()

Actual outcome

grafik

Expected outcome

A plot where the zorder is correct (although I have to admit, the result looks like a neat art piece).

Additional information

I would guess it has something to do with the zsort argument, but I actually didn't manage to find the documentation for bar3d, only this post. I tried all three possibilities, but none of them worked. Is bar3d depreciated?

Operating system

Windows 10

Matplotlib Version

3.6.1

Matplotlib Backend

backend_interagg

Python version

3.9.7

Jupyter version

No response

Installation

conda

mapfiable commented 1 year ago

I just tested it with matplotlib version 3.7.1 and get the same result.

jklymak commented 1 year ago

Can you make an example w/o astropy?

mapfiable commented 1 year ago

@jklymak sure, I updated the MWE.

ksunden commented 1 year ago

For reference, the docs for bar3d:

https://matplotlib.org/stable/api/_as_gen/mpl_toolkits.mplot3d.axes3d.Axes3D.bar3d.html

And the zsort param comes from:

https://matplotlib.org/stable/api/_as_gen/mpl_toolkits.mplot3d.art3d.Poly3DCollection.html#mpl_toolkits.mplot3d.art3d.Poly3DCollection

See also:

12620

16071

13728

25333

That said, I'm going to close as a duplicate of #13728, which is already open and essentially the same.

mapfiable commented 1 year ago

@ksunden I see, thanks a lot for the quick feedback! Sorry, I wasn't aware of the other issues. Somehow I didn't manage to find them. If I understand correctly, this behaviour is because of a missing proper 3d engine, which unfortunately will not come any time soon?

Btw, I "fixed" this by saving the figure as an svg file and then manually reordering the different elements.

ksunden commented 1 year ago

yeah, there may be a path to properly ordering at least the narrow case of bar charts (which have only orthogonal rectangles, so there might be something that can be done there)

Essentially, the three (I'm pretty sure we only draw visible sides) polygons for each bar need to be drawn together and in a back to front as a unit.

Currently the Poly3DCollection has no such grouping of polygons, so each side is determined what effective zorder (because the whole collection only has one actual zorder in relation to any other object) that bar has. and since the "average" "min" or "max" position of each of those polys is not consistent between bars which one should take precedence, adjacent bars are drawn between the sets.

A general solution is not likely to be had in the near-medium term.

mapfiable commented 1 year ago

That's a shame, but understandable. The general case is not an easy thing to fix. Thanks a lot for the explanations!