8000 Place 3D contourfs midway between levels · matplotlib/matplotlib@44e04d8 · GitHub
[go: up one dir, main page]

Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 44e04d8

Browse files
committed
Place 3D contourfs midway between levels
1 parent 7c2a3c4 commit 44e04d8

File tree

5 files changed

+79
-12
lines changed

5 files changed

+79
-12
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
3D contourf polygons placed between levels
2+
------------------------------------------
3+
The polygons used in a 3D `~mpl_toolkits.mplot3d.Axes3D.contourf` plot are
4+
now placed halfway between the contour levels, as each polygon represents the
5+
location of values that lie between two levels.

lib/matplotlib/contour.py

Lines changed: 11 additions & 7 deletions
8000
Original file line numberDiff line numberDiff line change
@@ -813,6 +813,8 @@ def __init__(self, ax, *args,
813813
kwargs = self._process_args(*args, **kwargs)
814814
self._process_levels()
815815

816+
self._extend_min = self.extend in ['min', 'both']
817+
self._extend_max = self.extend in ['max', 'both']
816818
if self.colors is not None:
817819
ncolors = len(self.levels)
818820
if self.filled:
@@ -821,25 +823,27 @@ def __init__(self, ax, *args,
821823

822824
# Handle the case where colors are given for the extended
823825
# parts of the contour.
824-
extend_min = self.extend in ['min', 'both']
825-
extend_max = self.extend in ['max', 'both']
826+
826827
use_set_under_over = False
827828
# if we are extending the lower end, and we've been given enough
828829
# colors then skip the first color in the resulting cmap. For the
829830
# extend_max case we don't need to worry about passing more colors
830831
# than ncolors as ListedColormap will clip.
831-
total_levels = ncolors + int(exten 8000 d_min) + int(extend_max)
832-
if len(self.colors) == total_levels and (extend_min or extend_max):
832+
total_levels = (ncolors +
833+
int(self._extend_min) +
834+
int(self._extend_max))
835+
if (len(self.colors) == total_levels and
836+
(self._extend_min or self._extend_max)):
833837
use_set_under_over = True
834-
if extend_min:
838+
if self._extend_min:
835839
i0 = 1
836840

837841
cmap = mcolors.ListedColormap(self.colors[i0:None], N=ncolors)
838842

839843
if use_set_under_over:
840-
if extend_min:
844+
if self._extend_min:
841845
cmap.set_under(self.colors[0])
842-
if extend_max:
846+
if self._extend_max:
843847
cmap.set_over(self.colors[-1])
844848

845849
self.collections = cbook.silent_list(None)

lib/mpl_toolkits/mplot3d/axes3d.py

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2068,12 +2068,32 @@ def add_contour_set(
20682068
art3d.line_collection_2d_to_3d(linec, z, zdir=zdir)
20692069

20702070
def add_contourf_set(self, cset, zdir='z', offset=None):
2071+
self._add_contourf_set(cset, zdir=zdir, offset=offset)
2072+
2073+
def _add_contourf_set(self, cset, zdir='z', offset=None):
2074+
"""
2075+
Returns
2076+
-------
2077+
levels : numpy.ndarray
2078+
Levels at which the filled contours are added.
2079+
"""
20712080
zdir = '-' + zdir
2072-
for z, linec in zip(cset.levels, cset.collections):
2081+
2082+
midpoints = cset.levels[:-1] + np.diff(cset.levels) / 2
2083+
# Linearly interpolate to get levels for any extensions
2084+
if cset._extend_min:
2085+
min_level = cset.levels[0] - np.diff(cset.levels[:2]) / 2
2086+
midpoints = np.insert(midpoints, 0, min_level)
2087+
if cset._extend_max:
2088+
max_level = cset.levels[-1] + np.diff(cset.levels[-2:]) / 2
2089+
midpoints = np.append(midpoints, max_level)
2090+
2091+
for z, linec in zip(midpoints, cset.collections):
20732092
if offset is not None:
20742093
z = offset
20752094
art3d.poly_collection_2d_to_3d(linec, z, zdir=zdir)
20762095
linec.set_sort_zpos(z)
2096+
return midpoints
20772097

20782098
@_preprocess_data()
20792099
def contour(self, X, Y, Z, *args,
@@ -2168,6 +2188,16 @@ def tricontour(self, *args,
21682188
self.auto_scale_xyz(X, Y, Z, had_data)
21692189
return cset
21702190

2191+
def _auto_scale_contourf(self, X, Y, Z, zdir, levels, had_data):
2192+
# Autoscale in the zdir based on the levels added, which are
2193+
# different from data range if any contour extensions are present
2194+
dim_vals = {'x': X, 'y': Y, 'z': Z, zdir: levels}
2195+
# Input data and levels have different sizes, but auto_scale_xyz
2196+
# expected same-size input, so manually take min/max limits
2197+
limits = [(np.nanmin(dim_vals[dim]), np.nanmax(dim_vals[dim]))
2198+
for dim in ['x', 'y', 'z']]
2199+
self.auto_scale_xyz(*limits, had_data)
2200+
21712201
@_preprocess_data()
21722202
def contourf(self, X, Y, Z, *args, zdir='z', offset=None, **kwargs):
21732203
"""
@@ -2195,9 +2225,9 @@ def contourf(self, X, Y, Z, *args, zdir='z', offset=None, **kwargs):
21952225

21962226
jX, jY, jZ = art3d.rotate_axes(X, Y, Z, zdir)
21972227
cset = super().contourf(jX, jY, jZ, *args, **kwargs)
2198-
self.add_contourf_set(cset, zdir, offset)
2228+
levels = self._add_contourf_set(cset, zdir, offset)
21992229

2200-
self.auto_scale_xyz(X, Y, Z, had_data)
2230+
self._auto_scale_contourf(X, Y, Z, zdir, levels, had_data)
22012231
return cset
22022232

22032233
contourf3D = contourf
@@ -2246,9 +2276,9 @@ def tricontourf(self, *args, zdir='z', offset=None, **kwargs):
22462276
tri = Triangulation(jX, jY, tri.triangles, tri.mask)
22472277

22482278
cset = super().tricontourf(tri, jZ, *args, **kwargs)
2249-
self.add_contourf_set(cset, zdir, offset)
2279+
levels = self._add_contourf_set(cset, zdir, offset)
22502280

2251-
self.auto_scale_xyz(X, Y, Z, had_data)
2281+
self._auto_scale_contourf(X, Y, Z, zdir, levels, had_data)
22522282
return cset
22532283

22542284
def add_collection3d(self, col, zs=0, zdir='z'):
1.64 KB
Loading

lib/mpl_toolkits/tests/test_mplot3d.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,34 @@ def test_contourf3d_fill():
155155
ax.set_zlim(-1, 1)
156156

157157

158+
@pytest.mark.parametrize('extend, levels', [['both', [2, 4, 6]],
159+
['min', [2, 4, 6, 8]],
160+
['max', [0, 2, 4, 6]]])
161+
@check_figures_equal(extensions=["png"])
162+
def test_contourf3d_extend(fig_test, fig_ref, extend, levels):
163+
X, Y = np.meshgrid(np.arange(-2, 2, 0.25), np.arange(-2, 2, 0.25))
164+
# Z is in the range [0, 8]
165+
Z = X**2 + Y**2
166+
167+
# Manually set the over/under colors to be the end of the colormap
168+
cmap = plt.get_cmap('viridis').copy()
169+
cmap.set_under(cmap(0))
170+
cmap.set_over(cmap(255))
171+
# Set vmin/max to be the min/max values plotted on the reference image
172+
kwargs = {'vmin': 1, 'vmax': 7, 'cmap': cmap}
173+
174+
ax_ref = fig_ref.add_subplot(projection='3d')
175+
ax_ref.contourf(X, Y, Z, levels=[0, 2, 4, 6, 8], **kwargs)
176+
177+
ax_test = fig_test.add_subplot(projection='3d')
178+
ax_test.contourf(X, Y, Z, levels, extend=extend, **kwargs)
179+
180+
for ax in [ax_ref, ax_test]:
181+
ax.set_xlim(-2, 2)
182+
ax.set_ylim(-2, 2)
183+
ax.set_zlim(-10, 10)
184+
185+
158186
@mpl3d_image_comparison(['tricontour.png'], tol=0.02)
159187
def test_tricontour():
160188
fig = plt.figure()

0 commit comments

Comments
 (0)
0