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

Results differ depending on save_steps #27

Closed
scaine1 opened this issue Apr 24, 2017 · 10 comments
Closed

Results differ depending on save_steps #27

scaine1 opened this issue Apr 24, 2017 · 10 comments

Comments

@scaine1
Copy link
Contributor

scaine1 commented Apr 24, 2017

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

@Phlya
Copy link
Owner

Phlya commented Apr 24, 2017

Thank you for reporting and glad you like the package!

Could you please provide an example with this behaviour? I can't reproduce it with a couple examples I tried, the results with and without saving steps are the same.

@scaine1
Copy link
Contributor Author

scaine1 commented Apr 24, 2017

Here is a stripped down version of my plotting code which should show the problem described above.
Without save_steps=True or adding ax.draw(r) to the adjust_text routine, the labels end up going over the border of the map.

adjust_figure_with_save_steps
adjust_figure_without_save_steps

import pickle
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits import basemap
from collections import namedtuple, OrderedDict
from adjustText import adjust_text

def polygon(x, y, r, n):
    angle= (2 * np.pi) / n
    #convert r to degrees
    r = r * 1.852 # conveting to nautical miles
    r = r / (111.12)
    px_list = []
    py_list = []
    for i in range(0, n):
        px = r / np.cos(y * 0.0174532925) * np.sin(angle * i) + x
        py = r * np.cos(angle * i) + y
        px_list.append(px)
        py_list.append(py)
    px_list.append(px_list[0])
    py_list.append(py_list[0])
    return px_list, py_list


def create_basemap(lat_array, lon_array):
    lon_2d, lat_2d = np.meshgrid(lon_array, lat_array)
    cen_lat = np.sum(lat_array) / len(lat_array)
    cen_lon = np.sum(lon_array) / len(lon_array)
    proj = 'merc'
    m = basemap.Basemap(projection=proj,
                        llcrnrlon=lon_2d[0, 0],
                        llcrnrlat=lat_2d[0, 0],
                        urcrnrlon=lon_2d[-1, -1],
                        urcrnrlat=lat_2d[-1, -1],
                        lat_0=cen_lat,
                        lon_0=cen_lon,
                        resolution='h')

    x , y = m(lon_2d[:, :],lat_2d[:, :])
    return m, x ,y


Point = namedtuple('Point', ['lat', 'lon'])

example_points = [Point(lat=-20.0, lon=110.7),
                  Point(lat=-20.2, lon=110.8),
                  Point(lat=-20.5, lon=110.9),
                  Point(lat=-20.3, lon=111.2),
                  Point(lat=-20.0, lon=111.1),
                  Point(lat=-19.8, lon=111.0),
                  Point(lat=-19.8, lon=110.8),
                  Point(lat=-19.9, lon=110.6),
                  Point(lat=-20.2, lon=110.4),
                  Point(lat=-20.6, lon=110.3),
                  Point(lat=-21.2, lon=109.9),
                  Point(lat=-21.9, lon=109.4),
                  Point(lat=-22.8, lon=108.8),
                  Point(lat=-23.9, lon=108.3),
                  Point(lat=-25.0, lon=107.8),
                  Point(lat=-26.1, lon=107.8),
                  Point(lat=-27.3, lon=107.7),
                  Point(lat=-28.5, lon=108.8),
                  Point(lat=-29.2, lon=109.6),
                  Point(lat=-29.8, lon=110.7),
                  Point(lat=-30.0, lon=112.0)
                  ]

lat_array = np.arange(-40, -9.8, 0.1)
lon_array = np.arange(97.7, 122.0, 0.1)
m, x, y = create_basemap(lat_array, lon_array)

# below is a basic figure with no adjust, looks horrible
fig = plt.figure()
ax = fig.add_subplot(111)
for indx, pt in enumerate(example_points):
    x_pt, y_pt = m(pt.lon, pt.lat)
    m.scatter(x_pt, y_pt, s=20, c='k')
    if np.mod(indx, 2) == 0:
        my_text = plt.text(x_pt, y_pt, 'str about this long',
                  ha='right', va='center', fontsize=10,
                  fontweight='bold', color='k')
plt.savefig('basic_figure.png')

# below is a very stripped down version of my plot, there are more things plotted near
# the points, but I have removed them for the example purposes.
# the aim is to not have the labels too close to the points, but also not have them go over
# the border of the map

fig = plt.figure()
ax = fig.add_subplot(111)
label_list = []
repel_obj = []
for indx, pt in enumerate(example_points):
    x_pt, y_pt = m(pt.lon, pt.lat)
    m.scatter(x_pt, y_pt, s=20, c='k')
    if np.mod(indx, 2) == 0:
        my_text = plt.text(x_pt,
                           y_pt,
                           'str this long',
                           ha='right',
                           va='center',
                           fontsize=10,
                           fontweight='bold',
                           color='k')
        label_list.append(my_text)

    # create an area around the points that the labels should not enter
    # this is done by creating a fake circle around each point, with alpha=0 so we
    # do not see it, normally the plot has additional items plotted near the points

    circle_x, circle_y = polygon(pt.lon, pt.lat, 70, 32)
    map_circle_x, map_circle_y = m(circle_x, circle_y)
    repel_circle = plt.fill(map_circle_x, map_circle_y, color='m', alpha=0.0)
    repel_obj += repel_circle

# without save_steps or adding ax.draw(r) to adjust_text routine the labels go
# over the border.

adjust_text(label_list,
            add_objects=repel_obj,
            ax=ax,
            force_points=0.5,
            force_objects=0.5,
            lim=100,
            arrowprops=dict(arrowstyle="->", connectionstyle='arc3', color='k',lw=1.0)#,
            #save_steps=True
            )
plt.savefig('adjust_figure.png')

@Phlya
Copy link
Owner

Phlya commented Apr 24, 2017

Any chance you can make an example without using basemap?.. I don't have it installed, and it doesn't seem to be as easy as I hoped it would be to install it.
Also, just in case, I have to mention that objects always work as rectangles, even if you create a circle, since the library uses their binding boxes to calculate overlaps...

@Phlya
Copy link
Owner

Phlya commented Apr 24, 2017

(Also, to remove labels further away from points you can specify higher expand_points values)

@scaine1
Copy link
Contributor Author

scaine1 commented Apr 24, 2017

Sorry I should have thought of that basemap could cause an issue, and it is not even really required for this example. Attached is the code with basemap removed (images attached before look the same so I won't upload them again)

At one stage I played with the expand_points value but didn't get the results I was looking for. However, I probably didn't give it a good enough go so I will look into that further.
For now, here is the code that shows the save_steps issue.

import pickle
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
from collections import namedtuple, OrderedDict
from adjustText import adjust_text



def polygon(x, y, r, n):
    angle= (2 * np.pi) / n
    #convert r to degrees
    r = r * 1.852 # conveting to nautical miles
    r = r / (111.12)
    px_list = []
    py_list = []
    for i in range(0, n):
        px = r / np.cos(y * 0.0174532925) * np.sin(angle * i) + x
        py = r * np.cos(angle * i) + y
        px_list.append(px)
        py_list.append(py)
    px_list.append(px_list[0])
    py_list.append(py_list[0])
    return px_list, py_list


Point = namedtuple('Point', ['lat', 'lon'])

example_points = [Point(lat=-20.0, lon=110.7),
                  Point(lat=-20.2, lon=110.8),
                  Point(lat=-20.5, lon=110.9),
                  Point(lat=-20.3, lon=111.2),
                  Point(lat=-20.0, lon=111.1),
                  Point(lat=-19.8, lon=111.0),
                  Point(lat=-19.8, lon=110.8),
                  Point(lat=-19.9, lon=110.6),
                  Point(lat=-20.2, lon=110.4),
                  Point(lat=-20.6, lon=110.3),
                  Point(lat=-21.2, lon=109.9),
                  Point(lat=-21.9, lon=109.4),
                  Point(lat=-22.8, lon=108.8),
                  Point(lat=-23.9, lon=108.3),
                  Point(lat=-25.0, lon=107.8),
                  Point(lat=-26.1, lon=107.8),
                  Point(lat=-27.3, lon=107.7),
                  Point(lat=-28.5, lon=108.8),
                  Point(lat=-29.2, lon=109.6),
                  Point(lat=-29.8, lon=110.7),
                  Point(lat=-30.0, lon=112.0)
                  ]

lat_array = np.arange(-40, -9.8, 0.1)
lon_array = np.arange(97.7, 122.0, 0.1)

# below is a basic figure with no adjust, looks horrible
fig = plt.figure()
ax = fig.add_subplot(111)
for indx, pt in enumerate(example_points):
    m.scatter(pt.lon, pt.lat, s=20, c='k')
    if np.mod(indx, 2) == 0:
        my_text = plt.text(pt.lont pt.lat, 'str about this long',
                  ha='right', va='center', fontsize=10,
                  fontweight='bold', color='k')
plt.savefig('basic_figure.png')

# below is a very stripped down version of my plot, there are more things plotted near
# the points, but I have removed them for the example purposes.
# the aim is to not have the labels too close to the points, but also not have them go over
# the border of the map

fig = plt.figure()
ax = fig.add_subplot(111)
label_list = []
repel_obj = []
for indx, pt in enumerate(example_points):
    m.scatter(pt.lon, pt.lat, s=20, c='k')
    if np.mod(indx, 2) == 0:
        my_text = plt.text(pt.lon,
                           pt.lat,
                           'str this long',
                           ha='right',
                           va='center',
                           fontsize=10,
                           fontweight='bold',
                           color='k')
        label_list.append(my_text)

    # create an area around the points that the labels should not enter
    # this is done by creating a fake circle around each point, with alpha=0 so we
    # do not see it, normally the plot has additional items plotted near the points

    circle_x, circle_y = polygon(pt.lon, pt.lat, 70, 32)
    repel_circle = plt.fill(circle_x, circle_y, color='m', alpha=0.0)
    repel_obj += repel_circle

# without save_steps or adding ax.draw(r) to adjust_text routine the labels go
# over the border.

adjust_text(label_list,
            add_objects=repel_obj,
            ax=ax,
            force_points=0.5,
            force_objects=0.5,
            lim=100,
            arrowprops=dict(arrowstyle="->", connectionstyle='arc3', color='k',lw=1.0)#,
            #save_steps=True
            )
plt.savefig('adjust_figure.png')

@Phlya
Copy link
Owner

Phlya commented Apr 25, 2017

I had to fix some things a bit because it wasn't working, but still, I can't reproduce the problem... m wasn't defined, so I substituted it for ax, and there was a comma missing.
I also see that your figsize and/or x/ylim are not standard, is it set to something in your rcparams? You don't specify it here.Or is it all the remnants of you basemap code?

I have to say, I never tested this with basemap, and it is possible that something from it might be at play here...

@scaine1
Copy link
Contributor Author

scaine1 commented Apr 25, 2017

My apologies, I think that I had half-modified the script to remove basemap, but then was running the old script.. stupid mistake.

It seems I also cannot reproduce the issue if basemap is not used. So I guess the issue is related to the use of basemap..

@Phlya
Copy link
Owner

Phlya commented Apr 25, 2017

OK, no problem. I guess adding draw calls shouldn't harm even without basemap, so maybe it's a good idea if it fixes the basemap situation... Since you have already modified the code, could you please check that the speed isn't affected much compared to the repo version? And if the difference is minimal, maybe submit a PR to introduce this?

@scaine1
Copy link
Contributor Author

scaine1 commented Apr 25, 2017

I ran some speed tests, adding ax.draw(r) changed the average time to run the adjust_text function from ~ 0.5 seconds to ~1 sec.
Instead of just adding this in and slowing the function down, i created an on_basemap argument (default=False) which means the ax.draw(r) will only be called if the user plans to plot on a basemap.
I did a fork and created a pull request. This is the first time I have done that so please let me know if It worked correctly.

@Phlya
Copy link
Owner

Phlya commented Apr 25, 2017

OK, great, thanks! Looks good to me, I'll merge it.

@Phlya Phlya closed this as completed Apr 25, 2017
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

2 participants