From b8654319b1e4f183b268c9cb3cafbe222c82f02c Mon Sep 17 00:00:00 2001 From: Roberto Moura Date: Fri, 18 Jul 2025 16:11:33 +0100 Subject: [PATCH 1/3] Set plot_bgcolor and paper_bgcolor from matplotlib figure backgrounds --- plotly/matplotlylib/renderer.py | 14 +++++++-- plotly/matplotlylib/tests/test_renderer.py | 34 ++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/plotly/matplotlylib/renderer.py b/plotly/matplotlylib/renderer.py index a282c67cec..933a609c98 100644 --- a/plotly/matplotlylib/renderer.py +++ b/plotly/matplotlylib/renderer.py @@ -11,6 +11,7 @@ import plotly.graph_objs as go from plotly.matplotlylib.mplexporter import Renderer +from plotly.matplotlylib.mplexporter.utils import export_color from plotly.matplotlylib import mpltools @@ -20,11 +21,15 @@ def _export_color(color): matplotlib uses "none" for fully transparent colors, which plotly does not accept, so transparent colors are exported as transparent black. Colors already exported by the mplexporter (hex or rgba strings) are - passed through unchanged. + passed through unchanged; raw matplotlib colors are converted with the + mplexporter's export_color. """ if isinstance(color, str): return "rgba(0,0,0,0)" if color == "none" else color - return [_export_color(c) for c in color] + if isinstance(color, (list, tuple)) and all(isinstance(c, str) for c in color): + return [_export_color(c) for c in color] + bgcolor = export_color(color) + return "rgba(0,0,0,0)" if bgcolor == "none" else bgcolor class PlotlyRenderer(Renderer): @@ -101,6 +106,9 @@ def open_figure(self, fig, props): autosize=False, hovermode="closest", ) + self.plotly_fig["layout"].paper_bgcolor = _export_color( + fig.patch.get_facecolor() + ) self.mpl_x_bounds, self.mpl_y_bounds = mpltools.get_axes_bounds(fig) margin = go.layout.Margin( l=int(self.mpl_x_bounds[0] * self.plotly_fig["layout"]["width"]), @@ -166,6 +174,8 @@ def open_axes(self, ax, props): ] self.current_bars = [] self.axis_ct += 1 + # update plot background with the axes background from mpl + self.plotly_fig["layout"].plot_bgcolor = _export_color(props["axesbg"]) # set defaults in axes xaxis = go.layout.XAxis( anchor="y{0}".format(self.axis_ct), zeroline=False, ticks="inside" diff --git a/plotly/matplotlylib/tests/test_renderer.py b/plotly/matplotlylib/tests/test_renderer.py index f56d830917..81b7c22271 100644 --- a/plotly/matplotlylib/tests/test_renderer.py +++ b/plotly/matplotlylib/tests/test_renderer.py @@ -199,3 +199,37 @@ 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_background_colors_from_matplotlib_defaults(): + fig, ax = plt.subplots() + ax.plot([0, 1], [0, 1]) + + plotly_fig = tls.mpl_to_plotly(fig) + + assert plotly_fig.layout.plot_bgcolor == "#FFFFFF" + assert plotly_fig.layout.paper_bgcolor == "#FFFFFF" + + +def test_custom_background_colors_are_preserved(): + fig, ax = plt.subplots() + fig.patch.set_facecolor("lightyellow") + ax.set_facecolor("lightgray") + ax.plot([0, 1], [0, 1]) + + plotly_fig = tls.mpl_to_plotly(fig) + + assert plotly_fig.layout.plot_bgcolor == "#D3D3D3" + assert plotly_fig.layout.paper_bgcolor == "#FFFFE0" + + +def test_semitransparent_axes_background_preserved(): + """Axes backgrounds with alpha export as mpl-style rgba strings, which + must be passed through as-is, not re-parsed by export_color.""" + fig, ax = plt.subplots() + ax.set_facecolor((0.1, 0.2, 0.3, 0.4)) + ax.plot([0, 1], [0, 1]) + + plotly_fig = tls.mpl_to_plotly(fig) + + assert plotly_fig.layout.plot_bgcolor == "rgba(26, 51, 76, 0.4)" From e5ced3ee25555c5c9df57e3b0f641827ca9b7ed1 Mon Sep 17 00:00:00 2001 From: Roberto Moura Date: Mon, 31 Aug 2026 18:52:44 +0100 Subject: [PATCH 2/3] Use mplexporter-exported figbg for paper background color --- plotly/matplotlylib/mplexporter/utils.py | 1 + plotly/matplotlylib/renderer.py | 13 +++---------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/plotly/matplotlylib/mplexporter/utils.py b/plotly/matplotlylib/mplexporter/utils.py index 23bfd414f7..8170ced8f0 100644 --- a/plotly/matplotlylib/mplexporter/utils.py +++ b/plotly/matplotlylib/mplexporter/utils.py @@ -271,6 +271,7 @@ def get_figure_properties(fig): "figwidth": fig.get_figwidth(), "figheight": fig.get_figheight(), "dpi": fig.dpi, + "figbg": export_color(fig.patch.get_facecolor()), } diff --git a/plotly/matplotlylib/renderer.py b/plotly/matplotlylib/renderer.py index 933a609c98..65bbcfabb1 100644 --- a/plotly/matplotlylib/renderer.py +++ b/plotly/matplotlylib/renderer.py @@ -11,7 +11,6 @@ import plotly.graph_objs as go from plotly.matplotlylib.mplexporter import Renderer -from plotly.matplotlylib.mplexporter.utils import export_color from plotly.matplotlylib import mpltools @@ -21,15 +20,11 @@ def _export_color(color): matplotlib uses "none" for fully transparent colors, which plotly does not accept, so transparent colors are exported as transparent black. Colors already exported by the mplexporter (hex or rgba strings) are - passed through unchanged; raw matplotlib colors are converted with the - mplexporter's export_color. + passed through unchanged. """ if isinstance(color, str): return "rgba(0,0,0,0)" if color == "none" else color - if isinstance(color, (list, tuple)) and all(isinstance(c, str) for c in color): - return [_export_color(c) for c in color] - bgcolor = export_color(color) - return "rgba(0,0,0,0)" if bgcolor == "none" else bgcolor + return [_export_color(c) for c in color] class PlotlyRenderer(Renderer): @@ -106,9 +101,7 @@ def open_figure(self, fig, props): autosize=False, hovermode="closest", ) - self.plotly_fig["layout"].paper_bgcolor = _export_color( - fig.patch.get_facecolor() - ) + self.plotly_fig["layout"].paper_bgcolor = _export_color(props["figbg"]) self.mpl_x_bounds, self.mpl_y_bounds = mpltools.get_axes_bounds(fig) margin = go.layout.Margin( l=int(self.mpl_x_bounds[0] * self.plotly_fig["layout"]["width"]), From 836c96ae06c194a9930aed5603b76a5285caa085 Mon Sep 17 00:00:00 2001 From: Roberto Moura Date: Mon, 31 Aug 2026 18:55:22 +0100 Subject: [PATCH 3/3] Add changelog entry --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd91e4979a..6b56e5ac90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Added - Support `marginal_x`/`marginal_y="heatmap"` in `density_heatmap`, drawing a single-row/column heatmap strip in the margin colored by the same `z`/`histfunc` aggregate as the main plot and sharing its color scale [[#5706](https://github.com/plotly/plotly.py/issues/5706)], with thanks to @lucasjamar for the contribution! +### Fixed +- Fix `mpl_to_plotly` not setting `paper_bgcolor` and `plot_bgcolor` from the matplotlib figure and axes backgrounds, so converted figures match the source figure's background colors [[#5285](https://github.com/plotly/plotly.py/pull/5285)], with thanks to @robertoffmoura for the contribution! + ## [7.0.0] - 2026-08-25 ### Fixed