Skip to content

Commit 7d7f0fb

Browse files
authored
Merge branch 'main' into feat/autoshare-auto-detect
2 parents caf91ec + 9067b9f commit 7d7f0fb

4 files changed

Lines changed: 103 additions & 10 deletions

File tree

.github/workflows/build-ultraplot.yml

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -221,31 +221,94 @@ jobs:
221221
echo "No valid nodeids found on PR branch; skipping image comparison."
222222
exit 0
223223
else
224+
status=0
224225
echo "=== Memory before image comparison ===" && free -h
226+
set +e
225227
pytest -n ${PYTEST_WORKERS} --dist loadfile --tb=short --disable-warnings -W ignore \
226-
--mpl \
227-
--mpl-baseline-path=./ultraplot/tests/baseline \
228-
--mpl-results-path=./results/ \
229-
--mpl-generate-summary=html \
230-
--mpl-default-style="./ultraplot.yml" \
231-
"${FILTERED_NODEIDS[@]}" || status=$?
228+
--mpl \
229+
--mpl-baseline-path=./ultraplot/tests/baseline \
230+
--mpl-results-path=./results/ \
231+
--mpl-generate-summary=html \
232+
--mpl-default-style="./ultraplot.yml" \
233+
--junitxml=./results/junit.xml \
234+
"${FILTERED_NODEIDS[@]}"
235+
status=$?
236+
set -e
232237
echo "=== Memory after image comparison ===" && free -h
238+
if [ "$status" -ne 0 ] && [ -f ./results/junit.xml ]; then
239+
if python - <<'PY'
240+
import sys
241+
import xml.etree.ElementTree as ET
242+
try:
243+
root = ET.parse("./results/junit.xml").getroot()
244+
except Exception:
245+
sys.exit(1)
246+
if root.tag == "testsuites":
247+
suites = list(root.findall("testsuite"))
248+
else:
249+
suites = [root]
250+
failures = 0
251+
errors = 0
252+
for suite in suites:
253+
failures += int(suite.attrib.get("failures", 0) or 0)
254+
errors += int(suite.attrib.get("errors", 0) or 0)
255+
sys.exit(0 if (failures == 0 and errors == 0) else 1)
256+
PY
257+
then
258+
echo "pytest exited with $status but junit reports no failures/errors; overriding exit status to 0."
259+
status=0
260+
fi
261+
fi
233262
if [ "$status" -eq 4 ] || [ "$status" -eq 5 ]; then
234263
echo "No tests collected from selected nodeids; skipping image comparison."
235264
status=0
236265
fi
237266
fi
238267
exit "$status"
239268
else
269+
status=0
240270
echo "=== Memory before image comparison ===" && free -h
271+
set +e
241272
pytest -n ${PYTEST_WORKERS} --dist loadfile --tb=short --disable-warnings -W ignore \
242273
--mpl \
243274
--mpl-baseline-path=./ultraplot/tests/baseline \
244275
--mpl-results-path=./results/ \
245276
--mpl-generate-summary=html \
246277
--mpl-default-style="./ultraplot.yml" \
278+
--junitxml=./results/junit.xml \
247279
ultraplot/tests
280+
status=$?
281+
set -e
248282
echo "=== Memory after image comparison ===" && free -h
283+
if [ "$status" -ne 0 ] && [ -f ./results/junit.xml ]; then
284+
if python - <<'PY'
285+
import sys
286+
import xml.etree.ElementTree as ET
287+
try:
288+
root = ET.parse("./results/junit.xml").getroot()
289+
except Exception:
290+
sys.exit(1)
291+
if root.tag == "testsuites":
292+
suites = list(root.findall("testsuite"))
293+
else:
294+
suites = [root]
295+
failures = 0
296+
errors = 0
297+
for suite in suites:
298+
failures += int(suite.attrib.get("failures", 0) or 0)
299+
errors += int(suite.attrib.get("errors", 0) or 0)
300+
sys.exit(0 if (failures == 0 and errors == 0) else 1)
301+
PY
302+
then
303+
echo "pytest exited with $status but junit reports no failures/errors; overriding exit status to 0."
304+
status=0
305+
fi
306+
fi
307+
if [ "$status" -eq 4 ] || [ "$status" -eq 5 ]; then
308+
echo "No tests collected; skipping image comparison."
309+
status=0
310+
fi
311+
exit "$status"
249312
fi
250313

251314
# Return the html output of the comparison even if failed

ultraplot/figure.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2517,18 +2517,23 @@ def _align_super_title(self, renderer):
25172517
ha = self._suptitle.get_ha()
25182518
va = self._suptitle.get_va()
25192519

2520-
# Use original centering algorithm for positioning (regardless of alignment)
2520+
# Use original centering algorithm for horizontal positioning.
25212521
x, _ = self._get_align_coord(
25222522
"top",
25232523
axs,
25242524
includepanels=self._includepanels,
25252525
align=ha,
25262526
)
2527-
y = self._get_offset_coord("top", axs, renderer, pad=pad, extra=labs)
2527+
y_target = self._get_offset_coord("top", axs, renderer, pad=pad, extra=labs)
25282528

2529-
# Set final position and alignment on the suptitle
2529+
# Place suptitle so its *bbox bottom* sits at the target offset.
2530+
# This preserves spacing for all vertical alignments (e.g. va='top').
25302531
self._suptitle.set_ha(ha)
25312532
self._suptitle.set_va(va)
2533+
self._suptitle.set_position((x, 0))
2534+
bbox = self._suptitle.get_window_extent(renderer)
2535+
y_bbox = self.transFigure.inverted().transform((0, bbox.ymin))[1]
2536+
y = y_target - y_bbox
25322537
self._suptitle.set_position((x, y))
25332538

25342539
def _update_axis_label(self, side, axs):

ultraplot/tests/test_figure.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,31 @@ def test_explicit_sharey_propagates_scale_changes():
403403

404404
assert axs[0].get_yscale() == "log"
405405
assert axs[1].get_yscale() == "log"
406+
@pytest.mark.parametrize("va", ["bottom", "center", "top"])
407+
def test_suptitle_vertical_alignment_preserves_top_spacing(va):
408+
"""
409+
Suptitle vertical alignment should not reduce the spacing above top content.
410+
"""
411+
fig, axs = uplt.subplots(ncols=2)
412+
fig.format(
413+
suptitle="Long figure title\nsecond line",
414+
suptitle_kw={"va": va},
415+
toplabels=("left", "right"),
416+
)
417+
fig.canvas.draw()
418+
renderer = fig.canvas.get_renderer()
419+
420+
axs_top = fig._get_align_axes("top")
421+
labs = tuple(t for t in fig._suplabel_dict["top"].values() if t.get_text())
422+
pad = (fig._suptitle_pad / 72) / fig.get_size_inches()[1]
423+
y_expected = fig._get_offset_coord("top", axs_top, renderer, pad=pad, extra=labs)
424+
425+
bbox = fig._suptitle.get_window_extent(renderer)
426+
y_actual = fig.transFigure.inverted().transform((0, bbox.ymin))[1]
427+
y_tol = 1.5 / (fig.dpi * fig.get_size_inches()[1]) # ~1.5 px tolerance
428+
assert y_actual >= y_expected - y_tol
429+
430+
uplt.close("all")
406431

407432

408433
def test_subplots_pixelsnap_aligns_axes_bounds():

ultraplot/tests/test_subplots.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -689,7 +689,7 @@ def test_non_rectangular_outside_labels_top():
689689
uplt.close(fig)
690690

691691

692-
@pytest.mark.mpl_image_compare
692+
@pytest.mark.mpl_image_compare(tolerance=4)
693693
def test_outside_labels_with_panels():
694694
fig, ax = uplt.subplots(
695695
ncols=2,

0 commit comments

Comments
 (0)