Cartopy, set_extent with NorthPoleStereo not working - cartopy

Using set_extent on Polar Stereo Graphic maps doesn't seem to be working #in a predictable fashion. I am following this Answered StackOverflow example but neither rotation #yields a map. I have set ax1.set_global() to get the data displayed.
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
from cartopy.examples.waves import sample_data
# read sample data
x, y, z = sample_data(shape=(73, 145))
fig = plt.figure(figsize=(8, 8))
# first plot with default rotation. Global extent, works fine
ax1 = fig.add_subplot(221, projection=ccrs.NorthPolarStereo())
cs1 = ax1.contourf(x, y, z, 50, transform=ccrs.PlateCarree(),
cmap='gist_ncar')
ax1.set_title('Global')
#next plot setting extent to 0,360,40,90, no display
ax2 = fig.add_subplot(222,
projection=ccrs.NorthPolarStereo())
cs2 = ax2.contourf(x, y, z, 50,
transform=ccrs.PlateCarree(),
cmap='gist_ncar')
ax2.set_extent([0,360,40,90],crs=ccrs.PlateCarree())
ax2.coastlines()
ax2.set_title('Centred on 0$^\circ$ (default)')
#Now resetting set_extent to [-180,180,40,90] strangely works!
ax3 = fig.add_subplot(
223, projection=ccrs.NorthPolarStereo())
cs3 = ax3.contourf(x, y, z, 50, transform=ccrs.PlateCarree(),
cmap='gist_ncar')
ax3.set_extent([-180, 180, 40, 90], crs=ccrs.PlateCarree())
ax3.coastlines()
ax3.set_title('Using -180,180 $^\circ$W')
#but now rotating projection yields just a corner of the map
ax4 = fig.add_subplot(
224,projection=ccrs.NorthPolarStereo(central_longitude=-45))
cs4 = ax4.contourf(x, y, z, 50, transform=ccrs.PlateCarree(),
cmap='gist_ncar')
ax4.set_extent([-180, 180, 40, 90], crs=ccrs.PlateCarree())
ax4.coastlines()
ax4.set_title('Rotated on -45 $^\circ$W')
plt.show()
I was expecting the set_extent to work as documented but there seems to be #a strange interaction between the rotation and the extent
Output

It seems to be an artifact of how CartoPy calculates its bounding boxes. What it does, when given extents in lon/lat, is calculate the 4 corners of the box based on the extent, then project those to the native projection, then figure out the extent from that. The problem is that in the stereographic projection, some of those combinations end of up with no separation in x or y--due to the periodic nature of (especially) longitude.
I can reproduce the math issues with just PyProj, so it's not a problem with the projection math in CartoPy, just a limitation of how it calculates bounds. You can override by using projected coordinates in in set_extent:
ax.set_extent((0, 500000, 0, 500000), crs=ccrs.NorthPolarStereo())
I know that's not ideal, but I can't think of any good ways to calculate what the appropriate bounds is based on box in lon/lat space.

Related

How to enlarge a Matplotlib group bar diagram

I have a group bar chart that I would like to scale.
I am running matplotlib in a Jupyter Notebook and the bar chart is very squashed. I would like to make the axis bigger but can't get it to work in a group bar chart. If I could make it wider it would be much more readable. But if I just increase "width" then the bars start to overlap each other.
The second problem is what to do about the labels. How can the labels be printed to three decimal places?
Note: I recognise that the the values plotted are orders of magnitude different so you cannot really read the small values. Ordinarily you would not combine these onto a single chart - but this is a class exercise to demonstrating why you would not do it so I expect that.
Here is the self-contained code to demonstrate the problem:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
labels = ['0-9', '10-19', '20-29', '30-39', '40-49', '50-59', '60-69', '70-79', '80-89', '90-99']
t3=[1.2833333333333332, 1.6970588235294117, 1.7189655172413794, 1.8090163934426229, 1.44140625, 1.5763157894736846, 1.3685185185185187, 1.430120481927711, 1.5352941176470587, 1.9]
tt4= [116.33333333333333, 106.0, 106.93103448275862, 109.47540983606558, 98.734375, 99.84210526315789, 96.72839506172839, 99.40963855421687, 104.94117647058823, 203.0]
tsh= [1.2833333333333332, 1.6970588235294117, 1.7189655172413794, 1.8090163934426229, 1.44140625, 1.5763157894736846, 1.3685185185185187, 1.430120481927711, 1.5352941176470587, 1.9]
hypo_count= [2, 15, 55, 58, 59, 69, 72, 74, 33, 1]
x = np.arange(len(labels)) # the label locations
width = 0.2 # the width of the bars
fig, ax = plt.subplots()
rects1 = ax.bar(x, t3, width, label='T3 avg')
rects2 = ax.bar(x+(width), tt4, width, label='TT4 avg')
rects3 = ax.bar(x+(width*2), tsh, width, label='TSH avg')
rects4 = ax.bar(x+(width*3), hypo_count, width, label='# Hypothyroid +ve')
# Add some text for labels, title and custom x-axis tick labels, etc.
ax.set_title('Age Bracket')
ax.set_xticks(x)
ax.set_xticklabels(labels)
ax.legend()
# Print the value on top of each bar
ax.bar_label(rects1, padding=3)
ax.bar_label(rects2, padding=3)
ax.bar_label(rects3, padding=3)
ax.bar_label(rects4, padding=3)
fig.tight_layout()
plt.show()

Is there a way to extract the pixel co-ordinates of a plotted line in matplotlib

Similar to in this StackOverflow post, I understand that it is possible to extract the pixel co-ordinates from points plotted in a pyplot figure.
How to get pixel coordinates for Matplotlib-generated scatterplot?
However, what if we plotted a line between each of those points and wanted to get the location of all the pixels of not just those plotted dots, but all pixels that make up the line.
Is this something that is possible with matplotlib?
A line isn't made up of pixels. The pixels in its trajectory are modified taking line width and antialiasing into account. Drawing a line with default settings and zooming in on the image looks like the image below. Very few pixels get the full 100% of the given color. Lots of pixels are changed.
Depending on your final goal, you could calculate pixel coordinates using the method described in the post you linked (note that the pixels on a saved image can deviate a bit from the pixels on-screen). And then use e.g. Bresenham's line algorithm to find the coordinates of points in-between. Note that a naive Bresenham's algorithm would draw a 45 degree line much thinner looking than a horizontal line. On a modern screen a one-pixel wide line would be almost invisible.
Here is a possible Bresenham-like interpretation of the linked code:
import numpy as np
import matplotlib.pyplot as plt
def points_in_line(x0, y0, x1, y1):
dx = np.round(np.abs(x1 - x0))
dy = np.round(np.abs(y1 - y0))
steps = int(np.round(max(dx, dy))) + 1
return np.vstack([np.linspace(x0, x1, steps), np.linspace(y0, y1, steps)]).T
fig, ax = plt.subplots()
points, = ax.plot([0, 1, 2, 4, 5, 6, 9], [0, 5, 3, 2, 2, 9, 8], 'b-')
ax.axis([-1, 10, -1, 10])
# Get the x and y data and transform them into pixel coordinates
x, y = points.get_data()
xy_pixels = ax.transData.transform(np.vstack([x, y]).T)
x_pix, y_pix = xy_pixels.T
# find all points in each line
all_pix = [points_in_line(x0, y0, x1, y1) for x0, y0, x1, y1 in zip(x_pix[:-1], y_pix[:-1], x_pix[1:], y_pix[1:])]
all_x_pix, all_y_pix = np.concatenate(all_pix).T
# In matplotlib, 0,0 is the lower left corner, whereas it's usually the upper
# left for most image software, so we'll flip the y-coords...
width, height = fig.canvas.get_width_height()
all_y_pix = height - all_y_pix
print('Coordinates of the lines in pixel coordinates...')
for xp, yp in zip(all_x_pix, all_y_pix):
print(f'{x:0.2f}\t{y:0.2f}')
# save the figure with its current DPI
fig.savefig('test.png', dpi=fig.dpi)

how to remove the white space of invisiable axes in matplotlib during active plot?

I want to completely remove white space around my axes during active plot (not save_fig as others asked).
Here we cannot use bbox_inches='tight'. I can use tight_layout(pad=0).
When axis is on, it works fine, it shows all the ticks and x-y labels.
However, in some cases, I set the axis off. What I expected is to see the contents expand to fill up the empty space where the axes are. However, this does not work. It still keep the padding as there are still x-y labels and axes.
How can I remove the white space of invisible axes objects?
edit:
I am aware that I can use ax.set_yticks([]) and ax.set_xticks([]) to turn those off. But this is clumsy, I have to remember the the ticks before I clear them. And if I remove-then-add those ticks. The ticks cannot automatically update any more.
I wonder is there any more straightforward way to do this?
We can still see there is a small border spacing even after removing all ticks. If someone can come up a way to remove that too. It will be fantastic.
I would also like to keep the title if there is one. Thus the hard-coded ax.set_position([0,0,1,x]) is not very good for this usage. Surely we can still try to get the top spacing when there is a title, but if someone can provide a more direct/simple way to handle this, it will be preferred.
Example code:
def demo_tight_layout(w=10, h=6, axisoff=False, removeticks=False):
fig,ax = plt.subplots()
fig.set_facecolor((0.8, 0.8, 0.8))
rect = patches.Rectangle((-w/2, -h/2), w, h, color='#00ffff', alpha=0.5)
ax.add_patch(rect)
ax.plot([-w/2,w/2], [-h/2,h/2])
ax.plot([-w/2,w/2], [h/2,-h/2])
ax.set_ylabel("ylabel")
ax.margins(0)
_texts = []
if axisoff:
ax.set_axis_off()
_texts.append("axisoff")
if removeticks:
ax.set_xticks([])
ax.set_yticks([])
ax.set_ylabel("")
_texts.append("removeticks")
fig.text(0.5, 0.6, " ".join(_texts))
fig.tight_layout(pad=0)
plt.show()
return fig, ax, text
You may adjust the subplot parameters depending on whether you turned the axis off or not.
import matplotlib.pyplot as plt
from matplotlib import patches
def demo_tight_layout(w=10, h=6, axisoff=False):
fig,ax = plt.subplots()
fig.set_facecolor((0.8, 0.8, 0.8))
rect = patches.Rectangle((-w/2, -h/2), w, h, color='#00ffff', alpha=0.5)
ax.add_patch(rect)
ax.plot([-w/2,w/2], [-h/2,h/2])
ax.plot([-w/2,w/2], [h/2,-h/2])
ax.set_ylabel("ylabel")
ax.margins(0)
_texts = []
fig.tight_layout()
if axisoff:
ax.set_axis_off()
_texts.append("axisoff")
params = dict(bottom=0, left=0, right=1)
if ax.get_title() == "":
params.update(top=1)
fig.subplots_adjust(**params)
fig.text(0.5, 0.6, " ".join(_texts))
plt.show()
Now demo_tight_layout(axisoff=True) produces
and demo_tight_layout(axisoff=False) produces
You need to set the axes position to fill the figure. If you create your figure and plot with
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.gca()
ax.plot(some_x_data, some_y_data)
you need to add the following line to fill the figure with the axes:
ax.set_position([0, 0, 1, 1], which='both')
This sets the axes location relative to the figure size in the following way:
[left, bottom, width, height]
So to completely fill the figure use [0, 0, 1, 1] as shown above.
So taking your code, it should look like this (using fill_figure bool to check):
def demo_tight_layout(w=10, h=6, axisoff=False, removeticks=False, fill_figure=False):
fig,ax = plt.subplots()
fig.set_facecolor((0.8, 0.8, 0.8))
rect = patches.Rectangle((-w/2, -h/2), w, h, color='#00ffff', alpha=0.5)
ax.add_patch(rect)
ax.plot([-w/2,w/2], [-h/2,h/2])
ax.plot([-w/2,w/2], [h/2,-h/2])
ax.set_ylabel("ylabel")
ax.margins(0)
_texts = []
if axisoff:
ax.set_axis_off()
_texts.append("axisoff")
if removeticks:
ax.set_xticks([])
ax.set_yticks([])
ax.set_ylabel("")
_texts.append("removeticks")
fig.text(0.5, 0.6, " ".join(_texts))
fig.tight_layout(pad=0)
if fill_figure:
ax.set_position([0, 0, 1, 1], which='both')
plt.show()
return fig, ax, text
ax.set_position needs to be after fig.tight_layout.
If a figure title is needed, there is no direct way to do it. This unluckily can't be avoided. You need to adapt the height parameters manually so that the title fits in the figure, for example with:
ax.set_position([0, 0, 1, .9], which='both')

How to hide contour lines / data from a specific area on Basemap

I am working some meteorological data to plot contour lines on a basemap. The full working example code I have done earlier is here How to remove/omit smaller contour lines using matplotlib. All works fine and I don’t complain with the contour plot. However there is a special case that I have to hide all contour lines over a specific region (irregular lat & lon) on a Basemap.
The only possible solution I can think of is to draw a ploygon lines over a desired region and fill with the color of same as Basemap. After lot of search I found this link How to draw rectangles on a Basemap (code below)
from mpl_toolkits.basemap import Basemap
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon
def draw_screen_poly( lats, lons, m):
x, y = m( lons, lats )
xy = zip(x,y)
poly = Polygon( xy, facecolor='red', alpha=0.4 )
plt.gca().add_patch(poly)
lats = [ -30, 30, 30, -30 ]
lons = [ -50, -50, 50, 50 ]
m = Basemap(projection='sinu',lon_0=0)
m.drawcoastlines()
m.drawmapboundary()
draw_screen_poly( lats, lons, m )
plt.show()
It seems to work partially. However, I want to draw a region which is irregular.
Any solution is appreciated.
Edit: 1
I have understood where the problem is. It seems that any colour (facecolor) filled within the polygon region does not make it hide anything below. Always it is transparent only, irrespective of alpha value used or not. To illustrate the problem, I have cropped the image which has all three regions ie. contour, basemap region and polygon region. Polygon region is filled with red colour but as you can see, the contour lines are always visible. The particular line I have used in the above code is :-
poly = Polygon(xy, facecolor='red', edgecolor='b')
Therefore the problem is not with the code above. It seem the problem with the polygon fill. But still no solution for this issue. The resulting image (cropped image) is below (See my 2nd edit below the attached image):-
Edit 2:
Taking clue from this http://matplotlib.1069221.n5.nabble.com/Clipping-a-plot-inside-a-polygon-td41950.html which has the similar requirement of mine, I am able to remove some the data. However, the removed data is only from outside of polygon region instead of within. Here is the code I have taken clue from:-
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import RegularPolygon
data = np.arange(100).reshape(10, 10)
fig = plt.figure()
ax = fig.add_subplot(111)
ax.contourf(data)
poly = RegularPolygon([ 0.5, 0.5], 6, 0.4, fc='none',
ec='k', transform=ax.transAxes)
for artist in ax.get_children():
artist.set_clip_path(poly)
Now my question is that what command is used for removing the data within the polygon region?
Didn't noticed there was a claim on this so I might just give the solution already proposed here. You can tinker with the zorder to hide stuff behind your polygon:
import matplotlib
import matplotlib.mlab as mlab
import matplotlib.pyplot as plt
matplotlib.rcParams['xtick.direction'] = 'out'
matplotlib.rcParams['ytick.direction'] = 'out'
delta = 0.025
x = np.arange(-3.0, 3.0, delta)
y = np.arange(-2.0, 2.0, delta)
X, Y = np.meshgrid(x, y)
Z1 = mlab.bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0)
Z2 = mlab.bivariate_normal(X, Y, 1.5, 0.5, 1, 1)
# difference of Gaussians
Z = 10.0 * (Z2 - Z1)
# Create a simple contour plot with labels using default colors. The
# inline argument to clabel will control whether the labels are draw
# over the line segments of the contour, removing the lines beneath
# the label
fig = plt.figure()
ax = fig.add_subplot(111)
CS = plt.contour(X, Y, Z,zorder=3)
plt.clabel(CS, inline=1, fontsize=10)
plt.title('Simplest default with labels')
rect1 = matplotlib.patches.Rectangle((0,0), 2, 1, color='white',zorder=5)
ax.add_patch(rect1)
plt.show()
, the result is:

central longitude for NorthPolarStereo

I'd like to plot a polar stereographic plot of the Northern Hemisphere with 180 at the bottom of the plot so I can emphasize the Pacific region. I'm using the latest cartopy from git, and can make a polar stereographic plot no problem, but I can't work out how to change which longitude is at the bottom of the plot. I tried setting the longitude extent to [-180, 180] but this doesn't help, and the NorthPolarStereo() doesn't accept any keyword arguments like central_longitude. Is this possible currently?
This feature has now been implemented in Cartopy (v0.6.x). The following example produces two subplots in Northern Hemisphere polar stereographic projections, one with the default settings and one with the central longitude changed:
"""Stereographic plot with adjusted central longitude."""
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
from cartopy.examples.waves import sample_data
# read sample data
x, y, z = sample_data(shape=(73, 145))
fig = plt.figure(figsize=(8, 4))
# first plot with default settings
ax1 = fig.add_subplot(121, projection=ccrs.NorthPolarStereo())
cs1 = ax1.contourf(x, y, z, 50, transform=ccrs.PlateCarree(),
cmap='gist_ncar')
ax1.set_extent([0, 360, 0, 90], crs=ccrs.PlateCarree())
ax1.coastlines()
ax1.set_title('Centred on 0$^\circ$ (default)')
# second plot with 90W at the bottom of the plot
ax2 = fig.add_subplot(
122, projection=ccrs.NorthPolarStereo(central_longitude=-90))
cs2 = ax2.contourf(x, y, z, 50, transform=ccrs.PlateCarree(),
cmap='gist_ncar')
ax2.set_extent([0, 360, 0, 90], crs=ccrs.PlateCarree())
ax2.coastlines()
ax2.set_title('Centred on 90$^\circ$W')
plt.show()
The output of this script is:
For Cartopy 0.17 and matplotlib 3.1.1 (Python 3.7), I got an error in set_extent() with the above solution.
It seems that set_extent() only works this way:
ax1.set_extent([-180, 180, 0, 90], crs=ccrs.PlateCarree())
ax2.set_extent([-179, 179, 0, 90], crs=ccrs.PlateCarree())
So, the rotated image needs some weird longitude boundaries..