Here is a major patch for mplot3d. Here is a summary of the changes:
* bug fix: placement of title in 3D plots to match 2D plot behavior (see
nonecolortester.py to demonstrate)
* bug fix: allow facecolors and edgecolors to be specified as 'none' in 3D
scatter plots to match the 2D scatter plot behavior (see nonecolortester.py to
demonstrate)
* bug fix: allow all keyword arguments to be used in text3D (see modified
example code text3d_demo.py)
* bug fix: allow an array of colors to be passed into bar3d to specify the
colors on a per-bar or per-face basis (see new example code hist3d_demo2.py)
* bug fix: allow all keyword arguments to be used in bar3d (see new example
code hist3d_demo2.py)
* bug fix: allow 3d scatter plots with 3 or 4 points with colors specified
(see colortester2.py to demonstrate)
* new feature: new method to disable mouse rotation in 3D plots
* new feature: allow mouse rotation and zoom buttons to be specified by user
* new feature: new Z-order sorting heuristic to eliminate rendering issues for
the common case of using bar3d to visualize a 2D histogram (see modified
example code hist3d_demo.py)
* new feature: new method text2D (see modified example code text3d_demo.py)
* code cleanup: warn when canvas is None which disables mouse callbacks
* code cleanup: document more methods in mplot3d
Thanks,
-Ben
#!/usr/bin/env python
#
# Test script to test:
# * facecolor and edgecolor = 'none' in Axes3D
# * tile placement on Axes3D
#
# Compare 2D and 3D axes with the same titles to match functionality.
#
# Note that before patch:
# * Title in figure 2 is missing
# * Figures 4 and 6 are blank with exceptions on the console
#
# After patch, everything works fine.
#
# Ben Axelrod
# Feb. 24, 2010
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
xs = [1,2,3,4,5,6]
ys = [1,2,3,4,5,6]
zs = [1,2,3,4,5,6]
cs = [1,2,3,4,5,6]
########################
fig1 = plt.figure()
ax1 = fig1.add_subplot(111)
ax1.scatter(xs, ys,
facecolor='y',
edgecolor='r')
ax1.set_title("2D facecolor='y' edgecolor='r'")
##########################
fig2 = plt.figure()
ax2 = Axes3D(fig2)
ax2.scatter(xs, ys, zs,
facecolor='y',
edgecolor='r')
ax2.set_title("3D facecolor='y' edgecolor='r'")
############################
fig3 = plt.figure()
ax3 = fig3.add_subplot(111)
ax3.scatter(xs, ys,
facecolor='none',
edgecolor=(1,0,0))
ax3.set_title("2D facecolor='none' edgecolor=(1,0,0)", ha='left', y=1, x=0.1)
##########################
fig4 = plt.figure()
ax4 = Axes3D(fig4)
ax4.scatter(xs, ys, zs,
facecolor='none',
edgecolor=(1,0,0))
ax4.set_title("3D facecolor='none' edgecolor=(1,0,0)", ha='left', y=1, x=0.1)
############################
fig5 = plt.figure()
ax5 = fig5.add_subplot(111)
ax5.scatter(xs, ys,
edgecolor='none')
ax5.set_title("2D edgecolor=(1,0,0)",color='g', y=0)
##########################
fig6 = plt.figure()
ax6 = Axes3D(fig6)
ax6.scatter(xs, ys, zs,
edgecolor='none')
ax6.set_title("3D edgecolor=(1,0,0)", color='g', y=0)
############################
plt.show()
#!/usr/bin/env python
#
# Test script to test color bug in Axes3d scatter plot when number of points
# is 3 or 4 and colors are specified.
#
# Note that before patch, figures 10, 11, 13, 14 are blank due to exceptions.
# After patch, these plots display like they should.
#
# Note that color map doesn't work yet for the 3d scatter plot
#
# Ben Axelrod
# Feb. 24, 2010
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
xs = [1,2,3,4,5]
ys = [1,2,3,4,5]
zs = [1,2,3,4,5]
cs = [1,2,3,4,5]
cstrings = ['r', 'g', 'b', 'c', 'm']
carrays = [(1,0,0,1), (0,1,0,1), (0,0,1,1), (0,1,1,1), (1,0,1,1)]
########################
for n in [3, 4, 5]:
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(xs[:n], ys[:n], c=cstrings[:n])
ax.set_title("2D using cstrings")
for n in [3, 4, 5]:
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(xs[:n], ys[:n], c=carrays[:n])
ax.set_title("2D using carrays")
for n in [3, 4, 5]:
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(xs[:n], ys[:n], c=cs[:n], cmap='jet')
ax.set_title("2D using cmap")
################################
for n in [3, 4, 5]:
fig = plt.figure()
ax = Axes3D(fig)
ax.scatter(xs[:n], ys[:n], zs[:n], c=cstrings[:n])
ax.set_title("3D using cstrings")
for n in [3, 4, 5]:
fig = plt.figure()
ax = Axes3D(fig)
ax.scatter(xs[:n], ys[:n], zs[:n], c=carrays[:n])
ax.set_title("3D using carrays")
for n in [3, 4, 5]:
fig = plt.figure()
ax = Axes3D(fig)
ax.scatter(xs[:n], ys[:n], zs[:n], c=cs[:n], cmap='jet')
ax.set_title("3D using cmap")
##############################
plt.show()
Index: lib/mpl_toolkits/mplot3d/art3d.py
===================================================================
--- lib/mpl_toolkits/mplot3d/art3d.py (revision 8150)
+++ lib/mpl_toolkits/mplot3d/art3d.py (working copy)
@@ -1,6 +1,7 @@
#!/usr/bin/python
# art3d.py, original mplot3d version by John Porter
# Parts rewritten by Reinier Heeres <rein...@heeres.eu>
+# Minor additions by Ben Axelrod <baxel...@coroware.com>
'''
Module containing 3D artist code and functions to convert 2D
@@ -52,8 +53,15 @@
Text object with 3D position and (in the future) direction.
'''
- def __init__(self, x=0, y=0, z=0, text='', zdir='z'):
- mtext.Text.__init__(self, x, y, text)
+ def __init__(self, x=0, y=0, z=0, text='', zdir='z', **kwargs):
+ '''
+ *x*, *y*, *z* Position of text
+ *text* Text string to display
+ *zdir* Direction of text
+
+ Keyword arguments are passed onto :func:`~matplotlib.text.Text`.
+ '''
+ mtext.Text.__init__(self, x, y, text, **kwargs)
self.set_3d_properties(z, zdir)
def set_3d_properties(self, z=0, zdir='z'):
@@ -86,6 +94,10 @@
'''
def __init__(self, xs, ys, zs, *args, **kwargs):
+ '''
+
+ Keyword arguments are passed onto :func:`~matplotlib.lines.Line2D`.
+ '''
lines.Line2D.__init__(self, [], [], *args, **kwargs)
self._verts3d = xs, ys, zs
@@ -145,6 +157,10 @@
'''
def __init__(self, segments, *args, **kwargs):
+ '''
+
+ Keyword arguments are passed onto :func:`~matplotlib.collections.LineCollection`.
+ '''
LineCollection.__init__(self, segments, *args, **kwargs)
def set_segments(self, segments):
@@ -425,6 +441,253 @@
def draw(self, renderer):
return Collection.draw(self, renderer)
+
+class Cuboid3DCollection(Poly3DCollection):
+ '''
+ A collection of 3D cuboids rendered as polygons.
+ '''
+
+ def __init__(self, verts, heuristic=None, *args, **kwargs):
+ '''
+ Create a Poly3DCollection.
+
+ *verts* should contain 3D coordinates.
+
+ Note that this class does a bit of magic with the _facecolors
+ and _edgecolors properties.
+ '''
+
+ if heuristic is None:
+ self._heuristic = self.get_heuristic_list()[0]
+ else:
+ self.set_heuristic(heuristic)
+ #end if
+
+ Poly3DCollection.__init__(self, verts, *args, **kwargs)
+ #end def
+
+ def get_heuristic(self):
+ '''
+ Returns the current heuristic to use for Z order sorting
+ '''
+ return self._heuristic
+ #end def
+
+ def get_heuristic_list(self):
+ '''
+ Returns the list of available Z order sorting heuristics.
+ '''
+ return ['average', 'histogram']
+ #end def
+
+ def set_heuristic(self, heuristic):
+ if heuristic in self.get_heuristic_list():
+ self._heuristic = heuristic
+ else:
+ print 'WARNING: Unknown heuristic'
+ #end if
+ #end def
+
+ def _sort_polygons_avg(self, polys):
+ """
+ Sort a list of polygon faces. The list of polygon faces will be in
+ the format:
+
+ ([face1, face2, face3, face4], facecolor, edgecolor)
+
+ where each face is: (x, y, z)
+ and each color is: array([r, g, b, a])
+
+ The list is returned in the same format. However, it is sorted
+ according to some heuristic for z-buffering. Polygons in the back
+ of the scene which will be drawn first should be at the front of the
+ list.
+
+ Note that when the list of polygons is passed in to this function,
+ it is grouped by box. Meaning that the 6 faces for each box shape
+ are next to each other in the list.
+
+ This implements the 'average' z order sorting heuristic. Where the
+ average Z value of the 4 corners of each face are used to sort the
+ faces. Each face is considered independantly of the box in which it
+ belongs. This heuristic may be best if you have bars that float in
+ space without a common ground plane.
+
+ There may be Z order issues if you have a very tall box next to a very
+ short box.
+ """
+
+ #compute the average z value of each face
+ newpolys = []
+ for [p1, p2, p3, p4], fc, ec in polys:
+ average_z = np.average((p1[2], p2[2], p3[2], p4[2]))
+ newpolys.append((average_z, ([p1, p2, p3, p4], fc, ec)))
+ #end for
+
+ # sort based on z value
+ newpolys.sort(cmp=lambda x, y: cmp(y[0], x[0]))
+
+ #strip z value out of array before returning
+ return [ b for (a, b) in newpolys]
+ #end def _sort_polygons_avg()
+
+ def _sort_polygons_hist(self, polys):
+ """
+ Sort a list of polygon faces. The list of polygon faces will be in
+ the format:
+
+ ([face1, face2, face3, face4], facecolor, edgecolor)
+
+ where each face is: (x, y, z)
+ and each color is: array([r, g, b, a])
+
+ The list is returned in the same format. However, it is sorted
+ according to some heuristic for z-buffering. Polygons in the back
+ of the scene which will be drawn first should be at the front of the
+ list.
+
+ Note that when the list of polygons is passed in to this function,
+ it is grouped by box. Meaning that the 6 faces for each box shape
+ are next to each other in the list.
+
+ This implements the 'histogram' z order sorting heuristic. Where
+ the max Z value of each box (set of 6 faces) is used to sort the faces
+ on a per-box basis. Then the faces within each box are sorted. This
+ heuristic may be best if your boxes all have the same value for \'z\'.
+ i.e. you are visualizing a 2D histogram.
+
+ Note that this heuristic may render improperly if the histogram is
+ viewed 'from below'. To fix this, the 'actual' XYZ coordinates of the
+ box vertecies should be passed in as well. Then, the 4 corners of the
+ box that are on the ground plane should be found. Then the *min* Z
+ value of these 4 points should be used to sort the boxes.
+
+ There are also slight Z order issues when viewed from directly above.
+ """
+
+ if len(polys) % 6 != 0:
+ print 'WARNING, number of polygons is not divisable by 6'
+
+ nfaces = int(len(polys) / 6)
+
+ # Compute the max z value of each face
+ # Format of face_zs is a list of:
+ # (max_z_face, poly[i])
+ face_zs = []
+ for [p1, p2, p3, p4], fc, ec in polys:
+ max_z_face = max(p1[2], p2[2], p3[2], p4[2])
+ face_zs.append((max_z_face, ([p1, p2, p3, p4], fc, ec)))
+ #end for
+
+ # Compute the max Z value for each set of 6 polys
+ # Format of box_zs is a list of:
+ #
+ box_zs = []
+ for i in range(nfaces):
+ max_z_box = max(face_zs[6*i+0][0],
+ face_zs[6*i+1][0],
+ face_zs[6*i+2][0],
+ face_zs[6*i+3][0],
+ face_zs[6*i+4][0],
+ face_zs[6*i+5][0])
+
+ # sort faces for this box
+ faces = [face_zs[6*i+0],
+ face_zs[6*i+1],
+ face_zs[6*i+2],
+ face_zs[6*i+3],
+ face_zs[6*i+4],
+ face_zs[6*i+5]]
+ faces.sort(cmp=lambda x, y: cmp(y[0], x[0]))
+
+ box_zs.append((max_z_box, faces))
+ #end for
+
+ # sort boxes based on z value
+ box_zs.sort(cmp=lambda x, y: cmp(y[0], x[0]))
+
+ #now combine sorted boxes and faces into a single list again.
+ #stripping z values also
+ newpolys = []
+ for bz, faces in box_zs:
+ for fz, poly in faces:
+ newpolys.append(poly)
+
+ #strip z value out of array before returning
+ return newpolys
+ #end def _sort_polygons_hist()
+
+ def do_3d_projection(self, renderer):
+ '''
+ Perform the 3D projection for this object.
+ '''
+
+ if self._A is not None:
+ self.update_scalarmappable()
+ self._facecolors3d = self._facecolors
+
+ txs, tys, tzs = proj3d.proj_transform_vec(self._vec, renderer.M)
+ xyzlist = [(txs[si:ei], tys[si:ei], tzs[si:ei]) \
+ for si, ei in self._segis]
+
+ # This extra fuss is to re-order face / edge colors
+ cface = self._facecolors3d
+ cedge = self._edgecolors3d
+ if len(cface) != len(xyzlist):
+ cface = cface.repeat(len(xyzlist), axis=0)
+ if len(cedge) != len(xyzlist):
+ if len(cedge) == 0:
+ cedge = cface
+ cedge = cedge.repeat(len(xyzlist), axis=0)
+
+ # if required sort by depth (furthest drawn first)
+ if self._zsort:
+
+ # Put polygon info into a big list. The format is a list of:
+ # ([face1, face2, face3, face4], facecolor, edgecolor)
+ # where each face is: (x, y, z)
+ # and each color is: array([r, g, b, a])
+
+ polys = [(zip(xs, ys, zs), fc, ec) for
+ (xs, ys, zs), fc, ec in zip(xyzlist, cface, cedge)]
+
+ # sort the polygons however is best
+ if self._heuristic == 'average':
+ sorted_polys = self._sort_polygons_avg(polys)
+ elif self._heuristic == 'histogram':
+ sorted_polys = self._sort_polygons_hist(polys)
+ else:
+ print 'WARNING: Unknown heuristic'
+ #end if
+
+ else:
+ raise ValueError, "whoops"
+
+ # Take out only the (X,Y) pairs and set the polygon vertices
+ onlyxy = []
+ for [p1, p2, p3, p4], fc, ec in sorted_polys:
+ onlyxy.append([p1[:2], p2[:2], p3[:2], p4[:2]])
+ #end for
+ PolyCollection.set_verts(self, onlyxy)
+
+ self._facecolors2d = [fc for s, fc, ec in sorted_polys]
+ if len(self._edgecolors3d) == len(cface):
+ self._edgecolors2d = [ec for s, fc, ec in sorted_polys]
+ else:
+ self._edgecolors2d = self._edgecolors3d
+
+## # Return zorder value
+## if self._sort_zpos is not None:
+## zvec = np.array([[0], [0], [self._sort_zpos], [1]])
+## ztrans = proj3d.proj_transform_vec(zvec, renderer.M)
+## return ztrans[2][0]
+## else:
+## return np.min(tzs)
+## #end if
+ #end def
+#end class
+
+
def poly_collection_2d_to_3d(col, zs=0, zdir='z'):
"""Convert a PolyCollection to a Poly3DCollection object."""
segments_3d = paths_to_3d_segments(col.get_paths(), zs, zdir)
@@ -468,9 +731,14 @@
def iscolor(c):
try:
- return (len(c) == 4 or len(c) == 3) and hasattr(c[0], '__float__')
- except (IndexError):
+ if len(c) == 4 or len(c) == 3:
+ if iterable(c[0]):
+ return False
+ if hasattr(c[0], '__float__'):
+ return True
+ except:
return False
+ return False
def get_colors(c, num):
"""Stretch the color argument to provide the required number num"""
@@ -484,6 +752,8 @@
return c
elif iscolor(c):
return [c] * num
+ elif len(c) == 0: #if edgecolor or facecolor is specified as 'none'
+ return [[0,0,0,0]] * num
elif iscolor(c[0]):
return [c[0]] * num
else:
Index: lib/mpl_toolkits/mplot3d/axes3d.py
===================================================================
--- lib/mpl_toolkits/mplot3d/axes3d.py (revision 8150)
+++ lib/mpl_toolkits/mplot3d/axes3d.py (working copy)
@@ -2,12 +2,14 @@
# axes3d.py, original mplot3d version by John Porter
# Created: 23 Sep 2005
# Parts fixed by Reinier Heeres <rein...@heeres.eu>
+# Minor additions by Ben Axelrod <baxel...@coroware.com>
"""
Module containing Axes3D, an object which can plot 3D objects on a
2D matplotlib figure.
"""
+import warnings
from matplotlib.axes import Axes, rcParams
from matplotlib import cbook
from matplotlib.transforms import Bbox
@@ -147,7 +149,7 @@
# Calculate projection of collections and zorder them
zlist = [(col.do_3d_projection(renderer), col) \
- for col in self.collections]
+ for col in self.collections]
zlist.sort()
zlist.reverse()
for i, (z, col) in enumerate(zlist):
@@ -156,6 +158,15 @@
# Calculate projection of patches and zorder them
zlist = [(patch.do_3d_projection(renderer), patch) \
for patch in self.patches]
+ #zlist = []
+ #for patch in self.patches:
+ # try:
+ # zlist.append((patch.do_3d_projection(renderer), patch))
+ # except:
+ # pass
+ # #end try
+ ##end for
+
zlist.sort()
zlist.reverse()
for i, (z, patch) in enumerate(zlist):
@@ -322,7 +333,23 @@
M = np.dot(perspM, M0)
return M
- def mouse_init(self):
+ def mouse_init(self, rotateBtn=1, zoomBtn=3):
+ """Initializes mouse button callbacks to enable 3D rotation of
+ the axes. Also optionally sets the mouse buttons for 3D rotation
+ and zooming.
+
+ ========== ================================================
+ Argument Description
+ ========== ================================================
+ *rotateBtn* The integer or list of integers specifying which mouse
+ button or buttons to use for 3D rotation of the axes.
+ Default = 1.
+
+ *zoomBtn* The integer or list of integers specifying which mouse
+ button or buttons to use to zoom the 3D axes.
+ Default = 3.
+ ========== ================================================
+ """
self.button_pressed = None
canv = self.figure.canvas
if canv != None:
@@ -330,15 +357,40 @@
c2 = canv.mpl_connect('button_press_event', self._button_press)
c3 = canv.mpl_connect('button_release_event', self._button_release)
self.cids = [c1, c2, c3]
+ else:
+ warnings.warn('Axes3D.figure.canvas is \'None\', mouse rotation disabled. Set canvas then call Axes3D.mouse_init().')
+ #end if
+ if isinstance(rotateBtn, int):
+ self.rotateBtn = [rotateBtn]
+ else:
+ self.rotateBtn = rotateBtn
+ #end if
+
+ if isinstance(zoomBtn, int):
+ self.zoomBtn = [zoomBtn]
+ else:
+ self.zoomBtn = zoomBtn
+ #end if
+
+ #end def
+
def cla(self):
+ """Clear axes and disable mouse button callbacks.
+ """
+ self.disable_mouse_rotation()
+ Axes.cla(self)
+ self.grid(rcParams['axes3d.grid'])
+
+ def disable_mouse_rotation(self):
+ """Disable mouse button callbacks.
+ """
# Disconnect the various events we set.
for cid in self.cids:
self.figure.canvas.mpl_disconnect(cid)
+ #endfor
self.cids = []
- Axes.cla(self)
- self.grid(rcParams['axes3d.grid'])
-
+
def _button_press(self, event):
if event.inaxes == self:
self.button_pressed = event.button
@@ -426,9 +478,10 @@
def _on_move(self, event):
"""Mouse moving
- button-1 rotates
- button-3 zooms
+ button-1 rotates by default. Can be set explicitly in mouse_init().
+ button-3 zooms by default. Can be set explicitly in mouse_init().
"""
+
if not self.button_pressed:
return
@@ -447,7 +500,8 @@
h = (y1-y0)
self.sx, self.sy = x, y
- if self.button_pressed == 1:
+ # Rotation
+ if self.button_pressed in self.rotateBtn:
# rotate viewing point
# get the x and y pixel coords
if dx == 0 and dy == 0:
@@ -456,12 +510,15 @@
self.azim = art3d.norm_angle(self.azim - (dx/w)*180)
self.get_proj()
self.figure.canvas.draw()
- elif self.button_pressed == 2:
- # pan view
- # project xv,yv,zv -> xw,yw,zw
- # pan
- pass
- elif self.button_pressed == 3:
+
+## elif self.button_pressed == 2:
+## # pan view
+## # project xv,yv,zv -> xw,yw,zw
+## # pan
+## pass
+
+ # Zoom
+ elif self.button_pressed in self.zoomBtn:
# zoom view
# hmmm..this needs some help from clipping....
minx, maxx, miny, maxy, minz, maxz = self.get_w_lims()
@@ -476,7 +533,7 @@
self.figure.canvas.draw()
def set_xlabel(self, xlabel, fontdict=None, **kwargs):
- '''Set xlabel. '''
+ '''Set xlabel.'''
label = self.w_xaxis.get_label()
label.set_text(xlabel)
@@ -511,14 +568,16 @@
'''
self._draw_grid = on
- def text(self, x, y, z, s, zdir=None):
+ def text(self, x, y, z, s, zdir=None, fontdict=None, withdash=False, **kwargs):
'''Add text to the plot.'''
- text = Axes.text(self, x, y, s)
+ text = Axes.text(self, x, y, s, fontdict=fontdict, withdash=withdash, **kwargs)
art3d.text_2d_to_3d(text, z, zdir)
return text
-
+
text3D = text
+ text2D = Axes.text
+
def plot(self, xs, ys, *args, **kwargs):
'''
Plot 2D or 3D data.
@@ -533,7 +592,7 @@
*zdir* Which direction to use as z ('x', 'y' or 'z')
when plotting a 2d set.
========== ================================================
-
+
Other arguments are passed on to
:func:`~matplotlib.axes.Axes.plot`
'''
@@ -591,6 +650,9 @@
*shade* Whether to shade the facecolors, default:
false when cmap specified, true otherwise
========== ================================================
+
+ Other arguments are passed on to
+ :func:`~mpl_toolkits.mplot3d.art3d.Poly3DCollection.__init__`
'''
had_data = self.has_data()
@@ -822,8 +884,9 @@
colors = self._shade_colors(color, normals)
colors2 = self._shade_colors(color, normals)
- polycol = art3d.Poly3DCollection(polyverts, facecolors=colors,
- edgecolors=colors2)
+ polycol = art3d.Poly3DCollection(polyverts,
+ facecolors=colors,
+ edgecolors=colors2)
polycol.set_sort_zpos(z)
self.add_collection3d(polycol)
@@ -848,6 +911,8 @@
Other keyword arguments are passed on to
:func:`~matplotlib.axes.Axes.contour`
+
+ Returns a :class:`~matplotlib.axes.Axes.contour`
'''
extend3d = kwargs.pop('extend3d', False)
@@ -881,7 +946,9 @@
*X*, *Y*, *Z*: data points.
Keyword arguments are passed on to
- :func:`~matplotlib.axes.Axes.contour`
+ :func:`~matplotlib.axes.Axes.contourf`
+
+ Returns a :class:`~matplotlib.axes.Axes.contourf`
'''
had_data = self.has_data()
@@ -1005,25 +1072,72 @@
return patches
- def bar3d(self, x, y, z, dx, dy, dz, color='b'):
+ def bar3d(self, x, y, z, dx, dy, dz, color='b',
+ heuristic=None, *args, **kwargs):
'''
Generate a 3D bar, or multiple bars.
When generating multiple bars, x, y, z have to be arrays.
- dx, dy, dz can still be scalars.
+ dx, dy, dz can be arrays or scalars.
+
+ *color* can be:
+ - A single color value, to color all bars the same color.
+ - An array of colors of length N bars, to color each bar
+ independently.
+ - An array of colors of length 6, to color the faces of the bars
+ similarly.
+ - An array of colors of length 6 * N bars, to color each face
+ independently.
+
+ When coloring the faces of the boxes specifically, this is the order
+ of the coloring:
+ 1. -Z (bottom of box)
+ 2. +Z (top of box)
+ 3. -Y
+ 4. +Y
+ 5. -X
+ 6. +X
+
+ *heuristic* determines the algorithm to use for z-order sorting the
+ bar3d faces. Can be one of:
+ - \'average\' - The average Z value of the 4 corners of each face are
+ used to sort the faces. This heuristic may be best if you have bars
+ that float in space without a common ground plane.
+ - \'histogram\' - The max Z value of each box (set of 6 faces) is used
+ to sort the faces on a per-box basis. Then the faces within each box
+ are sorted. This heuristic may be best if your boxes all have the
+ same value for \'z\'. i.e. you are visualizing a 2D histogram.
+
+ Keyword arguments are passed onto
+ :func:`~mpl_toolkits.mplot3d.art3d.Cuboid3DCollection`
'''
-
had_data = self.has_data()
if not cbook.iterable(x):
- x, y, z = [x], [y], [z]
+ x = [x]
+ if not cbook.iterable(y):
+ y = [y]
+ if not cbook.iterable(z):
+ z = [z]
+
if not cbook.iterable(dx):
- dx, dy, dz = [dx], [dy], [dz]
+ dx = [dx]
+ if not cbook.iterable(dy):
+ dy = [dy]
+ if not cbook.iterable(dz):
+ dz = [dz]
+
if len(dx) == 1:
dx = dx * len(x)
- dy = dy * len(x)
- dz = dz * len(x)
+ if len(dy) == 1:
+ dy = dy * len(y)
+ if len(dz) == 1:
+ dz = dz * len(z)
+ if len(x) != len(y) or len(x) != len(z):
+ warnings.warn('x, y, and z must be the same length.')
+
+
minx, miny, minz = 1e20, 1e20, 1e20
maxx, maxy, maxz = -1e20, -1e20, -1e20
@@ -1052,16 +1166,46 @@
((xi + dxi, yi, zi), (xi + dxi, yi + dyi, zi),
(xi + dxi, yi + dyi, zi + dzi), (xi + dxi, yi, zi + dzi)),
])
-
- color = np.array(colorConverter.to_rgba(color))
+ #end for
+
+ facecolors = []
+ if color is None:
+ # no color specified
+ facecolors = [None] * len(x)
+ elif len(color) == len(x):
+ # bar colors specified, need to expand to number of faces
+ for c in color:
+ for i in range(6):
+ facecolors.append(c)
+ #end for
+ #end for
+ else:
+ # a single color specified, or face colors specified explicitly
+ facecolors = list(colorConverter.to_rgba_array(color))
+ if len(facecolors) < len(x):
+ facecolors *= len(x)
+ #endif
+ #endif
+
normals = self._generate_normals(polys)
- colors = self._shade_colors(color, normals)
-
- col = art3d.Poly3DCollection(polys, facecolor=colors)
+ sfacecolors = self._shade_colors(facecolors, normals)
+ col = art3d.Cuboid3DCollection(polys,
+ heuristic=heuristic,
+ facecolor=sfacecolors,
+ *args, **kwargs)
self.add_collection(col)
self.auto_scale_xyz((minx, maxx), (miny, maxy), (minz, maxz), had_data)
+ #end def
+
+ def set_title(self, label, fontdict=None, **kwargs):
+ Axes.set_title(self, label, fontdict, **kwargs)
+ (x, y) = self.title.get_position()
+ self.title.set_y(0.92 * y)
+ #end def set_title
+#end class Axes3D
+
def get_test_data(delta=0.05):
'''
Return a tuple X, Y, Z with a test data set.
Index: examples/mplot3d/hist3d_demo.py
===================================================================
--- examples/mplot3d/hist3d_demo.py (revision 8150)
+++ examples/mplot3d/hist3d_demo.py (working copy)
@@ -17,7 +17,7 @@
dy = dx.copy()
dz = hist.flatten()
-ax.bar3d(xpos, ypos, zpos, dx, dy, dz, color='b')
+ax.bar3d(xpos, ypos, zpos, dx, dy, dz, color='b', heuristic='histogram')
plt.show()
Index: examples/mplot3d/text3d_demo.py
===================================================================
--- examples/mplot3d/text3d_demo.py (revision 8150)
+++ examples/mplot3d/text3d_demo.py (working copy)
@@ -13,6 +13,10 @@
label = '(%d, %d, %d), dir=%s' % (x, y, z, zdir)
ax.text(x, y, z, label, zdir)
+ax.text(1, 1, 1, "red", color='red')
+ax.text2D(0.05, 0.95, "2D Text", transform=ax.transAxes)
+
+
ax.set_xlim3d(0, 10)
ax.set_ylim3d(0, 10)
ax.set_zlim3d(0, 10)
Index: examples/mplot3d/hist3d_demo2.py
===================================================================
--- examples/mplot3d/hist3d_demo2.py (revision 0)
+++ examples/mplot3d/hist3d_demo2.py (revision 0)
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+"""
+An example of how to color the bar3d plot.
+"""
+from mpl_toolkits.mplot3d import Axes3D
+import matplotlib.pyplot as plt
+import numpy as np
+
+# One color per box
+fig1 = plt.figure()
+ax1 = Axes3D(fig1)
+
+ax1.bar3d([0, 1, 2, 3], #x pos
+ [0, 0, 0, 0], #y pos
+ [0, 0, 0, 0], #z pos
+ 0.5, #dx
+ 0.5, #dy
+ [1, 2, 3, 4], #dz
+ color=['r', 'g', 'b', 'y'])
+
+
+# One color per face
+fig2 = plt.figure()
+ax2 = Axes3D(fig2)
+
+ax2.bar3d([0, 1, 2, 3], #x pos
+ [0, 0, 0, 0], #y pos
+ [0, 0, 0, 0], #z pos
+ 0.5, #dx
+ 0.5, #dy
+ [1, 2, 3, 4], #dz
+ color=['r', 'g', 'b', 'c', 'm', 'y']*6)
+
+
+# Thicker red edges
+fig3 = plt.figure()
+ax3 = Axes3D(fig3)
+
+ax3.bar3d([0, 1, 2, 3], #x pos
+ [0, 0, 0, 0], #y pos
+ [0, 0, 0, 0], #z pos
+ 0.5, #dx
+ 0.5, #dy
+ [1, 2, 3, 4], #dz
+ edgecolors='r',
+ linewidths=5)
+
+plt.show()
+
------------------------------------------------------------------------------
Download Intel® Parallel Studio Eval
Try the new software tools for yourself. Speed compiling, find bugs
proactively, and fine-tune applications for parallel performance.
See why Intel Parallel Studio got high marks during beta.
http://p.sf.net/sfu/intel-sw-dev
_______________________________________________
Matplotlib-devel mailing list
Matplotlib-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/matplotlib-devel