Skip to content

Commit

Permalink
Merge pull request #13 from cvanelteren/triangulation-fix
Browse files Browse the repository at this point in the history
  • Loading branch information
cvanelteren authored Jan 11, 2025
2 parents 6b8223a + afa14ca commit fb762c5
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 20 deletions.
70 changes: 50 additions & 20 deletions ultraplot/axes/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -4401,75 +4401,105 @@ def streamplot(self, x, y, u, v, c, **kwargs):
self._update_guide(m.lines, queue_colorbar=False, **guide_kw) # use lines
return m

@inputs._preprocess_or_redirect("x", "y", "z")

@inputs._parse_triangulation_with_preprocess('x', 'y', 'z', keywords=['triangles'])
@docstring._concatenate_inherited
@docstring._snippet_manager
def tricontour(self, x, y, z, **kwargs):
def tricontour(self, *args, **kwargs):
"""
%(plot.tricontour)s
"""
kw = kwargs.copy()
if x is None or y is None or z is None:
raise ValueError("Three input arguments are required.")
triangulation, z, args, kwargs = inputs._parse_triangulation_inputs(*args, **kwargs)

# Update kwargs and handle cmap
kw.update(_pop_props(kw, "collection"))
kw = self._parse_cmap(
x, y, z, min_levels=1, plot_lines=True, plot_contours=True, **kw
triangulation.x, triangulation.y, z, min_levels=1, plot_lines=True, plot_contours=True, **kw
)

# Handle labels and guide parameters
labels_kw = _pop_params(kw, self._add_auto_labels)
guide_kw = _pop_params(kw, self._update_guide)

# Extract and assign label
label = kw.pop("label", None)
m = self._call_native("tricontour", x, y, z, **kw)
m = self._call_native("tricontour", triangulation, z, **kw)
m._legend_label = label

# Add labels and update guide
self._add_auto_labels(m, **labels_kw)
self._update_guide(m, queue_colorbar=False, **guide_kw)

return m

@inputs._preprocess_or_redirect("x", "y", "z")

@inputs._parse_triangulation_with_preprocess('x', 'y', 'z', keywords=['triangles'])
@docstring._concatenate_inherited
@docstring._snippet_manager
def tricontourf(self, x, y, z, **kwargs):
def tricontourf(self, *args, **kwargs):
"""
%(plot.tricontourf)s
"""
kw = kwargs.copy()
if x is None or y is None or z is None:
raise ValueError("Three input arguments are required.")
triangulation, z, args, kw = inputs._parse_triangulation_inputs(*args, **kwargs)

# Update kwargs and handle contour parameters
kw.update(_pop_props(kw, "collection"))
contour_kw = _pop_kwargs(kw, "edgecolors", "linewidths", "linestyles")
kw = self._parse_cmap(x, y, z, plot_contours=True, **kw)
kw = self._parse_cmap(triangulation.x, triangulation.y, z, plot_contours=True, **kw)

# Handle patch edges, labels, and guide parameters
edgefix_kw = _pop_params(kw, self._fix_patch_edges)
labels_kw = _pop_params(kw, self._add_auto_labels)
guide_kw = _pop_params(kw, self._update_guide)

label = kw.pop("label", None)
m = cm = self._call_native("tricontourf", x, y, z, **kw)

# Call native tricontourf function with triangulation
m = cm = self._call_native("tricontourf", triangulation, z, **kw)
m._legend_label = label
self._fix_patch_edges(m, **edgefix_kw, **contour_kw) # no-op if not contour_kw

# Fix edges and add labels
self._fix_patch_edges(m, **edgefix_kw, **contour_kw) # No-op if not contour_kw
if contour_kw or labels_kw:
cm = self._fix_contour_edges("tricontour", x, y, z, **kw, **contour_kw)
cm = self._fix_contour_edges("tricontour", triangulation.x, triangulation.y, z, **kw, **contour_kw)

# Add auto labels and update the guide
self._add_auto_labels(m, cm, **labels_kw)
self._update_guide(m, queue_colorbar=False, **guide_kw)

return m

@inputs._preprocess_or_redirect("x", "y", "z")

@inputs._parse_triangulation_with_preprocess('x', 'y', 'z', keywords=['triangles'])
@docstring._concatenate_inherited
@docstring._snippet_manager
def tripcolor(self, x, y, z, **kwargs):
def tripcolor(self, *args, **kwargs):
"""
%(plot.tripcolor)s
"""
kw = kwargs.copy()
if x is None or y is None or z is None:
raise ValueError("Three input arguments are required.")
triangulation, z, args, kw = inputs._parse_triangulation_inputs(*args, **kwargs)

# Update kwargs and handle cmap
kw.update(_pop_props(kw, "collection"))
kw = self._parse_cmap(x, y, z, **kw)
kw = self._parse_cmap(triangulation.x, triangulation.y, z, **kw)

# Handle patch edges, labels, and guide parameters
edgefix_kw = _pop_params(kw, self._fix_patch_edges)
labels_kw = _pop_params(kw, self._add_auto_labels)
guide_kw = _pop_params(kw, self._update_guide)

# Plot with the native tripcolor method
with self._keep_grid_bools():
m = self._call_native("tripcolor", x, y, z, **kw)
m = self._call_native("tripcolor", triangulation, z, **kw)

# Fix edges and add labels
self._fix_patch_edges(m, **edgefix_kw, **kw)
self._add_auto_labels(m, **labels_kw)
self._update_guide(m, queue_colorbar=False, **guide_kw)

return m

# WARNING: breaking change from native 'X'
Expand Down
28 changes: 28 additions & 0 deletions ultraplot/internals/inputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,34 @@ def _from_data(data, *args):
return args



def _parse_triangulation_inputs(*args, **kwargs):
"""
Parse inputs using Matplotlib's `get_from_args_and_kwargs` method.
Returns a Triangulation object, z values, and updated args/kwargs.
"""
from matplotlib.tri import Triangulation
triangulation, args, kwargs = Triangulation.get_from_args_and_kwargs(*args, **kwargs)
if not args:
raise ValueError("No z values provided. Provide at least one positional argument for z.")
z = args[0] # Assume the first remaining argument is z
return triangulation, z, args[1:], kwargs

def _parse_triangulation_with_preprocess(*keys, keywords=None, allow_extra=True):
"""
Combines _parse_triangulation with _preprocess_or_redirect for backwards compatibility.
"""
def _decorator(func):
@_preprocess_or_redirect(*keys, keywords=keywords, allow_extra=allow_extra)
def wrapper(self, *args, **kwargs):
# Parse triangulation inputs after preprocessing
triangulation, z, remaining_args, updated_kwargs = _parse_triangulation_inputs(*args, **kwargs)
# Call the original function with parsed inputs
return func(self, triangulation, z, *remaining_args, **updated_kwargs)

return wrapper
return _decorator

def _preprocess_or_redirect(*keys, keywords=None, allow_extra=True):
"""
Redirect internal plotting calls to native matplotlib methods. Also convert
Expand Down
55 changes: 55 additions & 0 deletions ultraplot/tests/test_1dplots.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,3 +371,58 @@ def test_scatter_sizes():
for i, d in enumerate(data):
ax.text(i, 0.5, format(d, ".0f"), va="center", ha="center")
return fig

# Test introduced by issue #https://github.com/Ultraplot/ultraplot/issues/12#issuecomment-2576154882
# Check for concave triangulation related functions
from matplotlib import tri

@pytest.mark.mpl_image_compare
@pytest.mark.mpl_image_compare
@pytest.mark.parametrize(
"x, y, z, triangles, use_triangulation, use_datadict",
[
# Base data that's common to all test cases
base_data := (
np.array([0, 1, 0, 0, 1]),
np.array([0, 0, 1, 2, 2]),
np.array([0, 1, -1, 0, 2]),
np.array([[0, 1, 2], [2, 3, 4]]),
False,
False,
),
# Test without triangles
(*base_data[:3], None, False, False),
# Test with triangulation
(*base_data[:4], True, False),
(*base_data[:3], None, False, False),
# Test using data dictionary
(*base_data[:3], None, False, True),
],
)
def test_triplot_variants(x, y, z, triangles, use_triangulation, use_datadict):
fig, ax = uplt.subplots(figsize=(4, 3))
if use_datadict:
df = {"x": x, "y": y, "z": z}

if use_triangulation:
# Use a Triangulation object
triangulation = tri.Triangulation(x, y, triangles)
ax.tricontourf(triangulation, z, levels=64, cmap="PuBu")
elif use_datadict:
ax.tricontourf("x", "y", "z", data = df)
return
else:
# Use direct x, y, z inputs
ax.tricontourf(x, y, z, triangles=triangles, levels=64, cmap="PuBu")

if triangles is not None:
ax.triplot(x, y, triangles, "ko-") # Display cell edges
else:
if use_datadict:
df = {"x": x, "y": y, "z": z}
ax.triplot("x", "y", "ko-", data=df)
else:
ax.triplot(x, y, "ko-") # Without specific triangles

return fig

0 comments on commit fb762c5

Please sign in to comment.