biotite-dev / biotite

A comprehensive library for computational molecular biology
https://www.biotite-python.org
BSD 3-Clause "New" or "Revised" License
682 stars 102 forks source link

Rotate/extend label radius to prevent overlaps #459

Open bhagesh-h opened 1 year ago

bhagesh-h commented 1 year ago

Hi, Thank you for this amazing module, It helped me a lot in learning about plasmids. I am trying to plot a plasmid map, and have a query regarding the same.

Is there any parameter to rotate or increase the radius of labels to prevent overlaps as seen in the figure? I tried almost all matplotlib parameters nothing seems to affect the alignment:

image

I know there is a switch omit_oversized_labels to hide the oversized ones, but I need all labels to be visible.

Thanks in advance.

padix-key commented 1 year ago

I fear this is not easily possible. The rotation of the labels is fixed by plot_plasmid_map(). Could you specifiy, what you mean by radius of labels? The only flexible parameter to influence all labels in the same way is label_properties.

Alternatively you can obtain the underlying Matplotlib Text objects via something like

labels = [artist for artist in ax.get_children() if isinstance(artist, Text)]

Note that this list will also contain the label for the plasmid name and that each feature label is separated into multiple Text objects to achieve the appearance of rotated text.

bhagesh-h commented 1 year ago

Thank you for the quick response @padix-key by radius of labels, I was referring to moving the overlapping labels inwards or outwards.

image

I found a package to adjust labels: https://github.com/Phlya/adjustText but this will only space the labels accordingly and it might misplace them.

padix-key commented 1 year ago

You could play with the horizontalalignment and verticalalignment parameters of the Text instances, but again, I think it will not be easy, as each label comprises multiple Text instances. I fear, in the moment there is no really satisfactory solution to this problem and I also see no easy algorithmic approach to solve this problem.

tfenne commented 1 year ago

I'm also running into this issue, and have a couple of suggestions, at least one of which I hope might be feasible.

plot_plasmid_map already automatically determines where there are overlapping features, and pushes features to extra layers so as to avoid overlap. And it would seem it also can compute the width of the text (to determine if it exceeds the shape). Could there be an option to use the maximum of the feature length or the label length, when deciding whether or not to push features to another layer/track?

Alternatively, what I've seen elsewhere for features that are too small to support labeling directly is to produce callouts with labels. I'm not sufficiently expert in matplotlib to do that myself, but it seems tractable.

padix-key commented 1 year ago

Could there be an option to use the maximum of the feature length or the label length, when deciding whether or not to push features to another layer/track?

The length of a feature is proportional to the angle the feature indicator comprises. However, the angle, the feature label requires, depends on the radius from the center, i.e. on the track itself. For example, if you push a feature indicator onto a more central track, the label angle increases and hence might now overlap with another feature. So this unfortunately becomes a non-trivial optimization problem, if I am not wrong. As alternative we could push an indicator onto higher tracks until the label fits, but this might lead to highly suboptimal feature placements, as much more tracks may be occupied. Would this be a viable solution?

Alternatively, what I've seen elsewhere for features that are too small to support labeling directly is to produce callouts with labels. I'm not sufficiently expert in matplotlib to do that myself, but it seems tractable.

I think this might be a doable solution, although the placement might be a problem in this case. The positions of annotations in Matploltlib cannot be automized, as the attempt of implemeting this were fruitless (https://github.com/matplotlib/matplotlib/issues/1313). However, if we would find dedicated positions for such labels this could work. Another possibility would be some kind of footnote for oversized labels.