diff --git a/CHANGELOG.md b/CHANGELOG.md index ea837e3d0b..346a1459c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed - Fix concurrent first access to lazily initialized graph object properties, which could raise `ValueError("Invalid value")` [[#5691](https://github.com/plotly/plotly.py/pull/5691)], with thanks to @hb1915 for the contribution! - Fix `mpl_to_plotly` crashing on touching bars (such as `plt.hist`) due to floating-point noise producing negative `bargap` values by clamping `bargap` to `[0, 1]` [[#5696](https://github.com/plotly/plotly.py/pull/5696)], with thanks to @robertoffmoura for the contribution! +- Export tick label colors to the plotly tickfont in `mpl_to_plotly` [[#5716](https://github.com/plotly/plotly.py/pull/5716)], with thanks to @robertoffmoura for the contribution! - Fix `px.sunburst`, `px.treemap` and `px.icicle` listing sectors in a different order on every run when `path` is used with a Polars DataFrame; sectors now follow their order of first appearance for all dataframe backends [[#5766](https://github.com/plotly/plotly.py/pull/5766)], with thanks to @Irahan2 for the contribution! ## [7.1.0] - 2026-09-15 diff --git a/plotly/matplotlylib/mplexporter/utils.py b/plotly/matplotlylib/mplexporter/utils.py index 8170ced8f0..e4ce206db3 100644 --- a/plotly/matplotlylib/mplexporter/utils.py +++ b/plotly/matplotlylib/mplexporter/utils.py @@ -239,12 +239,15 @@ def get_axis_properties(axis): # Get axis scale props["scale"] = axis.get_scale() - # Get major tick label size (assumes that's all we really care about!) + # Get major tick label size and color (assumes that's all we really + # care about!) labels = axis.get_ticklabels() if labels: props["fontsize"] = labels[0].get_fontsize() + props["fontcolor"] = export_color(labels[0].get_color()) else: props["fontsize"] = None + props["fontcolor"] = None # Get associated grid props["grid"] = get_grid_style(axis) diff --git a/plotly/matplotlylib/mpltools.py b/plotly/matplotlylib/mpltools.py index e1198c9792..35061e2552 100644 --- a/plotly/matplotlylib/mpltools.py +++ b/plotly/matplotlylib/mpltools.py @@ -516,6 +516,21 @@ def prep_ticks(ax, index, ax_type, props): return axis_dict +def _export_color(color): + """Export a matplotlib color for use as a plotly 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. + """ + if color is None: + return None + if isinstance(color, str): + return color if color != "none" else "rgba(0,0,0,0)" + return [_export_color(c) for c in color] + + def prep_xy_axis(ax, props, x_bounds, y_bounds): xaxis = dict( type=props["axes"][0]["scale"], @@ -523,7 +538,10 @@ def prep_xy_axis(ax, props, x_bounds, y_bounds): showgrid=props["axes"][0]["grid"]["gridOn"], domain=convert_x_domain(props["bounds"], x_bounds), side=props["axes"][0]["position"], - tickfont=dict(size=props["axes"][0]["fontsize"]), + tickfont=dict( + size=props["axes"][0]["fontsize"], + color=_export_color(props["axes"][0]["fontcolor"]), + ), ) xaxis.update(prep_ticks(ax, 0, "x", props)) yaxis = dict( @@ -532,7 +550,10 @@ def prep_xy_axis(ax, props, x_bounds, y_bounds): showgrid=props["axes"][1]["grid"]["gridOn"], domain=convert_y_domain(props["bounds"], y_bounds), side=props["axes"][1]["position"], - tickfont=dict(size=props["axes"][1]["fontsize"]), + tickfont=dict( + size=props["axes"][1]["fontsize"], + color=_export_color(props["axes"][1]["fontcolor"]), + ), ) yaxis.update(prep_ticks(ax, 1, "y", props)) return xaxis, yaxis diff --git a/plotly/matplotlylib/renderer.py b/plotly/matplotlylib/renderer.py index 65bbcfabb1..57a466b80c 100644 --- a/plotly/matplotlylib/renderer.py +++ b/plotly/matplotlylib/renderer.py @@ -14,17 +14,7 @@ from plotly.matplotlylib import mpltools -def _export_color(color): - """Export a matplotlib color for use as a plotly 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. - """ - if isinstance(color, str): - return "rgba(0,0,0,0)" if color == "none" else color - return [_export_color(c) for c in color] +from plotly.matplotlylib.mpltools import _export_color class PlotlyRenderer(Renderer): diff --git a/plotly/matplotlylib/tests/test_renderer.py b/plotly/matplotlylib/tests/test_renderer.py index 02dd6cf34a..84fa105778 100644 --- a/plotly/matplotlylib/tests/test_renderer.py +++ b/plotly/matplotlylib/tests/test_renderer.py @@ -366,3 +366,37 @@ def test_custom_date_xtickvals_given_as_numbers_are_converted(): "2023-01-07 00:00:00", "2023-01-10 00:00:00", ) + + +def test_tick_label_color_exports(): + """Tick label colors are exported to the plotly tickfont.""" + fig, ax = plt.subplots() + ax.plot([0, 1], [0, 1]) + + plotly_fig = tls.mpl_to_plotly(fig) + + assert plotly_fig.layout.xaxis.tickfont.color == "#000000" + + +def test_dark_tick_label_color_exports(): + """Dark-background tick label colors are exported to the plotly + tickfont.""" + with plt.style.context("dark_background"): + fig, ax = plt.subplots() + ax.plot([0, 1], [0, 1]) + + plotly_fig = tls.mpl_to_plotly(fig) + + assert plotly_fig.layout.xaxis.tickfont.color == "#FFFFFF" + + +def test_transparent_tick_label_color_exports(): + """Transparent tick label colors ('none') export as transparent rgba.""" + fig, ax = plt.subplots() + ax.plot([0, 1], [0, 1]) + ax.tick_params(labelcolor="none") + + plotly_fig = tls.mpl_to_plotly(fig) + + assert plotly_fig.layout.xaxis.tickfont.color == "rgba(0,0,0,0)" + assert plotly_fig.layout.yaxis.tickfont.color == "rgba(0,0,0,0)"