Skip to content

Commit 21d2935

Browse files
Convert only axis-aligned rectangles to span shapes
1 parent 8a0df73 commit 21d2935

2 files changed

Lines changed: 60 additions & 5 deletions

File tree

‎plotly/matplotlylib/renderer.py‎

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,27 @@ def _has_blended_transform(transform):
7878
return _find_blended_transform(transform) is not None
7979

8080

81+
def _is_axis_aligned_rectangle(points):
82+
"""Check whether polygon vertices trace an axis-aligned rectangle: every
83+
vertex is a corner of the bounding box and all four corners appear."""
84+
xs = [float(x) for x, _ in points]
85+
ys = [float(y) for _, y in points]
86+
if not xs:
87+
return False
88+
x_min, x_max = min(xs), max(xs)
89+
y_min, y_max = min(ys), max(ys)
90+
corners = set()
91+
for x, y in zip(xs, ys):
92+
at_x_min = math.isclose(x, x_min, abs_tol=1e-9)
93+
at_x_max = math.isclose(x, x_max, abs_tol=1e-9)
94+
at_y_min = math.isclose(y, y_min, abs_tol=1e-9)
95+
at_y_max = math.isclose(y, y_max, abs_tol=1e-9)
96+
if not (at_x_min or at_x_max) or not (at_y_min or at_y_max):
97+
return False
98+
corners.add((at_x_max, at_y_max))
99+
return len(corners) == 4
100+
101+
81102
class PlotlyRenderer(Renderer):
82103
"""A renderer class inheriting from base for rendering mpl plots in plotly.
83104
@@ -705,6 +726,19 @@ def _draw_axes_line(self, props):
705726
self.msg += " Heck yeah, I drew that reference line\n"
706727
return True
707728

729+
def _is_axes_span(self, props):
730+
"""Check whether a path qualifies as a reference span (e.g. axhspan,
731+
axvspan): an axis-aligned rectangle in a blended axes/data
732+
transform."""
733+
if not isinstance(props["mplobj"], (mpatches.Rectangle, mpatches.Polygon)):
734+
return False
735+
if not (
736+
props["coordinates"] == "display"
737+
and _has_blended_transform(props["mplobj"].get_transform())
738+
):
739+
return False
740+
return _is_axis_aligned_rectangle(props["data"])
741+
708742
def _draw_axes_span(self, props):
709743
"""Draw a reference span (axhspan/axvspan) as a layout shape.
710744
@@ -977,11 +1011,7 @@ def draw_path(self, **props):
9771011
elif isinstance(props["mplobj"], mpatches.StepPatch):
9781012
self.msg += " Drawing a step path\n"
9791013
self._draw_step_path(props)
980-
elif (
981-
isinstance(props["mplobj"], (mpatches.Rectangle, mpatches.Polygon))
982-
and props["coordinates"] == "display"
983-
and _has_blended_transform(props["mplobj"].get_transform())
984-
):
1014+
elif self._is_axes_span(props):
9851015
if self._processing_legend:
9861016
self.msg += " Using native legend\n"
9871017
else:

‎plotly/matplotlylib/tests/test_renderer.py‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import matplotlib
66
import matplotlib.pyplot as plt
77
import matplotlib.dates as mdates
8+
import matplotlib.patches as mpatches
89
from packaging.version import Version
910
from matplotlib import transforms
1011
import plotly.tools as tls
@@ -1482,3 +1483,27 @@ def test_axhspan_with_custom_style_converts():
14821483
assert shape.line.color == "rgba(0, 0, 255, 0.5)"
14831484
assert shape.line.dash == "dash"
14841485
assert shape.line.width == 1.0
1486+
1487+
1488+
def test_blended_unaligned_shapes_do_not_convert_to_spans():
1489+
"""A triangle and a rotated rectangle in a blended axes/data transform
1490+
warn and leave the layout shapes empty."""
1491+
fig, ax = plt.subplots()
1492+
ax.plot([0, 10], [0, 10])
1493+
ax.add_patch(
1494+
mpatches.Polygon(
1495+
[[5, 0.1], [9, 0.1], [7, 0.5]],
1496+
transform=ax.get_xaxis_transform(),
1497+
color="b",
1498+
)
1499+
)
1500+
ax.add_patch(
1501+
mpatches.Rectangle(
1502+
(6, 0.6), 2, 0.2, angle=30, transform=ax.get_xaxis_transform(), color="g"
1503+
)
1504+
)
1505+
1506+
with pytest.warns(UserWarning, match="path object"):
1507+
plotly_fig = tls.mpl_to_plotly(fig)
1508+
1509+
assert len(plotly_fig.layout.shapes) == 0

0 commit comments

Comments
 (0)