Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Arrow markers strike through text #79

Open
poripsasw opened this issue Mar 19, 2019 · 20 comments
Open

Arrow markers strike through text #79

poripsasw opened this issue Mar 19, 2019 · 20 comments

Comments

@poripsasw
Copy link

poripsasw commented Mar 19, 2019

The end point of the arrows is somehow defined on the corner edge of the text. I am using the text wrap library to break up long lines of text so that could be the problem (though removing that did'nt help either). The problem can be seen when the window is maximized. Please see attached picture for reference.

I edited the source code simply by dividing the line in half. I expected it to go to the midpoint of the text now but somehow the end of the arrow rests on the right location (tip of the text box). I didn't have time to look through the code as to the unexpected behavior. I'll be glad to submit the pull request (not sure how hacky it is).
Figure_2

The following picture is with fixed code:
Figure_1

Both windows are maximized.

@Phlya
Copy link
Owner

Phlya commented Mar 19, 2019

Hi, thank you for reporting.
Can you please post a minimal reproducible example? I've never seen this behaviour.

@poripsasw
Copy link
Author

poripsasw commented Mar 19, 2019

So I am using ROS (Robot operating System) to launch this node. If you have used it as well, the following node can be launched from a launch file and will display most of the above picture. The code is a bit round about in places, the string and points which are hard coded are actually pulled from a yaml file which loads those variables to the ros parameter server:


#!/usr/bin/env python
import rospy
from std_msgs.msg import String

import matplotlib.pyplot as plt
import numpy as np
import textwrap
from adjustText import adjust_text, get_text_position

def generateImage():
    x = []
    y = []
    texts = []
    for i in range(1, 6):

        events = ['[This is a  small sentense]', '[Smaller senstence]', '[A super duper long sentense which doesnt know when to quit]','[ a bit smaller than the longest]','[]']

        points = [[1, 1], [4, 1], [4, 4], [1, 4], [1, 1]]
        x.append(points[i-1][0])
        y.append(points[i-1][1])

        wrapper = textwrap.TextWrapper(width=20, break_long_words=False)
        temp_str = wrapper.fill(events[i-1])


        texts.append(plt.text(points[i-1][0], points[i-1][1], temp_str, ha='center', va='center'))
        
    #import pdb; pdb.set_trace()
    plt.plot(x, y, marker='o', markerfacecolor='blue', markersize=6)
    adjust_text(texts, arrowprops=dict(arrowstyle='->'))  # , x, y)

    plt.show()


if __name__ == '__main__':

    rospy.init_node('sew_image')
    try:
        generateImage()
        rospy.spin()

    except rospy.ROSInterruptException:
        pass

Working on a pure python code.

@Phlya
Copy link
Owner

Phlya commented Mar 19, 2019

I don't know what Robot operating System is, but just using the generateImage function from your code produces this
image

@poripsasw
Copy link
Author

poripsasw commented Mar 19, 2019

The following code with the ROS stuff removed:

#!/usr/bin/env python
#import rospy
from std_msgs.msg import String

import matplotlib.pyplot as plt
import numpy as np
import textwrap
from adjustText import adjust_text, get_text_position

def generateImage():
    x = []
    y = []
    texts = []
    for i in range(1, 6):

        events = ['[This is a  small sentense]', '[Smaller senstence]', '[A super duper long sentense which doesnt know when to quit]','[ a bit smaller than the longest]','[]']

        points = [[1, 1], [4, 1], [4, 4], [1, 4], [1, 1]]
        x.append(points[i-1][0])
        y.append(points[i-1][1])

        wrapper = textwrap.TextWrapper(width=20, break_long_words=False)
        temp_str = wrapper.fill(events[i-1])


        texts.append(plt.text(points[i-1][0], points[i-1][1], temp_str, ha='center', va='center'))
        
    #import pdb; pdb.set_trace()
    plt.plot(x, y, marker='o', markerfacecolor='blue', markersize=6)
    adjust_text(texts, arrowprops=dict(arrowstyle='->'))  # , x, y)

    plt.show()


if __name__ == '__main__':
    generateImage()

Cam you maximize the window? the arrow strike throughs are only visible when you maximize the window on linux.

@poripsasw
Copy link
Author

Thanks for creating this code btw, helped me out loads. This is a great functionality!

@Phlya
Copy link
Owner

Phlya commented Mar 19, 2019

Right... Well adjust_text is not really designed for interactive windows, I'm not sure what happens when the figure gets resized. I don't know why this behaviour is observed, and to be honest don't even know where to start. But I guess this has something to do with the fact that text has constant physical size, and the arrow - constant size in data coordinates. And if stretched to much from the original size, something breaks.

Generally, the arrow should start from the center of the test object, but should be clipped by its bounding box.

@poripsasw
Copy link
Author

Somehow changing the adjust_text function to end annotation at the following point works:

ax.annotate("", # Add an arrow from the text to the point
                        xy = (orig_xy[j]),
                        xytext=newpointpori, #was previously: get_midpoint(bbox)
                        arrowprops=ap, 
                        *args, **kwargs)

where newpointpori=

            x1,y1=orig_xy[j]
            x2,y2=get_midpoint(bbox)
            newpointpori = (x1+x2)/2, (y1+y2)/2

@poripsasw
Copy link
Author

Copying adjust_text code from line 646:


    if 'arrowprops' in kwargs:
        bboxes = get_bboxes(texts, r, (1, 1), ax)
        kwap = kwargs.pop('arrowprops')
        for j, (bbox, text) in enumerate(zip(bboxes, texts)):
            ap = {'patchA':text} # Ensure arrow is clipped by the text
            ap.update(kwap) # Add arrowprops from kwargs
            ############Edited by me: (incredibly hacky, finds the mid point of start and end points TODO: make it be 0.3% of line instead of 50)
            x1,y1=orig_xy[j]
            x2,y2=get_midpoint(bbox)
            newpointpori = (x1+x2)/2, (y1+y2)/2
            ##############
            ax.annotate("", # Add an arrow from the text to the point
                        xy = (orig_xy[j]),
                        xytext=newpointpori, #was previously: get_midpoint(bbox)
                        arrowprops=ap, 
                        *args, **kwargs)

    if save_steps:
        if add_step_numbers:
            plt.title(i+1)
            plt.savefig('%s%s.%s' % (save_prefix,
                        '{0:03}'.format(i+1), save_format),
                        format=save_format, dpi=150)

@poripsasw
Copy link
Author

poripsasw commented Mar 19, 2019

If it works for you (the arrows should scale with minimizing-maximizing window) I can submit a PR. I mentioned it to be hacky as I did not go through the rest of the code.

@Phlya
Copy link
Owner

Phlya commented Mar 19, 2019

Well, I imagine this solution will produce arrows that don't reach the text, if the text is far away from the point?..

@poripsasw
Copy link
Author

Yep possibly, though I haven't tested it. Do you know of any other libraries with dynamic spacing of texts? I can maybe look into solving this.

@Phlya
Copy link
Owner

Phlya commented Mar 19, 2019

What do you mean by dynamic spacing of texts?

@poripsasw
Copy link
Author

Changes the position of texts based on other texts around it, kind of what adjust text does.

@Phlya
Copy link
Owner

Phlya commented Mar 19, 2019

I don't know any other library that does it with matplotlib, sorry

@Phlya
Copy link
Owner

Phlya commented Mar 19, 2019

This can probably be solved with some transformations...

@thomasahle
Copy link

I have a similar issue:
adjust_text
Notice the fourth label from the top completely overlaps it's arrow.

I don't do any resizing, just simple plotting like this:

for index, row in table.iterrows():
    texts.append(ax.text(
         row[x_axis],
         row[y_axis],
         row[label_axis]))
adjust_text(
    texts,
    arrowprops=dict(arrowstyle='->', color='black'),
)

Is there some argument I need to add?

@Phlya
Copy link
Owner

Phlya commented Feb 19, 2023

From what I remember, it seemed like matplotlib bug or something like that. I had a feeling it was fixed or improved with some matplotlib update... But that's all I can say at the moment, unfortunately.

@thomasahle
Copy link

I had matplotlib 3.6.2. I just updated to 3.7.0, but it didn't change anything :(

@thomasahle
Copy link

I guess I just have to make my labels shorter.

@Phlya
Copy link
Owner

Phlya commented Feb 23, 2023

Found the mpl bug about this!
matplotlib/matplotlib#6162

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

No branches or pull requests

3 participants