Skip to content

Commit 26cbf88

Browse files
fill_between extended to 3D
1 parent 955432c commit 26cbf88

File tree

9 files changed

+215
-32
lines changed

9 files changed

+215
-32
lines changed

doc/api/toolkits/mplot3d/axes3d.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ Plotting
3030
plot_surface
3131
plot_wireframe
3232
plot_trisurf
33+
fill_between
3334

3435
clabel
3536
contour
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
Fill between 3D lines
2+
---------------------
3+
4+
The `.Axes.fill_between` method has been extended to 3D, allowing to fill the
5+
surface between two 3D lines with polygons through the `.Axes3D.fill_between`
6+
function.
7+
8+
.. plot::
9+
:include-source:
10+
:alt: Example of fill_between
11+
12+
N = 50
13+
theta = np.linspace(0, 2*np.pi, N)
14+
15+
xs1 = np.cos(theta)
16+
ys1 = np.sin(theta)
17+
zs1 = 0.1 * np.sin(6 * theta)
18+
19+
xs2 = 0.6 * np.cos(theta)
20+
ys2 = 0.6 * np.sin(theta)
21+
zs2 = 2 # Note that scalar values work in addition to length N arrays
22+
23+
fig = plt.figure()
24+
ax = fig.add_subplot(projection='3d')
25+
ax.fill_between(xs1, ys1, zs1, xs2, ys2, zs2,
26+
alpha=0.5, edgecolor='k')
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"""
2+
=====================
3+
Fill between 3D lines
4+
=====================
5+
6+
Demonstrate how to fill the space between 3D lines with surfaces. Here we
7+
create a sort of "lampshade" shape.
8+
"""
9+
10+
import matplotlib.pyplot as plt
11+
import numpy as np
12+
13+
N = 50
14+
theta = np.linspace(0, 2*np.pi, N)
15+
16+
xs1 = np.cos(theta)
17+
ys1 = np.sin(theta)
18+
zs1 = 0.1 * np.sin(6 * theta)
19+
20+
xs2 = 0.6 * np.cos(theta)
21+
ys2 = 0.6 * np.sin(theta)
22+
zs2 = 2 # Note that scalar values work in addition to length N arrays
23+
24+
fig = plt.figure()
25+
ax = fig.add_subplot(projection='3d')
26+
ax.fill_between(xs1, ys1, zs1, xs2, ys2, zs2, alpha=0.5, edgecolor='k')
27+
28+
plt.show()
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""
2+
=========================
3+
Fill under 3D line graphs
4+
=========================
5+
6+
Demonstrate how to create polygons which fill the space under a line
7+
graph. In this example polygons are semi-transparent, creating a sort
8+
of 'jagged stained glass' effect.
9+
"""
10+
11+
import math
12+
13+
import matplotlib.pyplot as plt
14+
import numpy as np
15+
16+
gamma = np.vectorize(math.gamma)
17+
N = 31
18+
x = np.linspace(0., 10., N)
19+
lambdas = range(1, 9)
20+
21+
ax = plt.figure().add_subplot(projection='3d')
22+
23+
facecolors = plt.colormaps['viridis_r'](np.linspace(0, 1, len(lambdas)))
24+
25+
for i, l in enumerate(lambdas):
26+
# Note fill_between can take coordinates as length N vectors, or scalars
27+
ax.fill_between(x, l, l**x * np.exp(-l) / gamma(x + 1),
28+
x, l, 0,
29+
facecolors=facecolors[i], alpha=.7)
30+
31+
ax.set(xlim=(0, 10), ylim=(1, 9), zlim=(0, 0.35),
32+
xlabel='x', ylabel=r'$\lambda$', zlabel='probability')
33+
34+
plt.show()

galleries/examples/mplot3d/polys3d.py

Lines changed: 21 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,36 @@
11
"""
2-
=============================================
3-
Generate polygons to fill under 3D line graph
4-
=============================================
2+
====================
3+
Generate 3D polygons
4+
====================
55
6-
Demonstrate how to create polygons which fill the space under a line
7-
graph. In this example polygons are semi-transparent, creating a sort
8-
of 'jagged stained glass' effect.
6+
Demonstrate how to create polygons in 3D. Here we stack 3 hexagons, somewhat
7+
like a traffic light.
98
"""
109

11-
import math
12-
1310
import matplotlib.pyplot as plt
1411
import numpy as np
1512

16-
from matplotlib.collections import PolyCollection
17-
18-
# Fixing random state for reproducibility
19-
np.random.seed(19680801)
13+
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
2014

15+
# Coordinates of a hexagon
16+
angles = np.linspace(0, 2 * np.pi, 6, endpoint=False)
17+
x = np.cos(angles)
18+
y = np.sin(angles)
19+
zs = [0, 1, 2]
2120

22-
def polygon_under_graph(x, y):
23-
"""
24-
Construct the vertex list which defines the polygon filling the space under
25-
the (x, y) line graph. This assumes x is in ascending order.
26-
"""
27-
return [(x[0], 0.), *zip(x, y), (x[-1], 0.)]
21+
# Close the hexagon by repeating the first vertex
22+
x = np.append(x, x[0])
23+
y = np.append(y, y[0])
2824

25+
verts = []
26+
for z in zs:
27+
verts.append(list(zip(x, y, np.full_like(x, z))))
2928

3029
ax = plt.figure().add_subplot(projection='3d')
3130

32-
x = np.linspace(0., 10., 31)
33-
lambdas = range(1, 9)
34-
35-
# verts[i] is a list of (x, y) pairs defining polygon i.
36-
gamma = np.vectorize(math.gamma)
37-
verts = [polygon_under_graph(x, l**x * np.exp(-l) / gamma(x + 1))
38-
for l in lambdas]
39-
facecolors = plt.colormaps['viridis_r'](np.linspace(0, 1, len(verts)))
40-
41-
poly = PolyCollection(verts, facecolors=facecolors, alpha=.7)
42-
ax.add_collection3d(poly, zs=lambdas, zdir='y')
43-
44-
ax.set(xlim=(0, 10), ylim=(1, 9), zlim=(0, 0.35),
45-
xlabel='x', ylabel=r'$\lambda$', zlabel='probability')
31+
poly = Poly3DCollection(verts, facecolors=['g', 'y', 'r'], alpha=.7)
32+
ax.add_collection3d(poly)
33+
ax.auto_scale_xyz(x, y, zs)
34+
ax.set_aspect('equal')
4635

4736
plt.show()

galleries/users_explain/toolkits/mplot3d.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,16 @@ See `.Axes3D.contourf` for API documentation.
111111
The feature demoed in the second contourf3d example was enabled as a
112112
result of a bugfix for version 1.1.0.
113113

114+
.. _fillbetween3d:
115+
116+
Fill between 3D lines
117+
=====================
118+
See `.Axes3D.fill_between` for API documentation.
119+
120+
.. figure:: /gallery/mplot3d/images/sphx_glr_fillbetween3d_001.png
121+
:target: /gallery/mplot3d/fillbetween3d.html
122+
:align: center
123+
114124
.. _polygon3d:
115125

116126
Polygon plots

lib/mpl_toolkits/mplot3d/axes3d.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1904,6 +1904,80 @@ def plot(self, xs, ys, *args, zdir='z', **kwargs):
19041904

19051905
plot3D = plot
19061906

1907+
def fill_between(self, xs1, ys1, zs1, xs2, ys2, zs2, *, where=None, **kwargs):
1908+
"""
1909+
Fill the area between two 2D or 3D curves.
1910+
1911+
The curves are defined by the points (*xs1*, *ys1*, *zs1*) and
1912+
(*xs2*, *ys2*, *zs2*). This creates one or multiple quadrangle
1913+
polygons that are filled. All points must be the same length N, or a
1914+
single value to be used for all points.
1915+
1916+
Parameters
1917+
----------
1918+
xs1, ys1, zs1 : float or 1D array-like
1919+
x, y, and z coordinates of vertices for 1st line.
1920+
1921+
xs2, ys2, zs2 : float or 1D array-like
1922+
x, y, and z coordinates of vertices for 2nd line.
1923+
1924+
where : array of bool (length N), optional
1925+
Define *where* to exclude some regions from being filled. The
1926+
filled regions are defined by the coordinates ``pts[where]``,
1927+
for all x, y, and z pts. More precisely, fill between ``pts[i]``
1928+
and ``pts[i+1]`` if ``where[i] and where[i+1]``. Note that this
1929+
definition implies that an isolated *True* value between two
1930+
*False* values in *where* will not result in filling. Both sides of
1931+
the *True* position remain unfilled due to the adjacent *False*
1932+
values.
1933+
1934+
**kwargs
1935+
All other keyword arguments are passed on to `.Poly3DCollection`.
1936+
1937+
Returns
1938+
-------
1939+
`.Poly3DCollection`
1940+
A `.Poly3DCollection` containing the plotted polygons.
1941+
1942+
"""
1943+
had_data = self.has_data()
1944+
xs1, ys1, zs1, xs2, ys2, zs2 = cbook._broadcast_with_masks(xs1, ys1, zs1,
1945+
xs2, ys2, zs2)
1946+
1947+
if where is None:
1948+
where = True
1949+
else:
1950+
where = np.asarray(where, dtype=bool)
1951+
if where.size != xs1.size:
1952+
raise ValueError(f"where size ({where.size}) does not match "
1953+
f"size ({xs1.size})")
1954+
where = where & ~np.isnan(xs1) # NaNs were broadcast in _broadcast_with_masks
1955+
1956+
polys = []
1957+
for idx0, idx1 in cbook.contiguous_regions(where):
1958+
xs1slice = xs1[idx0:idx1]
1959+
ys1slice = ys1[idx0:idx1]
1960+
zs1slice = zs1[idx0:idx1]
1961+
xs2slice = xs2[idx0:idx1]
1962+
ys2slice = ys2[idx0:idx1]
1963+
zs2slice = zs2[idx0:idx1]
1964+
1965+
if not len(xs1slice):
1966+
continue
1967+
1968+
for i in range(len(xs1slice) - 1):
1969+
poly = [(xs1slice[i], ys1slice[i], zs1slice[i]),
1970+
(xs1slice[i+1], ys1slice[i+1], zs1slice[i+1]),
1971+
(xs2slice[i+1], ys2slice[i+1], zs2slice[i+1]),
1972+
(xs2slice[i], ys2slice[i], zs2slice[i])]
1973+
polys.append(poly)
1974+
1975+
polyc = art3d.Poly3DCollection(polys, **kwargs)
1976+
self.add_collection(polyc)
1977+
1978+
self.auto_scale_xyz([xs1, xs2], [ys1, ys2], [zs1, zs2], had_data)
1979+
return polyc
1980+
19071981
def plot_surface(self, X, Y, Z, *, norm=None, vmin=None,
19081982
vmax=None, lightsource=None, **kwargs):
19091983
"""
70.7 KB
Loading

lib/mpl_toolkits/mplot3d/tests/test_axes3d.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,27 @@ def test_plot_3d_from_2d():
592592
ax.plot(xs, ys, zs=0, zdir='y')
593593

594594

595+
@mpl3d_image_comparison(['fill_between.png'], style='mpl20')
596+
def test_fill_between():
597+
fig = plt.figure()
598+
ax = fig.add_subplot(projection='3d')
599+
600+
theta = np.linspace(0, 2*np.pi, 50)
601+
602+
xs1 = np.cos(theta)
603+
ys1 = np.sin(theta)
604+
zs1 = 0.1 * np.sin(6 * theta)
605+
606+
xs2 = 0.6 * np.cos(theta)
607+
ys2 = 0.6 * np.sin(theta)
608+
zs2 = 2
609+
610+
where = (theta < np.pi/2) | (theta > 3*np.pi/2)
611+
612+
ax.fill_between(xs1, ys1, zs1, xs2, ys2, zs2,
613+
where=where, alpha=0.5, edgecolor='k')
614+
615+
595616
@mpl3d_image_comparison(['surface3d.png'], style='mpl20')
596617
def test_surface3d():
597618
# Remove this line when this test image is regenerated.

0 commit comments

Comments
 (0)