Custom markers with screen coordinate size in (older) matplotlib - matplotlib

Originally, I was hoping that I could create a sort of a class for a marker in matplotlib, which would be a square with text showing the, say, x coordinate and a label, so I could instantiate it with something like (pseudocode):
plt.plot(..., marker=myMarkerClass(label="X:"), ... )
... but as far as I can see, you cannot do stuff like that.
However, it seems that customization of markers is not available in older matplotlib; so I'd like to reduce my question to: how to get custom (path) markers in older matplotlib, so their sizes are defined in screen coordinates (so markers don't scale upon zoom)? To clarify, here are some examples:
Default (uncustomized) markers
Below is an example with the default matplotlib markers, which works with older matplotlib. Note that I've tried instead of using pyplot.plot(), I'm trying to work with matplotlib.figure.Figure directly (as that is the form usually used with diverse backends), requiring use of "figure manager" (see also matplotlib-devel - Backends object structure):
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.figure
import numpy as np
t = np.arange(0.0,1.5,0.25)
s = np.sin(2*np.pi*t)
mfigure = matplotlib.figure.Figure(figsize=(5,4), dpi=100)
ax = mfigure.add_subplot(111)
ax.plot(t,s, marker='o', color='b', markerfacecolor='orange', markersize=10.0)
fig = plt.figure() # create something (fig num 1) for fig_manager
figman = matplotlib._pylab_helpers.Gcf.get_fig_manager(1)
figman.canvas.figure = mfigure # needed
mfigure.set_canvas(figman.canvas) # needed
plt.show()
If we do an arbitrary zoom rect here, the markers remain the same size:
Customized markers via Path
This is documented in artists (Line2D.set_marker) — Matplotlib 1.2.1 documentation; however, it doesn't work with older matplotlib; here's an example:
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.figure
import matplotlib.path
import numpy as np
print("matplotlib version {0}".format(matplotlib.__version__))
def getCustomSymbol1(inx, iny, sc, yasp):
verts = [
(-0.5, -0.5), # left, bottom
(-0.5, 0.5), # left, top
(0.5, 0.5), # right, top
(0.5, -0.5), # right, bottom
(-0.5, -0.5), # ignored
]
codes = [matplotlib.path.Path.MOVETO,
matplotlib.path.Path.LINETO,
matplotlib.path.Path.LINETO,
matplotlib.path.Path.LINETO,
matplotlib.path.Path.CLOSEPOLY,
]
pathCS1 = matplotlib.path.Path(verts, codes)
return pathCS1, verts
t = np.arange(0.0,1.5,0.25)
s = np.sin(2*np.pi*t)
mfigure = matplotlib.figure.Figure(figsize=(5,4), dpi=100)
ax = mfigure.add_subplot(111)
pthCS1, vrtCS1 = getCustomSymbol1(0,0, 1,1)
# here either marker=pthCS1 or marker=np.array(vrtCS1)
# have the same effect:
ax.plot(t,s, marker=pthCS1, markerfacecolor='orange', markersize=10.0)
#ax.plot(t,s, marker=np.array(vrtCS1), markerfacecolor='orange', markersize=10.0)
fig = plt.figure() # create something (fig num 1) for fig_manager
figman = matplotlib._pylab_helpers.Gcf.get_fig_manager(1)
figman.canvas.figure = mfigure # needed
mfigure.set_canvas(figman.canvas) # needed
plt.show()
This runs fine for me in newer matplotlib, but fails in the older:
$ python3.2 test.py
matplotlib version 1.2.0
$ python2.7 test.py # marker=pthCS1
matplotlib version 0.99.3
Traceback (most recent call last):
File "test.py", line 36, in <module>
ax.plot(t,s, marker=pthCS1, markerfacecolor='orange', markersize=10.0)
...
File "/usr/lib/pymodules/python2.7/matplotlib/lines.py", line 804, in set_marker
self._markerFunc = self._markers[marker]
KeyError: Path([[-0.5 -0.5]
[-0.5 0.5]
[ 0.5 0.5]
[ 0.5 -0.5]
[-0.5 -0.5]], [ 1 2 2 2 79])
$ python2.7 test.py # marker=np.array(vrtCS1)
matplotlib version 0.99.3
Traceback (most recent call last):
File "test.py", line 38, in <module>
ax.plot(t,s, marker=np.array(vrtCS1), markerfacecolor='orange', markersize=10.0)
...
File "/usr/lib/pymodules/python2.7/matplotlib/lines.py", line 798, in set_marker
if marker not in self._markers:
TypeError: unhashable type: 'numpy.ndarray'
However, when it works in Python 3.2, the markers again keep their size across zoom of the graph, as I'd expect:
... though note this issue: Custom marker created from vertex list scales wrong · Issue #1980 · matplotlib/matplotlib · GitHub, in respect to this type of custom markers.
Through Paths in PatchCollection
I've picked up parts of this code from some internet postings, but cannot find the links now. In any case, we can avoid drawing the markers and PatchCollection can be used, to draw what should be the markers. Here is the code, which runs in older matplotlib:
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.figure
import matplotlib.path, matplotlib.patches, matplotlib.collections
import numpy as np
def getCustomSymbol1(inx, iny, sc, yasp):
verts = [
(inx-0.5*sc, iny-0.5*sc*yasp), # (0., 0.), # left, bottom
(inx-0.5*sc, iny+0.5*sc*yasp), # (0., 1.), # left, top
(inx+0.5*sc, iny+0.5*sc*yasp), # (1., 1.), # right, top
(inx+0.5*sc, iny-0.5*sc*yasp), # (1., 0.), # right, bottom
(inx-0.5*sc, iny-0.5*sc*yasp), # (0., 0.), # ignored
]
codes = [matplotlib.path.Path.MOVETO,
matplotlib.path.Path.LINETO,
matplotlib.path.Path.LINETO,
matplotlib.path.Path.LINETO,
matplotlib.path.Path.CLOSEPOLY,
]
pathCS1 = matplotlib.path.Path(verts, codes)
return pathCS1
def getXyIter(inarr):
# this supports older numpy, where nditer is not available
if np.__version__ >= "1.6.0":
return np.nditer(inarr.tolist())
else:
dimensions = inarr.shape
xlen = dimensions[1]
xinds = np.arange(0, xlen, 1)
return np.transpose(np.take(inarr, xinds, axis=1))
t = np.arange(0.0,1.5,0.25)
s = np.sin(2*np.pi*t)
mfigure = matplotlib.figure.Figure(figsize=(5,4), dpi=100)
ax = mfigure.add_subplot(111)
ax.plot(t,s)
customMarkers=[]
for x, y in getXyIter(np.array([t,s])): #np.nditer([t,s]):
#printse("%f:%f\n" % (x,y))
pathCS1 = getCustomSymbol1(x,y,0.05,1.5*500.0/400.0)
patchCS1 = matplotlib.patches.PathPatch(pathCS1, facecolor='orange', lw=1) # no
customMarkers.append(patchCS1)
pcolm = matplotlib.collections.PatchCollection(customMarkers)
pcolm.set_alpha(0.9)
ax.add_collection(pcolm)
fig = plt.figure() # create something (fig num 1) for fig_manager
figman = matplotlib._pylab_helpers.Gcf.get_fig_manager(1)
figman.canvas.figure = mfigure # needed
mfigure.set_canvas(figman.canvas) # needed
plt.show()
Now, here I tried to take figure initial aspect ratio into consideration and indeed, at first render, the "markers" look right in respect to size - but...:
... when we try to do arbitrary zoom, it is obvious that the paths have been specified in data coordinates, and so their sizes change depending on the zoom rect. (Another nuissance is that facecolor='orange' is not obeyed; but that can be fixed with pcolm.set_facecolor('orange'))
So, is there a way that I can use PatchCollection as markers for older matplotlib, in the sense that the rendered paths would be defined in screen coordinates, so they would not change their size upon arbitrary zooming?

Many thanks to #tcaswell for the comment on blended transform - in trying to figure out why that doesn't work for me, I finally found the solution. First, the code - using, essentially, the default marker engine of matplotlib (depending on whether using older matplotlib (0.99) or newer one):
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.figure
import numpy as np
# create vertices and Path of custom symbol
def getCustomSymbol1():
verts = [
(0.0, 0.0), # left, bottom
(0.0, 0.7), # left, top
(1.0, 1.0), # right, top
(0.8, 0.0), # right, bottom
(0.0, 0.0), # ignored
]
codes = [matplotlib.path.Path.MOVETO,
matplotlib.path.Path.LINETO,
matplotlib.path.Path.LINETO,
matplotlib.path.Path.LINETO,
matplotlib.path.Path.CLOSEPOLY,
]
pathCS1 = matplotlib.path.Path(verts, codes)
return pathCS1, verts
if matplotlib.__version__ < "1.0.0":
# define a marker drawing function, that uses
# the above custom symbol Path
def _draw_mypath(self, renderer, gc, path, path_trans):
gc.set_snap(renderer.points_to_pixels(self._markersize) >= 2.0)
side = renderer.points_to_pixels(self._markersize)
transform = matplotlib.transforms.Affine2D().translate(-0.5, -0.5).scale(side)
rgbFace = self._get_rgb_face()
mypath, myverts = getCustomSymbol1()
renderer.draw_markers(gc, mypath, transform,
path, path_trans, rgbFace)
# add this function to the class prototype of Line2D
matplotlib.lines.Line2D._draw_mypath = _draw_mypath
# add marker shortcut/name/command/format spec '#' to Line2D class,
# and relate it to our custom marker drawing function
matplotlib.lines.Line2D._markers['#'] = '_draw_mypath'
matplotlib.lines.Line2D.markers = matplotlib.lines.Line2D._markers
else:
import matplotlib.markers
def _set_mypath(self):
self._transform = matplotlib.transforms.Affine2D().translate(-0.5, -0.5)
self._snap_threshold = 2.0
mypath, myverts = getCustomSymbol1()
self._path = mypath
self._joinstyle = 'miter'
matplotlib.markers.MarkerStyle._set_mypath = _set_mypath
matplotlib.markers.MarkerStyle.markers['#'] = 'mypath'
matplotlib.lines.Line2D.markers = matplotlib.markers.MarkerStyle.markers
# proceed as usual - use the new marker '#'
t = np.arange(0.0,1.5,0.25)
s = np.sin(2*np.pi*t)
mfigure = matplotlib.figure.Figure(figsize=(5,4), dpi=100)
ax = mfigure.add_subplot(111)
ax.plot(t,s, marker='#', color='b', markerfacecolor='orange', markersize=20.0)
fig = plt.figure() # create something (fig num 1) for fig_manager
figman = matplotlib._pylab_helpers.Gcf.get_fig_manager(1)
figman.canvas.figure = mfigure # needed
mfigure.set_canvas(figman.canvas) # needed
plt.show()
It shows a slightly "creative" marker (if I may say so myself :) ) - and this is how it behaves under zoom:
... that is, exactly as I want it - markers keep their position in data coordinates; however preserve their size under zooming.
Discussion
One of the most confusing things for me here, was the following: Essentially, you can specify a rectangle through an x,y coordinate as "location", and size (height/width). So if I specify a rectangle at x,y=(2,3); with halfsize 2 (so, square); then I can calculate its path as vertices through:
[(x-hs,y-hs), (x-hs,y+hs), (x+hs,y+hs), (x+hs,y-hs)]
This is, essentially, what getCustomSymbol1 was trying to return all along. In addition, for instance matplotlib.patches.Rectangle is instantiated through location and size, as in Rectangle((x,y), width, height).
Now, the problem is - what I actually wanted, is that markers, as shapes, stay on their position - in data coordinates, so moving and zooming the graph keeps their position as data; however, they should have kept their size under zooming.
This means that I want (x,y) specified in one coordinate system (data), and size (or width, height, or halfsize) specified in another coordinate system, in this case, the screen coordinate system: since I want the shapes to keep their size under zoom, I actually want to keep their size in screen pixels the same!
That is why no transformation as such would help in my case - any transformation would work on all the vertices of a path, as interpreted in a single coordinate system! Whereas, what I want, would be to get something like:
hsd = screen2dataTransform(10px)
[(x-hsd,y-hsd), (x-hsd,y+hsd), (x+hsd,y+hsd), (x+hsd,y-hsd)]
... and this recalculation of the vertices of the marker path would have to be repeated each time the zoom level, or the figure pan, or the window size changes.
As such, vertices/paths (and patches) and transformation alone cannot be used for this purpose. However, thankfully, we can use matplotlibs own engine; we simply have to know that any call to ax.plot(...marker=...) actually delegates the drawing of markers to matplotlib.lines.Line2D; and Line2D maintains an internal dict, which relates the marker one-character format specifier/command (like 'o', '*' etc) to a specific drawing function; and the drawing function, finally, does the size transformation in code like (in the solution code above, the drawing is for the most part taken from the 's' (square) marker implementation):
side = renderer.points_to_pixels(self._markersize)
transform = matplotlib.transforms.Affine2D().translate(-0.5, -0.5).scale(side)
Note that this is the case for older matplotlib (0.99.3 in my case); for newer matplotlib (1.2.0 in my case), there is a separate class MarkerStyle which maintains the relationship between the marker format specifier, and the function - and the function is not _draw_ anymore, it is just _set_ - but other than that, it is the same principle.
NB: I'm actually not sure when MarkerStyle was introduced, I could only find matplotlib.markers — Matplotlib 1.3.x documentation and it doesn't say; so that if matplotlib.__version__ < "1.0.0": in the code above could be wrong; but worksforme (fornow).
Because the markers size is, so to speak, managed separately (from its position) - that means that you don't have to do any special calculation in your custom marker Path specification; all you need to do is make sure its vertices fit in the range (0.0,0.0) to (1.0, 1.0) - the marker drawing engine will do the rest.
Well, I hope I understood this right - but if there are other approaches that work like this, I'd sure like to know about those :)
Hope this helps someone,
Cheers!

Related

PyPlot ConnectionPatch between CartoPy GeoAxes

The ConnectionPatch is a useful way to draw a line between two points on two different axes (demo). Is it possible to use this class when one (or both) of the axes is of Cartopy GeoAxes type? A related answer suggests a work-around but I would prefer to avoid this.
I can not answer your question about the use of that class thing. But, if you are interested in plotting the lines between 2 different Cartopy geoaxes, or between matplotlib axes and a geoaxe, that can be achieved with some coordinate transformation. Here is a runnable code and the output plot. I have written some comments within the code to help explain the important steps.
For further information about coordinate system and tranformation:
Cartopy https://scitools.org.uk/cartopy/docs/latest/tutorials/understanding_transform.html
Since Cartopy is built on top of Matplotlib, you need to look into the related subject in Matplotlib.
Matplotlib https://matplotlib.org/3.2.1/tutorials/advanced/transforms_tutorial.html
import cartopy
import cartopy.mpl.geoaxes
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
fig, ax = plt.subplots()
fig.set_size_inches([8,8]) # 9,6; 8,9; 8,3 all OK
# Plot simple line on main axes
ax.plot([4,5,3,1,2])
p1 = [0.5,3.0] # Bangkok text location
p2 = [0.5,2.75] # Himalaya text location
# Plot texts (Bangkok, Himalaya) on the main axes
ax.text(*p1, "Bangkok", ha='right')
ax.text(*p2, "Himalaya", ha='right')
# Ploting on UR inset map (cartopy) on the main axes (ax)
bkk_lon, bkk_lat = 100, 13 # Bangkok
hml_lon, hml_lat = 83.32, 29.22 # Everest peak
# Create cartopy geoaxes inset axes as part of the main axes 'ax'
axins = inset_axes(ax, width="40%", height="30%", loc="upper right",
axes_class = cartopy.mpl.geoaxes.GeoAxes,
axes_kwargs = dict(map_projection = cartopy.crs.PlateCarree()))
# Set map limits on that axes (for Thailand)
llx, lly = 95, 0
urx, ury = 110, 25
axins.set_xlim((llx, urx))
axins.set_ylim((lly, ury))
# Plot coastlines
axins.add_feature(cartopy.feature.COASTLINE)
# Plot line across the inset mao, LL to UR; OK
#ll_p, ur_p = [llx,urx], [lly,ury]
#axins.plot(ll_p, ur_p, "r--")
axins.plot(bkk_lon, bkk_lat, 'ro', transform=cartopy.crs.PlateCarree()) # OK!
# Create another inset map on the main axes (ax)
axins2 = inset_axes(ax, width="40%", height="30%", loc="lower left",
axes_class = cartopy.mpl.geoaxes.GeoAxes,
axes_kwargs = dict(map_projection = cartopy.crs.PlateCarree()))
# Set map limits on that axes (second inset map)
llx2, lly2 = -60, -20
urx2, ury2 = 120, 90
axins2.set_xlim((llx2, urx2))
axins2.set_ylim((lly2, ury2))
axins2.add_feature(cartopy.feature.COASTLINE)
# Plot line from UK to BKK, OK
#p21, p22 = [0, 100], [40, 13]
#axins2.plot(p21, p22, "r--")
# Plot blue dot at Himalaya
axins2.plot(hml_lon, hml_lat, "bo")
plt.draw() # Do this to get updated position
# Do coordinate transformation to get BKK, HML locations in display coordinates
# from axins_data_xy to dp_xy
dpxy_bkk_axins = axins.transData.transform((bkk_lon, bkk_lat)) # get display coordinates
# from axins2_data_xy to dp_xy
dpxy_bkk_axins2 = axins2.transData.transform((hml_lon, hml_lat)) # get display coordinates
# Do coordinate transformation to get BKK, HML locations in data coordinates of the main axes 'ax'
# from both dp_xy to main_ax_data
ur_bkk = ax.transData.inverted().transform( dpxy_bkk_axins )
ll_hml = ax.transData.inverted().transform( dpxy_bkk_axins2 )
# Prep coordinates for line connecting BKK to HML
xs = ur_bkk[0], ll_hml[0]
ys = ur_bkk[1], ll_hml[1]
xs = ur_bkk[0], ll_hml[0]
ys = ur_bkk[1], ll_hml[1]
ax.plot(xs, ys, 'g--') # from Bkk to Himalaya of different inset maps
# Plot lines from texts (on main axes) to locations on maps
ax.plot([p1[0], ur_bkk[0]], [p1[1], ur_bkk[1]], 'y--')
ax.plot([p2[0], ll_hml[0]], [p2[1], ll_hml[1]], 'y--')
# Set cartopy inset background invisible
axins.background_patch.set_visible(False)
axins2.background_patch.set_visible(False)
plt.show()
The output plot:-

subplots_adjust moves axes unpredictably?

I'm working on a python module that creates a matplotlib figure with an on_resize listener. The listener forces the height of the lower axes to a specific number of pixels (rather than scaling relative to figure size). It works. However, if (in matplotlib interactive mode) after creating the plot the user calls fig.subplots_adjust() it messes up subplot sizes. Here's a radically simplified version of what the module does:
import matplotlib.pyplot as plt
plt.ion()
def make_plot():
fig = plt.figure()
gs = plt.GridSpec(10, 1, figure=fig)
ax_upper = fig.add_subplot(gs[:-1])
ax_lower = fig.add_subplot(gs[-1])
ax_upper.plot([0, 1])
ax_lower.plot([0, 1])
fig.canvas.mpl_connect('resize_event', on_resize)
return fig
def on_resize(event):
fig = event.canvas.figure
# get the current position
ax_lower_pos = list(fig.axes[1].get_position().bounds) # L,B,W,H
# compute desired height in figure-relative coords
desired_height_px = 40
xform = fig.transFigure.inverted()
desired_height_rel = xform.transform([0, desired_height_px])[1]
# set the new height
ax_lower_pos[-1] = desired_height_rel
fig.axes[1].set_position(ax_lower_pos)
# adjust ax_upper accordingly
ax_lower_top = fig.axes[1].get_position().extents[-1] # L,B,R,T
ax_upper_pos = list(fig.axes[0].get_position().bounds) # L,B,W,H
# new bottom
new_upper_bottom = ax_lower_top + desired_height_rel
ax_upper_pos[1] = new_upper_bottom
# new height
ax_upper_top = fig.axes[0].get_position().extents[-1] # L,B,R,T
new_upper_height = ax_upper_top - new_upper_bottom
ax_upper_pos[-1] = new_upper_height
# set the new position
fig.axes[0].set_position(ax_upper_pos)
fig.canvas.draw()
Here's the output if the user calls fig = make_plot():
Now if the user calls fig.subplots_adjust, the bottom axis is squished and the space between bottom and top axes is even more squished (the on_resize listener had set them both to 40px):
fig.subplots_adjust(top=0.7)
At this point, grabbing the corner of the window and dragging even a tiny bit is enough to trigger the on_resize listener and restore what I want (fixed pixel height for bottom axes and space between axes) while keeping the newly-added wide top margin intact:
How can I get that result without having to manually trigger a resize event? As far as I can tell, subplots_adjust does not fire off any events that I could listen for.
I think the problem lies in ax.update_params() updating the axes position with a figbox taken from the underlying subplotspec (which as far as I can tell doesn't get updated after initial figure creation?). (note: update_params is called from within subplots_adjust, see here).
The underlying problem seems to be to make an axes with a specific height in pixels. An easy solution to this is to use mpl_toolkits.axes_grid1's make_axes_locatable.
This allows to get rid of any callback and hence of the complete problem of the race condition in the events.
A note: The plot seems to be part of a bigger library. Since it is always nice not to patronize the users of such packages, one would usually allow them to specify the axes to plot to, such that they can put the plot into a bigger figure with other elements. The below solution makes this particularly easy.
Of course, also calling plt.subplots_adjust is still possible at any time.
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.axes_divider import make_axes_locatable
desired_height_px = 40 #pixel
def make_plot(ax=None):
if not ax:
fig, ax = plt.subplots()
else:
fig = ax.figure
div = make_axes_locatable(ax)
cax = div.append_axes("bottom", desired_height_px/fig.dpi, pad=0.25)
sc1 = ax.scatter([2,1,3], [2,3,1], c=[1,2,3])
sc2 = cax.scatter([3,2,1],[2,3,1], c=[3,1,2])
return fig, ax, cax, (sc1, sc2)
fig, (ax1, ax2) = plt.subplots(1,2)
make_plot(ax=ax1)
#user plot on ax2
ax2.plot([1,3])
fig.subplots_adjust(top=0.7)
plt.show()

how to overlay a shapefile in matplotlib

In matplotlib how to overlay the shapefile (available in folder) as attached below at the top right position outside the plot.
The code referenced by banderkat:
import matplotlib.pyplot as plt
import Image
import numpy as np
im = Image.open('Jbc4j.jpg')
width = im.size[0]
height = im.size[1]
# We need a float array between 0-1, rather than
# a uint8 array between 0-255
im = np.array(im).astype(np.float) / 255
a = np.random.randint(0,100,100)
b = range(100)
fig = plt.figure(1,figsize=(5, 7), dpi=80, facecolor='w')
ax = fig.add_subplot(111)
ax.scatter(a,b)
fig.canvas.draw()
# With newer (1.0) versions of matplotlib, you can
# use the "zorder" kwarg to make the image overlay
# the plot, rather than hide behind it... (e.g. zorder=10)
fig.figimage(im, fig.bbox.xmax - width, fig.bbox.ymax - height, zorder=0)
# (Saving with the same dpi as the screen default to
# avoid displacing the logo image)
fig.savefig('temp.png', dpi=80)
plt.show()
Produces the following result (imaged cropped to save space).
Changing the zorder=1 will place the image on top.
Other helpful references:
How to change background color for scatter plot in matplotlib
How do you change the size of figures drawn with matplotlib?
Python/Matplotlib - Change the relative size of a subplot
In Matplotlib, what does the argument mean in fig.add_subplot(111)?
Customizing Location of Subplot Using GridSpec
You can use basemap toolkit to load and plot shapefile. Here I've plotted shapeFile in a separate axes and aligned it to top-right of other axes plot using 'subplot2grid'.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
import matplotlib.gridspec as gridspec
def plotShapeFile():
# Lambert Conformal Conic map.
m = Basemap(llcrnrlon=-100.,llcrnrlat=0.,urcrnrlon=-20.,urcrnrlat=57.,
projection='lcc',lat_1=20.,lat_2=40.,lon_0=-60.,
resolution ='l',area_thresh=1000.)
# read shapefile.
shp_info = m.readshapefile('C:/basemap-1.0.6/basemap-1.0.6/examples/huralll020','hurrtracks',drawbounds=False)
# find names of storms that reached Cat 4.
names = []
for shapedict in m.hurrtracks_info:
cat = shapedict['CATEGORY']
name = shapedict['NAME']
if cat in ['H4','H5'] and name not in names:
# only use named storms.
if name != 'NOT NAMED': names.append(name)
# plot tracks of those storms.
for shapedict,shape in zip(m.hurrtracks_info,m.hurrtracks):
name = shapedict['NAME']
cat = shapedict['CATEGORY']
if name in names:
xx,yy = zip(*shape)
# show part of track where storm > Cat 4 as thick red.
if cat in ['H4','H5']:
m.plot(xx,yy,linewidth=1.5,color='r')
elif cat in ['H1','H2','H3']:
m.plot(xx,yy,color='k')
# draw coastlines, meridians and parallels.
m.drawcoastlines()
m.drawcountries()
m.drawmapboundary(fill_color='#99ffff')
m.fillcontinents(color='#cc9966',lake_color='#99ffff')
m.drawparallels(np.arange(10,70,20),labels=[1,1,0,0])
m.drawmeridians(np.arange(-100,0,20),labels=[0,0,0,1])
if __name__ == '__main__':
fig=plt.figure()
plt.subplots_adjust(wspace=0.001, hspace=0.001)
ax1=plt.subplot2grid((5,5), (0,0), colspan=4, rowspan=4)
labels = 'Frogs', 'Hogs', 'Dogs', 'Logs'
fracs = [15,30,45, 10]
explode=(0, 0.05, 0, 0)
p1,t1,at1 = plt.pie(fracs, explode=explode, labels=labels, autopct='%1.1f%%', shadow=True)
plt.title('Raining Hogs and Dogs', bbox={'facecolor':'0.8', 'pad':5})
ax2=plt.subplot2grid((5,5), (0,4), colspan=1, rowspan=1)
#draw shapeFile on the current active axes, i.e. ax2
plotShapeFile()
plt.tight_layout()
plt.show()
Below are links to references I've used:
http://sourceforge.net/projects/matplotlib/files/matplotlib-toolkits/basemap-1.0.6/
http://matplotlib.org/basemap/users/examples.html
Output:

hatched rectangle patches without edges in matplotlib

When trying to add a rectangle patch with a hatch pattern to a plot it seems that it is impossible to set the keyword argument edgecolor to 'none' when also specifying a hatch value.
In other words I am trying to add a hatched rectangle WITHOUT an edge but WITH a pattern filling. This doesnt seem to work. The pattern only shows up if I also allow an edge to be drawn around the rectangle patch.
Any help on how to achieve the desired behaviour?
You should use the linewidth argument, which has to be set to zero.
Example (based on your other question's answer):
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(111)
# generate some data:
x,y = np.meshgrid(np.linspace(0,1),np.linspace(0,1))
z = np.ma.masked_array(x**2-y**2,mask=y>-x+1)
# plot your masked array
ax.contourf(z)
# plot a patch
p = patches.Rectangle((20,20), 20, 20, linewidth=0, fill=None, hatch='///')
ax.add_patch(p)
plt.show()
You'll get this image:

Create simple Basemap that corresponds to a specific region on the Earth

I'm am trying to familarize myself with matplotlib and Basemap. As a start, I'm trying to generate an image of greenland that matches a specific grid for which I have data.
The gruesome details below describe my problem: I can't create an image that is the proper size matching the projection/area desired.
The projection and grid that I would like to match:
Projection as Proj4 string: "+proj=stere +lat_0=90 +lon_0=-45 +lat_ts=70 +ellps=WGS84 +datum=WGS84 +units=m"
The area defined by a grid is an 800x1400 2000m resolution grid where:
Outer edge of LowerLeft Corner(m): -700,000., -3,400,000.
Outer edge of UpperRight Corner (m): 900,000., -600,000. => (-700,000 + 2000 * 800, -3,400,000 + 2000 * 1400)
Basemap won't let me specify the corners in meters-xy for a stereographic projection so I have to convert these to lat/lon.
> gdaltransform -s_srs "+proj=stere +lat_0=90 +lon_0=-45 +lat_ts=70 +ellps=WGS84 +datum=WGS84 +units=m" -t_srs "+proj=latlong"`
-700000 -3400000
-56.6336339989404 58.7244253840871 0
900000 -600000
11.3099324740202 80.0389929796586 0
Now I should have all of the information to create an 800x1400 image.
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
def create_map():
fig = plt.figure(1, figsize=(8, 14), frameon=False, dpi=100)
fig.add_axes([0, 0, 1, 1])
m = Basemap(resolution="i",
projection='stere', lat_ts=70, lat_0=90., lon_0=-45.,
llcrnrlon=-56.6336339989404, llcrnrlat=58.7244253840871,
urcrnrlon=11.3099324740202, urcrnrlat=80.0389929796586,
rsphere=(6378137.0, 6356752.3142))
m.drawcoastlines()
m.fillcontinents(color='#c1c1c1')
m.drawmapboundary(fill_color='#6587ad', linewidth=0.0)
plt.savefig('greenland.png', pad_inches=0.0, bbox_inches='tight')
if __name__ == '__main__':
create_map()
The problem I face is that when I do this, I get an 800x1399 image. If I don't include the bbox_inches='tight' in the plt.savefig command, I get an 800x1400 image, with a single strip of invisible pixels along the (edit) bottom edge (edit).
Can anyone help me so that I can be sure that I am setting up my Basemap properly? I feel like I'm probably just missing a simple trick, but not getting an image the size I expect is strange.
As always, thanks in advance.
It appears that this might be a result of a bug in matplotlib. Jeff Whitaker took a look and said it looked right and I tried to reproduce this behavior without using Basemap and I was able to.
It seems that the aspect of the data values might cause the output image to be the wrong size.
Here's some code that shows the problem. Sorry for the false alarm.
# rectangle.py --
import matplotlib.pyplot as plt
def create_image():
fig = plt.figure(1, figsize=(8, 14), frameon=False, dpi=100)
fig.add_axes([0, 0, 1, 1])
ax = plt.gca()
# This isn't necessary to create the issue unless you want to see the
# transparent pixels at bottom.
# for spine in ax.spines.values():
# spine.set_linewidth(0.0)
limb = ax.axesPatch
limb.set_facecolor('#6587ad')
x1 = 0.0
y1 = 0.0
x2 = 16.
# Use this line and get what I was expecting:
# y2 = 27.999999999999994671 # produces 800 x 1400 image
# Use this line and get the wrong size
y2 = 27.999999999999994670 # produces (wrong?) 800 x 1399 image
corners = ((x1, y1), (x2, y2))
ax.update_datalim(corners)
ax.set_xlim((x1, x2))
ax.set_ylim((y1, y2))
ax.set_aspect('equal', anchor='C')
ax.set_xticks([])
ax.set_yticks([])
plt.savefig('rectangle.png', pad_inches=0.0, bbox_inches='tight')
# If you use this below, the file size is correct, but there is a single
# line transparent pixels along the bottom of the image if you set the
# linewidth to zero...
# plt.savefig('rectangle.png', pad_inches=0.0)
if __name__ == '__main__':
create_image()