Custom markers using Python (matplotlib) - python

I would like to know how I can generate the marker for the black colored line shown in this picture. (Source: NCEP & NOAA)
It's the marker for a storm or hurricane in standard weather maps.
I can probably generate an image file of the marker symbol. But, I am not aware of how I can tell matplotlib to use the image as a marker.

The marker looks like a 6. If this is the case, you can use a 6 as a marker as follows:
import matplotlib.pyplot as plt
x = [1,2,3,4]
y = [2,3,1,4]
plt.scatter(x,y, s= 100,marker="$6$")
plt.show()
If this is not an option, you may define your custom marker using a path. To this end, the coordinates of the path need to be known. I have invented some values below, maybe they already suit the needs here.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.path as mpath
def get_hurricane():
u = np.array([ [2.444,7.553],
[0.513,7.046],
[-1.243,5.433],
[-2.353,2.975],
[-2.578,0.092],
[-2.075,-1.795],
[-0.336,-2.870],
[2.609,-2.016] ])
u[:,0] -= 0.098
codes = [1] + [2]*(len(u)-2) + [2]
u = np.append(u, -u[::-1], axis=0)
codes += codes
return mpath.Path(3*u, codes, closed=False)
hurricane = get_hurricane()
plt.scatter([1,1,2],[1.4,2.3,2.8], s=350, marker=hurricane,
edgecolors="crimson", facecolors='none', linewidth=2)
plt.scatter([0,1,2],[1,3,1], s=150, marker=hurricane,
edgecolors="k", facecolors='none')
plt.scatter([0,1.8,3],[0,2,4], s=150, marker="o",
edgecolors="k", facecolors='none')
plt.show()

Related

Python matplotlib anotate overlaping points

I have a situation where I need to plot a list of points where they can overlap. The problem is that the Point label are overlapping, and I need to identify which point is where.
having sample code:
import matplotlib.pyplot as plt
Nu = [6.0155, 12.031, 12.031, 12.031, 12.031]
Ra = [40.22, 40.66, 40.66, 40.66, 40.66]
P = ["P_1", "P_2", "P_3", "P_4", "P_5"]
fig, ax = plt.subplots()
plt.scatter(Ra, Nu)
for i, txt in enumerate(P):
plt.annotate(txt, (Ra[i], Nu[i]))
Got this result:
Where 5 points and annotations are overlapping in top right corner.
I supose you have use those identical coordinates to over exagerate your example.
If that is the case there is a nice package that can hep you to adapt annotations in matplotlib.
package
https://pypi.org/project/adjustText/
documentation
https://adjusttext.readthedocs.io/en/latest/
I know it works good using plt.text instead of plt.annotate
import matplotlib.pyplot as plt
from adjustText import adjust_text
Nu = [6.0155, 12.031, 12.031, 12.031, 12.031]
Ra = [40.22, 40.66, 40.66, 40.66, 40.66]
P = ["P_1", "P_2", "P_3", "P_4", "P_5"]
fig, ax = plt.subplots()
plt.scatter(Ra, Nu)
texts = [plt.text(Ra[i], Nu[i], txt,size=16) for i, txt in enumerate(P)]
adjust_text(texts, arrowprops=dict(arrowstyle="->", color='r'))
The output is

Plotting lines over an image using the same projection

I want to make a plot using .fits files (astronomical images) and I am experiencing two issues which I think they are related:
Using this example from astropy:
from matplotlib import pyplot as plt
from astropy.io import fits
from astropy.wcs import WCS
from astropy.utils.data import download_file
fits_file = 'http://data.astropy.org/tutorials/FITS-images/HorseHead.fits'
image_file = download_file(fits_file, cache=True)
hdu = fits.open(image_file)[0]
wcs = WCS(hdu.header)
fig = plt.figure()
fig.add_subplot(111, projection=wcs)
plt.imshow(hdu.data, origin='lower', cmap='cubehelix')
plt.xlabel('RA')
plt.ylabel('Dec')
plt.show()
I can generate this image:
Now I would like to plot some points using the same coordinates as the image:
plt.scatter(85, -2, color='red')
However, when I do this:
I am ploting at the pixel coordinantes. Furthermore, the image no longer matches the frame size (although the coordinates seem fine)
Any advice on how to deal with these issues?
It is very easy to plot given coordinates. All you have to do is apply a transform.
I copied your example and added comments where I changed something and why.
from matplotlib import pyplot as plt
from astropy.io import fits
from astropy.wcs import WCS
from astropy.utils.data import download_file
fits_file = 'http://data.astropy.org/tutorials/FITS-images/HorseHead.fits'
image_file = download_file(fits_file, cache=True)
# Note that it's better to open the file with a context manager so no
# file handle is accidentally left open.
with fits.open(image_file) as hdus:
img = hdus[0].data
wcs = WCS(hdus[0].header)
fig = plt.figure()
# You need to "catch" the axes here so you have access to the transform-function.
ax = fig.add_subplot(111, projection=wcs)
plt.imshow(img, origin='lower', cmap='cubehelix')
plt.xlabel('RA')
plt.ylabel('Dec')
# Apply a transform-function:
plt.scatter(85, -2, color='red', transform=ax.get_transform('world'))
And the result is:
Note that if you want the Canvas to only show the region of the image just apply the limits again afterwards:
# Add a scatter point which is in the extend of the image:
plt.scatter(85.3, -2.5, color='red', transform=ax.get_transform('world'))
plt.ylim(0, img.shape[0])
plt.xlim(0, img.shape[1])
which gives:
A side note as well here. AstroPy has a great units support so instead of converting arcmins and arcsecs to degrees you can just define the "unit". You still need the transform though:
from astropy import units as u
x0 = 85 * u.degree + 20 * u.arcmin
y0 = -(2 * u.degree + 25 * u.arcmin)
plt.scatter(x0, y0, color='red', transform=ax.get_transform('world'))

Move 3D plot to avoid clipping by margins

I'm trying to figure out how I can get the 3D matplotlib images below to plot higher on the canvas so it doesn't get clipped. Here is the code I'm using to create the plot. I couldn't find a way to attach the text file containing the Z elevations (referenced in the code below), but it is simply a 2D array containing a surface made up of values ranging between 0 and 1.
import os
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm
from mpl_toolkits.mplot3d import Axes3D
nrow=30
ncol=100
f = open(r'C:\temp\fracEvapCume_200.txt','r')
fracEvapTS = np.loadtxt(f)
f.close()
X, Y = np.meshgrid(ncol, nrow)
Y3d, X3d = np.mgrid[0:Y, 0:X]
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.auto_scale_xyz([0, 100], [0, 30], [0, 0.2])
Y3d, X3d = np.mgrid[0:Y, 0:X]
Z = fracEvapTS
surf = ax.plot_surface(X3d, Y3d, Z, cmap='autumn', cstride=2, rstride=2)
ax.set_xlabel("X-Label")
ax.set_ylabel("Y-Label")
ax.set_zlabel("Z-Label")
ax.pbaspect = [1., .33, 0.25]
ax.dist = 7
plt.tight_layout()
plt.savefig('clipped.png')
In order to get the ax.pbaspect=[1., .33, 0.25] line to work, changes to the get_proj function inside site-packages\mpl_toolkits\mplot3d\axes3d.py were made as suggested in this post. In order to get the figure to draw larger, I added ax.dist = 7 based on this post. Lastly, based on this post I was hoping that plt.tight_layout() would roll back the margins and prevent the red/yellow surface shown below from being clipped, but that didn't work either. I'm failing to find the command that will move the image up on the canvas, thereby avoiding all of the unnecessary white space at the top of the figure and preventing the red/yellow surface from getting clipped. Is there one line of Python that will accomplish this?
after adding the line plt.tight_layout(), it made matters worse:
The problem is that your modification to site-packages\mpl_toolkits\mplot3d\axes3d.py changes the projection matrix, without changing the center of the view, messing up the position of the scene once transfomed in camera coordinates.
So when the view is zoomed (with ax.dist) then moved, the plot sometimes gets out of the canvas.
You need to replace the following line to the get_proj function in axes3d.py :
# look into the middle of the new coordinates
R = np.array([0.5, 0.5, 0.5])
By :
# look into the middle of the new coordinates
try:
R = np.array(self.pbaspect)/2
except AttributeError:
R = np.array([0.5, 0.5, 0.5])
And this should work :
PS : Code used to make the figures :
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm
from mpl_toolkits.mplot3d import Axes3D
nrow=30
ncol=100
X, Y = np.meshgrid(ncol, nrow)
Y3d, X3d = np.mgrid[0:Y, 0:X]
Z = np.sin(Y3d/Y)*np.sin(X3d/X)
fig = plt.figure()
for i in range(4):
ax = fig.add_subplot(2,2,i,projection='3d')
ax.auto_scale_xyz([0, 100], [0, 30], [0, 0.2])
surf = ax.plot_surface(X3d, Y3d, Z, cmap='autumn', cstride=2, rstride=2)
ax.set_xlabel("X-Label")
ax.set_ylabel("Y-Label")
ax.set_zlabel("Z-Label")
ax.pbaspect = [1., .33, 0.25]
ax.dist = 7

Python Matplotlib add Colorbar

i've got a problem using MatlobLib with "Custom" Shapes from a shapereader. Importing and viewing inserted faces works fine, but i'm not able to place a colorbar on my figure.
I already tried several ways from the tutorial, but im quite sure there is a smart solution for this problem.
maybe somebody can help me, my current code is attached below:
from formencode.national import pycountry
import itertools
from matplotlib import cm, pyplot
from matplotlib import
from mpl_toolkits.basemap import Basemap
from numpy.dual import norm
import cartopy.crs as ccrs
import cartopy.io.shapereader as shpreader
import matplotlib as mpl
import matplotlib.colors as colors
import matplotlib.mlab as mlab
import matplotlib.pyplot as plt
import numpy as np
def draw_map_for_non_normalized_data_with_alpha2_counrty_description(data, title=None):
m = Basemap()
ax = plt.axes(projection=ccrs.PlateCarree())
list = []
sum = 0
for key in data:
sum += data[key]
for key in data.keys():
new_val = (data[key]+0.00)/sum
list.append(new_val)
data[key] = new_val
#===========================================================================
# print str(min(list))
# print str(max(list))
#===========================================================================
cmap = mpl.cm.cool
colors = matplotlib.colors.Normalize(min(list)+0.0, max(list)+0.0)
labels = []
features = []
for country in shpreader.Reader(shapename).records():
a3_code = country.attributes["gu_a3"]
try :
a2_code = pycountry.countries.get(alpha3=a3_code).alpha2
except:
a2_code = ""
if a2_code in data:
val = data[a2_code]
color = cm.jet(norm(val))
print str(val) + " value for color: " + str(color)
labels.append(country.attributes['name_long'])
feat = ax.add_geometries(country.geometry, ccrs.PlateCarree(), facecolor=color, label=country.attributes['name_long'])
features.append(feat)
#ax.legend(features, labels, loc='upper right')
#===========================================================================
# fig = pyplot.figure(figsize=(8,3))
# ax1 = fig.add_axes([0.05, 0.80, 0.9, 0.15])
#===========================================================================
#cbar = m.colorbar(location='bottom')
cb1 = mpl.colorbar.ColorbarBase(ax, cmap=cmap,norm=colors,orientation='horizontal')
cb1.set_label('foo')
m.drawcoastlines()
m.drawcountries()
if title:
plt.title(title)
plt.show()
as you can see inside the code, i already tried several ways, but none of them worked for me.
maybe somebody has "the" hint for me.
thanks for help,
kind regards
As mentioned in the comments above, i would think twice about mixing Basemap and Cartopy, is there a specific reason to do so? Both are basically doing the same thing, extending Matplotlib with geographical plotting capabilities. Both are valid to use, they both have their pro's and con's.
In your example you have a Basemap axes m, a Cartopy axes ax and you are using the Pylab interface by using plt. which operates on the currently active axes. Perhaps it theoretically possible, but it seems prone to errors to me.
I cant modify your example to make it work, since the data is missing and your code is not valid Python, the indentation for the function is incorrect for example. But here is a Cartopy-only example showing how you can plot a Shapefile and use the same cmap/norm combination to add a colorbar to the axes.
One difference with your code is that you provide the axes containing the map to the ColorbarBase function, this should be a seperate axes specifically for the colorbar.
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import matplotlib as mpl
import cartopy.io.shapereader as shpreader
fig, ax = plt.subplots(figsize=(12,6),
subplot_kw={'projection': ccrs.PlateCarree()})
norm = mpl.colors.Normalize(vmin=0, vmax=1000000)
cmap = plt.cm.RdYlBu_r
for n, country in enumerate(shpreader.Reader(r'D:\ne_50m_admin_0_countries_lakes.shp').records()):
ax.add_geometries(country.geometry, ccrs.PlateCarree(),
facecolor=cmap(norm(country.attributes['gdp_md_est'])),
label=country.attributes['name'])
ax.set_title('gdp_md_est')
cax = fig.add_axes([0.95, 0.2, 0.02, 0.6])
cb = mpl.colorbar.ColorbarBase(cax, cmap=cmap, norm=norm, spacing='proportional')
cb.set_label('gdp_md_est')

Using Colormaps to set color of line in matplotlib

How does one set the color of a line in matplotlib with scalar values provided at run time using a colormap (say jet)? I tried a couple of different approaches here and I think I'm stumped. values[] is a storted array of scalars. curves are a set of 1-d arrays, and labels are an array of text strings. Each of the arrays have the same length.
fig = plt.figure()
ax = fig.add_subplot(111)
jet = colors.Colormap('jet')
cNorm = colors.Normalize(vmin=0, vmax=values[-1])
scalarMap = cmx.ScalarMappable(norm=cNorm, cmap=jet)
lines = []
for idx in range(len(curves)):
line = curves[idx]
colorVal = scalarMap.to_rgba(values[idx])
retLine, = ax.plot(line, color=colorVal)
#retLine.set_color()
lines.append(retLine)
ax.legend(lines, labels, loc='upper right')
ax.grid()
plt.show()
The error you are receiving is due to how you define jet. You are creating the base class Colormap with the name 'jet', but this is very different from getting the default definition of the 'jet' colormap. This base class should never be created directly, and only the subclasses should be instantiated.
What you've found with your example is a buggy behavior in Matplotlib. There should be a clearer error message generated when this code is run.
This is an updated version of your example:
import matplotlib.pyplot as plt
import matplotlib.colors as colors
import matplotlib.cm as cmx
import numpy as np
# define some random data that emulates your indeded code:
NCURVES = 10
np.random.seed(101)
curves = [np.random.random(20) for i in range(NCURVES)]
values = range(NCURVES)
fig = plt.figure()
ax = fig.add_subplot(111)
# replace the next line
#jet = colors.Colormap('jet')
# with
jet = cm = plt.get_cmap('jet')
cNorm = colors.Normalize(vmin=0, vmax=values[-1])
scalarMap = cmx.ScalarMappable(norm=cNorm, cmap=jet)
print scalarMap.get_clim()
lines = []
for idx in range(len(curves)):
line = curves[idx]
colorVal = scalarMap.to_rgba(values[idx])
colorText = (
'color: (%4.2f,%4.2f,%4.2f)'%(colorVal[0],colorVal[1],colorVal[2])
)
retLine, = ax.plot(line,
color=colorVal,
label=colorText)
lines.append(retLine)
#added this to get the legend to work
handles,labels = ax.get_legend_handles_labels()
ax.legend(handles, labels, loc='upper right')
ax.grid()
plt.show()
Resulting in:
Using a ScalarMappable is an improvement over the approach presented in my related answer:
creating over 20 unique legend colors using matplotlib
I thought it would be beneficial to include what I consider to be a more simple method using numpy's linspace coupled with matplotlib's cm-type object. It's possible that the above solution is for an older version. I am using the python 3.4.3, matplotlib 1.4.3, and numpy 1.9.3., and my solution is as follows.
import matplotlib.pyplot as plt
from matplotlib import cm
from numpy import linspace
start = 0.0
stop = 1.0
number_of_lines= 1000
cm_subsection = linspace(start, stop, number_of_lines)
colors = [ cm.jet(x) for x in cm_subsection ]
for i, color in enumerate(colors):
plt.axhline(i, color=color)
plt.ylabel('Line Number')
plt.show()
This results in 1000 uniquely-colored lines that span the entire cm.jet colormap as pictured below. If you run this script you'll find that you can zoom in on the individual lines.
Now say I want my 1000 line colors to just span the greenish portion between lines 400 to 600. I simply change my start and stop values to 0.4 and 0.6 and this results in using only 20% of the cm.jet color map between 0.4 and 0.6.
So in a one line summary you can create a list of rgba colors from a matplotlib.cm colormap accordingly:
colors = [ cm.jet(x) for x in linspace(start, stop, number_of_lines) ]
In this case I use the commonly invoked map named jet but you can find the complete list of colormaps available in your matplotlib version by invoking:
>>> from matplotlib import cm
>>> dir(cm)
A combination of line styles, markers, and qualitative colors from matplotlib:
import itertools
import matplotlib as mpl
import matplotlib.pyplot as plt
N = 8*4+10
l_styles = ['-','--','-.',':']
m_styles = ['','.','o','^','*']
colormap = mpl.cm.Dark2.colors # Qualitative colormap
for i,(marker,linestyle,color) in zip(range(N),itertools.product(m_styles,l_styles, colormap)):
plt.plot([0,1,2],[0,2*i,2*i], color=color, linestyle=linestyle,marker=marker,label=i)
plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.,ncol=4);
UPDATE: Supporting not only ListedColormap, but also LinearSegmentedColormap
import itertools
import matplotlib.pyplot as plt
Ncolors = 8
#colormap = plt.cm.Dark2# ListedColormap
colormap = plt.cm.viridis# LinearSegmentedColormap
Ncolors = min(colormap.N,Ncolors)
mapcolors = [colormap(int(x*colormap.N/Ncolors)) for x in range(Ncolors)]
N = Ncolors*4+10
l_styles = ['-','--','-.',':']
m_styles = ['','.','o','^','*']
fig,ax = plt.subplots(gridspec_kw=dict(right=0.6))
for i,(marker,linestyle,color) in zip(range(N),itertools.product(m_styles,l_styles, mapcolors)):
ax.plot([0,1,2],[0,2*i,2*i], color=color, linestyle=linestyle,marker=marker,label=i)
ax.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.,ncol=3,prop={'size': 8})
U may do as I have written from my deleted account (ban for new posts :( there was). Its rather simple and nice looking.
Im using 3-rd one of these 3 ones usually, also I wasny checking 1 and 2 version.
from matplotlib.pyplot import cm
import numpy as np
#variable n should be number of curves to plot (I skipped this earlier thinking that it is obvious when looking at picture - sorry my bad mistake xD): n=len(array_of_curves_to_plot)
#version 1:
color=cm.rainbow(np.linspace(0,1,n))
for i,c in zip(range(n),color):
ax1.plot(x, y,c=c)
#or version 2: - faster and better:
color=iter(cm.rainbow(np.linspace(0,1,n)))
c=next(color)
plt.plot(x,y,c=c)
#or version 3:
color=iter(cm.rainbow(np.linspace(0,1,n)))
for i in range(n):
c=next(color)
ax1.plot(x, y,c=c)
example of 3:
Ship RAO of Roll vs Ikeda damping in function of Roll amplitude A44

Categories