Facing weird problem when trying to plot simple lat/ lon points - matplotlib

I have following dataframe (corresponding csv is hosted here: http://www.sharecsv.com/s/3795d862c1973efa311d8a770e978215/t.csv)
lat lon
count 6159.000000 6159.000000
mean 37.764859 -122.355491
std 0.028214 0.038874
min 37.742200 -122.482783
25% 37.746317 -122.360133
50% 37.746417 -122.333717
75% 37.785825 -122.331300
max 37.818133 -122.331167
Following code plots correctly:
test_ax = plt.axes(projection=ccrs.Mercator())
test_ax.plot(test_df['lon'], test_df['lat'], color="blue", linewidth=4, alpha=1.0,
transform=ccrs.Geodetic())
plt.show()
But if I take one subset, it doesn't:
test_ax = plt.axes(projection=ccrs.Mercator())
test_ax.plot(test_df['lon'][:1001], test_df['lat'][:1001], color="blue", linewidth=4, alpha=1.0,
transform=ccrs.Geodetic())
plt.show()
But does so with another subset.
test_ax = plt.axes(projection=ccrs.Mercator())
test_ax.plot(test_df['lon'][:3501], test_df['lat'][:3501], color="blue", linewidth=4, alpha=1.0,
transform=ccrs.Geodetic())
plt.show()
I am pretty sure I am doing something stupid, but I am just unable to figure the reason for this behaviour.
Edit:
On further experimentation I found that if I set the extent of map manually to include 0 meridian, the plot for the subset :1001, which wasn't showing earlier starts showing (the blue dot near San Francisco).
test_ax = plt.axes(projection=ccrs.Mercator())
test_ax.plot(test_df['lon'][:1001], test_df['lat'][:1001], color="blue", linewidth=4, alpha=1.0, transform=ccrs.Geodetic())
test_ax.coastlines()
test_ax.set_extent([-130, 0, 30, 40], crs=ccrs.Geodetic())
test_ax.gridlines(draw_labels=True)
plt.show()
Edit: with reproducible example
(For jupyter notebook)
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import pandas as pd
df_csv_url = 'http://www.sharecsv.com/dl/76dd767525a37180ca54cd1d9314b9dc/t1.csv'
test_df = pd.read_csv(df_csv_url)
figure_params = { 'width': 9.6, 'height': 5.4 }
fig = plt.figure(
figsize=(figure_params["width"], figure_params["height"])
)
test_ax = fig.add_axes((0, 0.5, 0.5, 0.5), projection=ccrs.Mercator(), label="map1")
test_ax.plot(test_df['lon'], test_df['lat'], color="blue", linewidth=4, alpha=1.0, transform=ccrs.Geodetic())
test_ax.coastlines()
test_ax.gridlines(draw_labels=True)
test_ax.set_title("Path doesn\'t show", y=1.5)
# Including 0 meridian in extent shows the path
test_ax1 = fig.add_axes((0, 0, 0.5, 0.5), projection=ccrs.Mercator(), label="map2")
test_ax1.plot(test_df['lon'], test_df['lat'], color="blue", linewidth=4, alpha=1.0, transform=ccrs.Geodetic())
test_ax1.set_extent([-130, 0, 30, 40], crs=ccrs.Geodetic())
test_ax1.coastlines()
test_ax1.gridlines(draw_labels=True)
test_ax1.set_title("Path shows (blue dot near San Francisco)", y=1.1)
plt.show()
Edit
(with simplified reproducible example)
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
test_df = pd.DataFrame()
test_df['lon'] = np.linspace(-120, -60, num=1000)
test_df['lat'] = 38
test_df1 = pd.DataFrame()
test_df1['lon'] = np.linspace(-120, -60, num=1001)
test_df1['lat'] = 38
fig = plt.figure()
meridian=0
test_ax = fig.add_axes((0, 0, 1, 0.6), projection=ccrs.Mercator())
test_ax.plot(test_df['lon'], test_df['lat'], color="blue", linewidth=4, alpha=1.0, transform=ccrs.Geodetic())
test_ax.coastlines()
test_ax.set_extent((-125, meridian, 36, 38))
gl = test_ax.gridlines(draw_labels=True)
gl.xlabels_top = False
gl.ylabels_left = False
test_ax.set_title('Path with {} points, eastern edge={}'.format(len(test_df),meridian))
test_ax1 = fig.add_axes((0, 0.7, 1, 0.6), projection=ccrs.Mercator())
test_ax1.plot(test_df1['lon'], test_df1['lat'], color="red", linewidth=4, alpha=1.0, transform=ccrs.Geodetic())
test_ax1.coastlines()
test_ax1.set_extent((-125, meridian, 36, 38))
gl1 = test_ax1.gridlines(draw_labels=True)
gl1.xlabels_top = False
gl1.ylabels_left = False
test_ax1.set_title('Path with {} points, eastern edge={}'.format(len(test_df1),meridian))
meridian=-10
test_ax2 = fig.add_axes((0, 1.4, 1, 0.6), projection=ccrs.Mercator())
test_ax2.plot(test_df['lon'], test_df['lat'], color="black", linewidth=4, alpha=1.0, transform=ccrs.Geodetic())
test_ax2.coastlines()
test_ax2.set_extent((-125, -10, 36, 38))
gl2 = test_ax2.gridlines(draw_labels=True)
gl2.xlabels_top = False
gl2.ylabels_left = False
test_ax2.set_title('Path with {} points, eastern edge={}'.format(len(test_df),meridian))
test_ax3 = fig.add_axes((0, 2.1, 1, 0.6), projection=ccrs.Mercator())
test_ax3.plot(test_df1['lon'], test_df1['lat'], color="green", linewidth=4, alpha=1.0, transform=ccrs.Geodetic())
test_ax3.coastlines()
test_ax3.set_extent((-125, -10, 36, 38))
gl3 = test_ax3.gridlines(draw_labels=True)
gl3.xlabels_top = False
gl3.ylabels_left = False
test_ax3.set_title('Path with {} points, eastern edge={}'.format(len(test_df1),meridian))
plt.show()

Given there seems to be a some issue with cartopy at play, the best work around I see is to split your data into chunks of less than 1000, and then plot all parts of it.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
test_df = pd.DataFrame()
test_df['lon'] = np.linspace(-120, -60, num=1001)
test_df['lat'] = 38
fig = plt.figure()
meridian=0
test_ax = fig.add_axes((0, 0.05, 1, 0.3), projection=ccrs.Mercator())
test_ax.plot(test_df['lon'], test_df['lat'], color="red",
linewidth=4, alpha=1.0, transform=ccrs.Geodetic())
test_ax.coastlines()
test_ax.set_extent((-125, meridian, 36, 38))
gl = test_ax.gridlines(draw_labels=True)
gl.xlabels_top = False
gl.ylabels_left = False
test_ax.set_title('Path with {} points, eastern edge={}'.format(len(test_df),meridian))
meridian=-10
test_ax3 = fig.add_axes((0, 0.55, 1, 0.3), projection=ccrs.Mercator())
# plot first 500
test_ax3.plot(test_df['lon'][:500], test_df['lat'][:500], color="green",
linewidth=4, alpha=1.0, transform=ccrs.Geodetic())
# plot to cover the gap
test_ax3.plot(test_df['lon'][499:501], test_df['lat'][499:501], color="blue",
linewidth=4, alpha=1.0, transform=ccrs.Geodetic())
# plot last 501
test_ax3.plot(test_df['lon'][500:], test_df['lat'][500:], color="yellow",
linewidth=4, alpha=1.0, transform=ccrs.Geodetic())
test_ax3.coastlines()
test_ax3.set_extent((-125, -10, 36, 38))
gl3 = test_ax3.gridlines(draw_labels=True)
gl3.xlabels_top = False
gl3.ylabels_left = False
test_ax3.set_title('Path with {} points, eastern edge={}'.format(len(test_df),meridian))
plt.show()
For the case of 1001 points, I just split it into a section of 500 points and a section of 501 points.
Since you are plotting the line, I also added the plot to cover the gap, shown in blue when you zoom in.
The reason to set up the gap filler instead of overlapping the two sections comes in if you are also plotting the points, like this:
test_ax3.plot(test_df['lon'][:500], test_df['lat'][:500], color="green",
linewidth=1, alpha=1.0, transform=ccrs.Geodetic(), marker='.')
# plot to cover the gap
test_ax3.plot(test_df['lon'][499:501], test_df['lat'][499:501], color="blue",
linewidth=1, alpha=1.0, transform=ccrs.Geodetic(), marker=None)
# plot last 501
test_ax3.plot(test_df['lon'][500:], test_df['lat'][500:], color="yellow",
linewidth=1, alpha=1.0, transform=ccrs.Geodetic(), marker='.')
By separating out the filler, you can make sure you aren't duplicating points, which could be an issue if you have an alpha value of less than 1.0.
Applying this to your original data, you can create a function to loop over the dataframe in chunks equal to whatever size you want.:
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import pandas as pd
PLOT_LIMIT = 1000
df_csv_url = 'http://www.sharecsv.com/dl/76dd767525a37180ca54cd1d9314b9dc/t1.csv'
test_df = pd.read_csv(df_csv_url)
figure_params = { 'width': 9.6, 'height': 5.4 }
fig = plt.figure(
figsize=(figure_params["width"], figure_params["height"])
)
print(len(test_df['lon']))
def ax_plot(test_ax, test_df):
# this function will loop over the dataframe in chunks equal to PLOT_LIMIT
len_df = len(test_df)
n=0
for i in range(len_df//PLOT_LIMIT):
test_ax.plot(test_df['lon'][1000*i:1000*(i+1)], test_df['lat'][1000*i:1000*(i+1)], color="blue",
linewidth=4, alpha=1.0, transform=ccrs.Geodetic())
if (len_df-((n+1)*PLOT_LIMIT)) != 0:
test_ax.plot(test_df['lon'][(1000*i)-1:(1000*(i+1))+1], test_df['lat'][(1000*i)-1:(1000*(i+1))+1], color="blue",
linewidth=4, alpha=1.0, transform=ccrs.Geodetic(), marker='None')
n+=1
test_ax.plot(test_df['lon'][1000*n:], test_df['lat'][1000*n:], color="blue",
linewidth=4, alpha=1.0, transform=ccrs.Geodetic())
test_ax1 = fig.add_axes((0, 0.55, 1, 0.45), projection=ccrs.Mercator(), label="map1")
ax_plot(test_ax1, test_df)
test_ax1.coastlines()
test_ax1.gridlines(draw_labels=True)
test_ax1.set_title("Path shows", y=1.5)
# Including 0 meridian in extent shows the path
test_ax2 = fig.add_axes((0, 0.1, 1, 0.45), projection=ccrs.Mercator(), label="map2")
ax_plot(test_ax2, test_df)
test_ax2.set_extent([-130, -30, 30, 40], crs=ccrs.Geodetic())
test_ax2.coastlines()
test_ax2.gridlines(draw_labels=True)
test_ax2.set_title("Path shows (blue dot near San Francisco)", y=1.1)
plt.show()
As you can see, you should now have flexibility in setting the viewing window on the map. I haven't checked edge cases like crossing the antimeridian, but in the cases presented it is working.

I have been able to find another workaround. If the points are transformed prior to using the plot function (instead of passing the transform parameter), it works.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
test_df = pd.DataFrame()
test_df['lon'] = np.linspace(-120, -60, num=1000)
test_df['lat'] = 38
test_df1 = pd.DataFrame()
test_df1['lon'] = np.linspace(-120, -60, num=1001)
test_df1['lat'] = 38
fig = plt.figure()
meridian=0
test_ax = fig.add_axes((0, 0, 1, 0.6), projection=ccrs.Mercator())
test_ax.plot(test_df['lon'], test_df['lat'], color="blue", linewidth=4, alpha=1.0, transform=ccrs.Geodetic())
test_ax.coastlines()
test_ax.set_extent((-125, meridian, 36, 38))
gl = test_ax.gridlines(draw_labels=True)
gl.xlabels_top = False
gl.ylabels_left = False
test_ax.set_title('Path with {} points, eastern edge={}'.format(len(test_df),meridian))
test_ax1 = fig.add_axes((0, 0.7, 1, 0.6), projection=ccrs.Mercator())
test_ax1.plot(test_df1['lon'], test_df1['lat'], color="red", linewidth=4, alpha=1.0, transform=ccrs.Geodetic())
test_ax1.coastlines()
test_ax1.set_extent((-125, meridian, 36, 38))
gl1 = test_ax1.gridlines(draw_labels=True)
gl1.xlabels_top = False
gl1.ylabels_left = False
test_ax1.set_title('Path with {} points, eastern edge={}'.format(len(test_df1),meridian))
meridian=-10
test_ax2 = fig.add_axes((0, 1.4, 1, 0.6), projection=ccrs.Mercator())
test_ax2.plot(test_df['lon'], test_df['lat'], color="black", linewidth=4, alpha=1.0, transform=ccrs.Geodetic())
test_ax2.coastlines()
test_ax2.set_extent((-125, -10, 36, 38))
gl2 = test_ax2.gridlines(draw_labels=True)
gl2.xlabels_top = False
gl2.ylabels_left = False
test_ax2.set_title('Path with {} points, eastern edge={}'.format(len(test_df),meridian))
test_ax3 = fig.add_axes((0, 2.1, 1, 0.6), projection=ccrs.Mercator())
test_ax3.plot(test_df1['lon'], test_df1['lat'], color="green", linewidth=4, alpha=1.0, transform=ccrs.Geodetic())
test_ax3.coastlines()
test_ax3.set_extent((-125, -10, 36, 38))
gl3 = test_ax3.gridlines(draw_labels=True)
gl3.xlabels_top = False
gl3.ylabels_left = False
test_ax3.set_title('Path with {} points, eastern edge={}'.format(len(test_df1),meridian))
test_ax4 = fig.add_axes((0, 2.8, 1, 0.6), projection=ccrs.Mercator())
# Instead of transforming within the plot function, transform and then plot
transformed_points = ccrs.Mercator().transform_points(ccrs.Geodetic(), test_df1['lon'].values, test_df1['lat'].values)
test_ax4.plot([p[0] for p in transformed_points], [p[1] for p in transformed_points], color="green", linewidth=4, alpha=1.0)
test_ax4.coastlines()
test_ax4.set_extent((-125, -10, 36, 38))
gl3 = test_ax4.gridlines(draw_labels=True)
gl3.xlabels_top = False
gl3.ylabels_left = False
test_ax4.set_title('Path with {} prior transformed points, eastern edge={}'.format(len(test_df1),meridian))
plt.show()

Related

create a cartesian 2D plane coordinate system using plotly

This creates the graph that I'm talking about using matplotlib:
import matplotlib.pyplot as plt
import numpy as np
xmin, xmax, ymin, ymax = -9, 9, -9, 9
fig, ax = plt.subplots(figsize=(20, 20))
ax.set(xlim=(xmin - 1, xmax + 1), ylim=(ymin - 1, ymax + 1), aspect='equal')
ax.spines['bottom'].set(position="zero", linewidth=2.5)
ax.spines['left'].set(position="zero", linewidth=2.5)
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.text(10.15, 0, "x", fontdict=font, va="center")
ax.text(0, 10.15, "y", fontdict=font, ha="center")
x_ticks = np.arange(xmin, xmax)
y_ticks = np.arange(ymin, ymax)
ax.set_xticks(x_ticks[x_ticks != x_ticks])
ax.set_yticks(y_ticks[y_ticks != y_ticks])
ax.set_xticks(np.arange(xmin, xmax+1), minor=True)
ax.set_yticks(np.arange(ymin, ymax+1), minor=True)
ax.grid(which='both', color='grey', linewidth=1, linestyle='-', alpha=0.25)
plt.show()
Output: 2d cartesian plane as below
Is it possible to get a similar result with plotly?
This is how it can be done in Plotly:
import plotly.graph_objects as go
axis_range = [-9,9]
fig = go.Figure()
fig.update_xaxes(range=axis_range,title = 'y', tickmode = 'linear',
showticklabels = False, side='top',gridcolor="rgb(224,224,224)")
fig.update_yaxes(range=axis_range,title = 'x', tickmode = 'linear',
showticklabels = False, side='right', gridcolor="rgb(224,224,224)")
fig.add_vline(x=0, line_width=3)
fig.add_hline(y=0, line_width=3)
fig.update_layout(plot_bgcolor='rgb(255,255,255)', height=800, width=800)
fig.show()
The only drawback here is the label of x-axis cannot be rotated in Plotly as documented here.

Matplotlib subplots how to align colorbars with other legends, or how to justify subplots to left

How can I add a colorbar scale to the 2nd & 3rd subplots, such that it is inline with my legends in the 1st and 4th subplots? Or, another way to say the question: how can I add a colorbar scale without changing the alignment/justification of the 2nd & 3rd subplots?
There are good examples available on setting colorbar locations (e.g., here on stackoverflow and in the matplotlib docs), but I still haven't been able to solve this.
Below is a reproducible example. The real data are more complicated, and this is part of a loop to produce many figures, so the "extra" stuff about setting axis limits and subplot aspect ratios is needed and will change with different datasets.
Using Python 3.8.
Reproducible example without colorbar
## Specify axes limits, tick intervals, and aspect ratio
xl, yl, xytick, ar = [-40000,120000], [-30000,10000], 20000, 0.8
## Global plot layout stuff
fig = plt.figure(figsize=(10, 7.5), constrained_layout=True)
gs = fig.add_gridspec(4, 1)
ax1 = fig.add_subplot(gs[0, 0])
ax2 = fig.add_subplot(gs[1, 0], sharex = ax1, sharey = ax1)
ax3 = fig.add_subplot(gs[2, 0], sharex = ax1)
ax4 = fig.add_subplot(gs[3, 0], sharex = ax1, sharey = ax3)
fig.execute_constrained_layout()
fig.suptitle('Suptitle')
## First Plot
ax1.plot([-30000, 500], [-2000, -21000], c='red', label='A')
ax1.plot([80000, 110000], [-9000, 800], c='blue', label='B')
ax1.set_title('ax1', style='italic');
ax1.set_xlabel('x');
ax1.set_ylabel('beta');
ax1.set_xlim(xl)
ax1.set_ylim(yl)
ax1.xaxis.set_major_locator(ticker.MultipleLocator(xytick))
ax1.yaxis.set_major_locator(ticker.MultipleLocator(xytick))
ax1.legend(handles=leg, bbox_to_anchor=(1.05, 1), loc='upper left')
ax1.set_aspect(aspect=ar)
## Dummy data for plots 2/3/4
x = [-15000, -2000, 0, 5000, 6000, 11000, 18000, 21000, 25000, 36000, 62000]
beta = [1000, 200, -800, 100, 1000, -2000, -5000, -5000, -15000, -21000, -1500]
y = [0.01, 0.2, 1.3, 0.35, 0.88, 2.2, 2.5, 1.25, 3.4, 4.1, 2.1]
## Second Plot
vals = ax2.scatter(x, beta, c=y, norm=mcolors.LogNorm(), cmap='rainbow')
ax2.set_title('ax2', style='italic');
ax2.set_xlabel('x');
ax2.set_ylabel('beta');
ax2.set_aspect(aspect=ar)
## Attempt to add colorbar
#cbar = fig.colorbar(vals, ax=ax2, format = '%1.2g', location='right', aspect=25)
#cbar.ax.set_ylabel('y')
#cbar.ax.yaxis.set_label_position('left')
#cbar_range = [min(y), max(y)]
#ticklabels = cbar.ax.get_ymajorticklabels()
#cbarticks = list(cbar.get_ticks())
#cbar.set_ticks(cbar_range + cbarticks)
## Third Plot
ax3.scatter(x, y, c=y, norm=mcolors.LogNorm(), cmap='rainbow')
ax3.set_title('ax3', style='italic');
ax3.set_xlabel('x');
ax3.set_ylabel('y');
ax3.yaxis.set_major_formatter(FormatStrFormatter('%1.2g'))
## Fourth Plot
ax4.scatter(x, y, c='black', label='Dots')
ax4.set_title('ax4', style='italic');
ax4.set_xlabel('x');
ax4.set_ylabel('y');
ax4.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
## Clean-up, set aspect ratios
figW, figH = ax1.get_figure().get_size_inches()
_, _, w, h = ax1.get_position().bounds
disp_ratio = (figH * h) / (figW * w)
data_ratio = sub(*ax3.get_ylim()) / sub(*ax3.get_xlim())
ax3.set_aspect(aspect=disp_ratio / data_ratio )
ax4.set_aspect(aspect=disp_ratio / data_ratio)
## Clean-up, turn axis ticks back on after messing with cbar
#ax1.tick_params(axis='both', which='both', labelbottom='on')
#ax2.tick_params(axis='both', which='both', labelbottom='on')
#ax3.tick_params(axis='both', which='both', labelbottom='on')
Result when trying colorbar, note misalignment of second plot
Suggest you simplify your code and make sure it all works; for instance I have no idea what sub does.
A partial solution to your problem could be panchor=False, which is a bit of an obscure kwarg, but...
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
## Specify axes limits, tick intervals, and aspect ratio
ar = 1.2
## Global plot layout stuff
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 4), constrained_layout=True, sharex=True, sharey=True)
## First Plot
ax1.plot([-20_000, 20_000], [-20_000, 20_000] )
ax1.set_aspect(aspect=ar)
## Dummy data for plots 2/3/4
x = [-15000, -2000, 0, 5000, 6000, 11000, 18000, 21000, 25000, 36000, 62000]
beta = [1000, 200, -800, 100, 1000, -2000, -5000, -5000, -15000, -21000, -1500]
y = [0.01, 0.2, 1.3, 0.35, 0.88, 2.2, 2.5, 1.25, 3.4, 4.1, 2.1]
## Second Plot
vals = ax2.scatter(x, beta, c=y, norm=mcolors.LogNorm(), cmap='rainbow')
ax2.set_aspect(aspect=ar)
cbar = fig.colorbar(vals, ax=ax2, format = '%1.2g', location='right',
aspect=25, panchor=False)
plt.show()
Depending on the size of the figure, this could comically place the colorbar far to the right. The problem here is the aspect ratio of your plots, which makes the actual axes more narrow than the figure. But the colorbar doesn't really know about that, and places itself on the outside of the space allocated for the axes.
If this is displeasing, then you can also specify an inset axes for the colorbar.
cbax = ax2.inset_axes([1.05, 0.2, 0.05, 0.6], transform=ax2.transAxes)
cbar = fig.colorbar(vals, cax=cbax, format = '%1.2g', orientation='vertical')
Using inset_axes() solves this, as suggested in the other answer, but the parameters relative to the transform were not explained in the example, but I was able to figure it out with some research.
The parameters in inset_axes are [x-corner, y-corner, width, height] and the transform is like a local reference. So, using [1,0,0.5,0.75] means: x = 100% or end of parent ax; y = 0% or bottom of parent ax; width = 50% of parent ax; and height = 75% of parent ax.
Here I wanted the colorbar to be the same height as the parent ax (ax2 and ax3), very thin, and offset a little bit to be more in line with the other legends. Using cbax = ax2.inset_axes([1.1, 0, 0.03, 1], transform=ax2.transAxes) achieves this.
This code works for any aspect ratio ar.
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import matplotlib.colors as mcolors
from operator import sub
%matplotlib inline
plt.style.use('seaborn-whitegrid')
## Specify axes limits, tick intervals, and aspect ratio
xl, yl, ar = [-40000,120000], [-30000,10000], .5
## Global plot layout stuff
fig = plt.figure(figsize=(10, 7.5), constrained_layout=True)
gs = fig.add_gridspec(4, 1)
ax1 = fig.add_subplot(gs[0, 0])
ax2 = fig.add_subplot(gs[1, 0], sharex = ax1, sharey = ax1)
ax3 = fig.add_subplot(gs[2, 0], sharex = ax1)
ax4 = fig.add_subplot(gs[3, 0], sharex = ax1, sharey = ax3)
fig.execute_constrained_layout()
fig.suptitle('Suptitle')
## First Plot
ax1.plot([-30000, 500], [-2000, -21000], c='red', label='A')
ax1.plot([80000, 110000], [-9000, 800], c='blue', label='B')
ax1.set_title('ax1', style='italic');
ax1.set_xlim(xl)
ax1.set_ylim(yl)
ax1.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
ax1.set_aspect(aspect=ar)
## Dummy data for plots 2/3/4
x = [-15000, -2000, 0, 5000, 6000, 11000, 18000, 21000, 25000, 36000, 62000]
beta = [1000, 200, -800, 100, 1000, -2000, -5000, -5000, -15000, -21000, -1500]
y = [0.01, 0.2, 1.3, 0.35, 0.88, 2.2, 2.5, 1.25, 3.4, 4.1, 2.1]
## Second Plot
vals = ax2.scatter(x, beta, c=y, norm=mcolors.LogNorm(), cmap='rainbow')
ax2.set_title('ax2', style='italic');
ax2.set_aspect(aspect=ar)
cbax = ax2.inset_axes([1.1, 0, 0.03, 1], transform=ax2.transAxes)
cbar2 = fig.colorbar(vals, cax=cbax, format = '%1.2g', orientation='vertical')
## Third Plot
ax3.scatter(x, y, c=y, norm=mcolors.LogNorm(), cmap='rainbow')
ax3.set_title('ax3', style='italic');
cbax = ax3.inset_axes([1.1, 0, 0.03, 1], transform=ax3.transAxes)
cbar3 = fig.colorbar(vals, cax=cbax, format = '%1.2g', orientation='vertical')
## Fourth Plot
ax4.scatter(x, y, c='black', label='Dots')
ax4.set_title('ax4', style='italic');
ax4.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
## Clean-up, set aspect ratios
figW, figH = ax1.get_figure().get_size_inches()
_, _, w, h = ax1.get_position().bounds
disp_ratio = (figH * h) / (figW * w)
data_ratio = sub(*ax3.get_ylim()) / sub(*ax3.get_xlim())
ax3.set_aspect(aspect=disp_ratio / data_ratio )
ax4.set_aspect(aspect=disp_ratio / data_ratio)
## Colorbars
cbar2.ax.set_ylabel('y')
cbar2.ax.yaxis.set_label_position('left')
cbar3.ax.set_ylabel('y')
cbar3.ax.yaxis.set_label_position('left')
Result with aspect ratio = 0.5 for top 2 plots
Result with aspect ratio = 2 for top 2 plots

Python 3 matplotlit figure saved broken

I use the python 3 to make a figure, it displays very well, but when I save it, it is broken for the right half, as figures appended below. I am not sure what is wrong with it. The code I have is the following:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.ticker import rcParams, AutoMinorLocator
params = {'backend': 'ps',
'font.size': 30,
'font.style': 'normal',
'axes.labelsize': 30,
#'text.fontsize': 30,
'axes.linewidth': 2,
'legend.fontsize': 12,
'xtick.labelsize': 30,
'ytick.labelsize': 30,
'xtick.top': True,
'ytick.right': True,
#'xtick.major.top': True,
#'xtick.minor.top': True,
#'ytick.major.right': True,
#'ytick.minor.right': True,
'text.usetex': True,
'ps.usedistiller': 'xpdf'}
rcParams.update(params)
ion = [-2.0, -2.5, -3.0, -3.25, -3.5, -4.0, -4.5, -5.0, -5.5, -6.0]
cooling_mass = [0.036409, 0.035329, 0.042622, 0.069795, 0.090708, 0.107212, 0.109671, 0.110513, 0.107213, 0.109558]
fig, ax = plt.subplots(1, figsize=(8,6))
minor_locator1 = AutoMinorLocator(5)
ax.xaxis.set_minor_locator(minor_locator1)
minor_locator2 = AutoMinorLocator(5)
ax.yaxis.set_minor_locator(minor_locator2)
ax.tick_params('both', length=10, width=2, direction='in',which='major',pad=8)
ax.tick_params('both', length=5, width=2, direction='in',which='minor',pad=8)
ax.axhspan(0.036, 0.107, facecolor='lightskyblue', alpha=0.5)
ax.scatter(ion, cooling_mass, color='b', marker = 'o', s=50, zorder=2)
ax.set_ylim([0.008,0.14])
ax.set_yticks([0.03,0.06,0.09,0.12])
ax.set_xlim([-6.5,-1.5])
ax.set_xticks([-6.0,-4.0,-2.0])
ax.set_ylabel(r'$\rm Cooling ~Mass ~Rate ~[\rm M_\odot/yr]$', size=20, labelpad=5)
ax.set_xlabel(r'$\log {\rm U}$')
plt.gca().invert_xaxis()
plt.savefig('ion_cooling.eps',bbox_inches='tight')
plt.show()

Adding gridlines using Cartopy

I'm trying to add gridlines to a map I made using Cartopy, however, when I use the example code from the cartopy documentation, it doesn't display what I want and I can't figure out how to manipulate it to do so.
def plotMap():
proj = ccrs.Mercator(central_longitude=180, min_latitude=15,
max_latitude=55)
fig, ax = plt.subplots(subplot_kw=dict(projection=proj), figsize=(12,12))
ax.set_extent([255 ,115, 0, 60], crs=ccrs.PlateCarree())
ax.add_feature(cfeature.LAND, facecolor='0.3')
ax.add_feature(cfeature.LAKES, alpha=0.9)
ax.add_feature(cfeature.BORDERS, zorder=10)
ax.add_feature(cfeature.COASTLINE, zorder=10)
#(http://www.naturalearthdata.com/features/)
states_provinces = cfeature.NaturalEarthFeature(
category='cultural', name='admin_1_states_provinces_lines',
scale='50m', facecolor='none')
ax.add_feature(states_provinces, edgecolor='black', zorder=10)
#ax.gridlines(xlocs=grids_ma, ylocs=np.arange(-80,90,20), zorder=21,
draw_labels=True )
ax.gridlines(crs=ccrs.PlateCarree(), linewidth=2, color='black',
draw_labels=True, alpha=0.5, linestyle='--')
ax.xlabels_top = False
ax.ylabels_left = False
ax.ylabels_right=True
ax.xlines = True
ax.xlocator = mticker.FixedLocator([-160, -140, -120, 120, 140, 160, 180,])
ax.xformatter = LONGITUDE_FORMATTER
ax.yformatter = LATITUDE_FORMATTER
ax.xlabel_style = {'size': 15, 'color': 'gray'}
ax.xlabel_style = {'color': 'red', 'weight': 'bold'}
return fig, ax
I've attached a picture of the output. For reference, I only want the longitude gridlines to start at the left of my domain and end at the right side, preferably being spaced every 20 degrees. Ideally the same for latitude lines as well.
Bad gridline plot
Is the example you are following the one at the bottom of this page? If so, you are attempting to set attributes on the GeoAxes (ax) instance which should be set on the GridLiner (gl) instance:
import cartopy
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER
def plotMap():
proj = ccrs.Mercator(central_longitude=180, min_latitude=15,
max_latitude=55)
fig, ax = plt.subplots(subplot_kw=dict(projection=proj), figsize=(12,12))
ax.set_extent([255 ,115, 0, 60], crs=ccrs.PlateCarree())
ax.add_feature(cfeature.LAND, facecolor='0.3')
ax.add_feature(cfeature.LAKES, alpha=0.9)
ax.add_feature(cfeature.BORDERS, zorder=10)
ax.add_feature(cfeature.COASTLINE, zorder=10)
states_provinces = cfeature.NaturalEarthFeature(
category='cultural', name='admin_1_states_provinces_lines',
scale='50m', facecolor='none')
ax.add_feature(states_provinces, edgecolor='black', zorder=10)
gl = ax.gridlines(crs=ccrs.PlateCarree(), linewidth=2, color='black', alpha=0.5, linestyle='--', draw_labels=True)
gl.xlabels_top = False
gl.ylabels_left = False
gl.ylabels_right=True
gl.xlines = True
gl.xlocator = mticker.FixedLocator([120, 140, 160, 180, -160, -140, -120])
gl.ylocator = mticker.FixedLocator([0, 20, 40, 60])
gl.xformatter = LONGITUDE_FORMATTER
gl.yformatter = LATITUDE_FORMATTER
gl.xlabel_style = {'color': 'red', 'weight': 'bold'}
This produces the following map. The gridliner doesn't seem to be coping with the dateline. I do not know if there is a way around this, but there is a note at the top of the above linked documentation to say that there are currently known limitations with this class, so maybe not.
An alternative is to set the various labels and their styles directly with matplotlib. Note that you have to set the ticklabels separately from the ticks, otherwise you get labels corresponding to the Mercator coordinate reference system:
import cartopy.mpl.ticker as cticker
def plotMap2():
proj = ccrs.Mercator(central_longitude=180, min_latitude=15,
max_latitude=55)
fig, ax = plt.subplots(subplot_kw=dict(projection=proj), figsize=(12,12))
ax.set_extent([255 ,115, 0, 60], crs=ccrs.PlateCarree())
ax.add_feature(cfeature.LAND, facecolor='0.3')
ax.add_feature(cfeature.LAKES, alpha=0.9)
ax.add_feature(cfeature.BORDERS, zorder=10)
ax.add_feature(cfeature.COASTLINE, zorder=10)
states_provinces = cfeature.NaturalEarthFeature(
category='cultural', name='admin_1_states_provinces_lines',
scale='50m', facecolor='none')
ax.add_feature(states_provinces, edgecolor='black', zorder=10)
ax.set_xticks([120., 140., 160., 180., -160., -140., -120.], crs=ccrs.PlateCarree())
ax.set_xticklabels([120., 140., 160., 180., -160., -140., -120.], color='red', weight='bold')
ax.set_yticks([20, 40], crs=ccrs.PlateCarree())
ax.set_yticklabels([20, 40])
ax.yaxis.tick_right()
lon_formatter = cticker.LongitudeFormatter()
lat_formatter = cticker.LatitudeFormatter()
ax.xaxis.set_major_formatter(lon_formatter)
ax.yaxis.set_major_formatter(lat_formatter)
ax.grid(linewidth=2, color='black', alpha=0.5, linestyle='--')

How to set more margins

I have a pyplot code.
Since I want to group multiple bars, I am trying to write text in the graph using plt.annotate.
However, as you can see in the picture, the word 'Something' in left bottom gets cropped. Does anyone know How I can fix this?
Here is my code
#!/usr/bin/python
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import operator as o
import numpy as np
n_groups = 5
means_men = (20, 35, 30, 35, 27)
std_men = (2, 3, 4, 1, 2)
means_women = (25, 32, 34, 20, 25)
std_women = (3, 5, 2, 3, 3)
fig, ax = plt.subplots()
index = np.arange(n_groups)
bar_width = 0.35
opacity = 0.4
error_config = {'ecolor': '0.3'}
rects1 = plt.bar(index, means_men, bar_width, alpha=opacity, color='b', yerr=std_men, error_kw=error_config, label='Men')
rects2 = plt.bar(index + bar_width, means_women, bar_width,
alpha=opacity,
color='r',
yerr=std_women,
error_kw=error_config,
label='Women')
#plt.xlabel('Group')
plt.ylabel('Scores')
plt.title('Scores by group and gender')
plt.annotate('Something', (0,0), (50,-40), xycoords = 'axes fraction', textcoords='offset points', va='top');
plt.annotate('Something', (0,0), (200,-20), xycoords = 'axes fraction', textcoords='offset points', va='top');
plt.xticks(index + bar_width, ('A', 'B', 'C', 'D', 'E'))
plt.legend()
plt.savefig('barchart_3.png')
For some reason, matplotlib sometimes clips too aggressively. If you add bbox_inches='tight' to save fig this should include the figure correctly,
plt.savefig('barchart_3.png', bbox_inches='tight')
More generally, you can adjust your main figure with something like,
plt.subplots_adjust(bottom=0.1)