1

I'm trying to conciliate dots annotation in a Matplotlib scatter plot with a manual limit setting, but I either got an error message or I get a design problem.

Here is my code :

fig, ax = plt.subplots(figsize = (20,10)) #manual limit setting
plt.axis([-2,3,-2.5,5])
plt.scatter(x, y)


for i, txt in enumerate(n):   #dot annotation   
    ax.annotate(txt, (x[i], y[i]))

Here is a screen cap of the output (I got the final scatter plot as a small rectangle located in the left corner of a big white rectangle :

Output

I tried this also :

 fig, ax = plt.subplots(figsize = (20,10))
    ax = plt.axis([-2,3,-2.5,5])
    plt.scatter(x, y)


for i, txt in enumerate(n):
    ax.annotate(txt, (x[i], y[i]))

But of course I got the following error message (even though the chart correctly displays, but without the labels next to each corresponding dot).

AttributeError: 'list' object has no attribute 'annotate'

The error arises because my loop tries to iterate through ax = plt.axis([-2,3,-2.5,5]), which doesn't make sense indeed.

Any solution to overcome this issue ?

Thank you

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
alexnesov
  • 125
  • 2
  • 6
  • Which version of matplotlib are you using? Does this problem still occur in matplotlib 3.1.2? (I think it's fixed by now, but if not, it would be good to know.) – ImportanceOfBeingErnest Jan 18 '20 at 23:03

2 Answers2

6

The problem occurs because of the special casing of texts when it comes to clipping. Usually you might want text outside the axes to be shown. Therefore annotations and text have a annotation_clip argument. However, this interferes with the bbox_inches="tight" option when saving annotations, because the annotations is then still considered part of the layout and hence the figure takes annotations outside the axes still into account.

Two solutions:

  1. Set annotation_clip and clip_on. I.e. You may explicitely tell the annotation to clip at the axes:

    ax.annotate(txt, (x[i], y[i]), annotation_clip=True, clip_on=True)
    
  2. Set bbox_inches to None. When using the IPython inline backend you can tell it not to expand the figure via

    %config InlineBackend.print_figure_kwargs = {'bbox_inches':None}
    

    in a cell before starting to create your content. (This is seen in this answer)

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
  • So the incorrect sizing in the question is caused by text being placed outside the axes? Or am I misunderstanding? – William Miller Jan 20 '20 at 22:11
  • @WilliamMiller Correct. Unfortunately they did not provide a [mcve], so one needs to interprete a little. To reproduce use e.g. `x = y = [-1,2,9]` and run the code in IPython or Jupyter notebook with the inline backend. – ImportanceOfBeingErnest Jan 20 '20 at 22:28
  • Interesting, thanks - one other question: do you know why the text doesn't show up in the image in the question? Why there is just blank space above and right of the axes? – William Miller Jan 20 '20 at 22:31
  • 1
    @WilliamMiller Those texts are not drawn, but they are taken into account for the tightbbox calculation. – ImportanceOfBeingErnest Jan 21 '20 at 01:52
1

I can't replicate the first issue (tried in versions 2.2.3, 3.1.1, 3.1.2) - I get this (using random data). Try upgrading your version of matplotlib or using

plt.savefig('/path/to/output/image.png')

To save the figure to the disk instead of showing it directly and see if the problem persists.

I can however explain the error

AttributeError: 'list' object has no attribute 'annotate'

This occurs because plt.axis() returns [xmin, xmax, ymin, ymax], not an axes instance (fig, ax = plt.subplots(figsize=(20,10) returns an axes instance to ax).

William Miller
  • 9,839
  • 3
  • 25
  • 46