Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).

## Unreleased

### Added
- Add support for converting matplotlib fill polygons (`plt.fill`) to filled scatter traces in `mpl_to_plotly` [[#5795](https://github.com/plotly/plotly.py/pull/5795)], with thanks to @robertoffmoura for the contribution!

### 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!
Expand Down
35 changes: 35 additions & 0 deletions plotly/matplotlylib/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import warnings

import matplotlib.patches as mpatches
import plotly.graph_objs as go
from plotly.matplotlylib.mplexporter import Renderer
from plotly.matplotlylib import mpltools
Expand Down Expand Up @@ -602,13 +603,47 @@ def draw_path(self, **props):
is_bar = mpltools.is_bar(self.current_mpl_ax.containers, **props)
if is_bar:
self.current_bars += [props]
elif (
isinstance(props["mplobj"], mpatches.Polygon)
and props["coordinates"] == "data"
):
self.msg += " Drawing a filled polygon\n"
self._draw_filled_polygon(props)
else:
self.msg += " This path isn't a bar, not drawing\n"
warnings.warn(
"I found a path object that I don't think is part "
"of a bar chart. Ignoring."
)

def _draw_filled_polygon(self, props):
"""Draw a matplotlib Polygon patch as a filled scatter trace."""
style = props["style"]
verts = props["data"]
facecolor = _export_color(style["facecolor"])
edgecolor = _export_color(style["edgecolor"])
mplobj = props.get("mplobj")
label = mplobj.get_label() if mplobj is not None else None
if not label or (isinstance(label, str) and label.startswith("_")):
label = None
showlegend = False
else:
showlegend = None
self.plotly_fig.add_trace(
go.Scatter(
name=label,
showlegend=showlegend,
x=self._convert_x_dates([v[0] for v in verts]),
y=[v[1] for v in verts],
mode="lines",
line=go.scatter.Line(color=edgecolor, width=style["edgewidth"]),
fill="toself",
fillcolor=facecolor,
xaxis="x{0}".format(self.axis_ct),
yaxis="y{0}".format(self.axis_ct),
)
)

def draw_text(self, **props):
"""Create an annotation dict for a text obj.

Expand Down
42 changes: 42 additions & 0 deletions plotly/matplotlylib/tests/test_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,48 @@ def test_uneven_custom_date_xtickvals_are_converted():
)


def test_fill_converts():
"""plt.fill polygons convert to filled scatter traces."""
x = np.linspace(0, 2 * np.pi, 50)
fig, ax = plt.subplots()
ax.fill(x, np.sin(x), "g")

plotly_fig = tls.mpl_to_plotly(fig)

assert len(plotly_fig.data) == 1
trace = plotly_fig.data[0]
assert trace.type == "scatter"
assert trace.fill == "toself"
assert np.allclose(trace.x, x)
assert np.allclose(trace.y, np.sin(x))
assert trace.fillcolor == "#007F00"
assert trace.line.color == "rgba(0,0,0,0)"


def test_fill_with_legend_label():
"""plt.fill polygons with labels export their name and show in legend."""
fig, ax = plt.subplots()
ax.fill([0, 1, 2], [0, 2, 0], "b", label="Shaded Area")
ax.fill([0, 1, 2], [0, 1, 0], "g")
ax.plot([0, 1, 2], [1, 1, 1], "r-", label="Threshold")
ax.legend()

plotly_fig = tls.mpl_to_plotly(fig)

assert plotly_fig.layout.showlegend == True
assert len(plotly_fig.data) == 3

labeled_fill = next(
t for t in plotly_fig.data if t.fill == "toself" and t.name == "Shaded Area"
)
assert labeled_fill.showlegend is not False

unlabeled_fill = next(
t for t in plotly_fig.data if t.fill == "toself" and t.name is None
)
assert unlabeled_fill.showlegend is False


def test_custom_date_xtickvals_given_as_numbers_are_converted():
"""Custom date ticks given as matplotlib date numbers must be converted
to date strings."""
Expand Down