adjustText icon indicating copy to clipboard operation
adjustText copied to clipboard

Cartopy adjust_text problem with many texts

Open qdnse opened this issue 4 years ago • 16 comments

I'm using Cartopy for some sea routing project, and adjust_text to avoid the overlap of the texts with each others and the sea route. The problem here is, when using adjust_text with many data, it just place them at some weird places (see figure 1), while creating some good looking maps when there is not much (see figure 2)

Figure 1 download (5) Figure 2 download

Both maps were generated using adjust_text(texts,line[0],line[1],ax=ax, arrowprops=dict(arrowstyle='-', color='grey', lw=0.3)) where texts is my list of texts, line[0] and line[1] are my line coordinates (lat/lon).

Even when modifying parameters like force_text or force_points, i keep getting weird output when there is many data. What should i do to fix it?

qdnse avatar Sep 07 '20 12:09 qdnse

Can you share a minimal reproducible example?

I never use cartopy myself and thought I fixed it to work, but who knows, maybe I missed something...

Phlya avatar Sep 07 '20 15:09 Phlya

I do have a sample code, the minimal i could do :

import cartopy.crs as ccrs
import cartopy.feature as cfeature
import matplotlib.pyplot as plt
from adjustText import adjust_text
from shapely.geometry import LineString

map_border = cfeature.BORDERS.with_scale("110m")
map_ocean = cfeature.OCEAN.with_scale("110m")
map_lake = cfeature.LAKES.with_scale("110m")
map_land = cfeature.LAND.with_scale("110m")

data = [
    {"Text": 'Port_1', "lat": 6.4, 'lon': 3.4},
    {"Text": 'Port_2', "lat": 0.61, 'lon': -6.1},
    {"Text": 'Port_3', "lat": 4.4, 'lon': -17.1},
    {"Text": 'Port_4', "lat": 17.9, 'lon': -22.06},
    {"Text": 'Port_5', "lat": 32.99, 'lon': -18.01},
    {"Text": 'Port_6', "lat": 35.96, 'lon': -7.7},
    {"Text": 'Port_7', "lat": 37.09, 'lon': 1.6},
    {"Text": 'Port_8', "lat": 40.11, 'lon': 6.4},
    {"Text": 'Port_9', "lat": 43.77, 'lon': 8.7},
    {"Text": 'Port_10', "lat": 41.8, 'lon': 12.4},
]
map_proj = ccrs.PlateCarree()
color_ocean = '#7c9ccb'
color_land = '#e3f3f3'
color_border = '#d2dde8'
ur_lat = ur_lon = ll_lat = ll_lon = False
for j in data:
    if (j["lat"] > ur_lat): ur_lat = j["lat"]
    if (j["lon"] > ur_lon): ur_lon = j["lon"]
    if (j["lat"] < ll_lat): ll_lat = j["lat"]
    if (j["lon"] < ll_lon): ll_lon = j["lon"]
ur_lat += abs(ur_lat - ll_lat) / 3
ur_lon += abs(ur_lon - ll_lon) / 3
ll_lat -= abs(ur_lat - ll_lat) / 3
ll_lon -= abs(ur_lon - ll_lon) / 3
fig = plt.figure(figsize=(9, 6), frameon=False)
ax = fig.add_subplot(1,1,1,projection=map_proj)
if (ll_lon <= -180): ll_lon = -179
if (ll_lat <= -90): ll_lat = -89
if (ur_lon >= 180): ur_lon = 179
if (ur_lat >= 90): ur_lat = 89
ax.set_extent([ll_lon, ur_lon, ll_lat, ur_lat])
ax.add_feature(map_border, edgecolor='#d2dde8', linewidth=1.2)
ax.add_feature(map_ocean, facecolor='#7c9ccb')
ax.add_feature(map_lake, facecolor='#7c9ccb')
ax.add_feature(map_land, facecolor='#e3f3f3')
ax.outline_patch.set_visible(False)
line = []
texts = []
for j in data:
    line.append([j["lon"], j["lat"]])
    ax.plot(j["lon"], j["lat"], marker='o', color='#f68700', markeredgecolor='#ffffff', markersize=7, transform=map_proj)
    texts.append(ax.text(
        j["lon"], j["lat"], j["Text"],
        bbox={'facecolor':'none', 'edgecolor':'none', 'pad': 0},
        fontsize=7, ha='center', va='center'
    ))
l = LineString(line)
ax.add_geometries([l], map_proj, edgecolor='black', facecolor='none', alpha=1)
l = [[], []]
for i in line:
    l[0].append(i[0])
    l[1].append(i[1])
adjust_text(texts, l[0], l[1], ax=ax, arrowprops=dict(arrowstyle='-', color='grey', lw=0.3))
plt.show()

qdnse avatar Sep 08 '20 08:09 qdnse

Before I try it, what version are you using?

Phlya avatar Sep 08 '20 21:09 Phlya

Here are the versions i'm using:

Cartopy: 0.18.0
matplotlib: 3.3.1
adjustText: 0.7.3
Shapely: 1.7.1

qdnse avatar Sep 10 '20 07:09 qdnse

I see, can you try updating adjustText from GitHub?

On Thu, Sep 10, 2020, 08:34 Quentin Denis [email protected] wrote:

Here are the versions i'm using:

Cartopy: 0.18.0 matplotlib: 3.3.1 adjustText: 0.7.3 Shapely: 1.7.1

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/Phlya/adjustText/issues/105#issuecomment-690050112, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAWCZOTLIWCMACQLWRL74T3SFB6OZANCNFSM4Q6C4WXQ .

Phlya avatar Sep 10 '20 07:09 Phlya

Updated the version from github and i got an error, even using the example available here : https://adjusttext.readthedocs.io/en/latest/Examples.html Here's what i got:

> pip uninstall adjustText
...
> pip install https://github.com/Phlya/adjustText/archive/master.zip
...
Successfully installed adjustText-0.8b2
> python example.py
Traceback (most recent call last):
  File "example.py", line 19, in <module>
    adjust_text(texts, arrowprops=dict(arrowstyle='->', color='blue'), ax=ax)
  File "/home/qdenis/.local/lib/python3.7/site-packages/adjustText/__init__.py", line 641, in adjust_text
    ax=ax,
  File "/home/qdenis/.local/lib/python3.7/site-packages/adjustText/__init__.py", line 197, in optimally_align_text
    ax_bbox = ax.patch.get_extents()
  File "/home/qdenis/.local/lib/python3.7/site-packages/matplotlib/patches.py", line 256, in get_extents
    return self.get_path().get_extents(self.get_transform())
  File "/home/qdenis/.local/lib/python3.7/site-packages/matplotlib/path.py", line 603, in get_extents
    return Bbox([xys.min(axis=0), xys.max(axis=0)])
  File "/home/qdenis/.local/lib/python3.7/site-packages/numpy/core/_methods.py", line 43, in _amin
    return umr_minimum(a, axis, None, out, keepdims, initial, where)
ValueError: zero-size array to reduction operation minimum which has no identity

An error i did not have while using the version installed with pip install adjustText (adjustText: 0.7.3)

qdnse avatar Sep 10 '20 09:09 qdnse

Sorry, I can't reproduce this, I could run all examples in the notebook without this error! (I had some other errors caused by some pandas vs numpy inconsistencies, which I just fixed now)

Phlya avatar Sep 10 '20 09:09 Phlya

With the code you provided, I get normal results: image

What do you get?

Phlya avatar Sep 10 '20 09:09 Phlya

I get the same result, but you can see here it doesn't avoid the line (at Port_5, 6, 7 and 8), even if told to avoid it : adjust_text(texts, l[0], l[1]) You can get the same problem using another kind of data set:

data = [
    {"Text": 'Port_kjhblkmjiu', "lat": 43.197167282501276, 'lon': 4.921875},
    {"Text": 'Port_mljhpiug', "lat": 35.746512259918504, 'lon': -2.4609375},
    {"Text": 'Port_1biuh', "lat": 35.31736632923788, 'lon': -11.77734375},
    {"Text": 'Port_kugfuyftrdeyrst', "lat": 25.64152637306577, 'lon': -35.33203125},
    {"Text": 'Port_mkojjhbjljbuhgfouy', "lat": 22.917922936146045, 'lon': -70.3125},
    {"Text": 'Port_mouyrreq', "lat": 19.973348786110602, 'lon': -75.9375},
    {"Text": 'Port_YRZQTEZ<Qe', "lat": 8.928487062665504, 'lon': -76.9921875},
    {"Text": 'Port_KJBHCFRTDJFVGYTDSTYRSRS', "lat": 8.233237111274565, 'lon': -79.8046875},
    {"Text": 'Port_pichcriyguyvguvhct', "lat": 9.44906182688142, 'lon': -85.4296875},
    {"Text": 'Port_kgdyrd', "lat": 12.726084296948196, 'lon': -88.76953125},
    {"Text": 'Port_1', "lat": 15.961329081596647, 'lon': -95.44921875},
    {"Text": 'Port_akhgdfuygcvej', "lat": 19.476950206488414, 'lon': -105.8203125},
    {"Text": 'Port_ibvoufvioh', "lat": 31.353636941500987, 'lon': -113.203125}
]

Figure_1

qdnse avatar Sep 22 '20 08:09 qdnse

Sorry, do you expect the text to avoid lines too? That's not possible currently, except with a hacky solution presented in the examples.

Phlya avatar Sep 22 '20 11:09 Phlya

Oh, okay. In my first picture (world tour based, where the texts were far apart), the line is a succession of point (which give the curves). So maybe, because there is a bunch of points to avoid, the texts replacement act weird and spread the texts apart like what we saw before?

qdnse avatar Sep 22 '20 11:09 qdnse

Yep, that sounds exactly right! Adding a bunch of points along the lines is the hacky solution to avoiding lines I have in examples, but it makes the results quite unstable and doesn't always work well. You can try that again with fewer points, perhaps it would work better, but it doesn't always help.

On Tue, Sep 22, 2020, 12:48 Quentin Denis [email protected] wrote:

Oh, okay. In my first picture (world tour based, where the texts where far apart), the line is a succession of point (which give the curves). So maybe, because there is a bunch of points to avoid, the texts replacement act weird and spread the texts apart like what we saw before?

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/Phlya/adjustText/issues/105#issuecomment-696670316, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAWCZOTNLXXWHFWW5EAPRQLSHCFKBANCNFSM4Q6C4WXQ .

Phlya avatar Sep 22 '20 11:09 Phlya

I did some modification on the code i gave, on the part at the end where i put my line in an array, to put intermediates point in :

    for m in range(100):
        l[0].append(line[i][0] + ((line[i][0] - line[i + 1][0]) / 100 * m))
        l[1].append(line[i][1] + ((line[i][1] - line[i + 1][1]) / 100 * m))

Then, the original problem popped out: Figure_1 I will remove some intermediate point on my original code to fix it temporarily

qdnse avatar Sep 22 '20 12:09 qdnse

Right, looks like the labels with problems are the ones from very dense regions, and if you are adding 100 points for each segment it'll surely be way too much in those areas. Try adding points proportionally to the length of the segment, for example, and not that many.

Phlya avatar Sep 22 '20 14:09 Phlya

After some modifications, i did another test and worked pretty well image But it's still not satisfying enough, even after using the parameters like force_point and i'm kind of stuck now.

qdnse avatar Sep 24 '20 12:09 qdnse

OK, this doesn't look too bad - to make it perfect might be quite tricky unfortunately, it's a rather dumb iterative algorithm, so in complex situations it might not work optimally...

Phlya avatar Sep 24 '20 12:09 Phlya