Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 2 commits into from
Jul 12, 2022
Merged
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
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