Skip to content

Commit

Permalink
Change Area/Ribbon to generate Polygon patches and add clipping trick (
Browse files Browse the repository at this point in the history
…#2896)

* Change Area/Ribbon to generate Polygon patches and add clipping trick

* Fix Area(fill=False)
  • Loading branch information
mwaskom authored Jul 12, 2022
1 parent 5910d6e commit ca9ba9c
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 37 deletions.
57 changes: 43 additions & 14 deletions seaborn/_marks/area.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,34 +21,40 @@ class AreaBase:

def _plot(self, split_gen, scales, orient):

kws = {}
patches = defaultdict(list)

for keys, data, ax in split_gen():

kws.setdefault(ax, defaultdict(list))

kws = {}
data = self._standardize_coordinate_parameters(data, orient)
resolved = resolve_properties(self, keys, scales)
verts = self._get_verts(data, orient)

ax.update_datalim(verts)
kws[ax]["verts"].append(verts)

# TODO fill= is not working here properly
# We could hack a fix, but would be better to handle fill in resolve_color
# TODO should really move this logic into resolve_color
fc = resolve_color(self, keys, "", scales)
if not resolved["fill"]:
fc = mpl.colors.to_rgba(fc, 0)

kws["facecolor"] = fc
kws["edgecolor"] = resolve_color(self, keys, "edge", scales)
kws["linewidth"] = resolved["edgewidth"]
kws["linestyle"] = resolved["edgestyle"]

kws[ax]["facecolors"].append(resolve_color(self, keys, "", scales))
kws[ax]["edgecolors"].append(resolve_color(self, keys, "edge", scales))
patches[ax].append(mpl.patches.Polygon(verts, **kws))

kws[ax]["linewidth"].append(resolved["edgewidth"])
kws[ax]["linestyle"].append(resolved["edgestyle"])
for ax, ax_patches in patches.items():

for ax, ax_kws in kws.items():
ax.add_collection(mpl.collections.PolyCollection(**ax_kws))
for patch in ax_patches:
self._postprocess_artist(patch, ax, orient)
ax.add_patch(patch)

def _standardize_coordinate_parameters(self, data, orient):
return data

def _postprocess_artist(self, artist, ax, orient):
pass

def _get_verts(self, data, orient):

dv = {"x": "y", "y": "x"}[orient]
Expand All @@ -66,8 +72,12 @@ def _legend_artist(self, variables, value, scales):
keys = {v: value for v in variables}
resolved = resolve_properties(self, keys, scales)

fc = resolve_color(self, keys, "", scales)
if not resolved["fill"]:
fc = mpl.colors.to_rgba(fc, 0)

return mpl.patches.Patch(
facecolor=resolve_color(self, keys, "", scales),
facecolor=fc,
edgecolor=resolve_color(self, keys, "edge", scales),
linewidth=resolved["edgewidth"],
linestyle=resolved["edgestyle"],
Expand Down Expand Up @@ -95,6 +105,25 @@ def _standardize_coordinate_parameters(self, data, orient):
dv = {"x": "y", "y": "x"}[orient]
return data.rename(columns={"baseline": f"{dv}min", dv: f"{dv}max"})

def _postprocess_artist(self, artist, ax, orient):

# TODO copying a lot of code from Bar, let's abstract this
# See comments there, I am not going to repeat them too

artist.set_linewidth(artist.get_linewidth() * 2)

linestyle = artist.get_linestyle()
if linestyle[1]:
linestyle = (linestyle[0], tuple(x / 2 for x in linestyle[1]))
artist.set_linestyle(linestyle)

artist.set_clip_path(artist.get_path(), artist.get_transform() + ax.transData)
if self.artist_kws.get("clip_on", True):
artist.set_clip_box(ax.bbox)

val_idx = ["y", "x"].index(orient)
artist.sticky_edges[val_idx][:] = (0, np.inf)


@dataclass
class Ribbon(AreaBase, Mark):
Expand Down
51 changes: 28 additions & 23 deletions tests/_marks/test_area.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

import matplotlib as mpl
from matplotlib.colors import to_rgba_array
from matplotlib.colors import to_rgba, to_rgba_array

from numpy.testing import assert_array_equal

Expand All @@ -15,8 +15,8 @@ def test_single_defaults(self):
x, y = [1, 2, 3], [1, 2, 1]
p = Plot(x=x, y=y).add(Area()).plot()
ax = p._figure.axes[0]
poly, = ax.collections
verts = poly.get_paths()[0].vertices.T
poly = ax.patches[0]
verts = poly.get_path().vertices.T

expected_x = [1, 2, 3, 3, 2, 1, 1]
assert_array_equal(verts[0], expected_x)
Expand All @@ -25,13 +25,13 @@ def test_single_defaults(self):
assert_array_equal(verts[1], expected_y)

fc = poly.get_facecolor()
assert_array_equal(fc, to_rgba_array("C0", .2))
assert_array_equal(fc, to_rgba("C0", .2))

ec = poly.get_edgecolor()
assert_array_equal(ec, to_rgba_array("C0", 1))
assert_array_equal(ec, to_rgba("C0", 1))

lw = poly.get_linewidth()
assert_array_equal(lw, mpl.rcParams["patch.linewidth"])
assert_array_equal(lw, mpl.rcParams["patch.linewidth"] * 2)

def test_direct_parameters(self):

Expand All @@ -46,20 +46,20 @@ def test_direct_parameters(self):
)
p = Plot(x=x, y=y).add(mark).plot()
ax = p._figure.axes[0]
poly, = ax.collections
poly = ax.patches[0]

fc = poly.get_facecolor()
assert_array_equal(fc, to_rgba_array(mark.color, mark.alpha))
assert_array_equal(fc, to_rgba(mark.color, mark.alpha))

ec = poly.get_edgecolor()
assert_array_equal(ec, to_rgba_array(mark.edgecolor, mark.edgealpha))
assert_array_equal(ec, to_rgba(mark.edgecolor, mark.edgealpha))

lw = poly.get_linewidth()
assert_array_equal(lw, mark.edgewidth)
assert_array_equal(lw, mark.edgewidth * 2)

ls = poly.get_linestyle()
dash_on, dash_off = mark.edgestyle[1]
expected = [(0, [mark.edgewidth * dash_on, mark.edgewidth * dash_off])]
expected = (0, (mark.edgewidth * dash_on / 4, mark.edgewidth * dash_off / 4))
assert ls == expected

def test_mapped(self):
Expand All @@ -68,33 +68,38 @@ def test_mapped(self):
g = ["a", "a", "a", "b", "b", "b"]
p = Plot(x=x, y=y, color=g, edgewidth=g).add(Area()).plot()
ax = p._figure.axes[0]
polys, = ax.collections

paths = polys.get_paths()
expected_x = [1, 2, 3, 3, 2, 1, 1], [2, 3, 4, 4, 3, 2, 2]
expected_y = [0, 0, 0, 1, 2, 1, 0], [0, 0, 0, 2, 3, 1, 0]

for i, path in enumerate(paths):
verts = path.vertices.T
for i, poly in enumerate(ax.patches):
verts = poly.get_path().vertices.T
assert_array_equal(verts[0], expected_x[i])
assert_array_equal(verts[1], expected_y[i])

fc = polys.get_facecolor()
assert_array_equal(fc, to_rgba_array(["C0", "C1"], .2))
fcs = [p.get_facecolor() for p in ax.patches]
assert_array_equal(fcs, to_rgba_array(["C0", "C1"], .2))

ec = polys.get_edgecolor()
assert_array_equal(ec, to_rgba_array(["C0", "C1"], 1))
ecs = [p.get_edgecolor() for p in ax.patches]
assert_array_equal(ecs, to_rgba_array(["C0", "C1"], 1))

lw = polys.get_linewidths()
assert lw[0] > lw[1]
lws = [p.get_linewidth() for p in ax.patches]
assert lws[0] > lws[1]

def test_unfilled(self):

x, y = [1, 2, 3], [1, 2, 1]
p = Plot(x=x, y=y).add(Area(fill=False)).plot()
ax = p._figure.axes[0]
poly = ax.patches[0]
assert poly.get_facecolor() == to_rgba("C0", 0)

def test_ribbon(self):

x, ymin, ymax = [1, 2, 4], [2, 1, 4], [3, 3, 5]
p = Plot(x=x, ymin=ymin, ymax=ymax).add(Ribbon()).plot()
ax = p._figure.axes[0]
poly, = ax.collections
verts = poly.get_paths()[0].vertices.T
verts = ax.patches[0].get_path().vertices.T

expected_x = [1, 2, 4, 4, 2, 1, 1]
assert_array_equal(verts[0], expected_x)
Expand Down

0 comments on commit ca9ba9c

Please sign in to comment.