Skip to content

Commit 774a9a1

Browse files
committed
Handle figure legends as well as axes legends
1 parent 136250d commit 774a9a1

3 files changed

Lines changed: 100 additions & 37 deletions

File tree

pgfutils.py

Lines changed: 44 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -901,52 +901,59 @@ def save(figure: "matplotlib.figure.Figure|None" = None):
901901
# Figures to save.
902902
to_save = [("main_figure", figure, None)]
903903

904-
# Go through and fix up a few little quirks on the axes within this figure.
904+
# Find all legends in this figure.
905+
legends = list(figure.legends)
905906
for axes in figure.get_axes():
906-
# Check if these axes have a legend.
907907
legend = axes.get_legend()
908908
if legend:
909-
# Want to save the legend as a separate figure.
910-
if config.pgfutils["separate_legend"]:
911-
# Create a new figure to hold the legend with empty axes.
912-
legend_fig = plt.figure()
913-
legend_ax = legend_fig.add_subplot()
914-
legend_ax.axis("off")
915-
916-
# Regenerate the legend on the new axes, allowing it to use the whole
917-
# figure. Removing it from the original axes and then using add_artist
918-
# on these axes seems to get the bounding box wrong.
919-
legend_standalone = legend_ax.legend(
920-
*legend.axes.get_legend_handles_labels(),
921-
bbox_to_anchor=(0, 0, 1, 1),
922-
bbox_transform=legend_fig.transFigure,
923-
ncols=legend._ncols,
924-
numpoints=legend.numpoints,
925-
scatterpoints=legend.scatterpoints,
926-
)
909+
legends.append(legend)
910+
911+
# Configure the legends.
912+
for legend in legends:
913+
# Want to save the legend as a separate figure.
914+
if config.pgfutils["separate_legend"]:
915+
# Create a new figure to hold the legend with empty axes.
916+
legend_fig = plt.figure()
917+
legend_ax = legend_fig.add_subplot()
918+
legend_ax.axis("off")
919+
920+
# Regenerate the legend on the new axes, allowing it to use the whole
921+
# figure. Removing it from the original axes and then using add_artist
922+
# on these axes seems to get the bounding box wrong.
923+
legend_standalone = legend_ax.legend(
924+
legend.legend_handles,
925+
[text.get_text() for text in legend.texts],
926+
bbox_to_anchor=(0, 0, 1, 1),
927+
bbox_transform=legend_fig.transFigure,
928+
ncols=legend._ncols,
929+
numpoints=legend.numpoints,
930+
scatterpoints=legend.scatterpoints,
931+
)
927932

928-
# Measure its bounding box.
929-
bbox = legend_standalone.get_tightbbox()
930-
if not bbox:
931-
raise RuntimeError("could not determine legend bounding box")
933+
# Measure its bounding box.
934+
bbox = legend_standalone.get_tightbbox()
935+
if not bbox:
936+
raise RuntimeError("could not determine legend bounding box")
932937

933-
# This is in pixels; convert to inches as savefig() will need.
934-
bbox = bbox.transformed(legend_fig.dpi_scale_trans.inverted())
938+
# This is in pixels; convert to inches as savefig() will need.
939+
bbox = bbox.transformed(legend_fig.dpi_scale_trans.inverted())
935940

936-
# Remove the original legend and replace the reference.
937-
legend.remove()
938-
legend = legend_standalone
941+
# Remove the original legend and replace the reference.
942+
legend.remove()
943+
legend = legend_standalone
939944

940-
# And save the standalone figure as a legend.
941-
to_save.append(("legend", legend_fig, bbox))
945+
# And save the standalone figure as a legend.
946+
to_save.append(("legend", legend_fig, bbox))
942947

943-
# There are no rcParams for the legend properties; set them directly.
944-
frame = legend.get_frame()
945-
frame.set_linewidth(config.pgfutils["legend_border_width"])
946-
frame.set_alpha(config.pgfutils["legend_opacity"])
947-
frame.set_ec(config.pgfutils["legend_border_color"])
948-
frame.set_fc(config.pgfutils["legend_background"])
948+
# There are no rcParams for the legend properties; set them directly.
949+
frame = legend.get_frame()
950+
frame.set_linewidth(config.pgfutils["legend_border_width"])
951+
frame.set_alpha(config.pgfutils["legend_opacity"])
952+
frame.set_ec(config.pgfutils["legend_border_color"])
953+
frame.set_fc(config.pgfutils["legend_background"])
949954

955+
# Go through and fix up a few little quirks on the axes within this figure.
956+
for axes in figure.get_axes():
950957
# Some PDF viewers show white lines through vector colorbars. This is a bug in
951958
# the viewers, but can be worked around by forcing the edge of the patches in
952959
# the colorbar to have the same colour as their faces. This doesn't work with
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# SPDX-FileCopyrightText: Blair Bonnett
2+
# SPDX-License-Identifier: BSD-3-Clause
3+
4+
from pgfutils import save, setup_figure
5+
6+
setup_figure(width=1, height=1, separate_legend=True)
7+
8+
from matplotlib import pyplot as plt
9+
import numpy as np
10+
11+
t = np.arange(0, 1, 0.005)
12+
s1 = np.sin(2 * np.pi * t)
13+
s2 = np.sin(4 * np.pi * t)
14+
s3 = np.sin(6 * np.pi * t)
15+
16+
# Disable all other frames and sources of text.
17+
fig = plt.figure(frameon=False)
18+
ax = fig.add_axes([0, 0, 1, 1])
19+
ax.axis("off")
20+
21+
(a1,) = ax.plot(t, s1)
22+
(a2,) = ax.plot(t, s2)
23+
(a3,) = ax.plot(t, s3)
24+
fig.legend([a1, a2, a3], ["One", "Two", "Three"])
25+
26+
save()

tests/test_legend.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,3 +151,33 @@ def test_separate_legend():
151151
assert legend["stroke"] == approx([1, 0.5, 0]), "incorrect border colour"
152152
assert legend["strokeopacity"] == approx(0.7), "incorrect border opacity"
153153
assert legend["linewidth"] == approx(4, abs=0.1), "incorrect border width"
154+
155+
156+
def test_separate_figure_legend():
157+
"""Check plot with separate figure legend..."""
158+
with build_pypgf(srcdir, "plot_with_separate_figure_legend.py") as res:
159+
assert res.returncode == 0, (
160+
f"Building {srcdir / 'plot_with_separate_figure_legend.pypgf'} failed."
161+
)
162+
163+
# Should be no legend in main figure.
164+
legend = extract_legend(srcdir / "plot_with_separate_figure_legend.pypgf")
165+
assert legend is None, "legend retained in main figure"
166+
167+
# Get the legend from the separate output.
168+
legend = extract_legend(
169+
srcdir / "plot_with_separate_figure_legend_legend0.pypgf"
170+
)
171+
172+
# Check the text sizes are correct.
173+
assert legend["text_sizes"] == approx([14, 14, 14]), "incorrect font sizes"
174+
175+
# Now check the values are correct. Note the increased margin for the
176+
# line size -- the output value often seems to be a wee way off the
177+
# number. I'd guess this is due to some rounding in the exporter. At
178+
# the end of the day 0.1 of a point is not that noticeable!
179+
assert legend["fill"] == approx([0, 0.5, 1]), "incorrect background colour"
180+
assert legend["fillopacity"] == approx(0.7), "incorrect background opacity"
181+
assert legend["stroke"] == approx([1, 0.5, 0]), "incorrect border colour"
182+
assert legend["strokeopacity"] == approx(0.7), "incorrect border opacity"
183+
assert legend["linewidth"] == approx(4, abs=0.1), "incorrect border width"

0 commit comments

Comments
 (0)