Skip to content

mpl_to_plotly does not preserve axis labels (bar plots are useless) #5059

Open

Description

@krassowski

While mpl_to_plotly is little known and receives little love, this bug is pretty easy to fix. Would you be open to a PR?

Details
import matplotlib.pyplot as plt
from plotly.tools import mpl_to_plotly
from plotly.offline import plot

In matplotlib this produces a barplot with labels:

labels = ['a', 'b', 'c']
values = [1, 2, 3]

f = plt.figure(figsize=(6, 4))
plt.bar(labels, values)
plt.tight_layout()

Image

But conversion to plotly looses the labels:

plotly_fig = mpl_to_plotly(f)
plot(plotly_fig)

Image

A minimal fix would be to modify prep_ticks by appending:

    if axis_dict.get("type") == "date":
        return axis_dict
    
    vals = []
    texts = []
    for tick in axis.majorTicks:
        vals.append(tick.get_loc())
        texts.append(tick.label1.get_text())
    
    if texts:
        axis_dict = {}
        axis_dict['tickmode'] = 'array'
        axis_dict['tickvals'] = vals
        axis_dict['ticktext'] = texts
    
    return axis_dict

which produces:

Image

prep_ticks is defined in:

def prep_ticks(ax, index, ax_type, props):
"""Prepare axis obj belonging to axes obj.
positional arguments:
ax - the mpl axes instance
index - the index of the axis in `props`
ax_type - 'x' or 'y' (for now)
props - an mplexporter poperties dictionary
"""
axis_dict = dict()
if ax_type == "x":
axis = ax.get_xaxis()
elif ax_type == "y":
axis = ax.get_yaxis()
else:
return dict() # whoops!
scale = props["axes"][index]["scale"]
if scale == "linear":
# get tick location information
try:
tickvalues = props["axes"][index]["tickvalues"]
tick0 = tickvalues[0]
dticks = [
round(tickvalues[i] - tickvalues[i - 1], 12)
for i in range(1, len(tickvalues) - 1)
]
if all([dticks[i] == dticks[i - 1] for i in range(1, len(dticks) - 1)]):
dtick = tickvalues[1] - tickvalues[0]
else:
warnings.warn(
"'linear' {0}-axis tick spacing not even, "
"ignoring mpl tick formatting.".format(ax_type)
)
raise TypeError
except (IndexError, TypeError):
axis_dict["nticks"] = props["axes"][index]["nticks"]
else:
axis_dict["tick0"] = tick0
axis_dict["dtick"] = dtick
axis_dict["tickmode"] = None
elif scale == "log":
try:
axis_dict["tick0"] = props["axes"][index]["tickvalues"][0]
axis_dict["dtick"] = (
props["axes"][index]["tickvalues"][1]
- props["axes"][index]["tickvalues"][0]
)
axis_dict["tickmode"] = None
except (IndexError, TypeError):
axis_dict = dict(nticks=props["axes"][index]["nticks"])
base = axis.get_transform().base
if base == 10:
if ax_type == "x":
axis_dict["range"] = [
math.log10(props["xlim"][0]),
math.log10(props["xlim"][1]),
]
elif ax_type == "y":
axis_dict["range"] = [
math.log10(props["ylim"][0]),
math.log10(props["ylim"][1]),
]
else:
axis_dict = dict(range=None, type="linear")
warnings.warn(
"Converted non-base10 {0}-axis log scale to 'linear'" "".format(ax_type)
)
else:
return dict()
# get tick label formatting information
formatter = axis.get_major_formatter().__class__.__name__
if ax_type == "x" and "DateFormatter" in formatter:
axis_dict["type"] = "date"
try:
axis_dict["tick0"] = mpl_dates_to_datestrings(axis_dict["tick0"], formatter)
except KeyError:
pass
finally:
axis_dict.pop("dtick", None)
axis_dict.pop("tickmode", None)
axis_dict["range"] = mpl_dates_to_datestrings(props["xlim"], formatter)
if formatter == "LogFormatterMathtext":
axis_dict["exponentformat"] = "e"
return axis_dict

Activity

  1. gvwilson commented on Mar 3, 2025

    @gvwilson
    Contributor

    thanks very much @krassowski - yes, we would be very happy to get a PR. thanks - @gvwilson

  2. willtryagain commented on Sep 16, 2025

    @willtryagain

    Hi @krassowski,

    Thanks for looking into this issue. I noticed you have a fix ready for this and wanted to check in on its status. Are you still actively working on it or do you have plans to submit it for review soon?

    I'm asking because this bug is an upstream dependency blocker for me, where wandb relies on Plotly internally.

    If you're no longer able to work on it, would you be open to me submitting a separate PR for a fix?

    Let me know what works best. Thanks!

  3. krassowski commented on Sep 16, 2025

    @krassowski
    Author

    @willtryagain absolutely please submit sorry I forgot to open the PR earlier!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3backlogbugsomething broken

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions