From 6e401853a75c8eea8c8486c4f8ec05668d40fb01 Mon Sep 17 00:00:00 2001 From: Roberto Moura Date: Tue, 11 Aug 2026 10:07:30 +0000 Subject: [PATCH] Fix boxplot conversion by mapping 'none' colors to transparent rgba --- plotly/matplotlylib/renderer.py | 20 ++++++++++++-------- plotly/matplotlylib/tests/test_renderer.py | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/plotly/matplotlylib/renderer.py b/plotly/matplotlylib/renderer.py index a282c67cec..a688eccb8e 100644 --- a/plotly/matplotlylib/renderer.py +++ b/plotly/matplotlylib/renderer.py @@ -333,7 +333,7 @@ def draw_bar(self, coll): yaxis="y{0}".format(self.axis_ct), opacity=trace[0]["alpha"], # TODO: get all alphas if array? marker=go.bar.Marker( - color=trace[0]["facecolor"], # TODO: get all + color=_export_color(trace[0]["facecolor"]), # TODO: get all line=dict(width=trace[0]["edgewidth"]), ), ) # TODO ditto @@ -395,9 +395,13 @@ def draw_marked_line(self, **props): self.msg += "... with just markers\n" mode = "markers" if props["linestyle"]: - color = mpltools.merge_color_and_opacity( - props["linestyle"]["color"], props["linestyle"]["alpha"] - ) + if props["linestyle"]["color"] == "none": + # a fully transparent line; plotly rejects "none" as a color + color = "rgba(0,0,0,0)" + else: + color = mpltools.merge_color_and_opacity( + props["linestyle"]["color"], props["linestyle"]["alpha"] + ) if props["coordinates"] == "data": line = go.scatter.Line( @@ -417,22 +421,22 @@ def draw_marked_line(self, **props): if props["coordinates"] == "data": marker = go.scatter.Marker( opacity=props["markerstyle"]["alpha"], - color=props["markerstyle"]["facecolor"], + color=_export_color(props["markerstyle"]["facecolor"]), symbol=mpltools.convert_symbol(props["markerstyle"]["marker"]), size=props["markerstyle"]["markersize"], line=dict( - color=props["markerstyle"]["edgecolor"], + color=_export_color(props["markerstyle"]["edgecolor"]), width=props["markerstyle"]["edgewidth"], ), ) else: shape = dict( opacity=props["markerstyle"]["alpha"], - fillcolor=props["markerstyle"]["facecolor"], + fillcolor=_export_color(props["markerstyle"]["facecolor"]), symbol=mpltools.convert_symbol(props["markerstyle"]["marker"]), size=props["markerstyle"]["markersize"], line=dict( - color=props["markerstyle"]["edgecolor"], + color=_export_color(props["markerstyle"]["edgecolor"]), width=props["markerstyle"]["edgewidth"], ), ) diff --git a/plotly/matplotlylib/tests/test_renderer.py b/plotly/matplotlylib/tests/test_renderer.py index f56d830917..a3146f3007 100644 --- a/plotly/matplotlylib/tests/test_renderer.py +++ b/plotly/matplotlylib/tests/test_renderer.py @@ -199,3 +199,21 @@ def test_filled_path_collection_date_xaxis(): filled = [t for t in plotly_fig.data if t.fill == "toself"] assert len(filled) >= 1 assert all(isinstance(x, str) for x in filled[0].x) + + +def test_boxplot_converts_with_none_marker_facecolor(): + """Boxplot outlier markers use facecolor 'none', which plotly rejects.""" + fig, ax = plt.subplots() + ax.boxplot(np.random.randn(100, 4)) + plotly_fig = tls.mpl_to_plotly(fig) # used to raise ValueError + assert len(plotly_fig.data) > 0 + + +def test_line_with_none_color_converts(): + """Lines with color='none' use the string 'none' for the line color, + which plotly rejects; it must be exported as a transparent line.""" + fig, ax = plt.subplots() + ax.plot([0, 1], [0, 1], color="none") + plotly_fig = tls.mpl_to_plotly(fig) # used to raise ValueError + assert len(plotly_fig.data) == 1 + assert plotly_fig.data[0].line.color == "rgba(0,0,0,0)"