Giter Club home page Giter Club logo

adjusttext's Introduction

Documentation Status Gitter chat DOI PyPI version

adjustText - automatic label placement for matplotlib

Inspired by ggrepel package for R/ggplot2 (https://github.com/slowkow/ggrepel) Alt text

Alternative: textalloc https://github.com/ckjellson/textalloc

Brief description

The idea is that often when we want to label multiple points on a graph the text will start heavily overlapping with both other labels and data points. This can be a major problem requiring manual solution. However this can be largely automatized by smart placing of the labels (difficult) or iterative adjustment of their positions to minimize overlaps (relatively easy). This library (well... script) implements the latter option to help with matplotlib graphs. Usage is very straightforward with usually pretty good results with no tweaking (most important is to just make text slightly smaller than default and maybe the figure a little larger). However the algorithm itself is highly configurable for complicated plots.

Getting started

Installation

Should be installable from pypi:

pip install adjustText

Or with conda:

conda install -c conda-forge adjusttext 

For the latest version from github:

pip install https://github.com/Phlya/adjustText/archive/master.zip

Documentation

Wiki has some basic introduction, and more advanced usage examples can be found here.

Thanks to Christophe Van Neste @beukueb, adjustText has a simple documentation: http://adjusttext.readthedocs.io/en/latest/

Citing adjustText

To cite the library if you use it in scientific publications (or anywhere else, if you wish), please use the link to the GitHub repository (https://github.com/Phlya/adjustText) and a zenodo doi (see top of this page). Thank you!

adjusttext's People

Contributors

136s avatar andy1li avatar colinmorris avatar jasonmendoza2008 avatar jolespin avatar mski-iksm avatar naderm avatar nvaulin avatar oliver-s-lee avatar oscargus avatar penguinpee avatar phlya avatar ryananeff avatar scaine1 avatar sdedelbrock avatar victor-vazquez avatar zyxue avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

adjusttext's Issues

Plot with date/timestamp on the x-axis

I have a plot where I have datetime objects on the x axes, and a normal real number on the y axis; your library doesn't work with that, unfortunately, I get

File "/home/XXX/.local/lib/python2.7/site-packages/adjustText/adjustText.py", line 374, in adjust_text
orig_xy = [text.get_position() for text in texts]
AttributeError: 'datetime.datetime' object has no attribute 'get_position'

Is there any way to fix this for datetime?

Results differ depending on save_steps

I noticed that with save_steps=True, the results were perfect and exactly what I wanted.
However, when I set save_steps=False the results were strangely different and not what I wanted.

To solve this issue, I modified the code and where ever there was a save_steps if statement, i included an else ax.draw(r) statement.
e.g.

if save_steps:
some code is done here and figure is saved.
else:
ax.draw(r)

This fixed the issue for me and the results are now excellent and the same with save_steps on or off.

Love this package! thanks for all your hard work

type error at end of ajust_text

I was trying your examples. Everything worked well except at the end I get the error:

File "C:\Python27\lib\site-packages\adjustText\adjustText.py", line 563, in adjust_text
return i+1
TypeError: can only concatenate tuple (not "int") to tuple

This actually prevents the image from being saved with a plt.savefig() command.
I did not install pandas. Is that a prerequisite?

Thanks for paying attention!

Various colours to labels

Hi, how do we assign different colours to different letters in the same text label using adjust Text?

opposite effect

for point, label in zip(embed, labs):
    notes.append(plt.text(point[0], point[1], label, bbox={'pad':0, 'alpha':0}, size=5))
adjust_text(notes, arrowprops=dict(arrowstyle="->", color='r'))

plot

i expected the exact opposite result!

Confusing explanation/example for only_move parameter

    only_move (dict): a dict to restrict movement of texts to only certain
        axis. Valid keys are 'points', 'text', and 'objects'. For each of
        them valid values are 'x', 'y' and 'xy'. This way you can forbid
        moving texts along either of the axes due to overlaps with points,
        but let it happen if there is an overlap with texts:
        only_move={'points':'y', 'text':'xy'}. Default: everything is allowed.

Does only_move={'points':'y', 'text':'xy'} really mean forbid moving text along either axis due to overlaps with points? Or should it be only_move={'points':'', 'text':'xy'}?

And if so, shouldn't we say:

valid values are 'x', 'y', 'xy' and ''

overlap_bbox_and_point returns 0 for points exactly in the centre of the bbox

In the (rare) case where a point's x or y coordinate is exactly in the middle of the bbox, the sign of cx -xp or cy-yp will be 0, so overlap_bbox_and_point will return a displacement of 0 along that axis, whereas it should return either xp - bbox.xmax or xp - bbox.xmin (which will have the same absolute value but different signs).

How to stop crossing/overlapping lines?

Thank you for this package! It's very helpful to label a complicated plot.

I have a PCA plot that I'd like to annotate with the labels, and no matter what I do, the lines always end up crossing:

image

Two questions:

  1. Is there a flag or parameter that can be used to prevent the lines from crossing?
  2. Is there a flag or parameter to ensure that the labels are always "outward" from the data, i.e. pushed out using a unit circle from the middle of the plot?

Thanks!
Olga

Can't find the right combination of parameters to prevent text from overlapping

I know this sounds a really vague, but I'm having difficulty finding the right set of parameters that will prevent adjust_text from putting labels on top of each other, especially when points are close together. I've tried increasing force_text, force_points, expand_text and expand_points in various combinations without success. I've created a minimal example to reproduce the problem; there is tons of white space in the graph and I wouldn't mind if the labels used some of that. I've also tried increasing lim and decreasing precision.

image

For comparison, ggrepel works well for this example (using force = 2 and box.padding = 1).
image

Edit: updated minimal example.

The return in 2d: Why do labels move that are not overlapping?

This is an extension of issue #50
The main graphs that we'll be using in our paper are 2-dimensional. Maybe there is something that can be done with adjustText here, too:
So the unadjusted scatter plot is
image
adjustText gives me
image
Again, labels are moving that shouldn't. Moreover, some labels are moved right on top of other points. Below is the complete code. You can see a few commented out variables in the adjust_text call that I have experimented with, but they don't seem to make any difference.
Do you have any idea of parameter settings that would give a better uncluttering?
Ideally, one should be able to specify a maximum distance from the original position, and then an option would allow to remove remaining overlapping labels until only non-overlapping remain ๐Ÿ˜„

d1={'Afrikaans': 1.35, 'Amharic': 9.51, 'AncientGreek': 11.62, 'Arabic': 9.22, 'Armenian': 2.92, 'Bambara': 0.1, 'Basque': 8.46, 'Belarusian': 2.48, 'Breton': 24.0, 'Bulgarian': 5.12, 'Buryat': 0.0, 'Cantonese': 4.5, 'Catalan': 1.97, 'Chinese': 0.05, 'Coptic': 4.41, 'Croatian': 4.17, 'Czech': 8.74, 'Danish': 14.58, 'Dutch': 15.3, 'English': 0.79, 'Erzya': 21.74, 'Estonian': 17.1, 'Faroese': 8.92, 'Finnish': 5.82, 'French': 2.44, 'Galician': 7.0, 'German': 20.6, 'Gothic': 11.11, 'Greek': 4.88, 'Hebrew': 1.48, 'Hindi': 0.16, 'Hungarian': 7.46, 'Indonesian': 1.12, 'Irish': 98.16, 'Italian': 6.8, 'Japanese': 0.0, 'Kazakh': 0.46, 'Komi': 17.24, 'Korean': 0.04, 'Kurmanji': 0.37, 'Latin': 6.14, 'Latvian': 3.34, 'Lithuanian': 0.98, 'Maltese': 0.0, 'Marathi': 1.95, 'Naija': 0.11, 'NorthSami': 4.48, 'Norwegian': 12.59, 'OldChurchSlavonic': 13.42, 'OldFrench': 10.6, 'Persian': 2.45, 'Polish': 15.49, 'Portuguese': 3.1, 'Romanian': 12.75, 'Russian': 5.9, 'Sanskrit': 9.46, 'Serbian': 9.7, 'Slovak': 11.67, 'Slovenian': 12.08, 'Spanish': 3.41, 'Swedish': 13.36, 'SwedishSign': 18.89, 'Tagalog': 100.0, 'Tamil': 5.61, 'Telugu': 0.0, 'Thai': 0.0, 'Turkish': 9.95, 'Ukrainian': 5.39, 'UpperSorbian': 5.66, 'Urdu': 0.21, 'Uyghur': 1.96, 'Vietnamese': 0}                                                                                                                                                                
d2={'Afrikaans': 2.63, 'Amharic': 0.59, 'AncientGreek': 41.61, 'Arabic': 73.29, 'Armenian': 20.6, 'Bambara': 0.0, 'Basque': 18.53, 'Belarusian': 33.54, 'Breton': 53.99, 'Bulgarian': 30.08, 'Buryat': 0.38, 'Cantonese': 5.31, 'Catalan': 23.57, 'Chinese': 0.24, 'Coptic': 28.02, 'Croatian': 28.64, 'Czech': 37.94, 'Danish': 14.95, 'Dutch': 21.98, 'English': 9.93, 'Erzya': 42.54, 'Estonian': 38.92, 'Faroese': 16.07, 'Finnish': 23.02, 'French': 5.85, 'Galician': 19.7, 'German': 19.77, 'Gothic': 49.52, 'Greek': 35.74, 'Hebrew': 35.52, 'Hindi': 0.39, 'Hungarian': 28.8, 'Indonesian': 4.5, 'Irish': 98.64, 'Italian': 25.96, 'Japanese': 0.0, 'Kazakh': 0.44, 'Komi': 20.17, 'Korean': 0.04, 'Kurmanji': 0.46, 'Latin': 32.51, 'Latvian': 37.48, 'Lithuanian': 39.38, 'Maltese': 10.34, 'Marathi': 2.78, 'Naija': 4.44, 'NorthSami': 32.38, 'Norwegian': 19.04, 'OldChurchSlavonic': 53.81, 'OldFrench': 35.13, 'Persian': 0.73, 'Polish': 36.67, 'Portuguese': 13.93, 'Romanian': 30.23, 'Russian': 33.52, 'Sanskrit': 31.1, 'Serbian': 25.7, 'Slovak': 39.69, 'Slovenian': 31.77, 'Spanish': 22.06, 'Swedish': 19.8, 'SwedishSign': 18.69, 'Tagalog': 97.92, 'Tamil': 0.55, 'Telugu': 0.95, 'Thai': 0.15, 'Turkish': 4.67, 'Ukrainian': 32.81, 'UpperSorbian': 23.85, 'Urdu': 0.18, 'Uyghur': 4.06, 'Vietnamese': 1.62}  
langnameGroup={"AncientGreek":"Indo-European", "Arabic":"Semitic", "Basque":"isolate", "Belarusian":"Indo-European-Baltoslavic", "Bulgarian":"Indo-European-Baltoslavic", "Cantonese":"Sino-Austronesian", "Catalan":"Indo-European-Romance", "Chinese":"Sino-Austronesian", "Coptic":"Afroasiatic", "Croatian":"Indo-European-Baltoslavic", "Czech":"Indo-European-Baltoslavic", "Danish":"Indo-European-Germanic", "Dutch":"Indo-European-Germanic", "English":"Indo-European-Germanic", "Estonian":"Agglutinating", "Finnish":"Agglutinating", "French":"Indo-European-Romance", "Galician":"Indo-European-Romance", "German":"Indo-European-Germanic", "Gothic":"Indo-European-Germanic", "Greek":"Indo-European", "Hebrew":"Semitic", "Hindi":"Indo-European", "Hungarian":"Agglutinating", "Indonesian":"Sino-Austronesian", "Irish":"Indo-European", "Italian":"Indo-European-Romance", "Japanese":"Agglutinating", "Kazakh":"Agglutinating", "Korean":"Agglutinating", "Latin":"Indo-European-Romance", "Latvian":"Indo-European-Baltoslavic", "Lithuanian":"Indo-European-Baltoslavic", "Norwegian":"Indo-European-Germanic", "OldChurchSlavonic":"Indo-European-Baltoslavic", "Persian":"Indo-European", "Polish":"Indo-European-Baltoslavic", "Portuguese":"Indo-European-Romance", "Romanian":"Indo-European-Romance", "Russian":"Indo-European-Baltoslavic", "Sanskrit":"Indo-European", "Slovak":"Indo-European-Baltoslavic", "Slovenian":"Indo-European-Baltoslavic", "Spanish":"Indo-European-Romance", "Swedish":"Indo-European-Germanic", "Tamil":"Dravidian", "Turkish":"Agglutinating", "Ukrainian":"Indo-European-Baltoslavic", "Urdu":"Indo-European", "Uyghur":"Agglutinating", "Vietnamese":"Sino-Austronesian",'Afrikaans':'Indo-European-Germanic', 'SwedishSign':'Indo-European-Germanic', 'Kurmanji':'Indo-European', 'NorthSami':'Agglutinating', 'UpperSorbian':"Indo-European-Baltoslavic", 'Buryat':'Agglutinating', 'Telugu':'Dravidian', 'Serbian':"Indo-European-Baltoslavic", 'Marathi':'Indo-European','Naija':"Indo-European-Germanic", "OldFrench":"Indo-European-Romance", "Maltese":"Semitic", "Thai":"Sino-Austronesian","Amharic":"Afroasiatic", 'Erzya': 'Agglutinating', 'Faroese':"Indo-European-Germanic", 'Tagalog':"Sino-Austronesian", 'Bambara':'Niger-Congo', 'Breton':"Indo-European", 'Armenian':"Indo-European", 'Komi': 'Agglutinating'}
groupColors={"Indo-European-Romance":'brown',"Indo-European-Baltoslavic":'purple',"Indo-European-Germanic":'olive',"Indo-European":'royalBlue',"Sino-Austronesian":'limeGreen', "Agglutinating":'red'}
groupMarkers={"Indo-European-Romance":'<',"Indo-European-Baltoslavic":'^',"Indo-European-Germanic":'v',"Indo-European":'>',"Sino-Austronesian":'s', "Agglutinating":'+'}

col1 = pd.Series(d1)
col2 = pd.Series(d2)
	
c=[groupColors.get(langnameGroup[label],'k') for label in col1.index]
m=[groupMarkers.get(langnameGroup[label],'o') for label in col1.index]

fig = plt.figure(figsize=(10,10)) 
gs = gridspec.GridSpec(2, 2, width_ratios=[1, 25],height_ratios=[25, 1]) 
aa = plt.subplot(gs[0])
ax = plt.subplot(gs[1])
bb = plt.subplot(gs[3])
li,la = (0,100)
plt.xlim(li,la)
plt.ylim(li,la)
ax.set_xlim([li,la])
ax.set_ylim([li,la])
aa.set_xlim([0, 1])
aa.set_ylim([li,la])
bb.set_ylim([0, 1])
ax.set_xticks([50,100], minor=False) # only the 50% is major
ax.set_xticks([0,25,50,75,100], minor=True) # all 10th are minor
ax.set_yticks([50,100], minor=False) # only the 50% is major
ax.set_yticks([0,25,50,75,100], minor=True) # all 10th are minor
ax.grid(which='both', axis='both',alpha=.5) # draw grid
ax.plot([0, 1], [0, 1], transform=ax.transAxes, alpha=.5, color="gray") # diagonal
aa.set_xticks([], minor=False) 
aa.set_yticks([], minor=False)
bb.set_xticks([], minor=False) 
bb.set_yticks([], minor=False)	

for xx, yy, cc, mm in zip(col1, col2, c, m):
	ax.scatter(xx, yy, marker=mm, c=cc)
aa.scatter([0.5 for _ in col1], col2, c=c, alpha=0.5)
bb.scatter(col1, [0.5 for _ in col2], c=c, alpha=0.5)

texts=[]
for label, x, y in zip(col1.index, col1, col2):
	#texts+=[ax.text(x+1, y+1, label, color=groupColors.get(langnameGroup[label],'k'), fontsize=8)] # original
	texts+=[ax.text(x, y, label, color=groupColors.get(langnameGroup[label],'k'), fontsize=8)] # for adjustText

adjust_text(texts, col1, col2,
		#expand_text=(1, 1), ha='center', va='top',force_text=.6,lim=277,force_points=0.1,
		arrowprops=dict(arrowstyle='-', color='gray', alpha=.5)) 
plt.show()

Inconsistency with Lines

There seems to be some inconsistency with when lines appear. E.g. a line appears for 9 but not 10 (I think because 9 is close to the point) but 15 and 11, which both refer to the same point, have no lines even though the 11 is moved far away. This makes it a bit ambiguous about which point 11 actually refers to.

Orig:

orig

After:

inconsistency

Arrow color to text color?

Hi,

I found this package from http://stackoverflow.com/a/34762716/712624 and I have to say, it's amazing! I was trying to do this myself, manually, and when I realized how not-simple it was, I luckily stumbled upon this project. It does a great job!

The only downside seems to be that I can't set the arrowprops for each line. For example, I want the arrow color to match the text color, but since I pass adjust_text a list of text objects, I can't pass data for each object individually.

I realize that solving this would seem a bit hacky, but is it something you'd be willing to implement or accept a patch for?

Thanks for this great project!

Does not perform well in 4 subplots of different scales

Tried to apply this on 4 subplots - scatter plots:
subplot 1: x-axis: 0-3u; y-axis: 0-3u
subplot 2: x-axis: 3-4u; y-axis: 0-3u
subplot 3: x-axis: 0-3u; y-axis: 3-4u
subplot 4: x-axis: 3-4u; y-axis: 3-4u

However, it is not working well on the plots. Overlapping issue persists for force_text, force_points and force_objects factor up to 10 and above. Sometimes when it hits a certain value, the text will get repelled suddenly with too big the distance. Please advise. Thanks for your help!

A challenging scenario

I don't know if this is something you would be interested in "fixing", but here is a challenging scenario that adjustText doesn't handle quite as well as it could.

adjtext

As you can see, there is a lot of empty space on the plot but a lot of overlapping text, so there is room for improvement. I've experimented a lot with different settings for adjust_text(), but this is the best I've been able to get it to do.

Also, as you can see at the top, sometimes it puts text objects outside the plot borders even when it's not necessary. If I re-run the plot, sometimes it's outside the border, sometimes it's not--but when it is, it causes the vertical size of the plot image to increase.

Here is the script that generates the plot. Forgive the unrelated code; I've removed a lot that isn't used, but the returns are diminishing... :)

WINDOW_WIDTH=960
ALPHA=0.15
WINDOW="7d"
DAYS=42
workout_data=[
  [
    "!",
    "Date",
    "Movement",
    "Reps",
    "Type",
    "Comments"
  ],
  [
    "",
    "[2016-11-03 Thu]",
    "teetotaler's interview's sympathetically",
    25,
    "Intervals",
    ""
  ],
  [
    "",
    "[2016-11-03 Thu]",
    "transmitter famish bathmats",
    31,
    "Intervals",
    ""
  ],
  [
    "",
    "[2016-11-03 Thu]",
    "Weller misstatements upstage",
    13,
    "Intervals",
    ""
  ],
  [
    "",
    "[2016-11-03 Thu]",
    "predetermine Mars dissoluteness",
    36,
    "Intervals",
    ""
  ],
  [
    "",
    "[2016-11-01 Tue]",
    "pervasive sups shortcoming",
    34,
    "Intervals",
    ""
  ],
  [
    "",
    "[2016-11-01 Tue]",
    "evaporation dachshund's jives",
    36,
    "Intervals",
    ""
  ],
  [
    "",
    "[2016-11-01 Tue]",
    "passports Beadle benevolent",
    36,
    "Intervals",
    ""
  ],
  [
    "",
    "[2016-11-01 Tue]",
    "preserves foregoes virtuousness",
    32,
    "Intervals",
    ""
  ],
  [
    "",
    "[2016-10-28 Fri]",
    "peripatetic craziest hawking",
    36,
    "Intervals",
    ""
  ],
  [
    "",
    "[2016-10-28 Fri]",
    "bowsprit's reopens steeliest",
    14,
    "Intervals",
    ""
  ],
  [
    "",
    "[2016-10-28 Fri]",
    "sweetbriars effluent complication's",
    20,
    "Intervals",
    ""
  ],
  [
    "",
    "[2016-10-28 Fri]",
    "tonnage's solidness's drifting",
    36,
    "Intervals",
    "YAYOG W3D1"
  ],
  [
    "",
    "[2016-10-27 Thu]",
    "pasty's coincidence roughens",
    93,
    "Ladders",
    ""
  ],
  [
    "",
    "[2016-10-27 Thu]",
    "evaporation dachshund's jives",
    55,
    "Ladders",
    ""
  ],
  [
    "",
    "[2016-10-27 Thu]",
    "pervasive sups shortcoming",
    36,
    "Ladders",
    ""
  ],
  [
    "",
    "[2016-10-27 Thu]",
    "passports Beadle benevolent",
    40,
    "Ladders",
    "YAYOG W2D4"
  ],
  [
    "",
    "[2016-10-26 Wed]",
    "Weller misstatements upstage",
    40,
    "Ladders",
    ""
  ],
  [
    "",
    "[2016-10-26 Wed]",
    "peripatetic craziest hawking",
    48,
    "Ladders",
    ""
  ],
  [
    "",
    "[2016-10-26 Wed]",
    "predetermine Mars dissoluteness",
    43,
    "Ladders",
    ""
  ],
  [
    "",
    "[2016-10-26 Wed]",
    "fruitful Bahama's rewind's",
    21,
    "Ladders",
    "YAYOG W2D3"
  ],
  [
    "",
    "[2016-10-24 Mon]",
    "pariahs flaring outstript",
    51,
    "Ladders",
    ""
  ],
  [
    "",
    "[2016-10-24 Mon]",
    "rustproofing radishes perpetration",
    58,
    "Ladders",
    ""
  ],
  [
    "",
    "[2016-10-24 Mon]",
    "pervasive sups shortcoming",
    33,
    "Ladders",
    ""
  ],
  [
    "",
    "[2016-10-24 Mon]",
    "faun's nonmember's accoutrements",
    28,
    "Ladders",
    "YAYOG W2D2"
  ],
  [
    "",
    "[2016-10-21 Fri]",
    "Weller misstatements upstage",
    33,
    "Ladders",
    ""
  ],
  [
    "",
    "[2016-10-21 Fri]",
    "peripatetic craziest hawking",
    43,
    "Ladders",
    ""
  ],
  [
    "",
    "[2016-10-21 Fri]",
    "predetermine Mars dissoluteness",
    43,
    "Ladders",
    ""
  ],
  [
    "",
    "[2016-10-21 Fri]",
    "fruitful Bahama's rewind's",
    18,
    "Ladders",
    "YAYOG W2D1"
  ],
  [
    "",
    "[2016-10-19 Wed]",
    "pasty's coincidence roughens",
    85,
    "Ladders",
    "YAYOG W1D4"
  ],
  [
    "",
    "[2016-10-19 Wed]",
    "evaporation dachshund's jives",
    46,
    "Ladders",
    ""
  ],
  [
    "",
    "[2016-10-19 Wed]",
    "pervasive sups shortcoming",
    30,
    "Ladders",
    ""
  ],
  [
    "",
    "[2016-10-19 Wed]",
    "passports Beadle benevolent",
    36,
    "Ladders",
    ""
  ],
  [
    "",
    "[2016-10-10 Mon]",
    "fruitful Bahama's rewind's",
    15,
    "Ladders",
    "I did not realize how weak my triceps are.  I did \"let me downs\" 2x the number of push-up reps."
  ],
  [
    "",
    "[2016-10-10 Mon]",
    "predetermine Mars dissoluteness",
    33,
    "Ladders",
    ""
  ],
  [
    "",
    "[2016-10-10 Mon]",
    "peripatetic craziest hawking",
    40,
    "Ladders",
    ""
  ],
  [
    "",
    "[2016-10-10 Mon]",
    "Weller misstatements upstage",
    25,
    "Ladders",
    ""
  ],
  [
    "",
    "[2016-10-04 Tue]",
    "batteries prerequisite's elderberry's",
    25,
    "Ladders",
    ""
  ],
  [
    "",
    "[2016-10-04 Tue]",
    "pervasive sups shortcoming",
    28,
    "Ladders",
    ""
  ],
  [
    "",
    "[2016-10-04 Tue]",
    "bilaterally belay slighter",
    40,
    "Ladders",
    ""
  ],
  [
    "",
    "[2016-10-04 Tue]",
    "pariahs flaring outstript",
    46,
    "Ladders",
    ""
  ],
  [
    "",
    "[2016-09-28 Wed]",
    "fruitful Bahama's rewind's",
    13,
    "Ladders",
    ""
  ],
  [
    "",
    "[2016-09-28 Wed]",
    "predetermine Mars dissoluteness",
    31,
    "Ladders",
    ""
  ],
  [
    "",
    "[2016-09-28 Wed]",
    "peripatetic craziest hawking",
    36,
    "Ladders",
    ""
  ],
  [
    "",
    "[2016-09-28 Wed]",
    "Weller misstatements upstage",
    25,
    "Ladders",
    ""
  ]
]

# * Imports

import itertools, os, re, sys

from datetime import datetime, timedelta

import matplotlib.pyplot as plot
import matplotlib.lines as mlines

from matplotlib.dates import DateFormatter, MonthLocator, WeekdayLocator, num2date, SA, SU, date2num
from matplotlib.font_manager import FontProperties
from matplotlib.colors import ColorConverter

import numpy, pandas, random

# * Constants

PANDAS_DATE_FORMAT = "%Y-%m-%d %a %H:%M"
FOOD_DATE_FORMAT = "[%Y-%m-%d %a]"
ORG_DATE_FORMAT = "[%Y-%m-%d %a]"

DPI = 100
WIDTH = round(float(WINDOW_WIDTH) / 100, 1)
HEIGHT = 5.55

OLDEST_DATE = datetime.strptime(data[1][1], ORG_DATE_FORMAT) \
              if DAYS == "all" \
                 else datetime.now() - timedelta(days=DAYS)

HALF_WINDOW = "%s%s" % (int(float(re.search("[0-9]+", WINDOW).group(0)) / 2),
                        re.search("[a-z]+", WINDOW).group(0))

# Series type marker types
SERIES_MARKERS = {"Ladders": "D",
                  "Intervals": "+"}

# Solarized colors
yellow = '#b58900'
green = '#b58900'
orange = '#cb4b16'
red = '#dc322f'
magenta = '#d33682'
violet = '#6c71c4'
blue = '#268bd2'
cyan = '#2aa198'
green = '#859900'
base03 = '#002b36'
base02 = '#073642'
base01 = '#586e75'
base00 = '#657b83'
base0 = '#839496'
base1 = '#93a1a1'
base2 = '#eee8d5'
base3 = '#fdf6e3'

border_color = "#0e2329"
dark_bg = "#0e2329"

class SolarizedColors (object):
    yellow   = '#b58900'
    orange   = '#cb4b16'
    red      = '#dc322f'
    magenta  = '#d33682'
    violet   = '#6c71c4'
    blue     = '#268bd2'
    cyan     = '#2aa198'
    green    = '#859900'

    base0 = '#839496'
    base00 = '#657b83'
    base1 = '#93a1a1'
    base01 = '#586e75'
    base2 = '#eee8d5'
    base02 = '#073642'
    base3 = '#fdf6e3'
    base03 = '#002b36'

    yellow_hc = '#DEB542'
    yellow_lc = '#7B6000'
    orange_hc = '#F2804F'
    orange_lc = '#8B2C02'
    red_hc = '#FF6E64'
    red_lc = '#990A1B'
    magenta_hc = '#F771AC'
    magenta_lc = '#93115C'
    violet_hc = '#9EA0E5'
    violet_lc = '#3F4D91'
    blue_hc  = '#69B7F0'
    blue_lc  = '#00629D'
    cyan_hc  = '#69CABF'
    cyan_lc  = '#00736F'
    green_hc = '#B4C342'
    green_lc = '#546E00'

c = SolarizedColors
solarized_colors = [c.yellow, c.orange, c.red, c.magenta, c.blue, c.violet, c.cyan, c.green,
                    c.yellow_hc, c.orange_hc, c.red_hc, c.magenta_hc, c.blue_hc, c.violet_hc, c.cyan_hc, c.green_hc,
                    c.yellow_lc, c.orange_lc, c.red_lc, c.magenta_lc, c.blue_lc, c.violet_lc, c.cyan_lc, c.green_lc]

# Expand color list
# from grapefruit import Color
# solarized_colors.extend([str(Color.NewFromHtml(c).Desaturate(0.25).html)
#                          for c in solarized_colors])

def printerr(*args, **kwargs):
    sys.stderr.write("\nSTDERR:\n" + ' '.join([str(a) for a in args]) + "\n")
    if 'exit' in kwargs and kwargs['exit']:
        sys.exit(1)

# * main

# ** Process data

# Load into frames
frames = {'workouts': pandas.DataFrame.from_records(workout_data[1:], columns=workout_data[0])}

# Convert date fields to datetime objects
for frame in ['workouts']:
    frames[frame]['Date'] = frames[frame]['Date'].apply(pandas.to_datetime, format=FOOD_DATE_FORMAT)

# Drop old data
# for frame in frames:
#     frames[frame] = frames[frame][frames[frame]['Date'] > OLDEST_DATE]

# Set date as index
for frame in frames:
    frames[frame] = frames[frame].set_index(['Date'])

# Resample food frame, summing values for each day
#frames['food'] = frames['food'].resample('1d').sum()

# Drop old data

# Not strictly necessary to also drop workouts data, because we reset
# the ticks and axis limits after plotting the combined_frame.  But
# might as well do it for consistency and speed.
frames['workouts'] = frames['workouts'][frames['workouts'].index > OLDEST_DATE]

# ** Plot data

# Set params
#plot.rcParams['font.family'] = 'DejaVu Sans'  # It already gets DejaVu Sans as my default, but in case I ever want to change it...

# Setup figure (before plotting)
fig, axes = plot.subplots(nrows=2, ncols=1, facecolor=c.base03, dpi=DPI, figsize=(WIDTH, HEIGHT), sharex=True)
workouts_plot = axes[0]
weight_plot = axes[1]
calories_plot = weight_plot.twiny()
calories_plot.get_shared_x_axes().join(calories_plot, workouts_plot)


# *** Plot workouts

# Plot this first, because otherwise the shared x axis is restricted
# to the range of the workouts data, which is smaller at the moment.
# I wish order didn't matter so much...

# Version without merging...which fixed it!  No more jagged edges on the food/calorie/weight data!
workouts_frame = frames['workouts']
series_types = workouts_frame.Type.unique().tolist()
movement_types = workouts_frame.Movement.unique().tolist()

workouts_plot.set_color_cycle(solarized_colors)

for st in series_types:
    for m in movement_types:
        f = workouts_frame.query("Type == \"%s\" and Movement == \"%s\"" % (st, m))
        if not f.empty:
            f.Reps.plot(sharex=workouts_plot, ax=workouts_plot, label=m, marker=SERIES_MARKERS[st])

# **** Label workout lines

lines = workouts_plot.get_lines()
num_lines = len(lines)
xmin, xmax = plot.xlim()

x_values = numpy.linspace(xmin, xmax, num_lines * 200).tolist()
skip = int(len(x_values) * 0.2)
x_values = x_values[skip:-skip]
random.shuffle(x_values)
ymin, ymax = workouts_plot.get_ylim()
valid_y_min = ymin + ymin * 0.1
valid_y_max = ymax - ymax * 0.1

annotations = []

for line in lines:
    label = line.get_label()
    label_length = len(label)

    x_offset = 0

    # Convert dates to x-axis positions
    x_data = [date2num(d) for d in line.get_xdata()]

    # Choose random spot within line x values
    #x = random.choice(linspace(min(x_data), max(x_data), 100).tolist()[10:-10])

    # Choose random x point
    x = random.choice(x_data)

    y_data = line.get_ydata()

    # From <http://stackoverflow.com/a/39402483/712624>
    # Find corresponding y co-ordinate and angle of the...?
    ip = 0
    # Not sure if this is needed but keeping it for now
    # for i in range(len(x_data)):
    #     if x < x_data[i]:
    #         ip = i
    #         break
    if len(x_data) == 1:
        x = x_data[0]
        y = y_data[0]
        x_offset = random.randint(-20, 20)
    else:
        y = y_data[ip-1] + (y_data[ip] - y_data[ip-1]) * (x - x_data[ip-1]) / (x_data[ip] - x_data[ip-1])

    # Compute text y-offset
    y_offset = random.randint(-40, 40)
    if y > valid_y_max:
        y_offset = random.randint(-20, 0)
    elif y < valid_y_min:
        y_offset = random.randint(0, 20)

    x_offset = 0
    y_offset = 0

    # annotations.append(plot.annotate(label, xy=(x, y), xytext=(x_offset, y_offset), size=8, alpha=0.75,
    #                                  textcoords='offset points', ha='right', va='bottom', color=line.get_color(),
    #                                  bbox=dict(boxstyle='round,pad=0.5', fc='#002b36', alpha=0.25),
    #                                  arrowprops=dict(arrowstyle='->', color=line.get_color(), alpha=0.5, fill=False)))

    annotations.append(workouts_plot.text(x, y, line.get_label(), size=8, alpha=0.75,
                                     color=line.get_color(),
                                     bbox=dict(boxstyle='round,pad=0.25', ec='#002b36', fc='#002b36', alpha=0.5)))

x_points = [date2num(p)
            for p in line.get_xdata()
            for line in workouts_plot.get_lines()]
y_points = [p
            for p in line.get_ydata()
            for line in workouts_plot.get_lines()]


# ** Set plot appearance

# Background color of secondary plot (primary plot is transparent on top)
weight_plot.set_axis_bgcolor(c.base02)
workouts_plot.set_axis_bgcolor(c.base02)
calories_plot.set_axis_bgcolor(c.base02)

# Set spine colors
for spine in workouts_plot.spines.values() + weight_plot.spines.values() + calories_plot.spines.values():
    spine.set_edgecolor(border_color)

# Put primary plot (weight) above secondary plot
weight_plot.set_zorder(calories_plot.get_zorder() + 1)
weight_plot.patch.set_visible(False)
calories_plot.patch.set_visible(True)

# *** Ticks and grid

workouts_plot.xaxis.set_minor_locator(WeekdayLocator(byweekday=[SA,SU]))

# Set tick appearance

workouts_plot.yaxis.set_tick_params(labelcolor=c.base01, color=c.base03, labelright='on')
workouts_plot.tick_params(which='both', color=c.base03)

# Make major x-axis ticks manually (instead of using the
# major_locator, because it only makes the ticks when the plot is
# shown, and we need the ticks so we can change the labels)
major_ticks = MonthLocator().tick_values(workouts_frame.index[0], workouts_frame.index[-1])
workouts_plot.xaxis.set_ticks(major_ticks)

#weight_plot.xaxis.set_ticklabels([])
calories_plot.set_xlabel('')



# Set grid appearance
calories_plot.grid(which='major', axis='x', linestyle='-', linewidth=1, color=c.base03)
calories_plot.grid(which='minor', linestyle=':', linewidth=1, color=c.base03)
calories_plot.grid(which='major', axis='y', linestyle='-', color=c.base03)
calories_plot.grid(which='minor', axis='y', linestyle=':', color=c.base03)

workouts_plot.grid(which='major', axis='x', linestyle='-', linewidth=1, color=c.base03)
workouts_plot.grid(which='minor', linestyle=':', linewidth=1, color=c.base03)
workouts_plot.grid(which='major', axis='y', linestyle='-', color=c.base03)
workouts_plot.grid(which='minor', axis='y', linestyle=':', color=c.base03)

weight_plot.grid(which='major', axis='x', linestyle='-', linewidth=1, color=c.base03)
weight_plot.grid(which='minor', linestyle=':', linewidth=1, color=c.base03)
weight_plot.grid(which='major', axis='y', linestyle='-', color=c.base03)
weight_plot.grid(which='minor', axis='y', linestyle=':', color=c.base03)


# Draw grid below data (this sort of works...not sure)
weight_plot.set_axisbelow(True)
calories_plot.set_axisbelow(True)

# *** Legend

# Get lines for legend (from each subplot)
legend_lines = calories_plot.get_lines()

# Filter unwanted lines
legend_lines = [l for l in legend_lines
                if not l.get_label().startswith('_')]

# Setup legend
legend = weight_plot.legend([l for l in legend_lines],
                     [l.get_label() for l in legend_lines],
                     loc='upper left', fontsize='small', frameon=False, labelspacing=0.25)
legend.get_frame().set_facecolor(c.base02)
for text in legend.get_texts():
    text.set_color(c.base01)

# Legend for workout types
# Good info: http://matplotlib.org/users/legend_guide.html#plotting-guide-legend

# Make line objects for legend
color_generator = itertools.cycle(solarized_colors)
workout_legend_lines = [mlines.Line2D([], [], marker=m, label=n, color=c.base0 # color_generator.next()
                                  )
                        for n, m in SERIES_MARKERS.iteritems()]

workouts_legend = workouts_plot.legend(handles=workout_legend_lines, numpoints=1, markerscale=0.75,
                                       loc='upper left', fontsize='small', frameon=False, labelspacing=0.25)
workouts_legend.get_frame().set_facecolor(c.base02)
for text in workouts_legend.get_texts():
    text.set_color(c.base01)


# *** Set tight layout

# Do this after changing everything else

fig.tight_layout(h_pad=0)

for ax in [workouts_plot]:
    ax.margins(0)


import adjustText
adjustText.adjust_text(annotations, ax=workouts_plot, arrowprops=dict(arrowstyle='->', alpha=0.5, fill=False), force_points=2, force_text=2,
                       #only_move={'text': 'y', 'points': 'y'},
                       # precision=-1, expand_text=(50,100), expand_points=(10,40)
                       expand_points=(2,2)
)

# ** Save plot

# Write file and filename
#filename = os.path.expanduser(filename)
#fig.savefig(filename, facecolor=c.base03, pad_inches=0, bbox_inches='tight', dpi=DPI)

# Display in window
plot.show()

In optimally_align_text, we shouldn't consider a text's overlap with itself

In this line, when calculating the overlaps of text t using alignment a, we're including the size of the overlap with t itself, as originally placed.

One undesirable effect of this is that, if there are several alignments that don't overlap any points or fall outside the axes, we'll prefer the one that is most different from the original alignment, because it will have the least self-intersection. e.g. if the user originally chose to place their texts with ha=left, va=bottom, we'll prefer to change it to ha=right, va=top.

Not working with seaborn jointplot

When trying to use seaborn's text I've ran into really strange issues. Unsure if they are related to any of the previous seaborn issues (it didn't seem so)

Here's a notebook with the issue
https://github.com/nickwan/adjustText_jointplot/blob/master/adjustText_jointplot.ipynb

I'm able to plot text labels no problem with the seaborn function. I try using a regular for loop like in the adjustText docs and get this really tiny figure. I try it again, this time accessing the texts through the figure and (no surprise) I get the same issue.

I don't think I'm skipping a step? Would really like to get this to work!

Add to documentation that adjust_text() needs to be called last!

Hey there,

It's a very very useful library, but I banged my head so many times trying to use it that I find I unfortunate that it could have saved so many hours if the documentation was a bit more guiding, and I guess most users either lost a lot of time like me or just dropped the library in frustration.

I feel the documentation should be updated to at least include:

  1. the function signature (name of function, arguments, docstring -- the latter will be a quite convenient replacement for a documentation until a proper one is redacted)
  2. a simple example (just something basic like your first example -- if possible outside of a function to avoid confusing new users as much as possible, unnecessary constructs should be kept at a minimum for a minimal quickstart example)
  3. add an important note: For consistent behavior and best display results, adjust_text() should be called last, after drawing the whole figure, the labels and resizing. To do that, simply store all text objects in a list texts, and call adjust_text(texts) just before plt.show().

I think this last note is the most important of all additions to the readme, as calling adjust_text() anytime before is bound to produce undefined behavior, which is not specified in a clear manner anywhere.

Indeed, even if one calls adjust_text() only on a subset of text objects, it will still produce erratic behavior, like text moved far away from points (I could not pinpoint the root cause), or outside the boundary of the image (the latter only if the image gets resized afterward).

Why do labels move that are not overlapping?

Is it possible to stop the adjust_text function from moving labels even if there is no other label overlapping with them?
Here is my plot that i'd love to unclutter:
image
What i get is that:
image
Why is the "Arabic" label moving?
Here is the code to reproduce what I'm dong (wrong):

d={'Afrikaans': 1.93, 'Amharic': 44.56, 'AncientGreek': 33.06, 'Arabic': 65.9, 'Armenian': 20.16, 'Bambara': 0.13, 'Basque': 20.4, 'Belarusian': 26.28, 'Breton': 53.21, 'Bulgarian': 25.77, 'Buryat': 0.4, 'Cantonese': 4.4, 'Catalan': 19.14, 'Chinese': 0.19, 'Coptic': 11.67, 'Croatian': 24.72, 'Czech': 36.6, 'Danish': 16.38, 'Dutch': 21.72, 'English': 4.9, 'Erzya': 40.76, 'Estonian': 36.45, 'Faroese': 14.19, 'Finnish': 17.88, 'French': 4.67, 'Galician': 17.52, 'German': 21.45, 'Gothic': 34.23, 'Greek': 34.27, 'Hebrew': 28.75, 'Hindi': 1.4, 'Hungarian': 27.91, 'Indonesian': 2.6, 'Irish': 87.93, 'Italian': 22.75, 'Japanese': 0.0, 'Kazakh': 0.89, 'Komi': 19.34, 'Korean': 0.35, 'Kurmanji': 0.61, 'Latin': 27.5, 'Latvian': 24.22, 'Lithuanian': 28.8, 'Maltese': 7.26, 'Marathi': 2.64, 'Naija': 2.29, 'NorthSami': 21.18, 'Norwegian': 19.43, 'OldChurchSlavonic': 37.51, 'OldFrench': 20.14, 'Persian': 0.99, 'Polish': 30.55, 'Portuguese': 12.84, 'Romanian': 29.0, 'Russian': 29.15, 'Sanskrit': 20.09, 'Serbian': 24.1, 'Slovak': 33.18, 'Slovenian': 31.72, 'Spanish': 19.09, 'Swedish': 18.84, 'SwedishSign': 19.23, 'Tagalog': 98.18, 'Tamil': 2.95, 'Telugu': 0.85, 'Thai': 0.06, 'Turkish': 6.38, 'Ukrainian': 26.38, 'UpperSorbian': 22.03, 'Urdu': 0.74, 'Uyghur': 3.58, 'Vietnamese': 1.78}
df = pd.Series(d)
fig, aa = plt.subplots(figsize=(10, 2.5))
aa.axes.get_yaxis().set_visible(False)
plt.ylim(-2,0.2)
plt.xlim(-2,102)
aa.scatter( df, [0 for _ in df], alpha=0.5, edgecolors='none') 
aa.spines['left'].set_visible(False)
aa.spines['right'].set_visible(False)
aa.spines['bottom'].set_visible(False)
aa.xaxis.set_label_position('top') 
aa.xaxis.set_ticks_position('top')
plt.tight_layout()
texts=[]
for label, x in zip(df.index, df):
	texts+=[aa.text(x,-.3,label, fontsize=8,  horizontalalignment='center', verticalalignment='top',rotation=90)] 
adjust_text(texts, autoalign='y', only_move={'points':'y', 'text':'y'})
plt.show()

What I'd like is that the labels move only down, and only if they are overlapping. Is there a way to do that? I've experimented with quite a few parameters (expand_align, force_text, force_points, ...) for a few hours, and I don't seem to get the gist of these parameters.

Here's approximately what I'd like to obtain: Labels moved down if necessary, overlapping only if not enough room is available for all the labels:
image
(The data is the percentage of subjects to the right of their verb, if anyone cares :) )

AttributeError: 'Bbox' object has no attribute 'intersection'

I did not installed the package since I don't have the privilege to install any packages on our server . So I copied all the code to my project. However, I am not able to call adjust_text function. I am getting below error all the time:
AttributeError: 'Bbox' object has no attribute 'intersection'

Please help and thank you so much.
Frank

'precision' parameter doesn't work as described

The docstring says...

precision (float): up to which sum of all overlaps along both x and y
to iterate; may need to increase for complicated situations;

In practice, at each iteration, precision is compared with the sum of the q values returned by the repel_text* functions, and these functions seem to calculate q as the sum of the absolute x/y displacements of texts. This is definitely not the same as the sum of the overlaps.

For example, a text bbox may have an overlapping point (or text) left of its center that demands a dx of 1, and another overlapping point right of center that demands a dx of -1. Thus the net x displacement (q) of the text is 0, but the overlaps are not 0.

Converting Units to Account For New Data Types

I just stumbled across this package. Good stuff! I see an opportunity for improvement though.

Right now this package will not correctly handle datatypes that matplotlib supports, for example Pandas Period datatypes. The fault lies in your use of text.get_position(). That method returns raw data values, not converted data values. If you replace:

x, y = text.get_position()

with

def get_text_position(text, ax=None):
    ax = ax or plt.gca()
    x, y = text.get_position()
    return (ax.xaxis.convert_units(x),
            ax.yaxis.convert_units(y))

x, y = get_text_position(text)

it will support all data types that matplotlib currently supports.

Problem using the function

Not working in my project, gives the following error:

/usr/lib64/python2.7/site-packages/matplotlib/artist.pyc in update(self, props)
737 func = getattr(self, 'set_' + k, None)
738 if func is None or not callable(func):
--> 739 raise AttributeError('Unknown property %s' % k)
740 func(v)
741 changed = True

AttributeError: Unknown property prefer_move

Tried to apply in the example, returned the following error:

In[5]
texts = plot_eucs_covers()
adjust_text(texts, arrowprops=dict(arrowstyle="->", color='r', lw=0.5))

Out[5]
ValueError Traceback (most recent call last)
/usr/lib/python2.7/site-packages/IPython/core/formatters.pyc in call(self, obj)
305 pass
306 else:
--> 307 return printer(obj)
308 # Finally look for special method names
309 method = get_real_method(obj, self.print_method)

/usr/lib/python2.7/site-packages/IPython/core/pylabtools.pyc in (fig)
225
226 if 'png' in formats:
--> 227 png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png', **kwargs))
228 if 'retina' in formats or 'png2x' in formats:
229 png_formatter.for_type(Figure, lambda fig: retina_figure(fig, **kwargs))

/usr/lib/python2.7/site-packages/IPython/core/pylabtools.pyc in print_figure(fig, fmt, bbox_inches, **kwargs)
117
118 bytes_io = BytesIO()
--> 119 fig.canvas.print_figure(bytes_io, **kw)
120 data = bytes_io.getvalue()
121 if fmt == 'svg':

/usr/lib64/python2.7/site-packages/matplotlib/backend_bases.pyc in print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, **kwargs)
2218 orientation=orientation,
2219 bbox_inches_restore=_bbox_inches_restore,
-> 2220 **kwargs)
2221 finally:
2222 if bbox_inches and restore_bbox:

/usr/lib64/python2.7/site-packages/matplotlib/backends/backend_agg.pyc in print_png(self, filename_or_obj, *args, **kwargs)
503
504 def print_png(self, filename_or_obj, *args, **kwargs):
--> 505 FigureCanvasAgg.draw(self)
506 renderer = self.get_renderer()
507 original_dpi = renderer.dpi

/usr/lib64/python2.7/site-packages/matplotlib/backends/backend_agg.pyc in draw(self)
444 if debug: verbose.report('FigureCanvasAgg.draw', 'debug-annoying')
445
--> 446 self.renderer = self.get_renderer(cleared=True)
447 # acquire a lock on the shared font cache
448 RendererAgg.lock.acquire()

/usr/lib64/python2.7/site-packages/matplotlib/backends/backend_agg.pyc in get_renderer(self, cleared)
463
464 if need_new_renderer:
--> 465 self.renderer = RendererAgg(w, h, self.figure.dpi)
466 self._lastKey = key
467 elif cleared:

/usr/lib64/python2.7/site-packages/matplotlib/backends/backend_agg.pyc in init(self, width, height, dpi)
82 self.height = height
83 if debug: verbose.report('RendererAgg.init width=%s, height=%s'%(width, height), 'debug-annoying')
---> 84 self._renderer = _RendererAgg(int(width), int(height), dpi, debug=False)
85 self._filter_renderers = []
86

ValueError: width and height must each be below 32768

I am using python 2.7 in a linux machine, any idea of how to solve that?

Tries to make enormous image when passed annotations instead of texts

Hi again, quick bug report: when I pass adjust_text a list of annotations made with plot.annotate(), it tries to make an enormous image, over 900,000 pixels wide. When I switched to passing it a list of objects made with plot.text(), it worked. I think this is probably a bug, but I'm not sure. Thanks.

"Warning: converting a masked element to nan." No points shown.

I am trying to use this on a plot that looks like this right now:

image

I am then trying to alter it with adjust_text(text), where text is:

[<matplotlib.text.Text at 0x7f6fde11d0f0>,
 <matplotlib.text.Text at 0x7f6fde11d6a0>,
 <matplotlib.text.Text at 0x7f6fde3fa4a8>,
 <matplotlib.text.Text at 0x7f6fde3f77b8>,
 <matplotlib.text.Text at 0x7f6fde3fd0f0>,
 <matplotlib.text.Text at 0x7f6fe04b4940>,
 <matplotlib.text.Text at 0x7f6fdfd3cd68>,
 <matplotlib.text.Text at 0x7f6fdfd9e208>,
 <matplotlib.text.Text at 0x7f6fde421ef0>,
 <matplotlib.text.Text at 0x7f6fde421198>,
 <matplotlib.text.Text at 0x7f6fdfe63f60>,
 <matplotlib.text.Text at 0x7f6fde0c7f98>]

Which is made up of:

4.99589203359e-05 0.136983144207 negative regulation of NF-kappaB transcription factor activity
1.42708419659e-07 0.00037604688739 regulation of I-kappaB kinase/NF-kappaB cascade
0.000122124137553 0.21743354123 LCP2 subnetwork
4.50311931626e-05 0.0790067851443 positive regulation of NF-kappaB import into nucleus
2.47281533515e-07 0.00039942634259 positive regulation of I-kappaB kinase/NF-kappaB cascade
9.8434675214e-07 0.00144148348153 B cell derived lymphoma
0.0036200813333 4.64673586023e-11 induction of apoptosis
0.00375139560284 6.10942616437e-11 induction of programmed cell death
7.12426700332e-05 4.99145223337e-12 myeloid leukocyte differentiation
0.00328031836042 3.21793224938e-09 dermatitis
0.00259303191503 4.66745286746e-09 decreased susceptibility to parasitic infection
0.181946564329 2.29179363976e-06 MCL1 subnetwork

When I try to do that though, I end up with the following error:

/usr/local/anaconda3/lib/python3.5/site-packages/numpy/ma/core.py:4185: UserWarning: Warning: converting a masked element to nan.
  warnings.warn("Warning: converting a masked element to nan.")

And no points are shown at all.

Any idea why this is happening? I am scratching my head.

Using python 3.5.2 with anaconda. Installed adjustText with pip today.

Allow force_text and force_points to be tuples with force in x and y directions

Nice tool, used it when studied math.
Because text annotations are usually longer along X axis it is sometimes more optimal to distribute them vertically with higher force. For this reason force_text and force_points arguments could be extended to accept both scalar and tuple with x and y forces specified separately. As far as I understand only adjust_text(...) needs modifications when dx and dy are calculated.

I can't seem to get adjust_text to work with horizontal bar charts




%matplotlib inline
def add_data_labels(
    ax,
    labels_axis='x',
    x_offset=0.0,
    y_offset=0.0,
    N=1,
    label_value_func=None,
    replace_empty_with='0%',
    *args
    ):
    import numpy as np


    labels=[]
    rects=ax.patches# Assuming is a bar chart. TODO: make this work with scatters, etc.

    #Create tuples of rect and position, with position determined by axis to provide labels for
    for rect, pos in zip(rects, [rect.get_y() for rect in rects] if labels_axis == 'x' else [rect.get_x() for rect in rects]):

        #Get length and height of each rectangle
        length=rect.get_width() if labels_axis == 'x' else rect.get_height()
        height=rect.get_height() if labels_axis == 'x' else rect.get_width()

        if not label_value_func:
            #TODO: Make this function manipulable via another function. i.e. I want to set the function to the default function()*100
            if labels_axis == 'x':
                label_value_func = lambda rect, N: str(int(((rect.get_x() + length) / N) * 100)) + '%'
            else:
                label_value_func = lambda rect, N: str(int(((rect.get_y() + length) / N) * 100)) + '%'

        #Bars with no length should be 0, not nan
        if np.isnan(length):
            length = 0.0
            label_value_func = lambda rect, N: replace_empty_with

        f=label_value_func

        #Add label for each rectangle to a list
        if labels_axis=='x':
            labels.append(ax.text(x = length+x_offset, y = (pos + height / 2.)+y_offset,
                    s = f(rect, N), ha = 'left', va = 'center'))
        else:
            labels.append(ax.text(y = length+y_offset, x = (pos + height / 2.)+x_offset,
                    s = f(rect, N), ha = 'center', va = 'bottom'))

    #TODO: Add support for 'adjustText'
    return ax

import seaborn as sns
from adjustText import adjust_text
sns.set_style("whitegrid")
tips = sns.load_dataset("tips")
ax = sns.barplot(x="tip", y="day", data=tips)
add_data_labels(ax)
adjust_text(ax.texts)

The labels seem to have the correct X position in the end, but it moves every text to the very top of the plot.

"AttributeError: 'numpy.float64' object has no attribute 'get_position'" when doing text.get_position()

Making a volcano plot with text, throws an error, have no clue how to give you more information.
Python 3.5
Scipy 0.18

File "/home/pdiracdelta/Documents/KUL/Master of Bioinformatics/Thesis/scripts/report.py", line 57, in getVolcanoPlot
    adjust_text(xdataYES, ydataYES, textsYES, arrowprops=dict(arrowstyle="-", color='k', lw=0.5))
  File "/home/pdiracdelta/.local/lib/python3.5/site-packages/adjustText/adjustText.py", line 301, in adjust_text
    orig_xy = [text.get_position() for text in texts]
  File "/home/pdiracdelta/.local/lib/python3.5/site-packages/adjustText/adjustText.py", line 301, in <listcomp>
    orig_xy = [text.get_position() for text in texts]
AttributeError: 'numpy.float64' object has no attribute 'get_position'

not working with plot_date

Thanks for creating such an awesome tool!
I am trying to use it with plot_date, however I get this error:

File "C:\Anaconda3\lib\site-packages\adjustText\adjustText.py", line 407, in adjust_text
ax=ax)

File "C:\Anaconda3\lib\site-packages\adjustText\adjustText.py", line 111, in optimally_align_text
c = len(get_points_inside_bbox(x, y, bbox))

File "C:\Anaconda3\lib\site-packages\adjustText\adjustText.py", line 24, in get_points_inside_bbox
x_in = np.logical_and(x>x1, x<x2)

File "pandas\tslib.pyx", line 941, in pandas.tslib._Timestamp.richcmp (pandas\tslib.c:18619)

TypeError: Cannot compare type 'Timestamp' with type 'float'

I also tried to set locale as you recommended in the example using:
import locale locale.setlocale(locale.LC_ALL,'en_GB.utf8')
but I get this error:

File "", line 2, in
locale.setlocale(locale.LC_ALL,'en_GB.utf8')

File "C:\Anaconda3\lib\locale.py", line 594, in setlocale
return _setlocale(category, locale)

Error: unsupported locale setting

Font size?

Hi,
Hope all is well!

I somehow cant change the font size:

adjust_text(texts, force_text=(2,2), arrowprops=dict(arrowstyle="-|>", color='k', alpha=0.4), fontsize=60)

Changing the value of the fonrsize arg has no effect.
What is happening? Thanks!

Arrow crossing point

Does the routine check if an arrow crosses a point?
I have a case with medium difficulty and I was able to configure it fine setting all values for force and expand to 1.5, however there is one case where an arrow goes right through a point and I can't resolve it without strongly altering the values which worsens the layout of the rest.

don't work on mac

File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/adjustText/adjustText.py", line 373, in adjust_text
    r = ax.get_figure().canvas.get_renderer()
AttributeError: 'FigureCanvasMac' object has no attribute 'get_renderer'

Dead links in the wiki

Really like this project, it's made my plots look much better. Just a minor FYI, there are some dead links in the wiki.

Best,
Robert

text_from_points affects layout even when no points are provided

Repro:

from adjustText import adjust_text
import numpy as np
from matplotlib import pyplot as plt

np.random.seed(1337)
n = 50
X1, Y1 = np.random.rand(n), .4 + np.random.rand(n)/5
X2, Y2 = .4+np.random.rand(n)/5, np.random.rand(n)

texts = [plt.text(x, y, '1', color='b') for x, y in zip(X1, Y1)]\
  + [plt.text(x, y, '2', color='r') for x, y in zip(X2, Y2)]

adjust_text(texts,
  #np.concatenate([X1, X2]), np.concatenate([Y1, Y2]),
  text_from_points=False,
)

plt.show()

When text_from_points is set to True, the annotations are separated perfectly. But setting it to False causes a number of text markers to overlap. (However, the result is still better than without calling adjust_text at all).

Since, in this example, I'm only passing in texts, and not points, I would think that the value of text_from_points should make no difference.

how to work with adjustText with multiple subplots?

I cannot make it work well with the following code patterns

  1. adjust text for each subplot
fig, axes = plt.subplots(2, 2, figsize=(14, 12))
axes = axes.ravel()

for k, ax in enumerate(axes):
    # do some plotting
    texts = []
    # collect texts in a for loop
    # call adjust_text
  1. adjust text for texts in all subplots together
fig, axes = plt.subplots(2, 2, figsize=(14, 12))
axes = axes.ravel()

texts = []
for k, ax in enumerate(axes):
    # do some plotting
    # collect texts in a for loop

# call adjust_text

What's the right way to do it? I haven't made it work in either case yet.

Seaborn swarmplot flips ylim

When using adjust text to annotate a seaborn swarmplot, all the text gets moved to the top of the plot. For example:

sns.set_style("whitegrid")
tips = sns.load_dataset("tips")
ax = sns.swarmplot(x=tips["total_bill"])
for collection in ax.collections:
    points = collection.get_offsets()
    texts = []
    for x,y in points[:10]:
        texts.append(ax.text(x,y,str(x)))
adjust_text(texts)

displays
seaborn

This seems to be because seaborn flips the ylim of the axis, as ax.get_ylim() has the larger number first. A temporary workaround is to reset the ylim in the correct order before calling adjust_text (ax.set_ylim(reversed(ax.get_ylim()))) which flips the entire plot. A permanent workaround is to check for this on line 295-296 of adjustText.py in repel_text_from_axes to ensure that ymin < ymax.

kwargs for annotate() have no effect

I'm giving kwargs to adjust_text to be passed to annotate, they have no effect at all. There is no error or anything, they are simply ignored.

Sample code:

import matplotlib.pyplot as mpp
import pandas as pd
import numpy as np
from adjustText import adjust_text

df = pd.DataFrame({'A':np.random.rand(20),'B':np.random.rand(20)})
fig, axes = mpp.subplots(1,1)

texts = []
for xt, yt, s in zip(df.B, df.A, df.A):
    texts.append(mpp.text(xt, yt, s))

kwargs = {'fontsize':'x-small','style':'italic'}

adjust_text(texts, ax=axes **kwargs)
fig.show()

Tried different fontsizes and styles, am I doing something wrong?

Not working properly with pyplot.imshow

I'm trying to annotate a picture (numpy 2D array) using pyplot.imshow, but adjust_text is not very successful. It seems to place the texts outside the axes limits nor does it handle the overlapping well

I'm running Python 3.6.1, matplotlib 2.2.2, adjusttext 0.7.2.

Example code:

import numpy as np
import matplotlib.pyplot as plt
from adjustText import adjust_text

img = np.random.randint(low=0, high=10, size=(100, 100))
xr = np.random.randint(low=10, high=90, size=5)
yr = np.random.randint(low=10, high=90, size=5)

fig = plt.figure()
ax = fig.add_subplot(111)
ax.imshow(img, cmap='Greys', vmax='100')
texts = [plt.text(x, y, s='text here', color='red', fontsize=16) for x, y in zip(xr, yr)]
adjust_text(texts, arrowprops=dict(arrowstyle='-', color='red'))
plt.show()

support for GTK3Cairo backend (AttributeError in get_renderer)

Hi,

I found with GTK3Cairo backend FigureCanvas has neither fig.canvas.get_renderer() nor fig.canvas.renderer. I changed the code (at __init__.py line 35) so I could use this

def get_renderer(fig):
    try:
        return fig.canvas.get_renderer()
    except AttributeError:
        try:
            return fig.canvas.renderer
        except AttributeError:
            return fig.canvas._renderer

and get rid of

AttributeError: 'FigureCanvasGTK3Cairo' object has no attribute 'renderer'

As I don't feel this is a clean patch and I want to discuss before submitting a pull request. I also seen there were an issue with macos backend and I guess the first try statement is there for that reason. I would suggest hasattr or isinstance tests but there could be a clean a way I'm not aware. Any suggestions ?

Handle a negative log plot?

When trying to plot a negative log plot, and adding labels to the most divergent points, I end up with the following:

image

If I now call adjust_text after setting the inverted loglog axes, I get this:

image

If I set the labels before computing any points, I get this:

image

Code is here: https://github.com/MikeDacre/mike_tools/blob/test_plot/python/plots.py

Line 110 in scatter is where I am calling adust_text in the second figure above. I move lines 105โ€“111 up to line 52 to get the last figure.

This is all on python 3.5.2 installed by anaconda

bug with axes being passed in conjunction with Tkinter

While using this together with Tkinter, adjusttext would do funny things to my axis. I found the bug though.
At line 497-8 (I think it's on the newest version) get_bboxes() is missing the ax=ax argument and so it uses plt.gca() instead.
move_texts(texts, dx, dy,
bboxes = get_bboxes(texts, r, (1, 1)), ax=ax)

I changed it on my side and it fixed the problem working with Tkinter

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.