FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

[Annotations] ValueError: lines do not intersect when computing tight bounding box containing arrow with filled paths · Issue #12820 · matplotlib/matplotlib · GitHub

Repository navigation

[Annotations] ValueError: lines do not intersect when computing tight bounding box containing arrow with filled paths #12820

Closed

Description

Bug report

During computation of tight_layout of subplots containing a histogram one gets a ValueError in bezier.py:35: Given lines do not intersect. Please verify that the angles are not equal or differ by 180 degrees.

Could be related to #6076

See the attached script and csv files to reproduce the bug. I have marked three FIXME code lines, that are all conditions for the bug to be triggered.

  1. use subplots with more than one axes.
  2. use annotations with an arrowprops argument.

This seems to be a regression in matplotlib 3, since the LTS version is running fine.

test files: files.zip

import matplotlib.pyplot as plt
import numpy as np


def plot_1D_histogram_trajectories(data, centers=None,dtrajs=None, max_traj_length=200, ax=None):
    for n, _traj in enumerate(data):
        ax.hist(_traj, bins=30, alpha=.33, density=True, color='C{}'.format(n))
    ylims = ax.get_ylim()
    xlims = ax.get_xlim()

    for n, _traj in enumerate(data):
        ax.plot(
            _traj[:min(len(_traj), max_traj_length)],
            np.linspace(*ylims, min(len(_traj), max_traj_length)),
            alpha=0.6, color='C{}'.format(n), label='traj {}'.format(n))
        ax.plot(
                centers[dtrajs[n][:min(len(_traj), max_traj_length)]],
                np.linspace(*ylims, min(len(_traj), max_traj_length)),
                '.-', alpha=.6, label='dtraj {}'.format(n), linewidth=.3)
    ax.annotate(
        '', xy=(0.85 * xlims[1], 0.7 * ylims[1]), xytext=(0.85 * xlims[1], 0.3 * ylims[1]),
        # FIXME: if one omits this argument, it succeeds
        arrowprops=dict(),
    )
    ax.text(0.86 * xlims[1], 0.5 * ylims[1], '$x(time)$', ha='left', va='center', rotation=90)
    ax.legend(loc=2)

# FIXME: if only one subfigure is created it succeeds
fig, axes = plt.subplots(1, 2, figsize=(10, 4))
data = [np.loadtxt('data.csv')]
dtraj = np.loadtxt('dtraj.csv').astype(int)
centers = np.loadtxt('centers.csv')

plot_1D_histogram_trajectories(data, centers=centers, dtrajs=[dtraj], ax=axes[0])
axes[1].plot(x=np.arange(10), y=np.arange(10))
fig.tight_layout()

leads to

  File "/home/mi/marscher/sources/pyemma_tutorials/notebooks/minimal_example.py", line 34, in <module>
    fig.tight_layout()
  File "/srv/public/marscher/miniconda3/envs/nbtut/lib/python3.6/site-packages/matplotlib/figure.py", line 2374, in tight_layout
    pad=pad, h_pad=h_pad, w_pad=w_pad, rect=rect)
  File "/srv/public/marscher/miniconda3/envs/nbtut/lib/python3.6/site-packages/matplotlib/tight_layout.py", line 366, in get_tight_layout_figure
    pad=pad, h_pad=h_pad, w_pad=w_pad)
  File "/srv/public/marscher/miniconda3/envs/nbtut/lib/python3.6/site-packages/matplotlib/tight_layout.py", line 115, in auto_adjust_subplotpars
    tight_bbox_raw = union([ax.get_tightbbox(renderer) for ax in subplots
  File "/srv/public/marscher/miniconda3/envs/nbtut/lib/python3.6/site-packages/matplotlib/tight_layout.py", line 116, in <listcomp>
    if ax.get_visible()])
  File "/srv/public/marscher/miniconda3/envs/nbtut/lib/python3.6/site-packages/matplotlib/axes/_base.py", line 4396, in get_tightbbox
    bbox = a.get_tightbbox(renderer)
  File "/srv/public/marscher/miniconda3/envs/nbtut/lib/python3.6/site-packages/matplotlib/artist.py", line 271, in get_tightbbox
    bbox = self.get_window_extent(renderer)
  File "/srv/public/marscher/miniconda3/envs/nbtut/lib/python3.6/site-packages/matplotlib/text.py", line 2425, in get_window_extent
    bboxes.append(self.arrow_patch.get_window_extent())
  File "/srv/public/marscher/miniconda3/envs/nbtut/lib/python3.6/site-packages/matplotlib/patches.py", line 548, in get_window_extent
    return self.get_path().get_extents(self.get_transform())
  File "/srv/public/marscher/miniconda3/envs/nbtut/lib/python3.6/site-packages/matplotlib/patches.py", line 4228, in get_path
    _path, fillable = self.get_path_in_displaycoord()
  File "/srv/public/marscher/miniconda3/envs/nbtut/lib/python3.6/site-packages/matplotlib/patches.py", line 4258, in get_path_in_displaycoord
    self.get_mutation_aspect())
  File "/srv/public/marscher/miniconda3/envs/nbtut/lib/python3.6/site-packages/matplotlib/patches.py", line 3218, in __call__
    return self.transmute(path, mutation_size, linewidth)
  File "/srv/public/marscher/miniconda3/envs/nbtut/lib/python3.6/site-packages/matplotlib/patches.py", line 3718, in transmute
    tail_width / 2.)
  File "/srv/public/marscher/miniconda3/envs/nbtut/lib/python3.6/site-packages/matplotlib/bezier.py", line 381, in get_parallels
    cos_t2, sin_t2)
  File "/srv/public/marscher/miniconda3/envs/nbtut/lib/python3.6/site-packages/matplotlib/bezier.py", line 35, in get_intersection
    raise ValueError("Given lines do not intersect. Please verify that "
ValueError: Given lines do not intersect. Please verify that the angles are not equal or differ by 180 degrees.

Expected outcome

It works, if the arrowprops are omitted, which means not to draw an arrow.

Matplotlib version

  • Operating system: linux64
  • Matplotlib version: 3.0.2
  • Matplotlib backend (print(matplotlib.get_backend())): module://backend_interagg
  • Python version: 3.6
  • Other libraries: see attached conda_list.txt

Installed with conda from conda-forge.

Activity

  1. marscher commented on Nov 16, 2018

    Author
  2. changed the title [-]do not intersect[/-] [+]ValueError: lines do not intersect when drawing arrow with filled paths[/+] on Nov 16, 2018
  3. changed the title [-]ValueError: lines do not intersect when drawing arrow with filled paths[/-] [+]ValueError: lines do not intersect when computing tight bounding box containing arrow with filled paths[/+] on Nov 16, 2018
  4. anntzer commented on Nov 16, 2018

    Contributor

    bisects to #11801.

  5. marscher commented on Nov 16, 2018

    Author

    that was quick!

  6. changed the title [-]ValueError: lines do not intersect when computing tight bounding box containing arrow with filled paths[/-] [+][Annotations] ValueError: lines do not intersect when computing tight bounding box containing arrow with filled paths[/+] on Nov 16, 2018
  7. ImportanceOfBeingErnest commented on Nov 16, 2018

    Member

    Since I have currently no idea at which point in the code this needs to be fixed, a workaround is to not use parallel lines,

    ax.annotate('', xy=(0.8500001 * xlims[1], 0.7 * ylims[1]), 
                    xytext=(0.85 * xlims[1], 0.3 * ylims[1]), arrowprops=dict(), )
    
  8. added a commit that references this issue on Nov 16, 2018
  9. iruletheworld commented on Feb 28, 2019

    I would like to report that I got this error also when doing animation with matplotlib and this particular error did not occur in 2017. I only ran into it after updating matplotlib. I found that I would not run into this error if I disable a certain line in the animation.

    Also found out that if I downgrade to ver 2.3.3, then it works.

  10. darienmorrow commented on Feb 25, 2020

    I also would like to report getting this error. I am on Matplotlib 3.1.3 I will note that by switching the backend to TkAggmatplotlib.use('TkAgg') I was able to save the figure as desired.

  11. added this to the v3.2.1 milestone on Feb 25, 2020
  12. tacaswell commented on Feb 25, 2020

    Member

    @darienmorrow do you have a more minimal example that reproduces this?

  13. ksunden commented on Feb 25, 2020

    Member

    I was working with @darienmorrow on his figure, I was unable to reproduce on current master (9895288)

  14. tacaswell commented on Feb 25, 2020

    Member

    Can you try with the v3.2.0 rc (pip install --pre -U matplotlib )?

  15. ksunden commented on Feb 25, 2020

    Member

    Okay, so it is backend dependent (I have not done an exhaustive search, but there is something weird going on, that I cannot replicate all behavior in a clean venv)

    So (@darienmorrow uses conda on his main machine, for what it is worth, unless otherwise noted, packages are from conda-forge):

    In his base venv, the figure fails to build under Agg and Qt5Agg, but succeeds under TkAgg
    His base venv is python 3.6.7, mpl 3.1.3, installed via conda-forge.

    All other configurations I have tried fail only under Qt5Agg (having tested Agg, Qt5Agg, TkAgg).

    This includes clean venv installs only, but including Qt (installed the conda forge matplotlib full package, not just matplotlib-base):

    • Python 3.8, mpl 3.1.3

    • Python 3.6.7, mpl 3.1.3 (would have thought this to be functionally identical to the original venv, but something isn't the same, not sure what)

    • Python 3.8, mpl 3.2.0rc2 (from pip, after first installing 3.1.3 from conda-forge, to maintain all dependencies as identical)

    • Python 3.8, matplotlib freshly pulled from master (dc1f0d9) (On my own machine, not conda, just did more testing, was able to actually reproduce, I hadn't zeroed in on Qt5Agg as the major culprit yet, did not have qt bindings installed yet)

    Unfortunately, this figure is burried in a fairly long script, and attempts to break out only the failing parts have not triggered the error. I may have another go at it, though.

  16. ksunden commented on Feb 26, 2020

    Member

    Okay, I have a simplified script. It seems to be a very unlucky rounding condition.

    import matplotlib as mpl
    mpl.use("qt5agg")
    import matplotlib.pyplot as plt
    import pathlib
    
    print(mpl.get_backend())
    
    fig_type = '.png'
    here = pathlib.Path(__file__).parent
    save=True
    
    nrows =4
    ncols = 4
    hspace = wspace = 0.09523809523809526
    fig = plt.figure(figsize=[6.5, 6.5])
    fig.subplots_adjust(1/6.5, 1/6.5, 1-1/6.5, 1-1/6.5)
    gs = mpl.gridspec.GridSpec(nrows, ncols, hspace=hspace, wspace=wspace)
    axstark = plt.subplot(gs[2:4,0:2])
    axstarkins = axstark.inset_axes([0.0,0.0,0.8,1])
    ax = axstarkins
    # plot states
    y0 = [0.2, .8]
    x0 = [.2,.45]
    y0p = [.3,.7]
    # pump arrows
    x = (x0[1] - x0[0]) / 2 + x0[0]
    arrowprops={"width":1, "headwidth":7, "headlength":7}
    ax.annotate(s='', xy=(x-.04,y0p[1]), xytext=(x-.04, y0[0]), arrowprops=arrowprops)
    ax.annotate(s='', xy=(x+.04,y0p[0]), xytext=(x+.04, y0[1]), arrowprops=arrowprops)
    # finish up
    if save:
        p = "intro_fig"+fig_type
        plt.savefig(here / p)

    Some notes:

    • It is very sensitive to the hspace/wspace number (13 sig figs, any larger perturbation, the error disappears). I just made our create_figure function print out what it actually passed to gridspec when it was failing.
    • It did NOT fail if I did not use inset_axes, and just used the whole axis from that subplot (I do not think this is due to the fact that they are inset, just that it changes the positions and proportions to be just right, the way it is proportioned)
    • It does NOT fail if I omit the subplots_adjust call
    • Both annotate calls are needed to trigger the error
    • Many of the numbers (such as x and y positions, figure size) are also sensative, but I have not quatitated how sensitive (just added e.g. a 1 after the least significant digit)
    • Does NOT fail if I take "headlength" out of arrowprops, continues to fail if the other params are removed

    Also, the figure script is why I opened up #15049, just noticed that we are still explicitly passing s here... @darienmorrow, may be a good idea to just pass that in as a positional arg, since the name is changing

  17. tacaswell commented on Feb 26, 2020

    Member

    Yikes, thanks for running this to ground @ksunden !

    From a very cursory investigation it looks like we have one part of the code that is checking "are you parallel" using one threshold and then calling code that checks "are you not parallel" using a slightly different threshold.

  18. anntzer commented on Feb 26, 2020

    Contributor

    Per @tacaswell's comment, the following patch appears to fix the issue:

    diff --git i/lib/matplotlib/bezier.py w/lib/matplotlib/bezier.py
    index 9e347ce87..8cec64f15 100644
    --- i/lib/matplotlib/bezier.py
    +++ w/lib/matplotlib/bezier.py
    @@ -389,23 +389,23 @@ def get_parallels(bezier2, width):
         # find cm_left which is the intersecting point of a line through
         # c1_left with angle t1 and a line through c2_left with angle
         # t2. Same with cm_right.
    -    if parallel_test != 0:
    -        # a special case for a straight line, i.e., angle between two
    -        # lines are smaller than some (arbitrary) value.
    -        cmx_left, cmy_left = (
    -            0.5 * (c1x_left + c2x_left), 0.5 * (c1y_left + c2y_left)
    -        )
    -        cmx_right, cmy_right = (
    -            0.5 * (c1x_right + c2x_right), 0.5 * (c1y_right + c2y_right)
    -        )
    -    else:
    +    try:
             cmx_left, cmy_left = get_intersection(c1x_left, c1y_left, cos_t1,
                                                   sin_t1, c2x_left, c2y_left,
                                                   cos_t2, sin_t2)
    -
             cmx_right, cmy_right = get_intersection(c1x_right, c1y_right, cos_t1,
                                                     sin_t1, c2x_right, c2y_right,
                                                     cos_t2, sin_t2)
    +    except ValueError:
    +        # Special case straight lines, i.e., angle between two lines is
    +        # less than the threshold used by get_intersection (we don't use
    +        # check_if_parallel as the threshold is not the same).
    +        cmx_left, cmy_left = (
    +            0.5 * (c1x_left + c2x_left), 0.5 * (c1y_left + c2y_left)
    +        )
    +        cmx_right, cmy_right = (
    +            0.5 * (c1x_right + c2x_right), 0.5 * (c1y_right + c2y_right)
    +        )
     
         # the parallel Bezier lines are created with control points of
         # [c1_left, cm_left, c2_left] and [c1_right, cm_right, c2_right]

    Just needs a test, yada yada.

  19. ksunden commented on Feb 26, 2020

    Member
    def test_almost_parallel():
        matplotlib.bezier.get_parallels([[1, 1],[1, 2],[1 + 1e-14, 3 ]], 1)
    

    That is a test which fails (raises ValueError) on current master (8ac3ea1), but passes with the above patch applied.

    We could add checks that it gives expected results, but really all that we are looking for is that it doesn't error out. Unless I am mistaken, there are no unit tests targeting only bezier.py (though it is called often in the rest of the test suite)

  20. anntzer commented on Feb 27, 2020

    Contributor

    I don't think we should worry about such a case; clearly we should be able to pick whatever threshold for parallel-checking makes sense for us (in a sense get_parallels should be private API).

  21. tacaswell commented on Feb 27, 2020

    Member

    I agree with @anntzer . We don't usually consider tweaking floating point thresholds an API change.

  22. ksunden commented on Feb 27, 2020

    Member

    Fair enough, then it is unclear to me that there are any actual tests required in this instance.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions


      Back | FazBrowse Home | New Git URL