diff --git a/plotly/matplotlylib/mpltools.py b/plotly/matplotlylib/mpltools.py index 0a3206998ba..06732cb9466 100644 --- a/plotly/matplotlylib/mpltools.py +++ b/plotly/matplotlylib/mpltools.py @@ -269,7 +269,9 @@ def get_bar_gap(bar_starts, bar_ends, tol=1e-10): gap0 = gaps[0] uniform = all([abs(gap0 - gap) < tol for gap in gaps]) if uniform: - return gap0 + # plotly's bargap must be in [0, 1]; clamp to guard against + # floating point noise (e.g. -8.9e-16 for touching bars) + return min(max(gap0, 0.0), 1.0) def convert_rgba_array(color_list): diff --git a/plotly/matplotlylib/tests/test_renderer.py b/plotly/matplotlylib/tests/test_renderer.py index 0d63e4815b9..3ca4f1ed421 100644 --- a/plotly/matplotlylib/tests/test_renderer.py +++ b/plotly/matplotlylib/tests/test_renderer.py @@ -84,3 +84,17 @@ def test_multiple_traces_native_legend(): assert plotly_fig.data[0].mode == "lines" assert plotly_fig.data[1].mode == "markers" assert plotly_fig.data[2].mode == "lines+markers" + + +def test_get_bar_gap_clamps_negative_float_noise(): + """Touching bars can produce a tiny negative gap from floating point + noise (e.g. -8.88e-16 for a histogram); plotly rejects bargap outside + [0, 1], so the gap must be clamped.""" + from plotly.matplotlylib.mpltools import get_bar_gap + + # touching bars: gap is exactly 0 + assert get_bar_gap([0.0, 1.0], [1.0, 2.0]) == 0.0 + # overlapping-by-noise bars: gap is a tiny negative float, clamped to 0 + assert get_bar_gap([0.0, 1.0], [1.0 + 1e-15, 2.0]) == 0.0 + # positive gaps are unchanged + assert get_bar_gap([0.0, 2.0], [1.0, 3.0]) == 1.0