matplotlib diagram background depending on data - python

I'm trying to plot some data and want to have a colored background depending on data.
In the following sample I want to have data1 and data2 on the left yaxis and data3 on right yaxis. This is working. But additionally I tried to colorize the background depending on data3.
How do I need to format the data to get it working?
import matplotlib.pyplot as plt
from datetime import datetime as dt
import matplotlib.dates as md
fig, ax1 = plt.subplots(constrained_layout=True)
data1 = [51.2, 51.2, 51.2, 50.7, 50.7, 50.5, 50.4, 50.7, 50.6]
data2 = [46.5, 46.1, 46.2, 46.3, 46.4, 46.3, 46.2, 46.1, 45.5]
data3 = [ 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0]
timestamps = [1524614516, 1524615134, 1524615587, 1524615910, 1524616235, 1524616559, 1524616866, 1524617189, 1524617511]
timestamps_ = [dt.utcfromtimestamp(x) for x in timestamps]
for data in (data1,data2):
ax1.plot(timestamps_, data, marker='.', linestyle='-')
ax1.set_ylabel("degC")
ax2 = ax1.twinx()
ax2.plot(timestamps_, data3, marker='x', linestyle='-')
ax2.pcolor(ax2.get_xlim(), ax2.get_ylim(), zip(timestamps_, data3), cmap='RdGn', alpha=0.3)
ax2.set_ylabel("ON OFF")
ax1.set_title("Mytitle")
for tick in ax1.xaxis.get_major_ticks():
tick.label1.set_horizontalalignment('right')
tick.label1.set_rotation(35)
xfmt = md.DateFormatter('%Y-%m-%d %H:%M:%S')
ax1.xaxis.set_major_formatter(xfmt)
plt.show()
Error message:
Traceback (most recent call last):
File "/home/tobias/workspace/python_pyplot_test/main.py", line 25, in <module>
ax2.pcolor(ax2.get_xlim(), ax2.get_ylim(), zip(timestamps_, data3), cmap='RdGn', alpha=0.3)
File "/usr/local/lib/python2.7/dist-packages/matplotlib/__init__.py", line 1855, in inner
return func(ax, *args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/matplotlib/axes/_axes.py", line 5732, in pcolor
X, Y, C = self._pcolorargs('pcolor', *args, allmatch=False)
File "/usr/local/lib/python2.7/dist-packages/matplotlib/axes/_axes.py", line 5576, in _pcolorargs
C.shape, Nx, Ny, funcname))
TypeError: Dimensions of C (9, 2) are incompatible with X (2) and/or Y (2); see help(pcolor)

Here's a minimal solution to what you want:
import matplotlib.pyplot as plt
from datetime import datetime as dt
import matplotlib.dates as md
import numpy as np
data3 = np.array([ 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0])
x=np.arange(9)
xp,yp=np.meshgrid(x,data3)
xp=xp.astype(float)-0.5
bgcolor=np.ones(xp.shape)*data3[None,:]
plt.pcolor(xp,yp,bgcolor)
plt.plot(x, data3, marker='x', linestyle='-')
I took out the second axis and all the tick stuff as they were not related to the problem itself.

Another option is to use axvspans:
One difference between using axvspan and pcolor is that the vertical span (rectangles) drawn by axvspan are unbounded in the y-direction while the pcolor rectangles are not. So if you use the zoom button to resize the plot, the axvspan rectangles will stretch to infinity (roughly speaking) while zooming out the pcolor rectangles will expose white areas. It's not a big deal, just thought you'd like to know.
Also note that if the vertical spans start at the first data point and extend to the next data point, then the last value in data3 never gets used. (Nine data points make eight vertical spans). If, however, you center the vertical spans around the data points -- so each data point is in the center of a span, then all 9 values in data3 can be used.
Uncomment the commented code below (and comment-out the current definition of timestamps_left and timestamps_right) to see the difference.
import numpy as np
import matplotlib.pyplot as plt
from datetime import datetime as dt
import matplotlib.dates as md
def topydates(timestamps):
return [dt.utcfromtimestamp(x) for x in timestamps]
fig, ax1 = plt.subplots(constrained_layout=True)
data1 = [51.2, 51.2, 51.2, 50.7, 50.7, 50.5, 50.4, 50.7, 50.6]
data2 = [46.5, 46.1, 46.2, 46.3, 46.4, 46.3, 46.2, 46.1, 45.5]
data3 = [ 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0]
timestamps = np.array([1524614516, 1524615134, 1524615587, 1524615910,
1524616235, 1524616559, 1524616866, 1524617189, 1524617511])
timestamps_ = topydates(timestamps)
for data in (data1,data2):
ax1.plot(timestamps_, data, marker='.', linestyle='-')
ax1.set_ylabel("degC")
ax2 = ax1.twinx()
ax2.plot(timestamps_, data3, marker='x', linestyle='-')
# if you want the axvspans to be centered around the data points
# widths = np.diff(timestamps)
# midpoints = timestamps[:-1] + widths/2.0
# timestamps_left = topydates(np.r_[timestamps[0]-widths[0]/2, midpoints])
# timestamps_right = topydates(np.r_[midpoints, timestamps[-1] + widths[-1]/2.0])
# if you uncomment the code above, then comment-out the line below:
timestamps_left, timestamps_right = timestamps_[:-1], timestamps_[1:]
cmap = plt.get_cmap('RdYlGn')
for left, right, val in zip(timestamps_left, timestamps_right, data3):
print(left, right)
color = cmap(val)
ax2.axvspan(left, right, facecolor=color, alpha=0.3)
ax2.set_ylabel("ON OFF")
ax1.set_title("Mytitle")
for tick in ax1.xaxis.get_major_ticks():
tick.label1.set_horizontalalignment('right')
tick.label1.set_rotation(35)
xfmt = md.DateFormatter('%Y-%m-%d %H:%M:%S')
ax1.xaxis.set_major_formatter(xfmt)
plt.show()

Related

How to get a unique plot when using matplolib gca()?

I am working on a program to add text labels above dots in a matplolib's 3d animation.
However when I use gca() to work on my label's axis (ax); I end up with two figures when calling plt.show().
two figures instead of one
---The program starts here---
import numpy as np
from PyAstronomy import pyasl
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from mpl_toolkits.mplot3d import proj3d
import mpl_toolkits.mplot3d.axes3d as p3
satellites_values = [[1.0, 2.0, 0.50, 0.0, 30.0, 0.0], [1.0, 1.0, 0.20, 0.0, 90.0, 0.0], [10.0, 2.0, 0.39, 0.0, 180.0, 0.0]]
satellites_pos = []
red_dots_pos = []
fig = plt.figure()
ax = plt.figure().gca(projection='3d')
anim = []
def update(i, pos, red_dot, annotation):
red_dot.set_data([pos[i][1], pos[i][0]])
red_dot.set_3d_properties(pos[i][2])
x2, y2, _ = proj3d.proj_transform(pos[i][1], pos[i][0], pos[i][2], ax.get_proj())
annotation.set_position((x2,y2))
return red_dot, annotation
def create_system(satellites_list):
for satellite in satellites_list:
t = np.linspace(0, 4, 200)
orbit = pyasl.KeplerEllipse(a=satellite[0], per=satellite[1], e=satellite[2], Omega=satellite[3], i=satellite[4], w=satellite[5])
pos = orbit.xyzPos(t)
red_dot, = ax .plot(pos[::, 1], pos[::, 0], pos[::, 2], 'ro')
text = 'aaa'
annotation = ax.text2D(pos[::, 1],pos[::, 0], text)
anim.append(animation.FuncAnimation(fig, update, 200, fargs=(pos, red_dot, annotation), interval=100, blit=False))
ax.plot(pos[::, 1], pos[::, 0], pos[::, 2], 'k-')
create_system(satellites_values)
ax.plot([0], [0], [0], 'bo', markersize=20, label="Earth")
# Hide grid lines
ax.grid(False)
# Hide axes ticks
ax.set_xticks([])
ax.set_yticks([])
ax.set_zticks([])
plt.style.use('default')
plt.legend()
plt.show() ```
When I try:
ax = fig.gca(projection='3d')
or
ax = fig.add_subplot(111, projection='3d')
I get the following error:
TypeError: only size-1 arrays can be converted to Python scalars.
I want to have a unique figure because this program is part of a GUI that's supposed to show the dots with their label.
I was wondering if anyone here knew how to fix this.

Pcolormesh value not mapped to correct bin in matplotlib BoundaryNorm

I'm plotting a single row of data using pcolormesh to provide color coding for statistical test output alongisde a timeseries.
I use a discretized colormap, and with the the correct number of colors versus bins in boundarynorm, I'm seeing that the values I'm trying to plot are not being mapped to the correct color. For example, the third data point and bar has the value 94, but is taking the color mapped between 95 and 97.5. Example below.
import matplotlib.pyplot as plt
import numpy as np
import matplotlib as mpl
# bbox
cb_x0 = 0.95
cb_y0 = 0.1225
cb_xlen = 0.05
cb_ylen = 0.205
cmap_whole = plt.cm.get_cmap('YlOrRd')
cmap0 = 'white'
cmap1 = cmap_whole(0.1)
cmap2 = cmap_whole(0.2)
cmap3 = cmap_whole(0.4)
cmap4 = cmap_whole(0.6)
cmap5 = cmap_whole(0.9)
colors = [cmap0,cmap1,cmap2,cmap3,cmap4,cmap5]
cmap_cl = mpl.colors.ListedColormap(colors, N=len(colors))
values = [85,90,92.5,95,97.5,99,100]
# coordinates and data
x = np.arange(1985,2020,3)
y = np.arange(0,2,1)
data = [17.0, 99.0, 94.0, 98.0, 82.0, 99.0, 99.0, 45.0, 55.0, 57.0, 94.0]
# plotting
f,ax = plt.subplots(nrows=1,ncols=1)
h = ax.pcolormesh(x,y,np.reshape(data,(1,len(data))), cmap = cmap_cl, vmin = 85, vmax = 100)
ax.tick_params(left=False,labelleft=False,bottom=True,labelbottom=False)
norm = mpl.colors.BoundaryNorm(values,6,clip=True)
cbax = f.add_axes([cb_x0, cb_y0, cb_xlen, cb_ylen])
cb = mpl.colorbar.ColorbarBase(ax=cbax, cmap=cmap_cl,
norm=norm,
spacing='proportional',
orientation='vertical',
boundaries=values,
extend='neither',
ticks=values)
cb.ax.tick_params(labelcolor='0.2', labelsize=7, color='k', width=0.5, direction='out');
cb.ax.set_yticklabels(['0%','90%',None,'95%',None,None,'100%'])
cb.outline.set_linewidth(0.6)
plt.show(h)
here is my figure

Matplotlib Table Y-axis Alignment to Chart

I've seen plenty of examples online of x-axis alignment for matplotlib Table with charts, but I can't figure out how to y-axis align my table. The following code produces the table pictured after it.
# For minimum viable example
dfx = [['ARS FX', 0.025346713729, 0.028238, 0.021889, 0.07701426, 0.0, 35, 39, '14.7%', 0.0, 0.07701426], ['BRL FX',1.83316130513e-05,0.025746,-0.072473, 0.143642325, 0.0, 40, 45, '12.3%', 0.0, 0.143642325], ['EUR FX', -0.301254060209, -0.300762, -0.290554, 0.0, -0.30127866, -60, -40, '5.2%', -0.30127866, 0.0], ['ZAR FX', 0.0515621470331, 0.053191, 0.044245, 0.07344438, 0.0, 10, 29, '14.1%', 0.0, 0.07344438], ['AR Eqity', 3.68762762118e-06, 0.0,0.0, 0.08817912, 0.0, 45, 45, '23.9%', 0.0, 0.08817912]]
dfx = pd.DataFrame(dfx)
dfx.columns = ['IdeaName', 'ModelWeight', 'Exposure_FXA', 'Exposure','Adj_Ubound', 'Adj_Lbound', 'lt_rob', 'st_rob', 'implied_vol', 'Lower Bound', 'Upper Bound']
# Plot
_, ax = plt.subplots()
dfx[['Lower Bound']].plot(kind='barh',ax=ax,color='red')
dfx[['Upper Bound']].plot(kind='barh',ax=ax,color='green')
plt.plot(dfx['ModelWeight'],range(len(dfx)), linestyle="", markersize=5, marker="o", color="#ff6600", label="ModelWeight", markeredgecolor="k")
plt.plot(dfx['Exposure'],range(len(dfx)), linestyle="", markersize=5, marker="o", color='lightblue', label="Exposure", markeredgecolor="k")
# Add a table at the bottom of the axes
columns = ['LT','ST','Vol']
the_table = ax.table(cellText=dfx[['lt_rob','st_rob','implied_vol']].values,
rowLabels=list(dfx['IdeaName']),
# rowColours=colors,
colWidths=[0.1 for x in columns],
colLabels=columns,
cellLoc='center',
loc=15)
the_table.auto_set_font_size(False)
the_table.set_fontsize(9)
the_table.scale(1, 1.05)
plt.subplots_adjust(left=0.4)
plt.yticks([])
plt.legend()
plt.show()
As you can see, despite me manually playing with the_table.scale() to get it as close as possible, I can't get the rows aligned with my bars, since the column headers of the table are taking up the first row.
Any help appreciated. Thanks.
The following would put the table into a bounding box that is one nth larger than the number of bars n. One then needs to make sure the margins within the axes are correct as well as the subplot parameter. The below only adjusts the vertical direction (the horizontal direction needs to be done by hand for longer labels).
import numpy as np
import matplotlib.pyplot as plt
data = np.random.randint(1,999,size=(10,4))
col = list("ABCD")
row = np.arange(1, len(data)+1)
n = len(data) # number of bars
barwidth = 0.8 # bar width
t = 0.05
b = 0.125
# Plot
fig, ax = plt.subplots()
fig.subplots_adjust(left=0.4, top=1-t-(1-t-b)/(n+1))
ax.margins(y=(1-barwidth)/2/n)
ax.barh(row, data[:,0], height=barwidth, label="bars", )
the_table = ax.table(cellText=data,
rowLabels=row,
colLabels=col,
cellLoc='center',
bbox=(-0.6, 0.0, 0.6, (n+1) / n))
the_table.auto_set_font_size(False)
the_table.set_fontsize(9)
fig.canvas.draw() # need to draw the figure twice
plt.yticks([])
plt.legend()
plt.show()

Plotting Curved Lines in Python

I'd like to plot curved lines of a specific arch like shape, below is how far I've gotten using specific values (these values need to be used) but it plots straight lines.
I'm also having trouble formatting the y axis the way I want. It's a log scale and I'd like it to go up to 1 (like in the ideal plot above). Some help would be great, thanks! =)
The reason why your line is not stretching on a log scale plot is because there are no points between the points that are on the top and on the bottom. log plot does not curve the lines, only place the points on a different scale, the line between them are still straight.
To change this, we add more points between dots. and the result will become curved.
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.ticker import ScalarFormatter
# Data for plotting
t = [0.0, 62.5, 125.0, 187.5, 250, 312.5, 375, 437.5, 500]
s = [0.1, 0.005, 0.1, 0.005, 0.1, 0.005, 0.1, 0.005, 0.1]
def extendlist(l):
master = []
for i in range(len(l)-1):
x = np.linspace(l[i], l[i+1], 50)
master.extend(x)
return master
t = extendlist(t)
s = extendlist(s)
fig, ax = plt.subplots()
ax.semilogy(t, s)
ax.set(xlabel='x axis', ylabel='y axis', title='Stuff')
plt.xlim((0,500))
plt.ylim((0.001, 1))
plt.show()
This will generate what you graphed on paper.
you can use interp1d
import matplotlib.pyplot as plt
import numpy as np
from scipy.interpolate import interp1d
t = [0.0, 62.5, 125.0, 187.5, 250, 312.5, 375, 437.5, 500]
s = [0.1, 0.005, 0.1, 0.005, 0.1, 0.005, 0.1, 0.005, 0.1]
tnew = np.linspace(0, 500, num=1001, endpoint=True)
f = interp1d(t, s)
plt.semilogy(tnew, f(tnew))
plt.ylim((0.001, 1))
plt.show()

matplotlib bar3d clipping problems

I am trying to create a 3D bar graph with Matplotlib 1.2.0 and Python 2.7.3. I followed the advice in http://www.mail-archive.com/matplotlib-users#lists.sourceforge.net/msg19740.html and plotted the bar one by one, but I am still getting rendering problems (i.e., bars on top of each other).
Moreover, I get the following when I invoke my code:
/usr/apps/python/lib/python2.7/site-packages/mpl_toolkits/mplot3d/axes3d.py:1476: RuntimeWarning: divide by zero encountered in divide for n in normals])
/usr/apps/python/lib/python2.7/site-packages/mpl_toolkits/mplot3d/axes3d.py:1476: RuntimeWarning: invalid value encountered in divide for n in normals])
My questions:
Are these serious warnings? Do I need to look into them and try to
eliminate them? How do I eliminate them?
What is the difference between zsort='max' and zsort='average'?
What else can I do to eliminate rendering problems?
Thanks in advance!
Here is my code:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.colors as colors
import matplotlib.cm as cmx
# my data
dat = [2.31778665482167e-310, 0.006232785101850947, 0.0285075971030949, 0.0010248181570355695, 0.0048776795767614825, 0.02877090365176044, 0.002459331469834533, 0.0008594610645495889, 0.002919824084878003, 0.000968081117692596, 0.0, 0.0, 0.0319623949119874, 0.00568752311279771, 0.009994801469036968, 0.03248018520506219, 0.006686905726805326, 0.005987863156039365, 0.0072955095915350045, 0.005568911905473998, 0.0, 0.0, 0.0, 0.028483143996551524, 0.031030793902192794, 0.06125216053962635, 0.02935971973938871, 0.028507530280092265, 0.030112963748812088, 0.028293406731749605, 0.0, 0.0, 0.0, 0.0, 0.004510645022825792, 0.028998119822468988, 0.0013993630391143715, 0.0010726572949244424, 0.002288215944285159, 0.0006513973584945584, 0.0, 1.1625e-320, 1.15348834e-316, 2.3177866547513e-310, 0.0, 0.03148966953869102, 0.005215047563268979, 0.004491716298086729, 0.006010166308872446, 0.005186976949223524, 0.0, 0.0, 0.0, 0.0, 0.0, 1.107e-320, 0.02983657915729719, 0.028893006725328373, 0.030526067389954753, 0.028629390713739978, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0015217840289869456, 0.002751587509779179, 0.001413669523724954, 1.15348834e-316, 2.3177866547513e-310, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0024680339073824705, 0.0008254364860386303, 0.0, 0.0, 0.0, 9.965e-321, 1.15348834e-316, 2.3177866547513e-310, 0.0, 0.0, 0.0, 0.002621588539481613, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 9.41e-321, 1.15348834e-316, 2.3177866547513e-310]
dat = np.reshape(dat,[10,10],order='F')
lx = len(dat[0])
ly = len(dat[:,0])
n = lx*ly
# generate colors
cm = plt.get_cmap('jet')
vv = range(len(dat))
cNorm = colors.Normalize(vmin=0, vmax=vv[-1])
scalarMap = cmx.ScalarMappable(norm=cNorm, cmap=cm)
colorVals = [scalarMap.to_rgba(i) for i in range(ly)]
# generate plot data
xpos = np.arange(0,lx,1)
ypos = np.arange(0,ly,1)
xpos, ypos = np.meshgrid(xpos+0.25, ypos+0.25)
xpos = xpos.flatten()
ypos = ypos.flatten()
zpos = np.zeros(n)
dx = 0.5*np.ones_like(zpos)
dy = dx.copy()
dz = dat.flatten()
cc = np.tile(range(lx), (ly,1))
cc = cc.T.flatten()
# generate plot
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
opacity = 1
for i in range(n):
ax.bar3d(xpos[i], ypos[i], zpos[i], dx[i], dy[i], dz[i],
color=colorVals[cc[i]], alpha=opacity, zsort='max')
plt.autoscale(enable=True, axis='both', tight=True)
plt.grid()
plt.show(block=False)
This isn't the answer that you are looking for, but I think that this might be a bug in matplotlib. I think that the same problem was encountered here. The problem was described as "intractable" according to the mplot3d FAQ.
But to me it doesn't seem intractable. You simple need to figure out which object is closer to the viewer and set the z-order accordingly. So, I think that the problem might just be a bug.
If I take the matplotlib 3D histogram example and just change "bins=4" to "bins=6" or a higher number, then I get the same "axes3d.py:1476: RuntimeWarning: invalid value encountered in divide / for n in normals])". Also, I can reproduce the wrong z-order of the bars (check out the tall guy near the front who jumps in front of his short friend):
The incorrect ordering of the bars seems linked to the divide by zero error, since the plots look just fine when I use a smaller number of bins.
Line 1476 in axes.py is:
shade = np.array([np.dot(n / proj3d.mod(n), [-1, -1, 0.5]) for n in normals])
Basically, I think it is trying to figure out the shading using the normal vectors to each face. But, one or more of the normal vectors is zero, which should not be the case. So, I think that this is just some bug in matplotlib that can probably be fixed by someone with more programming skills than myself.
The mplot3d FAQ is correct that MayaVI can be used if you want a better 3D engine. I used
from mayavi import mlab
mlab.barchart(xpos,ypos,dz*100)
to generate a plot of your data:
I hope that this gets figured out soon. I would like to make some similar 3D barcharts in the near future.
This answer is a quick fix solution that allows you to produce certain types of 3D bar charts in matplotlib with correct rendering. The trick is to A) plot the bars individually, B) hack the zsort algorithm to force sorting the bars w.r.t. the "distance" from the camera. This can be done by overwrite the _sort_zpos attribute of the PolyCollection3D instance returned by ax.bar3d. The following code demonstrates the solution using data drawn from a 2D Gaussian.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from scipy.stats import multivariate_normal
def sph2cart(r, theta, phi):
'''spherical to cartesian transformation.'''
x = r * np.sin(theta) * np.cos(phi)
y = r * np.sin(theta) * np.sin(phi)
z = r * np.cos(theta)
return x, y, z
def sphview(ax):
'''returns the camera position for 3D axes in spherical coordinates'''
r = np.square(np.max([ax.get_xlim(), ax.get_ylim()], 1)).sum()
theta, phi = np.radians((90-ax.elev, ax.azim))
return r, theta, phi
def ravzip(*itr):
'''flatten and zip arrays'''
return zip(*map(np.ravel, itr))
#Generate data
res = 15
sl = slice(-3, 3, complex(res))
Y, X = np.mgrid[sl, sl]
grid = np.array([X, Y])
(dx,), (dy,) = 0.8*np.diff(X[0,:2]), 0.8*np.diff(Y[:2,0])
#2D Gaussian
mu = (0, 0)
covm = np.array([[ 0.8, 0.3],
[ 0.3, 0.5]])
rv = multivariate_normal(mu, covm)
Zg = rv.pdf(grid.transpose(1,2,0)).T
#generate the figure
fig, (ax1, ax2) = plt.subplots(1,2, subplot_kw=dict(projection='3d'))
#standard bar3d
ax1.set_title('Standard')
ax1.bar3d(X.ravel(), Y.ravel(), np.zeros(X.size), dx, dy, Zg.ravel(), '0.85')
#Fixed bar3d
ax2.set_title('Fixed')
xyz = np.array(sph2cart(*sphview(ax2)), ndmin=3).T #camera position in xyz
zo = np.multiply([X, Y, np.zeros_like(Zg)], xyz).sum(0) #"distance" of bars from camera
bars = np.empty(X.shape, dtype=object)
for i, (x,y,dz,o) in enumerate(ravzip(X, Y, Zg, zo)):
j, k = divmod(i, res)
bars[j, k] = pl = ax2.bar3d(x, y, 0, dx, dy, dz, '0.85')
pl._sort_zpos = o
plt.show()
Which produces the following figure:
Note: This will only work for the initial viewing angle. If you rotate the axes, you will have to set the _sort_zpos for all the bars again and redraw the canvas to fix the rendering.
I took apodemus's code, which apparently works, unpacked it, and applied it to the original question to provide a direct answer for it. My code could certainly be cleaned up - particularly the loop in getDistances() - but it does solve the presented problem, and should be much easier to follow. To surmise, the distance to the viewer, i.e., the camera distance, has to be determined by calling sphview() and sph2cart(). Then the distances of all of the bars from the camera must be calculated by calling getdistances(). Thereafter, the bars should be drawn one and a time, and crucially, each bar's z-order must be explicitly set based on the previously determined distances.
If the resultant graph is rotated live in the plot window, it may not be updated correctly. However, presetting the location of the camera allows arbitrary initial views to be plotted without error. (There may well be a callback mechanism that could be invoked to cause the z-orders of the bars to be explicitly recalculated, but I have no knowledge of such an API.) The location of the camera can be preset by passing azim and elev to fig.add_subplot(). Its distance can be changed by setting the dist field of the Axes instance returned by fig.add_subplot().
Following is the graph produced by the updated code applied to the original question:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.colors as colors
import matplotlib.cm as cmx
# from apodemus's Stackoverflow answer,
# https://stackoverflow.com/questions/18602660/matplotlib-bar3d-clipping-problems
def sph2cart(r, theta, phi):
'''spherical to Cartesian transformation.'''
x = r * np.sin(theta) * np.cos(phi)
y = r * np.sin(theta) * np.sin(phi)
z = r * np.cos(theta)
return x, y, z
def sphview(ax):
'''returns the camera position for 3D axes in spherical coordinates'''
r = np.square(np.max([ax.get_xlim(), ax.get_ylim()], 1)).sum()
theta, phi = np.radians((90-ax.elev, ax.azim))
return r, theta, phi
#
# end of apodemus's code
def getDistances(view):
distances = []
a = np.array((xpos, ypos, dz))
for i in range(len(xpos)):
distance = (a[0, i] - view[0])**2 + (a[1, i] - view[1])**2 + (a[2, i] - view[2])**2
distances.append(np.sqrt(distance))
return distances
# ================================================================
# my data
dat = [2.31778665482167e-310, 0.006232785101850947, 0.0285075971030949, 0.0010248181570355695, 0.0048776795767614825, 0.02877090365176044, 0.002459331469834533, 0.0008594610645495889, 0.002919824084878003, 0.000968081117692596, 0.0, 0.0, 0.0319623949119874, 0.00568752311279771, 0.009994801469036968, 0.03248018520506219, 0.006686905726805326, 0.005987863156039365, 0.0072955095915350045, 0.005568911905473998, 0.0, 0.0, 0.0, 0.028483143996551524, 0.031030793902192794, 0.06125216053962635, 0.02935971973938871, 0.028507530280092265, 0.030112963748812088, 0.028293406731749605, 0.0, 0.0, 0.0, 0.0, 0.004510645022825792, 0.028998119822468988, 0.0013993630391143715, 0.0010726572949244424, 0.002288215944285159, 0.0006513973584945584, 0.0, 1.1625e-320, 1.15348834e-316, 2.3177866547513e-310, 0.0, 0.03148966953869102, 0.005215047563268979, 0.004491716298086729, 0.006010166308872446, 0.005186976949223524, 0.0, 0.0, 0.0, 0.0, 0.0, 1.107e-320, 0.02983657915729719, 0.028893006725328373, 0.030526067389954753, 0.028629390713739978, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0015217840289869456, 0.002751587509779179, 0.001413669523724954, 1.15348834e-316, 2.3177866547513e-310, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0024680339073824705, 0.0008254364860386303, 0.0, 0.0, 0.0, 9.965e-321, 1.15348834e-316, 2.3177866547513e-310, 0.0, 0.0, 0.0, 0.002621588539481613, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 9.41e-321, 1.15348834e-316, 2.3177866547513e-310]
dat = np.reshape(dat,[10,10],order='F')
lx = len(dat[0])
ly = len(dat[:,0])
n = lx*ly
# generate colors
cm = plt.get_cmap('jet')
vv = range(len(dat))
cNorm = colors.Normalize(vmin=0, vmax=vv[-1])
scalarMap = cmx.ScalarMappable(norm=cNorm, cmap=cm)
colorVals = [scalarMap.to_rgba(i) for i in range(ly)]
# generate plot data
xpos = np.arange(0,lx,1)
ypos = np.arange(0,ly,1)
xpos, ypos = np.meshgrid(xpos+0.25, ypos+0.25)
xpos = xpos.flatten()
ypos = ypos.flatten()
zpos = np.zeros(n)
dx = 0.5*np.ones_like(zpos)
dy = dx.copy()
dz = dat.flatten()
cc = np.tile(range(lx), (ly,1))
cc = cc.T.flatten()
# generate plot
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
opacity = 1
# Get the camera's location in Cartesian coordinates.
x1, y1, z1 = sph2cart(*sphview(ax))
camera = np.array((x1,y1,0))
# Calculate the distance of each bar from the camera.
z_order = getDistances(camera)
max = max(z_order)
for i in range(n):
pl = ax.bar3d(xpos[i], ypos[i], zpos[i], dx[i], dy[i], dz[i],
color=colorVals[cc[i]], alpha=opacity, zsort='max')
# The z-order must be set explicitly.
#
# z-order values are somewhat backwards in magnitude, in that the largest
# value is closest to the camera - unlike, in say, a coordinate system.
# Therefore, subtracting the maximum distance from the calculated distance
# inverts the z-order to the proper form.
pl._sort_zpos = max - z_order[i]
plt.autoscale(enable=True, axis='both', tight=True)
plt.grid()
plt.show()
This approach (as opposed to using Mayavi to handle 3D drawing, for example) allows the matplotlib appearance to be retained in the graph itself, as well as its adornments such as axes numbers, labels, and legends.
I think marisano 's answer has a problem of rendering with various height because it uses the Euclidean distance from the top of a bar to the camera position and substract this value from maximal z_order, I don't think it is the correct way. Finally I adopt the same measurement of z_order from apodemus and updated it below:
z_order = np.multiply([xpos,ypos, np.zeros_like(xpos)],camera).sum(0)
and
pl._sort_zpos = z_order[i]
Now it works in my case.

Categories