I have a very simple basic bar's graphic like this one
but i want to display the bars with some 3d effect, like this
I just want the bars to have that 3d effect...my code is:
fig = Figure(figsize=(4.6,4))
ax1 = fig.add_subplot(111,ylabel="Valeur",xlabel="Code",autoscale_on=True)
width = 0.35
ind = np.arange(len(values))
rects = ax1.bar(ind, values, width, color='#A1B214')
ax1.set_xticks(ind+width)
ax1.set_xticklabels( codes )
ax1.set_ybound(-1,values[0] * 1.1)
canvas = FigureCanvas(fig)
response = HttpResponse(content_type='image/png')
canvas.print_png(response)
i've been looking in the gallery of matplotlib,tried a few things but i wasn't lucky, Any ideas? Thxs
I certainly understand your reason for needing a 3d bar plot; i suspect that's why they were created.
The libraries ('toolkits') in Matplotlib required to create 3D plots are not third-party libraries, etc., rather they are included in the base Matplotlib installation.
(This is true for the current stable version, which is 1.0, though i don't believe it was for 0.98, so the change--from 'add-on' to part of the base install--occurred within the past year, i believe)
So here you are:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as PLT
import numpy as NP
fig = PLT.figure()
ax1 = fig.add_subplot(111, projection='3d')
xpos = NP.random.randint(1, 10, 10)
ypos = NP.random.randint(1, 10, 10)
num_elements = 10
zpos = NP.zeros(num_elements)
dx = NP.ones(10)
dy = NP.ones(10)
dz = NP.random.randint(1, 5, 10)
ax1.bar3d(xpos, ypos, zpos, dx, dy, dz, color='#8E4585')
PLT.show()
To create 3d bars in Maplotlib, you just need to do three (additional) things:
import Axes3D from mpl_toolkits.mplot3d
call the bar3d method (in my scriptlet, it's called by ax1 an instance of the Axes class). The method signature:
bar3d(x, y, z, dy, dz, color='b', zsort="average", *args, **kwargs)
pass in an additional argument to add_subplot, projection='3d'
As far as I know Matplotlib doesn't by design support features like the "3D" effect you just mentioned. I remember reading about this some time back. I don't know it has changed in the meantime.
See this discussion thread for more details.
Update
Take a look at John Porter's mplot3d module. This is not a part of standard matplotlib but a custom extension. Never used it myself so can't say much about its usefulness.
Related
I have some code which produces a 3D scatter plot using matplotlib's scatter in conjunction with tight_layout, see the simplified code below:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import proj3d
fig = plt.figure()
ax = fig.gca(projection='3d')
N = 100
x = np.random.random(N)
y = np.random.random(N)
z = np.random.random(N)
ax.scatter(x, y, z)
plt.tight_layout() # <-- Without this, everything is fine
plt.savefig('scatter.png')
In matplotlib 2.2.3 this makes a figure like so:
Similar output is generated by older versions, at least back to 1.5.1. When using the new version 3.0.0, something goes wrong at plt.tight_layout() and I get the following output:
Accompanying this is the warning
.../matplotlib/tight_layout.py:177: UserWarning: The left and right margins cannot be made large enough to accommodate all axes decorations
One may argue that using tight_layout with no arguments as here does not (on older matplotlibs) consistently lead to the expected tightened margins anyway, and so one should refrain from using tight_layout with 3D plots in the first place. However, by manually tweaking the arguments to tight_layout it is (used to be) a decent way to trim the margins even on 3D plots.
My guess is that this is a bug in matplotlib, but maybe they've made some deliberate change I havn't picked up on. Any pointers about a fix is appreciated.
Thanks to the comment by ImportanceOfBeingErnest, it now works:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import proj3d
fig = plt.figure()
ax = fig.gca(projection='3d')
N = 100
x = np.random.random(N)
y = np.random.random(N)
z = np.random.random(N)
ax.scatter(x, y, z)
# The fix
for spine in ax.spines.values():
spine.set_visible(False)
plt.tight_layout()
plt.savefig('scatter.png')
From the links in the comment, it seems that this will be fixed in matplotlib 3.0.x. For now, the above may be used.
plt.tight_layout()
plt.show()
Right below your main body code of plotting.
I'm doing some 3D surface plots using Matplotlib in Python and have noticed an annoying phenomenon. Depending on how I set the viewpoint (camera location), the vertical (z) axis moves between the left and right side. Here are two examples: Example 1, Axis left, Example 2, Axis right. The first example has ax.view_init(25,-135) while the second has ax.view_init(25,-45).
I would like to keep the viewpoints the same (best way to view the data). Is there any way to force the axis to one side or the other?
I needed something similar: drawing the zaxis on both sides. Thanks to the answer by #crayzeewulf I came to following workaround (for left, righ, or both sides):
First plot your 3d as you need, then before you call show() wrap the Axes3D with a Wrapper class that simply overrides the draw() method.
The Wrapper Class calls simply sets the visibility of some features to False, it draws itself and finally draws the zaxis with modified PLANES. This Wrapper Class allows you to draw the zaxis on the left, on the rigth or on both sides.
import matplotlib
matplotlib.use('QT4Agg')
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
class MyAxes3D(axes3d.Axes3D):
def __init__(self, baseObject, sides_to_draw):
self.__class__ = type(baseObject.__class__.__name__,
(self.__class__, baseObject.__class__),
{})
self.__dict__ = baseObject.__dict__
self.sides_to_draw = list(sides_to_draw)
self.mouse_init()
def set_some_features_visibility(self, visible):
for t in self.w_zaxis.get_ticklines() + self.w_zaxis.get_ticklabels():
t.set_visible(visible)
self.w_zaxis.line.set_visible(visible)
self.w_zaxis.pane.set_visible(visible)
self.w_zaxis.label.set_visible(visible)
def draw(self, renderer):
# set visibility of some features False
self.set_some_features_visibility(False)
# draw the axes
super(MyAxes3D, self).draw(renderer)
# set visibility of some features True.
# This could be adapted to set your features to desired visibility,
# e.g. storing the previous values and restoring the values
self.set_some_features_visibility(True)
zaxis = self.zaxis
draw_grid_old = zaxis.axes._draw_grid
# disable draw grid
zaxis.axes._draw_grid = False
tmp_planes = zaxis._PLANES
if 'l' in self.sides_to_draw :
# draw zaxis on the left side
zaxis._PLANES = (tmp_planes[2], tmp_planes[3],
tmp_planes[0], tmp_planes[1],
tmp_planes[4], tmp_planes[5])
zaxis.draw(renderer)
if 'r' in self.sides_to_draw :
# draw zaxis on the right side
zaxis._PLANES = (tmp_planes[3], tmp_planes[2],
tmp_planes[1], tmp_planes[0],
tmp_planes[4], tmp_planes[5])
zaxis.draw(renderer)
zaxis._PLANES = tmp_planes
# disable draw grid
zaxis.axes._draw_grid = draw_grid_old
def example_surface(ax):
""" draw an example surface. code borrowed from http://matplotlib.org/examples/mplot3d/surface3d_demo.html """
from matplotlib import cm
import numpy as np
X = np.arange(-5, 5, 0.25)
Y = np.arange(-5, 5, 0.25)
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X**2 + Y**2)
Z = np.sin(R)
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.coolwarm, linewidth=0, antialiased=False)
if __name__ == '__main__':
fig = plt.figure(figsize=(15, 5))
ax = fig.add_subplot(131, projection='3d')
ax.set_title('z-axis left side')
ax = fig.add_axes(MyAxes3D(ax, 'l'))
example_surface(ax) # draw an example surface
ax = fig.add_subplot(132, projection='3d')
ax.set_title('z-axis both sides')
ax = fig.add_axes(MyAxes3D(ax, 'lr'))
example_surface(ax) # draw an example surface
ax = fig.add_subplot(133, projection='3d')
ax.set_title('z-axis right side')
ax = fig.add_axes(MyAxes3D(ax, 'r'))
example_surface(ax) # draw an example surface
plt.show()
As pointed out in a comment below by OP, the method suggested below did not provide adequate answer to the original question.
As mentioned in this note, there are lots of hard-coded values in axis3d that make it difficult to customize its behavior. So, I do not think there is a good way to do this in the current API. You can "hack" it by modifying the _PLANES parameter of the zaxis as shown below:
tmp_planes = ax.zaxis._PLANES
ax.zaxis._PLANES = ( tmp_planes[2], tmp_planes[3],
tmp_planes[0], tmp_planes[1],
tmp_planes[4], tmp_planes[5])
view_1 = (25, -135)
view_2 = (25, -45)
init_view = view_2
ax.view_init(*init_view)
Now the z-axis will always be on the left side of the figure no matter how you rotate the figure (as long as positive-z direction is pointing up). The x-axis and y-axis will keep flipping though. You can play with _PLANES and might be able to get the desired behavior for all axes but this is likely to break in future versions of matplotlib.
Similarly to a previous question of mine, I'd like to control the capstyle of lines being drawn using matplotlib. However, I have an extremely large number of lines, and drawing with anything other than a line collection takes way too long. Are there any workarounds to control the capstyle of lines in a line collection in a generic way (or alternatively, super fast ways of drawing a large number of Line2D lines). For instance, I've tried using the matplotlib rc settings via:
import matplotlib as mpl
mpl.rcParams['lines.solid_capstyle'] = 'round'
mpl.rcParams['lines.solid_joinstyle'] = 'round'
But this doesn't appear to have any affect. From the docstring for collections.py:
The classes are not meant to be as flexible as their single element counterparts (e.g. you may not be able to select all line styles) but they are meant to be fast for common use cases (e.g. a large set of solid line segemnts)
Which explains why I can't seem to control various parameters, but I still want to do it! I've had a look at the code for the AGG backend (_backend_agg.cpp: not that I really understand it), and it appears that line_cap and line_join are controlled by gc.cap and gc.join, where gc comes from the GCAgg class. Does anyone know how one can control this from Python? Am I asking the right question here? Perhaps that are easier ways to control these parameters?
Any help is greatly appreciated... I'm desperate to get this working, so even crazy hacks are welcome!
Thanks,
Carson
Since you mention in your question that you don't mind "dirty" solutions, one option would as follows.
The "drawing process" of a particular LineCollection is handled by the draw method defined in the Collection class (the base of LineCollection). This method creates an instance of GraphicsContextBase (defined in backend_bases.py) via the statement gc = renderer.new_gc(). It seems to be exactly this object which governs among other things the properties controlling the capstyle (property _capstyle). Therefore, one could subclass GraphicsContextBase, override the _capstyle property, and inject a new new_gc method into the RendererBase class so that consequent calls to new_gc return the customized instance:
Borrowing the example from the answer by #florisvb (assuming Python3):
#!/usr/bin/env python
import types
import numpy as np
from matplotlib.backend_bases import GraphicsContextBase, RendererBase
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
class GC(GraphicsContextBase):
def __init__(self):
super().__init__()
self._capstyle = 'round'
def custom_new_gc(self):
return GC()
RendererBase.new_gc = types.MethodType(custom_new_gc, RendererBase)
#----------------------------------------------------------------------
np.random.seed(42)
x = np.random.random(10)
y = np.random.random(10)
points = np.array([x, y]).T.reshape((-1, 1, 2))
segments = np.concatenate([points[:-1], points[1:]], axis=1)
fig = plt.figure()
ax = fig.add_subplot(111)
linewidth = 10
lc = LineCollection(segments, linewidths=linewidth)
ax.add_collection(lc)
fig.savefig('fig.png')
This produces:
To update the answer from #ewcz as this thread still comes up in search results.
One can now use path_effects instead of defining their own GraphicsContextBase.
e.g.
import numpy as np
import matplotlib.patheffects as path_effects
from matplotlib.collections import LineCollection
np.random.seed(42)
x = np.random.random(10)
y = np.random.random(10)
points = np.array([x, y]).T.reshape((-1, 1, 2))
segments = np.concatenate([points[:-1], points[1:]], axis=1)
fig = plt.figure()
ax = fig.add_subplot(111)
linewidth = 10
### Stroke redraws the segment passing kwargs down to the GC renderer
lc = LineCollection(segments, linewidths=linewidth,
path_effects=[path_effects.Stroke(capstyle="round")])
ax.add_collection(lc)
fig.show()
Example png output with smooth lines
and it also seems to work well with pdf output
I was struggling with the same issue. I ended up plotting a scatter plot on top of my line collection. It's not perfect, but it may work for your application. There's a few subtleties - below is a working example.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
x = np.random.random(10)
y = np.random.random(10)
z = np.arange(0,10)
points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
fig = plt.figure()
ax = fig.add_subplot(111)
linewidth = 10
cmap = plt.get_cmap('jet')
norm = plt.Normalize(np.min(z), np.max(z))
color = cmap(norm(z))
lc = LineCollection(segments, linewidths=linewidth, cmap=cmap, norm=norm)
lc.set_array(z)
lc.set_zorder(z.tolist())
ax.add_collection(lc)
ax.scatter(x,y,color=color,s=linewidth**2,edgecolor='none', zorder=(z+2).tolist())
I am trying to create a 3D bar histogram in Python using bar3d() in Matplotlib.
I have got to the point where I can display my histogram on the screen after passing it some data, but I am stuck on the following:
Displaying axes labels correctly (currently misses out final (or initial?) tick labels)
Either making the ticks on each axis (e.g. that for 'Mon') either point to it's corresponding blue bar, or position the tick label for between the major tick marks.
Making the bars semi-transparent.
image of plot uploaded here
I have tried passing several different arguments to the 'ax' instance, but have not got anything to work despite and suspect I have misunderstood what to provide it with. I will be very grateful for any help on this at all.
Here is a sample of the code i'm working on:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
#from IPython.Shell import IPShellEmbed
#sh = IPShellEmbed()
data = np.array([
[0,1,0,2,0],
[0,3,0,2,0],
[6,1,1,7,0],
[0,5,0,2,9],
[0,1,0,4,0],
[9,1,3,4,2],
[0,0,2,1,3],
])
column_names = ['a','b','c','d','e']
row_names = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun']
fig = plt.figure()
ax = Axes3D(fig)
lx= len(data[0]) # Work out matrix dimensions
ly= len(data[:,0])
xpos = np.arange(0,lx,1) # Set up a mesh of positions
ypos = np.arange(0,ly,1)
xpos, ypos = np.meshgrid(xpos+0.25, ypos+0.25)
xpos = xpos.flatten() # Convert positions to 1D array
ypos = ypos.flatten()
zpos = np.zeros(lx*ly)
dx = 0.5 * np.ones_like(zpos)
dy = dx.copy()
dz = data.flatten()
ax.bar3d(xpos,ypos,zpos, dx, dy, dz, color='b')
#sh()
ax.w_xaxis.set_ticklabels(column_names)
ax.w_yaxis.set_ticklabels(row_names)
ax.set_xlabel('Letter')
ax.set_ylabel('Day')
ax.set_zlabel('Occurrence')
plt.show()
To make the bars semi-transparent, you just have to use the alpha parameter. alpha=0 means 100% transparent, while alpha=1 (the default) means 0% transparent.
Try this, it will work out to make the bars semi-transparent:
ax.bar3d(xpos,ypos,zpos, dx, dy, dz, color='b', alpha=0.5)
Regarding the ticks location, you can do it using something like this (the first list on plt.xticks or plt.yticks contains the "values" where do you want to locate the ticks, and the second list contains what you actually want to call the ticks):
#ax.w_xaxis.set_ticklabels(column_names)
#ax.w_yaxis.set_ticklabels(row_names)
ticksx = np.arange(0.5, 5, 1)
plt.xticks(ticksx, column_names)
ticksy = np.arange(0.6, 7, 1)
plt.yticks(ticksy, row_names)
In the end, I get this figure:
Related to this question, I want a 3D scatter plot with prescribed colors for each point. The example posted in the question works on my system, but after the first redraw (for instance after saving or if I rotate the image) the color seems to be lost, i.e. all the points are drawn in blue color with the usual depth information. Please see the modified example below.
My system is Python 2.6.7 with matplotlib 1.1.0 installed from macports on a mac 10.8.0. I use the MacOSX backend.
Does anyone know how to circumvent this problem?
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# Create Map
cm = plt.get_cmap("RdYlGn")
x = np.random.rand(30)
y = np.random.rand(30)
z = np.random.rand(30)
col = np.arange(30)
fig = plt.figure()
ax3D = fig.add_subplot(111, projection='3d')
ax3D.scatter(x, y, z, s=30, c=col, marker='o', cmap=cm)
plt.savefig('image1.png')
plt.savefig('image2.png')
Here are the two images, I get:
It's not clear why this is happening, and it certainly is a bug. Here I provide a hack to get the result you want, though it is not as automatic as one would want.
For some reason, the Patch3DCollection representing the scatter points is not updated after the first rendering. This update is essential, because it is where unique colors are set for each collection patch. To force it to reinitialize, you can use the changed method on the Patch3DCollection (really a ScalarMappable method), and this just documents that a change happend. When the figure is drawn, it checks if an update happened, and then it redefines the colors. If it didn't, this process is skipped.
To force this update to occur automatically, one would like to do this on every 'draw' event. To do this, one must register a method using the canvas's mpl_connect method (see linked tutorial).
This example shows how saving the figure twice preserves the color mapping, but if you uncomment the plt.show() line, it will still work (on rotation for example).
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# Create Map
cm = plt.get_cmap("RdYlGn")
# added a seed so consistant plotting of points
np.random.seed(101)
x = np.random.rand(30)
y = np.random.rand(30)
z = np.random.rand(30)
col = np.arange(30)
fig = plt.figure()
#ax = fig.add_subplot(111)
#scatCollection = ax.scatter(x,y,
ax3D = fig.add_subplot(111, projection='3d')
# keep track of the Patch3DCollection:
scatCollection = ax3D.scatter(x, y, z, s=30,
c=col,
marker='o',
cmap=cm
)
def forceUpdate(event):
global scatCollection
scatCollection.changed()
fig.canvas.mpl_connect('draw_event',forceUpdate)
#plt.show()
plt.savefig('image1.png')
plt.savefig('image2.png')
Ideally it should not be required to do this, and the global scatCollection should be accessed using other methods (I'm working on doing this). But this works for now...