Skip to content

fix(draw): save with tight bounding box and add pad_inches kwarg - #103

Merged
john-boyer-phd merged 4 commits into
graph-algorithms:masterfrom
YuEfSaEDU:fix/draw-tight-bbox-100
Sep 18, 2026
Merged

john-boyer-phd merged 4 commits into
graph-algorithms:masterfrom
YuEfSaEDU:fix/draw-tight-bbox-100

Conversation

@YuEfSaEDU

Copy link
Copy Markdown
Contributor

Images produced by draw() were framed by arbitrary whitespace from both the data-coordinate limits and the default savefig margins. savefig() is now invoked with bbox_inches="tight", and a new pad_inches keyword (default 0.1) gives callers explicit control of the padding in inches; axes limits are set flush to the drawn geometry with a box-adjustable fixed aspect, so pad_inches=0.0 extends the drawing to the image borders.

The K5-minus-edge.png example image was regenerated with the default padding, and its README reference is now an absolute URL so it renders on the PyPI landing page. Tests cover the savefig() call arguments and the resulting output padding.

Fixes #100

Pass bbox_inches="tight" to the savefig() invocation in draw() so
generated images are not padded arbitrarily, and respond to a new
pad_inches keyword (default 0.1) that gives the caller control over
the padding, in inches, around the drawing. Axes limits are now flush
with the drawn geometry and the fixed-aspect axes adjusts its box, so
a pad_inches of 0.0 causes the drawing to extend to the borders of
the image.

Regenerate examples/classic/output/K5-minus-edge.png with the default
0.1 padding, use an absolute URL for the image in README.md so it also
renders on the PyPI landing page, and add tests covering the savefig()
call arguments and output padding behavior.

Fixes graph-algorithms#100
@wbkboyer

Copy link
Copy Markdown
Member

Thank you for the PR @YuEfSaEDU ! I'm going to resolve some merge conflicts and push the changes directly to your branch, so you'll have to pull down changes from the remote!

@wbkboyer
wbkboyer self-requested a review September 17, 2026 22:00
@wbkboyer

wbkboyer commented Sep 17, 2026 •

Copy link
Copy Markdown
Member

I have some bad news: after merging in master (i.e. #102 ), the following test seem to be failing:

tests/test_draw_colors.py::test_png_background_and_transparency[function-False] FAILED           [ 25%]
tests/test_draw_colors.py::test_png_background_and_transparency[function-True] FAILED            [ 26%]
tests/test_draw_colors.py::test_png_background_and_transparency[method-False] FAILED             [ 28%]
tests/test_draw_colors.py::test_png_background_and_transparency[method-True] FAILED              [ 30%]
Full pytest logs from run on MacOS with planarity built using Clang 22.1.6, Python 3.14.6, Cython 3.3.0
(.venv) wbkboyer@Wandas-MacBook-Pro planarity % pytest -vvv                                 
========================================= test session starts ==========================================
platform darwin -- Python 3.14.6, pytest-9.1.1, pluggy-1.6.0 -- /Users/wbkboyer/git/planarity/.venv/bin/python3.14
cachedir: .pytest_cache
rootdir: /Users/wbkboyer/git/planarity
configfile: pyproject.toml
collected 52 items                                                                                     

tests/test_draw_colors.py::test_individual_colors_reach_artists[function-facecolor-#abcdef] PASSED [  1%]
tests/test_draw_colors.py::test_individual_colors_reach_artists[function-vertex_facecolor-gold] PASSED [  3%]
tests/test_draw_colors.py::test_individual_colors_reach_artists[function-vertex_bordercolor-color2] PASSED [  5%]
tests/test_draw_colors.py::test_individual_colors_reach_artists[function-vertex_label_facecolor-lightgreen] PASSED [  7%]
tests/test_draw_colors.py::test_individual_colors_reach_artists[function-vertex_label_bordercolor-#aabbcc] PASSED [  9%]
tests/test_draw_colors.py::test_individual_colors_reach_artists[function-edge_linecolor-magenta] PASSED [ 11%]
tests/test_draw_colors.py::test_individual_colors_reach_artists[method-facecolor-#abcdef] PASSED [ 13%]
tests/test_draw_colors.py::test_individual_colors_reach_artists[method-vertex_facecolor-gold] PASSED [ 15%]
tests/test_draw_colors.py::test_individual_colors_reach_artists[method-vertex_bordercolor-color2] PASSED [ 17%]
tests/test_draw_colors.py::test_individual_colors_reach_artists[method-vertex_label_facecolor-lightgreen] PASSED [ 19%]
tests/test_draw_colors.py::test_individual_colors_reach_artists[method-vertex_label_bordercolor-#aabbcc] PASSED [ 21%]
tests/test_draw_colors.py::test_individual_colors_reach_artists[method-edge_linecolor-magenta] PASSED [ 23%]
tests/test_draw_colors.py::test_png_background_and_transparency[function-False] FAILED           [ 25%]
tests/test_draw_colors.py::test_png_background_and_transparency[function-True] FAILED            [ 26%]
tests/test_draw_colors.py::test_png_background_and_transparency[method-False] FAILED             [ 28%]
tests/test_draw_colors.py::test_png_background_and_transparency[method-True] FAILED              [ 30%]
tests/test_draw_colors.py::test_color_controls_combine_without_labels[function] PASSED           [ 32%]
tests/test_draw_colors.py::test_color_controls_combine_without_labels[method] PASSED             [ 34%]
tests/test_draw_colors.py::test_default_background_resets_after_custom_draw[function] PASSED     [ 36%]
tests/test_draw_colors.py::test_default_background_resets_after_custom_draw[method] PASSED       [ 38%]
tests/test_draw_colors.py::test_existing_figure_options[function] PASSED                         [ 40%]
tests/test_draw_colors.py::test_existing_figure_options[method] PASSED                           [ 42%]
tests/test_draw_colors.py::test_unknown_keyword_still_rejected[function] PASSED                  [ 44%]
tests/test_draw_colors.py::test_unknown_keyword_still_rejected[method] PASSED                    [ 46%]
tests/test_draw_colors.py::test_invalid_color_rejected[function] PASSED                          [ 48%]
tests/test_draw_colors.py::test_invalid_color_rejected[method] PASSED                            [ 50%]
tests/test_planarity.py::TestPlanarity::test_is_planar_edgelist_input PASSED                     [ 51%]
tests/test_planarity.py::TestPlanarity::test_is_planar_edgelist_input_function PASSED            [ 53%]
tests/test_planarity.py::TestPlanarity::test_is_planar_adj_input PASSED                          [ 55%]
tests/test_planarity.py::TestPlanarity::test_is_planar_adj_input_function PASSED                 [ 57%]
tests/test_planarity.py::TestPlanarity::test_is_planar_adj_symmetric PASSED                      [ 59%]
tests/test_planarity.py::TestPlanarity::test_is_planar_adj_set PASSED                            [ 61%]
tests/test_planarity.py::TestPlanarity::test_is_planar_adj_list PASSED                           [ 63%]
tests/test_planarity.py::TestPlanarity::test_goldner_harary PASSED                               [ 65%]
tests/test_planarity.py::TestPlanarity::test_kuratowski_k5 PASSED                                [ 67%]
tests/test_planarity.py::TestPlanarity::test_kuratowski_k5_function PASSED                       [ 69%]
tests/test_planarity.py::TestPlanarity::test_no_kuratowski_k5m PASSED                            [ 71%]
tests/test_planarity.py::TestPlanarity::test_draw_text PASSED                                    [ 73%]
tests/test_planarity.py::TestPlanarity::test_write_adjlist PASSED                                [ 75%]
tests/test_planarity.py::TestPlanarity::test_write_adjmatrix PASSED                              [ 76%]
tests/test_planarity.py::TestPlanarity::test_write_g6 PASSED                                     [ 78%]
tests/test_planarity.py::TestPlanarity::test_write_function_g6 PASSED                            [ 80%]
tests/test_planarity.py::TestPlanarity::test_draw_savefig_tight_bounding_box_default_pad PASSED  [ 82%]
tests/test_planarity.py::TestPlanarity::test_draw_savefig_pad_inches_kwarg PASSED                [ 84%]
tests/test_planarity.py::TestPlanarity::test_draw_function_pad_inches_kwarg PASSED               [ 86%]
tests/test_planarity.py::TestPlanarity::test_draw_pad_inches_controls_output_padding PASSED      [ 88%]
tests/test_planarity_networkx.py::TestPlanarityNetworkX::test_is_planar PASSED                   [ 90%]
tests/test_planarity_networkx.py::TestPlanarityNetworkX::test_is_planar_unions PASSED            [ 92%]
tests/test_planarity_networkx.py::TestPlanarityNetworkX::test_goldner_harary PASSED              [ 94%]
tests/test_planarity_networkx.py::TestPlanarityNetworkX::test_kuratowski_k5 PASSED               [ 96%]
tests/test_planarity_networkx.py::TestPlanarityNetworkX::test_kuratowski_k5m PASSED              [ 98%]
tests/test_planarity_networkx.py::TestPlanarityNetworkX::test_networkx_graph PASSED              [100%]

=============================================== FAILURES ===============================================
_________________________ test_png_background_and_transparency[function-False] _________________________

draw = <function draw.<locals>.<lambda> at 0x10b8659b0>
tmp_path = PosixPath('/private/var/folders/t1/2kcfnw2902x6gjpfcq7l1r800000gn/T/pytest-of-wbkboyer/pytest-8/test_png_background_and_transp0')
transparent = False

    @pytest.mark.parametrize('transparent', [False, True])
    def test_png_background_and_transparency(draw, tmp_path, transparent):
        output = tmp_path / 'colors.png'
        # A user's savefig style must not override an explicit draw() facecolor.
        matplotlib.rcParams['savefig.facecolor'] = 'red'
        draw(outfileName=str(output), facecolor='#abcdef', transparent=transparent,
             figsize=(4, 3), dpi=100)
        with Image.open(output) as image:
>           assert image.size == (400, 300)
E           assert (330, 188) == (400, 300)
E             
E             At index 0 diff: 330 != 400
E             
E             Full diff:
E               (
E             -     400,
E             -     300,
E             ?       -
E             +     330,
E             ?     +
E             +     188,
E               )

tests/test_draw_colors.py:73: AssertionError
_________________________ test_png_background_and_transparency[function-True] __________________________

draw = <function draw.<locals>.<lambda> at 0x10b8428d0>
tmp_path = PosixPath('/private/var/folders/t1/2kcfnw2902x6gjpfcq7l1r800000gn/T/pytest-of-wbkboyer/pytest-8/test_png_background_and_transp1')
transparent = True

    @pytest.mark.parametrize('transparent', [False, True])
    def test_png_background_and_transparency(draw, tmp_path, transparent):
        output = tmp_path / 'colors.png'
        # A user's savefig style must not override an explicit draw() facecolor.
        matplotlib.rcParams['savefig.facecolor'] = 'red'
        draw(outfileName=str(output), facecolor='#abcdef', transparent=transparent,
             figsize=(4, 3), dpi=100)
        with Image.open(output) as image:
>           assert image.size == (400, 300)
E           assert (330, 188) == (400, 300)
E             
E             At index 0 diff: 330 != 400
E             
E             Full diff:
E               (
E             -     400,
E             -     300,
E             ?       -
E             +     330,
E             ?     +
E             +     188,
E               )

tests/test_draw_colors.py:73: AssertionError
__________________________ test_png_background_and_transparency[method-False] __________________________

draw = <function draw.<locals>.<lambda> at 0x10b91b110>
tmp_path = PosixPath('/private/var/folders/t1/2kcfnw2902x6gjpfcq7l1r800000gn/T/pytest-of-wbkboyer/pytest-8/test_png_background_and_transp2')
transparent = False

    @pytest.mark.parametrize('transparent', [False, True])
    def test_png_background_and_transparency(draw, tmp_path, transparent):
        output = tmp_path / 'colors.png'
        # A user's savefig style must not override an explicit draw() facecolor.
        matplotlib.rcParams['savefig.facecolor'] = 'red'
        draw(outfileName=str(output), facecolor='#abcdef', transparent=transparent,
             figsize=(4, 3), dpi=100)
        with Image.open(output) as image:
>           assert image.size == (400, 300)
E           assert (330, 188) == (400, 300)
E             
E             At index 0 diff: 330 != 400
E             
E             Full diff:
E               (
E             -     400,
E             -     300,
E             ?       -
E             +     330,
E             ?     +
E             +     188,
E               )

tests/test_draw_colors.py:73: AssertionError
__________________________ test_png_background_and_transparency[method-True] ___________________________

draw = <function draw.<locals>.<lambda> at 0x10b996da0>
tmp_path = PosixPath('/private/var/folders/t1/2kcfnw2902x6gjpfcq7l1r800000gn/T/pytest-of-wbkboyer/pytest-8/test_png_background_and_transp3')
transparent = True

    @pytest.mark.parametrize('transparent', [False, True])
    def test_png_background_and_transparency(draw, tmp_path, transparent):
        output = tmp_path / 'colors.png'
        # A user's savefig style must not override an explicit draw() facecolor.
        matplotlib.rcParams['savefig.facecolor'] = 'red'
        draw(outfileName=str(output), facecolor='#abcdef', transparent=transparent,
             figsize=(4, 3), dpi=100)
        with Image.open(output) as image:
>           assert image.size == (400, 300)
E           assert (330, 188) == (400, 300)
E             
E             At index 0 diff: 330 != 400
E             
E             Full diff:
E               (
E             -     400,
E             -     300,
E             ?       -
E             +     330,
E             ?     +
E             +     188,
E               )

tests/test_draw_colors.py:73: AssertionError
======================================= short test summary info ========================================
FAILED tests/test_draw_colors.py::test_png_background_and_transparency[function-False] - assert (330, 188) == (400, 300)
  
  At index 0 diff: 330 != 400
  
  Full diff:
    (
  -     400,
  -     300,
  ?       -
  +     330,
  ?     +
  +     188,
    )
FAILED tests/test_draw_colors.py::test_png_background_and_transparency[function-True] - assert (330, 188) == (400, 300)
  
  At index 0 diff: 330 != 400
  
  Full diff:
    (
  -     400,
  -     300,
  ?       -
  +     330,
  ?     +
  +     188,
    )
FAILED tests/test_draw_colors.py::test_png_background_and_transparency[method-False] - assert (330, 188) == (400, 300)
  
  At index 0 diff: 330 != 400
  
  Full diff:
    (
  -     400,
  -     300,
  ?       -
  +     330,
  ?     +
  +     188,
    )
FAILED tests/test_draw_colors.py::test_png_background_and_transparency[method-True] - assert (330, 188) == (400, 300)
  
  At index 0 diff: 330 != 400
  
  Full diff:
    (
  -     400,
  -     300,
  ?       -
  +     330,
  ?     +
  +     188,
    )
===================================== 4 failed, 48 passed in 0.87s =====================================
(.venv) wbkboyer@Wandas-MacBook-Pro planarity % 

I added print() statements that checked the value from get_size_inches() both before and after the statement to fig.set_size_inches(value), which returned [4. 3.]; it appears that there's some sort of discrepancy with the result coming from image.size() arising from the addition of bbox_inches='tight'.

@YuEfSaEDU may you please find a fix (i.e., fix truncation issues of top and bottom vertex rounded boxes and capture new expected image sizes) to integrate with @kevin-lozada-santos 's work from #102 (including the changes I made to the docstrings to merge both your changes)?

Thank you again!



MORE INFO: If one comments out bbox_inches='tight', in call to plt.savefig(), then the dimension asserts now pass:

tests/test_draw_colors.py::test_png_background_and_transparency[function-False] PASSED           [ 25%]
tests/test_draw_colors.py::test_png_background_and_transparency[function-True] PASSED            [ 26%]
tests/test_draw_colors.py::test_png_background_and_transparency[method-False] PASSED             [ 28%]
tests/test_draw_colors.py::test_png_background_and_transparency[method-True] PASSED              [ 30%]

Indicating that the assert statements for image.size should be updated.

Full pytest logs from second run on MacOS with planarity built using Clang 22.1.6, Python 3.14.6, Cython 3.3.0, after removing bbox_inches='tight'
(.venv) wbkboyer@Wandas-MacBook-Pro planarity % pytest -vvv                                  
========================================= test session starts ==========================================
platform darwin -- Python 3.14.6, pytest-9.1.1, pluggy-1.6.0 -- /Users/wbkboyer/git/planarity/.venv/bin/python3.14
cachedir: .pytest_cache
rootdir: /Users/wbkboyer/git/planarity
configfile: pyproject.toml
collected 52 items                                                                                     

tests/test_draw_colors.py::test_individual_colors_reach_artists[function-facecolor-#abcdef] PASSED [  1%]
tests/test_draw_colors.py::test_individual_colors_reach_artists[function-vertex_facecolor-gold] PASSED [  3%]
tests/test_draw_colors.py::test_individual_colors_reach_artists[function-vertex_bordercolor-color2] PASSED [  5%]
tests/test_draw_colors.py::test_individual_colors_reach_artists[function-vertex_label_facecolor-lightgreen] PASSED [  7%]
tests/test_draw_colors.py::test_individual_colors_reach_artists[function-vertex_label_bordercolor-#aabbcc] PASSED [  9%]
tests/test_draw_colors.py::test_individual_colors_reach_artists[function-edge_linecolor-magenta] PASSED [ 11%]
tests/test_draw_colors.py::test_individual_colors_reach_artists[method-facecolor-#abcdef] PASSED [ 13%]
tests/test_draw_colors.py::test_individual_colors_reach_artists[method-vertex_facecolor-gold] PASSED [ 15%]
tests/test_draw_colors.py::test_individual_colors_reach_artists[method-vertex_bordercolor-color2] PASSED [ 17%]
tests/test_draw_colors.py::test_individual_colors_reach_artists[method-vertex_label_facecolor-lightgreen] PASSED [ 19%]
tests/test_draw_colors.py::test_individual_colors_reach_artists[method-vertex_label_bordercolor-#aabbcc] PASSED [ 21%]
tests/test_draw_colors.py::test_individual_colors_reach_artists[method-edge_linecolor-magenta] PASSED [ 23%]
tests/test_draw_colors.py::test_png_background_and_transparency[function-False] PASSED           [ 25%]
tests/test_draw_colors.py::test_png_background_and_transparency[function-True] PASSED            [ 26%]
tests/test_draw_colors.py::test_png_background_and_transparency[method-False] PASSED             [ 28%]
tests/test_draw_colors.py::test_png_background_and_transparency[method-True] PASSED              [ 30%]
tests/test_draw_colors.py::test_color_controls_combine_without_labels[function] PASSED           [ 32%]
tests/test_draw_colors.py::test_color_controls_combine_without_labels[method] PASSED             [ 34%]
tests/test_draw_colors.py::test_default_background_resets_after_custom_draw[function] PASSED     [ 36%]
tests/test_draw_colors.py::test_default_background_resets_after_custom_draw[method] PASSED       [ 38%]
tests/test_draw_colors.py::test_existing_figure_options[function] PASSED                         [ 40%]
tests/test_draw_colors.py::test_existing_figure_options[method] PASSED                           [ 42%]
tests/test_draw_colors.py::test_unknown_keyword_still_rejected[function] PASSED                  [ 44%]
tests/test_draw_colors.py::test_unknown_keyword_still_rejected[method] PASSED                    [ 46%]
tests/test_draw_colors.py::test_invalid_color_rejected[function] PASSED                          [ 48%]
tests/test_draw_colors.py::test_invalid_color_rejected[method] PASSED                            [ 50%]
tests/test_planarity.py::TestPlanarity::test_is_planar_edgelist_input PASSED                     [ 51%]
tests/test_planarity.py::TestPlanarity::test_is_planar_edgelist_input_function PASSED            [ 53%]
tests/test_planarity.py::TestPlanarity::test_is_planar_adj_input PASSED                          [ 55%]
tests/test_planarity.py::TestPlanarity::test_is_planar_adj_input_function PASSED                 [ 57%]
tests/test_planarity.py::TestPlanarity::test_is_planar_adj_symmetric PASSED                      [ 59%]
tests/test_planarity.py::TestPlanarity::test_is_planar_adj_set PASSED                            [ 61%]
tests/test_planarity.py::TestPlanarity::test_is_planar_adj_list PASSED                           [ 63%]
tests/test_planarity.py::TestPlanarity::test_goldner_harary PASSED                               [ 65%]
tests/test_planarity.py::TestPlanarity::test_kuratowski_k5 PASSED                                [ 67%]
tests/test_planarity.py::TestPlanarity::test_kuratowski_k5_function PASSED                       [ 69%]
tests/test_planarity.py::TestPlanarity::test_no_kuratowski_k5m PASSED                            [ 71%]
tests/test_planarity.py::TestPlanarity::test_draw_text PASSED                                    [ 73%]
tests/test_planarity.py::TestPlanarity::test_write_adjlist PASSED                                [ 75%]
tests/test_planarity.py::TestPlanarity::test_write_adjmatrix PASSED                              [ 76%]
tests/test_planarity.py::TestPlanarity::test_write_g6 PASSED                                     [ 78%]
tests/test_planarity.py::TestPlanarity::test_write_function_g6 PASSED                            [ 80%]
tests/test_planarity.py::TestPlanarity::test_draw_savefig_tight_bounding_box_default_pad FAILED  [ 82%]
tests/test_planarity.py::TestPlanarity::test_draw_savefig_pad_inches_kwarg FAILED                [ 84%]
tests/test_planarity.py::TestPlanarity::test_draw_function_pad_inches_kwarg FAILED               [ 86%]
tests/test_planarity.py::TestPlanarity::test_draw_pad_inches_controls_output_padding FAILED      [ 88%]
tests/test_planarity_networkx.py::TestPlanarityNetworkX::test_is_planar PASSED                   [ 90%]
tests/test_planarity_networkx.py::TestPlanarityNetworkX::test_is_planar_unions PASSED            [ 92%]
tests/test_planarity_networkx.py::TestPlanarityNetworkX::test_goldner_harary PASSED              [ 94%]
tests/test_planarity_networkx.py::TestPlanarityNetworkX::test_kuratowski_k5 PASSED               [ 96%]
tests/test_planarity_networkx.py::TestPlanarityNetworkX::test_kuratowski_k5m PASSED              [ 98%]
tests/test_planarity_networkx.py::TestPlanarityNetworkX::test_networkx_graph PASSED              [100%]

=============================================== FAILURES ===============================================
____________________ TestPlanarity.test_draw_savefig_tight_bounding_box_default_pad ____________________

self = <test_planarity.TestPlanarity object at 0x109c4a150>
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x109b2dba0>

    def test_draw_savefig_tight_bounding_box_default_pad(self, monkeypatch):
        plt = self._get_pyplot_agg()
        P = planarity.PGraph(self.p4_edgelist)
        fname = tempfile.mktemp(suffix='.png')
        captured = self._capture_savefig(plt, monkeypatch)
        P.draw(outfileName=fname)
>       assert captured.get('bbox_inches') == 'tight'
E       AssertionError: assert None == 'tight'
E        +  where None = <built-in method get of dict object at 0x109ecd800>('bbox_inches')
E        +    where <built-in method get of dict object at 0x109ecd800> = {'dpi': 100.0, 'pad_inches': 0.1, 'transparent': False, 'facecolor': (1.0, 1.0, 1.0, 1.0)}.get

tests/test_planarity.py:181: AssertionError
___________________________ TestPlanarity.test_draw_savefig_pad_inches_kwarg ___________________________

self = <test_planarity.TestPlanarity object at 0x109c4a4c0>
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x109b2e780>

    def test_draw_savefig_pad_inches_kwarg(self, monkeypatch):
        plt = self._get_pyplot_agg()
        P = planarity.PGraph(self.p4_edgelist)
        fname = tempfile.mktemp(suffix='.png')
        captured = self._capture_savefig(plt, monkeypatch)
        P.draw(outfileName=fname, pad_inches=0.25)
>       assert captured.get('bbox_inches') == 'tight'
E       AssertionError: assert None == 'tight'
E        +  where None = <built-in method get of dict object at 0x109f026c0>('bbox_inches')
E        +    where <built-in method get of dict object at 0x109f026c0> = {'dpi': 100.0, 'pad_inches': 0.25, 'transparent': False, 'facecolor': (1.0, 1.0, 1.0, 1.0)}.get

tests/test_planarity.py:190: AssertionError
__________________________ TestPlanarity.test_draw_function_pad_inches_kwarg ___________________________

self = <test_planarity.TestPlanarity object at 0x109c20ff0>
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x109e60b90>

    def test_draw_function_pad_inches_kwarg(self, monkeypatch):
        plt = self._get_pyplot_agg()
        fname = tempfile.mktemp(suffix='.png')
        captured = self._capture_savefig(plt, monkeypatch)
        planarity.draw(self.p4_edgelist, outfileName=fname, pad_inches=0.0)
>       assert captured.get('bbox_inches') == 'tight'
E       AssertionError: assert None == 'tight'
E        +  where None = <built-in method get of dict object at 0x109e671c0>('bbox_inches')
E        +    where <built-in method get of dict object at 0x109e671c0> = {'dpi': 100.0, 'pad_inches': 0.0, 'transparent': False, 'facecolor': (1.0, 1.0, 1.0, 1.0)}.get

tests/test_planarity.py:198: AssertionError
______________________ TestPlanarity.test_draw_pad_inches_controls_output_padding ______________________

self = <test_planarity.TestPlanarity object at 0x109c21090>

    def test_draw_pad_inches_controls_output_padding(self):
        plt = self._get_pyplot_agg()
        P = planarity.PGraph(self.p4_edgelist)
        zero_fname = tempfile.mktemp(suffix='.png')
        half_fname = tempfile.mktemp(suffix='.png')
        try:
            P.draw(outfileName=zero_fname, pad_inches=0.0)
            P.draw(outfileName=half_fname, pad_inches=0.5)
            zero = plt.imread(zero_fname)
            half = plt.imread(half_fname)
            # With pad_inches=0.0, the drawing extends to the borders of
            # the image.
            nonwhite = (zero[:, :, :3] < 0.999).any(axis=2)
            rows = nonwhite.any(axis=1).nonzero()[0]
            cols = nonwhite.any(axis=0).nonzero()[0]
            h, w = nonwhite.shape
            margins = (cols.min(), w - 1 - cols.max(),
                       rows.min(), h - 1 - rows.max())
>           assert max(margins) <= 2
E           assert np.int64(193) <= 2
E            +  where np.int64(193) = max((np.int64(193), np.int64(177), np.int64(45), np.int64(40)))

tests/test_planarity.py:219: AssertionError
======================================= short test summary info ========================================
FAILED tests/test_planarity.py::TestPlanarity::test_draw_savefig_tight_bounding_box_default_pad - AssertionError: assert None == 'tight'
 +  where None = <built-in method get of dict object at 0x109ecd800>('bbox_inches')
 +    where <built-in method get of dict object at 0x109ecd800> = {'dpi': 100.0, 'pad_inches': 0.1, 'transparent': False, 'facecolor': (1.0, 1.0, 1.0, 1.0)}.get
FAILED tests/test_planarity.py::TestPlanarity::test_draw_savefig_pad_inches_kwarg - AssertionError: assert None == 'tight'
 +  where None = <built-in method get of dict object at 0x109f026c0>('bbox_inches')
 +    where <built-in method get of dict object at 0x109f026c0> = {'dpi': 100.0, 'pad_inches': 0.25, 'transparent': False, 'facecolor': (1.0, 1.0, 1.0, 1.0)}.get
FAILED tests/test_planarity.py::TestPlanarity::test_draw_function_pad_inches_kwarg - AssertionError: assert None == 'tight'
 +  where None = <built-in method get of dict object at 0x109e671c0>('bbox_inches')
 +    where <built-in method get of dict object at 0x109e671c0> = {'dpi': 100.0, 'pad_inches': 0.0, 'transparent': False, 'facecolor': (1.0, 1.0, 1.0, 1.0)}.get
FAILED tests/test_planarity.py::TestPlanarity::test_draw_pad_inches_controls_output_padding - assert np.int64(193) <= 2
 +  where np.int64(193) = max((np.int64(193), np.int64(177), np.int64(45), np.int64(40)))
===================================== 4 failed, 48 passed in 0.89s =====================================
(.venv) wbkboyer@Wandas-MacBook-Pro planarity % 

It seems to be that the tests must be adjusted to accommodate the new options from this PR:

    bbox_inches='tight',
    pad_inches=pad_inches,

@wbkboyer wbkboyer left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your contribution @YuEfSaEDU !! I left some comments on the main body of the PR which indicate that the tests/test_draw_colors.py needs to be updated to work with the changes you've made (i.e. update the expected image.size values).

May you please fix the issue of the top and bottom vertices (i.e., the long rounded boxes) being cut off in K5-minus-edge.png, since this means that the vertex labels are rendered outside the vertex bounds? As well, the leftmost and rightmost edges are cut off, in addition to the sides of the rounded boxes. Once these issues are fixed, please regenerate the examples/classic/output/K5-minus-edge.png, as well as to update the assert statements in test_draw_colors.py to include the new expected values for the image sizes.

Thanks again!!

YuEfSaEDU and others added 2 commits September 18, 2026 12:57
The four test_png_background_and_transparency cases assumed the saved
image fills the nominal figsize canvas, but draw() now crops to the
drawing plus pad_inches (Issue graph-algorithms#100). Assert the image is strictly
smaller than the nominal size instead of exactly (400, 300); the
pixel-level background and transparency checks are unchanged.

Full suite: 52 passed (verified against a fresh Cythonization of the
merged branch).
…d dead code; provided regenerated image

Co-authored-by: Wanda Boyer <wbkboyer@gmail.com>
@wbkboyer
wbkboyer self-requested a review September 18, 2026 21:48

@wbkboyer wbkboyer left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks again, @YuEfSaEDU ! After @john-boyer-phd and I fixed the issues with the truncated vertex rounded boxes using ax.set_aspect(), ax.relim(), and ax.autoscale_view(), the examples/classic/output/K5-minus-edge.png has also been refreshed.

However, the change to test_png_background_and_transparency() in tests/test_draw_colors.py nullifies the utility of the test (i.e. if figsize is given as (400, 300) then the image.size should exactly match these values), since we erroneously pass when the figsize specified is not being respected. We will file a separate issue to determine how we may simultaneously respect the figsize while also using the specified pad_inches, and to not stretch the height of the vertex rounded boxes.

Thanks again for your contribution!

@YuEfSaEDU

Copy link
Copy Markdown
Contributor Author

Thank you both for fixing the truncation and refreshing the PNG! Understood on the test — happy to help with the figsize/pad_inches reconciliation issue when you file it.

@john-boyer-phd
john-boyer-phd merged commit 3fb6ceb into graph-algorithms:master Sep 18, 2026
0 of 5 checks passed
@john-boyer-phd

Copy link
Copy Markdown
Member

Thank you both for fixing the truncation and refreshing the PNG! Understood on the test — happy to help with the figsize/pad_inches reconciliation issue when you file it.

Thank you for offering Yusuf. It's a bit of an ugly problem, partly because matplotlib is ugly.

The tests are failing because they test certain exact pixels or pixel dimensions. We had to change what was going on inside the image to fix the truncation problems, but that seems to change what some of the tests should be. They locally fail, so it's easy to reproduce, but it might be hard to fix the tests in a way that makes them remain useful.

Your original change caused only one test to fail, but the way you amended the test made it pass but also made it useless, so we would need better fixes than that.

And, any test fixing would need to wait until the real remaining problem is addressed. The real problem is that the figsize should, at least for zero padding, be equal to the figsize dimensions times the dpi, not less than or equal. The images being generated are somehow too small now, and it's not just because of zero padding. Both your original fix and how we amended it both are producing images that are unexpectedly small. If that gets fixed, then it might change what one would do to fix the tests that are failing, or it might even cause the tests to go back to passing. So, better to see if we can get the current nice-looking image to be the right size and then see what if any tests we have to fix.

The issue containing a version of the above comments will be posted tomorrow, and I'll tag you there to ask if you'd like me to assign it to you, ok?

@YuEfSaEDU

Copy link
Copy Markdown
Contributor Author

Thank you both for fixing the truncation and refreshing the PNG! Understood on the test — happy to help with the figsize/pad_inches reconciliation issue when you file it.

Thank you for offering Yusuf. It's a bit of an ugly problem, partly because matplotlib is ugly.

The tests are failing because they test certain exact pixels or pixel dimensions. We had to change what was going on inside the image to fix the truncation problems, but that seems to change what some of the tests should be. They locally fail, so it's easy to reproduce, but it might be hard to fix the tests in a way that makes them remain useful.

Your original change caused only one test to fail, but the way you amended the test made it pass but also made it useless, so we would need better fixes than that.

And, any test fixing would need to wait until the real remaining problem is addressed. The real problem is that the figsize should, at least for zero padding, be equal to the figsize dimensions times the dpi, not less than or equal. The images being generated are somehow too small now, and it's not just because of zero padding. Both your original fix and how we amended it both are producing images that are unexpectedly small. If that gets fixed, then it might change what one would do to fix the tests that are failing, or it might even cause the tests to go back to passing. So, better to see if we can get the current nice-looking image to be the right size and then see what if any tests we have to fix.

The issue containing a version of the above comments will be posted tomorrow, and I'll tag you there to ask if you'd like me to assign it to you, ok?

Yeah sure! I'll be there 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Express API: Need control of whitespace around draw() image

3 participants