I'm trying to make an interactive spider chart using Bokeh.
The chart it's almost done, there are only a couple of things missing that even with the documentation I'm not able to get. In particular I would like to have a label that come out when I pass the mouse over a zone (eg: for the "yellow area" I would like to see its count values for each vertex with the vertex name). Finally the last problem is that my chart polygon is missing the edge connecting the first and last vertex.
I leave here my code with a sample of the data I'm using, thank you for the help.
import numpy as np
import pandas as pd
import math
from bokeh.io import output_file, show
from bokeh.models import BasicTicker, ColorBar, LinearColorMapper, ColumnDataSource, PrintfTickFormatter, LabelSet, Legend, HoverTool
from bokeh.plotting import figure
from bokeh.transform import transform
from bokeh.palettes import viridis
mod_cat_ID = pd.DataFrame(columns=['ID','Name'])
mod_cat_ID['ID'] = [1,2,3,4,5,6,7,8,9,10,'X','?']
mod_cat_ID['Name'] = ['Carbohydrate metabolism','Energy metabolism','Lipid metabolism','Nucleotide metabolism','Amino acid metabolism','Glycan metabolism','Metabolism of cofactors and vitamins','Biosynthesis of terpenoids and polyketides','Biosynthesis of other secondary metabolites','Xenobiotics biodegradation','Signature Modules','Unknown']
df=pd.DataFrame(columns=['Pheno_ID','Genes_count','Norm_count'])
df['Pheno_ID'] = ['MP:0000218','MP:0000321','MP:0010093','MP:0001985','MP:0011094']
df['Genes_count'] = [[689, 248, 476, 97, 0, 800, 0, 0, 0, 0],[0, 0, 180, 0, 0, 0, 236, 0, 0, 0],[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],[120, 0, 0, 0, 88, 0, 0, 0, 0, 0],[367, 600, 347, 240, 361, 404, 236, 0, 0, 0]]
df['Norm_count'] = [[0.86125, 0.31, 0.595, 0.12125, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0],[0.0, 0.0, 0.225, 0.0, 0.0, 0.0, 0.295, 0.0, 0.0, 0.0],[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],[0.15, 0.0, 0.0, 0.0, 0.11, 0.0, 0.0, 0.0, 0.0, 0.0],[0.45875, 0.75, 0.43375, 0.3, 0.45125, 0.505, 0.295, 0.0, 0.0, 0.0]]
#The last 2 categories are not counted and showed
n_vertex = len(mod_cat_ID) - 2
theta = np.linspace(0, 2 * np.pi, n_vertex, endpoint=False)
# rotate theta such that the first axis is at the top
theta += np.pi / 2
centre = 1
def unit_poly_verts(theta, centre):
"""Return vertices of polygon for subplot axes.
This polygon is circumscribed by a unit circle centered at (centre, centre)
"""
x0, y0, r = [centre] * 3
verts = [(r * np.cos(t) + x0, r * np.sin(t) + y0) for t in theta]
return verts
def radar_patch(r, theta, centre):
""" Returns the x and y coordinates corresponding to the magnitudes of
each variable displayed in the radar plot
"""
# offset from centre of circle
offset = 0
yt = (r * centre + offset) * np.sin(theta) + centre
xt = (r * centre + offset) * np.cos(theta) + centre
return xt, yt
verts = unit_poly_verts(theta, centre)
x = [v[0] for v in verts]
y = [v[1] for v in verts]
fig = figure(title="Phenotype/Pathway module - Radar plot")
#Here I need only the first 10 categories, the last 2 are not counted and showed
text = mod_cat_ID['Name'][0:10]
source = ColumnDataSource({'x': x , 'y': y , 'text': text})
fig.line(x="x", y="y", source=source)
labels = LabelSet(x="x", y="y", text="text", source=source)
fig.add_layout(labels)
cols = viridis(len(df))
for i in range(len(df)):
xt, yt = radar_patch(np.asarray(df['Norm_count'][i]), theta, centre)
fig.patch(x=xt, y=yt, fill_alpha=0.15, fill_color=cols[i], line_color=cols[i], legend_label=str(df['Pheno_ID'][i]))
fig.circle([centre],[centre], color = 'black')
fig.axis.visible = False
fig.xgrid.grid_line_color = None
fig.ygrid.grid_line_color = None
fig.legend.location = 'top_left'
fig.legend.click_policy = 'hide'
show(fig)
Related
I have been struggling to find a way to get the determined parameters for the curve fit function below to print. The graph properly matches my data, but I can't figure out how to get the equation it produced. Any help would be appreciated!
import matplotlib.pyplot as plt
import numpy as np
from scipy.optimize import curve_fit
x_data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]
y_data = [.99, 1, .98, .93, .85, .77, .67, .56, .46, .36, .27, .19, .12, .07, .03, .01, 0, .01, .05, .09, .16, .24, .33, .44, .55, .65, .76, .85, .93, .98, 1]
x_val = np.array(x_data)
y_val = np.array(y_data)
def fitFunc(x, a, b, c, d):
return a * np.sin((2* np.pi / b) * x - c) + d
print(a, b, c, d)
plt.plot(x_val, y_val, marker='.', markersize=0, linewidth='0.5', color='green')
popt, pcov = curve_fit(fitFunc, x_val, y_val)
plt.plot(x_val, fitFunc(x_val, *popt), color='orange', linestyle='--')
Here is a graphing example that uses your data, note the equation. This example uses initial parameter estimates that were manually estimated from a scatterplot of the data, the default curve_fit estimates are all 1.0 by default and those do not work well in this case.
import numpy as np
import scipy, matplotlib
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
xData = np.array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0, 30.0, 31.0])
yData = np.array([.99, 1.0, 0.98, 0.93, 0.85, 0.77, 0.67, 0.56, 0.46, 0.36, 0.27, 0.19, 0.12, 0.07, 0.03, 0.01, 0, 0.01, 0.05, 0.09, 0.16, 0.24, 0.33, 0.44, 0.55, 0.65, 0.76, 0.85, 0.93, 0.98, 1.0])
def fitFunc(x, amplitude, center, width, offset):
return amplitude * np.sin(np.pi * (x - center) / width) + offset
# these are the curve_fit default parameter estimates, and
# do not work well for this data and equation - manually estimate below
#initialParameters = np.array([1.0, 1.0, 1.0, 1.0])
# eyeball the scatterplot for some better, simple, initial parameter estimates
initialParameters = np.array([0.5, 1.0, 16.0, 0.5])
# curve fit the test data using initial parameters
fittedParameters, pcov = curve_fit(fitFunc, xData, yData, initialParameters)
print(fittedParameters)
modelPredictions = fitFunc(xData, *fittedParameters)
absError = modelPredictions - yData
SE = np.square(absError) # squared errors
MSE = np.mean(SE) # mean squared errors
RMSE = np.sqrt(MSE) # Root Mean Squared Error, RMSE
Rsquared = 1.0 - (np.var(absError) / np.var(yData))
print('RMSE:', RMSE)
print('R-squared:', Rsquared)
print()
##########################################################
# graphics output section
def ModelAndScatterPlot(graphWidth, graphHeight):
f = plt.figure(figsize=(graphWidth/100.0, graphHeight/100.0), dpi=100)
axes = f.add_subplot(111)
# first the raw data as a scatter plot
axes.plot(xData, yData, 'D')
# create data for the fitted equation plot
xModel = np.linspace(min(xData), max(xData))
yModel = fitFunc(xModel, *fittedParameters)
# now the model as a line plot
axes.plot(xModel, yModel)
axes.set_xlabel('X Data') # X axis data label
axes.set_ylabel('Y Data') # Y axis data label
plt.show()
plt.close('all') # clean up after using pyplot
graphWidth = 800
graphHeight = 600
ModelAndScatterPlot(graphWidth, graphHeight)
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()
Stacked plotting in matplotlib with equal x data is as easy as
from matplotlib import pyplot as plt
x0 = [0.0, 0.5, 2.0]
y0 = [1.0, 1.5, 1.0]
# x1 = [0.0, 1.5, 2.0]
y1 = [1.0, 1.5, 1.0]
plt.stackplot(x0, (y0, y1))
plt.show()
Is it possible to stack two plots with different x data too?
It does not seem to be possible. If you look at the code for Matplotlib's stackplot, then this is the part that draws the stacked plot itself:
# Color between array i-1 and array i
for i in xrange(len(y) - 1):
color = axes._get_lines.get_next_color()
r.append(axes.fill_between(x, stack[i, :], stack[i + 1, :],
facecolor=color,
label= six.next(labels, None),
**kwargs))
So it will always use the same x for all stacks.
You could on the other hand create a new x array for the stacked plot, and include all values from all the different x arrays you have, and then calculate the missing y stack values using linear interpolation.
A possible solution using interpolation could look like this:
from matplotlib import pyplot as plt
def interp_nans(x, y):
is_nan = np.isnan(y)
res = y * 1.0
res[is_nan] = np.interp(x[is_nan], x[-is_nan], y[-is_nan])
return res
x = np.array([0.0, 0.5, 1.5, 2.0])
y0 = np.array([1.0, 1.5, np.nan, 1.0])
y1 = np.array([1.0, np.nan, 1.5, 1.0])
plt.stackplot(x, (interp_nans(x, y0), interp_nans(x, y1)))
plt.show()
But if interpolation can not be used in this case, then it would not work.
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.
I would like to know how to simply reverse the color order of a given colormap in order to use it with plot_surface.
The standard colormaps also all have reversed versions. They have the same names with _r tacked on to the end. (Documentation here.)
The solution is pretty straightforward. Suppose you want to use the "autumn" colormap scheme. The standard version:
cmap = matplotlib.cm.autumn
To reverse the colormap color spectrum, use get_cmap() function and append '_r' to the colormap title like this:
cmap_reversed = matplotlib.cm.get_cmap('autumn_r')
In matplotlib a color map isn't a list, but it contains the list of its colors as colormap.colors. And the module matplotlib.colors provides a function ListedColormap() to generate a color map from a list. So you can reverse any color map by doing
colormap_r = ListedColormap(colormap.colors[::-1])
As of Matplotlib 2.0, there is a reversed() method for ListedColormap and LinearSegmentedColorMap objects, so you can just do
cmap_reversed = cmap.reversed()
Here is the documentation.
As a LinearSegmentedColormaps is based on a dictionary of red, green and blue, it's necessary to reverse each item:
import matplotlib.pyplot as plt
import matplotlib as mpl
def reverse_colourmap(cmap, name = 'my_cmap_r'):
"""
In:
cmap, name
Out:
my_cmap_r
Explanation:
t[0] goes from 0 to 1
row i: x y0 y1 -> t[0] t[1] t[2]
/
/
row i+1: x y0 y1 -> t[n] t[1] t[2]
so the inverse should do the same:
row i+1: x y1 y0 -> 1-t[0] t[2] t[1]
/
/
row i: x y1 y0 -> 1-t[n] t[2] t[1]
"""
reverse = []
k = []
for key in cmap._segmentdata:
k.append(key)
channel = cmap._segmentdata[key]
data = []
for t in channel:
data.append((1-t[0],t[2],t[1]))
reverse.append(sorted(data))
LinearL = dict(zip(k,reverse))
my_cmap_r = mpl.colors.LinearSegmentedColormap(name, LinearL)
return my_cmap_r
See that it works:
my_cmap
<matplotlib.colors.LinearSegmentedColormap at 0xd5a0518>
my_cmap_r = reverse_colourmap(my_cmap)
fig = plt.figure(figsize=(8, 2))
ax1 = fig.add_axes([0.05, 0.80, 0.9, 0.15])
ax2 = fig.add_axes([0.05, 0.475, 0.9, 0.15])
norm = mpl.colors.Normalize(vmin=0, vmax=1)
cb1 = mpl.colorbar.ColorbarBase(ax1, cmap = my_cmap, norm=norm,orientation='horizontal')
cb2 = mpl.colorbar.ColorbarBase(ax2, cmap = my_cmap_r, norm=norm, orientation='horizontal')
EDIT
I don't get the comment of user3445587. It works fine on the rainbow colormap:
cmap = mpl.cm.jet
cmap_r = reverse_colourmap(cmap)
fig = plt.figure(figsize=(8, 2))
ax1 = fig.add_axes([0.05, 0.80, 0.9, 0.15])
ax2 = fig.add_axes([0.05, 0.475, 0.9, 0.15])
norm = mpl.colors.Normalize(vmin=0, vmax=1)
cb1 = mpl.colorbar.ColorbarBase(ax1, cmap = cmap, norm=norm,orientation='horizontal')
cb2 = mpl.colorbar.ColorbarBase(ax2, cmap = cmap_r, norm=norm, orientation='horizontal')
But it especially works nice for custom declared colormaps, as there is not a default _r for custom declared colormaps. Following example taken from http://matplotlib.org/examples/pylab_examples/custom_cmap.html:
cdict1 = {'red': ((0.0, 0.0, 0.0),
(0.5, 0.0, 0.1),
(1.0, 1.0, 1.0)),
'green': ((0.0, 0.0, 0.0),
(1.0, 0.0, 0.0)),
'blue': ((0.0, 0.0, 1.0),
(0.5, 0.1, 0.0),
(1.0, 0.0, 0.0))
}
blue_red1 = mpl.colors.LinearSegmentedColormap('BlueRed1', cdict1)
blue_red1_r = reverse_colourmap(blue_red1)
fig = plt.figure(figsize=(8, 2))
ax1 = fig.add_axes([0.05, 0.80, 0.9, 0.15])
ax2 = fig.add_axes([0.05, 0.475, 0.9, 0.15])
norm = mpl.colors.Normalize(vmin=0, vmax=1)
cb1 = mpl.colorbar.ColorbarBase(ax1, cmap = blue_red1, norm=norm,orientation='horizontal')
cb2 = mpl.colorbar.ColorbarBase(ax2, cmap = blue_red1_r, norm=norm, orientation='horizontal')
There is no built-in way (yet) of reversing arbitrary colormaps, but one simple solution is to actually not modify the colorbar but to create an inverting Normalize object:
from matplotlib.colors import Normalize
class InvertedNormalize(Normalize):
def __call__(self, *args, **kwargs):
return 1 - super(InvertedNormalize, self).__call__(*args, **kwargs)
You can then use this with plot_surface and other Matplotlib plotting functions by doing e.g.
inverted_norm = InvertedNormalize(vmin=10, vmax=100)
ax.plot_surface(..., cmap=<your colormap>, norm=inverted_norm)
This will work with any Matplotlib colormap.
There are two types of LinearSegmentedColormaps. In some, the _segmentdata is given explicitly, e.g., for jet:
>>> cm.jet._segmentdata
{'blue': ((0.0, 0.5, 0.5), (0.11, 1, 1), (0.34, 1, 1), (0.65, 0, 0), (1, 0, 0)), 'red': ((0.0, 0, 0), (0.35, 0, 0), (0.66, 1, 1), (0.89, 1, 1), (1, 0.5, 0.5)), 'green': ((0.0, 0, 0), (0.125, 0, 0), (0.375, 1, 1), (0.64, 1, 1), (0.91, 0, 0), (1, 0, 0))}
For rainbow, _segmentdata is given as follows:
>>> cm.rainbow._segmentdata
{'blue': <function <lambda> at 0x7fac32ac2b70>, 'red': <function <lambda> at 0x7fac32ac7840>, 'green': <function <lambda> at 0x7fac32ac2d08>}
We can find the functions in the source of matplotlib, where they are given as
_rainbow_data = {
'red': gfunc[33], # 33: lambda x: np.abs(2 * x - 0.5),
'green': gfunc[13], # 13: lambda x: np.sin(x * np.pi),
'blue': gfunc[10], # 10: lambda x: np.cos(x * np.pi / 2)
}
Everything you want is already done in matplotlib, just call cm.revcmap, which reverses both types of segmentdata, so
cm.revcmap(cm.rainbow._segmentdata)
should do the job - you can simply create a new LinearSegmentData from that. In revcmap, the reversal of function based SegmentData is done with
def _reverser(f):
def freversed(x):
return f(1 - x)
return freversed
while the other lists are reversed as usual
valnew = [(1.0 - x, y1, y0) for x, y0, y1 in reversed(val)]
So actually the whole thing you want, is
def reverse_colourmap(cmap, name = 'my_cmap_r'):
return mpl.colors.LinearSegmentedColormap(name, cm.revcmap(cmap._segmentdata))