前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >气象绘图cmap、cbar超详细版(附示例)

气象绘图cmap、cbar超详细版(附示例)

作者头像
自学气象人
发布2022-11-02 10:23:46
12.7K0
发布2022-11-02 10:23:46
举报
文章被收录于专栏:自学气象人

以下文章来源于云台书使 ,作者雲台書使

本节提要:关于colormap与colorbar的一站式教程。


章节引言:

在matplotlib和cartopy中,其常见的绘图命令,若是带有颜色映射的collection(s)类,则基本都可以引入cmap与colorbar功能来分析数据。cmap即是颜色映射表,colorbar即是颜色分析色条,前者只起到对绘图对象上色的功能,后者实现色阶与数值的对应。

常见的绘图命令scatter、contour、contourf、pcolormesh等都可以引入cmap与colorbar,下面四幅图分别使用了前述四种绘图命令绘制,并更改了每一幅图使用的颜色映射表:

cmap的引入

作为一个专门的数据可视化库包,matplotlib专门开辟了一个cm功能来供绘图者使用,如果需要使用一个颜色映射表,你可以使用get语句获取该颜色映射表:

代码语言:javascript
复制
importmatplotlib as mpl                                                                                    
colormap=mpl.cm.get_cmap(' Reds ' )                                                              
ax.contourf(cmap=colormap)

你也可以在cm后直接加上颜色映射表名称,而不是字符串,这两种方法在此处是等效的:

代码语言:javascript
复制
colormap=mpl.cm.Reds

当然,这种get命令的在此处显得繁琐了,更简便的方法是径直将代表该颜色映射表的字符串直接传入绘图命令中的cmap中:

代码语言:javascript
复制
ax.contourf(cmap='Reds ')

使用颜色映射表时不必要记住全部的代表字符串,我们可以在使用的时候去官网查找后使用。

Matplotlib的默认cmap是viridis。

翻转cmap的颜色顺序

由于cmap的颜色映射表是有固定存储顺序的数组,所以我们可以在需要的时候翻转cmap与数值的对应顺序,翻转命令为在颜色映射表的字符串最后加上’_r’。

代码语言:javascript
复制
ax1.contourf(X,Y,Z,cmap='Reds')                                               
ax2.contourf(X,Y,Z,cmap='Reds_r')

cmap的分类

matplotlib专门提供了多样的颜色映射表,他们在官网Tutorials下的Choosing Colormaps in Matplotlib说明书有详细讲解。

按照官网的说明,colormap简要有以下六类:

1. 连续类(Sequential):色彩的亮度和饱和度递增变化,用单一色调展示有序的信息。Matplotlib的默认cmap——‘viridis’,即属于这一类。这类视觉冲击性较小,在梯度划分较细时,连续临近的色阶在一定的时候可能出现混淆的情况。

2. 分色类(Diverging):不同颜色的亮度和饱和度逐渐变化。主要用于展示关于0对称的数据。

3. 循环类(Cyclic):两种不同颜色的亮度逐渐变化,在色彩映射的中间位置以及开始(或结束)端以非饱和色结束。一般应用于在端点处循环的值。

4. 定性类(Qualitative):将各个颜色无序拼接的颜色映射表。用于展示无序的信息。

5. 混杂类(Miscellaneous):多种对比明显的各种颜色拼接在一起,一般用于展示信息中的细节变化。

以下为matplotlib官网提供的各种颜色映射表:

这里推荐几种比较实用的cmap:

GnBu、gray、cool、bwr、RdBu、tab20c

其中GnBu、RdBu、tab20c比较温和,cool、bwr视觉冲击性强。

引入外部cmap

由于matplotlib提供的颜色映射表是有限的,所以我们还需要借助外部的库包提供额外的颜色映射表。大气科学与海洋科学常用的两个外部颜色库包为Palettable与cmaps,这两个库包都可以使用conda命令安装。Palettable作为经典颜色库包在很多地方都有使用;cmaps库包是将NCL平台的颜色移植到python平台,嘉惠学林。

下面是cmaps一些颜色映射表举例:

更多的颜色映射表名称请至官网查询。

在使用cmaps时,只需引入库包,然后填写颜色名,引入后可以视为正常的matplotlib内部colormap对待与实用:

代码语言:javascript
复制
import cmaps                                                               
cmap=cmaps.MPL_RdYlGn                                                  
ax.contourf(cmap=cmap)

截取cmap

在日常使用时,一条colormap我们很可能只需要其中一部分,这时候就需要进行cmap的截取工作,前面已经提到了作为一连串的颜色列表合并的数组是cmap的本质,所以我们很容易想到通过切片的方式来截取cmap,通过切片,我们将jet_r颜色条中的暖色调全部截去不用。

代码语言:javascript
复制
import numpy asnp                                                            
importmatplotlib as mpl                                                         
importmatplotlib.pyplot as plt                                                  
frommatplotlib.colors import ListedColormap                                   
cmap=mpl.cm.jet_r#获取色条                                                    
newcolors=cmap(np.linspace(0,1,256))#分片操作                                  
newcmap=ListedColormap(newcolors[125:])#切片取舍                                  
fig=plt.figure(figsize=(1.5,0.3),dpi=500)                                  
ax1=fig.add_axes([0,0,1,0.45])                                                 
ax2=fig.add_axes([0,0.5,1,0.45])                                              
norm =mpl.colors.Normalize(vmin=0, vmax=10)                                
fc1=fig.colorbar(mpl.cm.ScalarMappable(norm=norm,cmap='jet_r'),              
                 cax=ax1,                                                      
                 orientation='horizontal',                                                         
                 extend='both')                                                                    
fc2=fig.colorbar(mpl.cm.ScalarMappable(norm=norm,cmap=newcmap),                                            
                 cax=ax2,                                                      
                 orientation='horizontal',                                         
                 extend='both')                                                 
for i in[fc1,fc2]:                                                           
    i.ax.tick_params(labelsize=3,width=0.5,length=0.5)                           
    i.outline.set_linewidth(0.5)                                              

拼接cmap

既然能够通过切片的方式截取cmap,那么自然反向的我们可以通过组合的方式拼接cmap。

代码语言:javascript
复制
import numpy asnp                                                        
importmatplotlib as mpl                                                       
importmatplotlib.pyplot as plt                                                        
frommatplotlib.colors import ListedColormap                                 
import cmaps                                                                    
plt.rcParams['font.sans-serif']=['FangSong']                                 
cmap1=cmaps.spread_15lev_r                                                   
cmap2=cmaps.sunshine_diff_12lev                                                
list_cmap1=cmap1(np.linspace(0,1,15))                                      
list_cmap2=cmap2(np.linspace(0,1,12))                                           
new_color_list=np.vstack((list_cmap1,list_cmap2))                            
new_cmap=ListedColormap(new_color_list,name='new_cmap ')                                                                      
fig=plt.figure(figsize=(1.5,0.5),dpi=500)                                        
ax1=fig.add_axes([0,0,1,0.33])                                                 
ax2=fig.add_axes([0,0.33,1,0.33])                                            
ax3=fig.add_axes([0,0.66,1,0.33])                                              
norm =mpl.colors.Normalize(vmin=0, vmax=10)                              
fc1=fig.colorbar(mpl.cm.ScalarMappable(norm=norm,                            
                 cmap=cmap1),cax=ax1,                                     
                 orientation='horizontal',extend='both')                       
fc2=fig.colorbar(mpl.cm.ScalarMappable(norm=norm,                      
                 cmap=cmap2),cax=ax2,                                      
                 orientation='horizontal',extend='both')                    
fc3=fig.colorbar(mpl.cm.ScalarMappable(norm=norm,                         
                 cmap=new_cmap),cax=ax3,                                
                 orientation='horizontal',extend='both')                    

colorbar基础

colorbar是在有颜色映射的绘图命令中使用的一种表征颜色与数值的对应关系的特殊图形。与Legend图例命令不同,matplotlib允许使用者在不使用其他功能的情况下,无限次的添加colorbar。

colorbar的引入既可以是有源的,也可以是无源的。此处的有源无源,针对的是colorbar与子图绘图命令的关联性。

colorbar的添加可以使用如下命令:

代码语言:javascript
复制
importmatplotlib.pyplot as plt                                               
fig=plt.figure()                                                               
fig.colorbar()

代码语言:javascript
复制
plt.colorbar()

有源colorbar引入

有源colorbar指直接与当前要添加色条的子图的绘图命令相关联的添加方式,具体来说,就是缩写代称该绘图命令,然后作为映射源传入生成colorbar命令中,如:

代码语言:javascript
复制
CS=ax1.contourf(X,Y,Z,cmap=cmaps.sunshine_diff_12lev)                    
cb=fig.colorbar(CS,shrink=1,ax=ax1)                                     
CS2=ax2.contourf(X,Y,Z,cmap=cmaps.MPL_RdYlGn)                        
cb2=fig.colorbar(CS2,shrink=1,ax=ax2)                        

这里的两张图形都是有源的colorbar,其中在子图1中,我们称CS、CS2为我们生成的colorbar的源头。通过传入CS、CS2代表的contourfcollections,我们比较简便的生成了colorbar。

无源colorbar引入

无源colorbar主要是指不使用子图中的绘图命令的关联性,由使用者通过定义norm、cmap等参数,生成一个与子图没有直接映射关系的colorbar,如:

代码语言:javascript
复制
CS=ax1.contourf(X,Y,Z,cmap=cmaps.MPL_RdYlGn)                        
cb=fig.colorbar(CS,ax=ax1)                                      
ax2.contourf(X,Y,Z,cmap=cmaps.MPL_RdYlGn)                                   
cmap=cmaps.MPL_RdYlGn                                                        
bounds=np.arange(-2.0,2.5,0.5)                                               
norm =mpl.colors.BoundaryNorm(bounds,cmap.N)                            
cb2=fig.colorbar(mpl.cm.ScalarMappable(norm=norm,cmap=cmap),         
                 ax=ax2)

通过使用者声明的norm与cmap,我们获得了一个与有源colorbar一模一样的colorbar。这里固然不如直接传入有源的办法简捷,但是在后期某些高级定制时,是比较有用的。

Colorbar重要参数列举

colorbar作为一个绘制图形命令,自身必定携带多样的修饰参数。下面简答列举各参数:

1. mappable(可映射源)

该参数即需要绘制的映射图像源头,一般默认当前子图的绘图命令,如上节提到的CS,或者mpl.cm.ScalarMappable自动生成的映射源。对于fig.colorbar命令来说,该参数是必要的,但是plt.colorbar命令在使用时如果没有填写显性的源头,程序会自动确认当前子图可映射项。

2. ax(colorbar摆放的子图位置)

该参数控制绘制的colorbar摆放在某个子图旁边,默认为当前子图。可以传入单独的一个子图,也可以传入一个子图的列表。

3. cax(colorbar摆放的子图位置)

该参数设定后,拥有最高优先级,将覆盖shrink 、ax、aspect等参数,colorbar将放置在指定位置,如:

代码语言:javascript
复制
cax=fig.add_axes([0.33,0,0.4,0.05])                                         
CS=ax1.contourf(X,Y,Z,cmap=cmaps.MPL_RdYlGn)                        
cb=fig.colorbar(CS,shrink=1,cax=cax,orientation='horizontal')                
ax2.contourf(X,Y,Z,cmap=cmaps.MPL_RdYlGn)                             

4. orientation(axes特性,colorbar摆放的横竖位置)

该参数控制colorbar的横竖位置。默认为竖向。其中竖向命令为vertical,横向命令为horizontal,如3中图片所示。

5. shrink(axes特性,colorbar的收缩比例)

该参数控制colorbar的收缩比例,在收缩时,colorbar长宽都会变化,如:

代码语言:javascript
复制
CS=ax1.contourf(X,Y,Z,cmap=cmaps.MPL_RdYlGn)                                  
cb=fig.colorbar(CS,shrink=0.5,ax=ax1)                                     
CS2=ax2.contourf(X,Y,Z,cmap=cmaps.MPL_RdYlGn)                          
cb2=fig.colorbar(CS,shrink=1.25,ax=ax2)                                 

6. aspect(axes特性,colorbar的长宽比例)

该参数控制colorbar长宽的比例,如:

7. pad(axes特性,colorbar与子图距离)

该参数控制colorbar与子图的间距,如:

8. extend(colorbar特性,colorbar两端扩充)

该参数可以控制colorbar两端是否允许扩充,扩充后生成尖角,可选择的变量有'neither','both', 'min', 'max'。需要注意,该参数不仅是生成尖角,填色亦会出现变化,如:

9. extendfrac(colorbar特性,colorbar两端扩充的相对长度)

该参数控制衍生的小三角与colorbar主体的相对长度,如:

10. extendrect(colorbar特性,colorbar两端扩充的相对形状)

该参数控制两端衍生的小三角的形状,拥有True、False两个可选变量,False时两端为小三角,True时两端为矩形,如:

11. spacing(colorbar特性,colorbar填色间隔)

该参数控制colorbar的填色间隔是否与数值间隔相关,拥有uniform, proportional两个可选变量。其中uniform表示无论数值间隔是否相等,所有填色长度均匀相等,这是默认变量。proportional表示若数值间隔有大有小,则填色长度亦随之变化,数值间隔越大,填色长度也越长,反之相反,如:

12. ticks(colorbar特性,colorbar刻度间隔)

该参数控制颜色条的刻度显示。

13. format(colorbar特性,colorbar刻度单位制)

该参数控制colorbar显示的刻度值的单位,如:

14. drawedges(colorbar特性,colorbar是否在边界上划线)

该参数控制是否在每个颜色交界处画线,布尔值控制,如:

15. label(colorbar特性,colorbar的label)

该参数允许传入一个字符串,将作为colorbar的标签。

Colorbar的axes特性

在生成脚本中我们可知,colorbar是必然要生成一个axes来作为寄生轴的,所以colobar可以调用一些子图的特性,调用方式如下:

代码语言:javascript
复制
CS=ax1.contourf(X,Y,Z,cmap=cmaps.MPL_RdYlGn)                            
cb=fig.colorbar(CS,shrink=1,ax=ax1)                                      
cb.ax                                                                       

在调用了子图属性以后,我们可以使用子图的属性来进一步修饰colorbar,如:

代码语言:javascript
复制
def make_colorbar(cb):                                                      
    cb.ax.tick_params(labelsize=4,length=0.5,width=0.5)                      
    cb.outline.set_color('none')                                                 
cb=fig.colorbar(CS,shrink=1,ax=ax1)                                       
cb2=fig.colorbar(mpl.cm.ScalarMappable(norm=norm,cmap=cmap),         
                 ax=ax2)                          
for cb in[cb,cb2]:                                                           
    make_colorbar(cb)                                                     

我们还可以使用在前面提到的刻度生成器的方式修饰colorbar,如:

代码语言:javascript
复制
CS=ax1.contourf(X,Y,Z,cmap=cmaps.MPL_RdYlGn)                             
cb=fig.colorbar(CS,shrink=1,ax=ax1)                                        
cb.ax.yaxis.set_minor_locator(mticker.MultipleLocator(0.25))                  
ax2.contourf(X,Y,Z,cmap=cmaps.MPL_RdYlGn)                            
cmap=cmaps.MPL_RdYlGn                                                    
bounds=np.arange(-2.5,2.5,0.5)                                             
norm =mpl.colors.BoundaryNorm(bounds,cmap.N)                         
cb2=fig.colorbar(mpl.cm.ScalarMappable(norm=norm,cmap=cmap),         
                 ax=ax2)                          

不同特色的colorbar绘制技巧

在某些时候,程序自动生成的colorbar在修饰后,仍然不能满足我们的需求,这时,我们就会利用一些手段,制造各种不同特色的colorbar,下面我们介绍几种常见异形colorbar的绘制方法。

1. 环状colorbar

环状colorbar表现在其色条不为直线型,在目前matplotlib的框架下,不能利用现有的colorbar参数修改为类似的形状。这时,我们只能利用其它的方式仿制出colorbar

这里我们提供两种方式:第一,使用极坐标系下的柱形图方法;第二,使用楔形形状命令绘制圆环。

1.1极坐标系下的柱形图绘制方法

由于极坐标系可以绘制柱状图,而柱状图又可以通过facecolor参数实现上色,所以可以用到环状colorbar的绘制中来,如:

代码语言:javascript
复制
import numpy asnp                                                               
import xarray asxr                                                           
importmatplotlib.pyplot as plt                                                  
importcartopy.crs as ccrs                                                          
importcartopy.feature as cfeature                                             
importcartopy.io.shapereader as shpreader                                        
importmatplotlib.path as mpath                                                    
importmatplotlib as mpl                                                         
fromcartopy.util import add_cyclic_point                                           
from matplotlibimport cm,colors                                                 
fromcartopy.mpl.gridliner import LONGITUDE_FORMATTER,              
LATITUDE_FORMATTER                                                            
plt.rcParams['font.sans-serif']=['FangSong']#用来正常显示中文             
plt.rcParams['axes.unicode_minus']=False#用来正常显示负号                
##数据准备工作##                                                                
file=r'E:\aaaa\datanc\2019.6.23\fnl_20190620_00_00.grib2'                
data=xr.open_dataset(file,engine='cfgrib',                                   
backend_kwargs={'filter_by_keys':{'typeOfLevel': 'surface'}})                                                     
data['t']-273.15                                                             
data['latitude']                                                                 
data['longitude']                                                             
cycle_t,cycle_lon = add_cyclic_point(data['t']-273.15,                         
coord=data['longitude'])                                      
cycle_LON,cycle_LAT = np.meshgrid(cycle_lon,data['latitude'])                 
##绘图工作##                                                                
fig=plt.figure(figsize=(2,2),dpi=700)                                             
ax1=fig.add_axes([0,0,1,1],polar=True)                                    
ax1.set_axis_off()                                                            
ax2=fig.add_axes([0.127,0.123,0.75,0.75],                                  
projection=ccrs.SouthPolarStereo())                    
ax2.add_feature(cfeature.LAND)                                              
ax2.add_feature(cfeature.OCEAN)                                                
ax2.add_feature(cfeature.COASTLINE,lw=0.75)                               
##裁剪子图2的边界为圆形##                                                  
theta =np.linspace(0, 2*np.pi, 100)                                       
center, radius =[0.5, 0.5], 0.5                                               
verts =np.vstack([np.sin(theta), np.cos(theta)]).T                             
circle =mpath.Path(verts * radius + center)                                    
ax2.set_boundary(circle,transform=ax2.transAxes)                          
##绘制等值线填色图##                                                         
cs=ax2.contourf(cycle_LON,cycle_LAT, cycle_t,                              
levels=np.arange(60,60,10),                               
cmap='RdBu_r',transform=ccrs.PlateCarree())              
##取得等值线填色图的相关信息##                                              
cs.levels                                                                    
cmap=cm.get_cmap('RdBu_r',len(cs.levels)-1)                                 
angle=np.arange(0,0.5*np.pi,0.5*np.pi/(len(cs.levels)-1))                  
radius=np.array([2,2,2,2,2,2,2,2,2,2,2])                                     
cmaps=cmap(range(len(cs.levels)-1))                                    
ax1.set_theta_offset(np.pi/2)                                               
ax1.bar(angle,radius,width=0.5*np.pi/11,color=cmaps,align='center')           
for i,x,y inzip(cs.levels,angle,radius):                                         
    ax1.text(x-0.1,y+0.17,i,fontsize=3)                                      
ax1.text(0.9,2.6,'气温:℃',fontsize=4)                                       

1.2楔形图形绘制环状colorbar

利用matplotlib.patches中的楔形图形命令Wedge,在循环迭代的方式下,添加一个环状的colorbar。

代码语言:javascript
复制
import numpy asnp                                                                  
import xarray asxr                                                        
importmatplotlib.pyplot as plt                                                 
importcartopy.crs as ccrs                                                     
importcartopy.feature as cfeature                                            
importcartopy.io.shapereader as shpreader                                   
importmatplotlib.path as mpath                                                
importmatplotlib as mpl                                                     
fromcartopy.util import add_cyclic_point                                      
from matplotlibimport cm,colors                                                
fromcartopy.mpl.gridliner import LONGITUDE_FORMATTER,                 
LATITUDE_FORMATTER                  
frommatplotlib.patches import Wedge                                           
plt.rcParams['font.sans-serif']=['FangSong']                                
plt.rcParams['axes.unicode_minus']=False                                    
file=r'E:\aaaa\datanc\2019.6.23特大暴雨\fnl_20190620_00_00.grib2'             
data=xr.open_dataset(file,engine='cfgrib',                                  
        backend_kwargs={'filter_by_keys':{'typeOfLevel': 'surface'}})                
data['t']-273.15                                                             
data['latitude']                                                                   
data['longitude']                                                               
cycle_t,cycle_lon = add_cyclic_point(data['t']-273.15,                             coord=data['longitude'])                               
cycle_LON,cycle_LAT = np.meshgrid(cycle_lon,data['latitude'])           
fig=plt.figure(figsize=(2,2),dpi=700)                                            
ax=fig.add_axes([0,0,1,1],projection=ccrs.PlateCarree())                    
ax.add_feature(cfeature.LAND)                                            
ax.add_feature(cfeature.OCEAN)                                               
ax.add_feature(cfeature.COASTLINE,lw=0.3)                                   
ax.spines['geo'].set_linewidth(0.5)                                        
cs=ax.contourf(cycle_LON,cycle_LAT, cycle_t,                               
               levels=np.arange(-60,60,10),cmap='RdBu_r',            
               transform=ccrs.PlateCarree(),extend='both')          
cmap=cm.get_cmap('RdBu_r',len(cs.levels)-1)                                
cmaps=cmap(range(len(cs.levels)-1))                                       
ax2=fig.add_axes([0.05,0.3,0.2,0.2])                                            
delta=360/(len(cs.levels)-1)                                                  
for c,p inzip(cmaps,range(len(cs.levels)-1)):                                    
    wedge=Wedge((0.5,0.5),0.4,delta*p,delta*(p+1),facecolor=c,         
    edgecolor='k',width=0.1,linewidth=0.1)                             
    ax2.add_patch(wedge)                                               
ax2.axis('off')                                                                   

2. 两端三角形与色条主体分离的colorbar

代码语言:javascript
复制
import numpy asnp                                                                  
import xarray asxr                                                        
importmatplotlib.pyplot as plt                                                  
importmatplotlib.path as mpath                                               
importmatplotlib as mpl                                                       
from matplotlibimport cm,colors                                              
from matplotlib.patchesimport Rectangle                                      
importcartopy.crs as ccrs                                                   
fromcartopy.util import add_cyclic_point                                       
fromcartopy.mpl.gridliner import LONGITUDE_FORMATTER,               
LATITUDE_FORMATTER                  
importcartopy.feature as cf                                                   
importcartopy.io.shapereader as shpreader                                         
plt.rcParams['font.sans-serif']=['SimHei']                                     
file=r'E:\aaaa\datanc\2019.6.23\fnl_20190620_00_00.grib2'                    
data=xr.open_dataset(file,engine='cfgrib',                                 
backend_kwargs={'filter_by_keys':{'typeOfLevel': 'surface'}})
t=data['t']-278.15                                                            
lat=data['latitude']                                                               
lon=data['longitude']                                                             
proj=ccrs.LambertConformal(central_longitude=105,central_latitude=35)    
reader= shpreader.Reader( r'F:\B\china.shp')                                           
provinces=cf.ShapelyFeature(reader.geometries(),                              
crs=ccrs.PlateCarree(),                       
edgecolor='k',                                 
facecolor='none',                                
alpha=1,lw=0.5)                            
extent=[80,130,10,60]                                                      
fig=plt.figure(figsize=(1.5,1.5),dpi=800)                                      
ax=fig.add_axes([0,0.1,1,0.85],projection=proj)                               
ax.add_feature(cf.COASTLINE.with_scale('50m'),lw=0.25)                    
ax.add_feature(cf.OCEAN.with_scale('50m'),lw=0.5)                           
ax.add_feature(cf.LAND.with_scale('50m'),lw=0.5,edgecolor='none')         
ax.add_feature(cf.RIVERS.with_scale('50m'),lw=0.25)                        
ax.add_feature(provinces,linewidth=0.3)                                       
ax.add_geometries(shpreader.Reader(r'F:\B\nine.shp').geometries(),          
                  crs=ccrs.PlateCarree(),edgecolor='k',                     
facecolor='k',alpha=1,lw=0.5)                          
ax.spines['geo'].set_linewidth(0.25)                                           
ax.set_extent(extent)                                                       
ax2=fig.add_axes([0.054,0,0.892,0.1])                                          
ax2.tick_params(axis='both',which='major',                                    
direction='in',width=0.5,                                      
length=0.5,labelsize=3)                                 
for i in ['top','bottom','left','right']:                                              
    ax2.spines[i].set_linewidth(0.5)                                           
ac=ax.contourf(lon,lat,t,cmap='Spectral_r',                                  
levels=np.arange(-20,40,2),                             
transform=ccrs.PlateCarree())                            
ax.spines['geo'].set_linewidth(0.5)                                             
ax2.set(xlim=(-3,30),ylim=(0,1))                                            
num=ac.levels                                                                 
colormap=cm.get_cmap('Spectral_r',len(num)-1)                                  
cmaps=colormap(range(len(num)-1))                                          
camps=cmaps.tolist()                                                        
for i,x,y,colorin zip(range(len(num)-2),                                    
                      range(len(num)-2),                                          
                      [0.25]*(len(num)-2),                                      
                      cmaps[1:-1]):                                                
    rectangle=Rectangle([x,y],1,0.5,                                        
                       facecolor=color,                                          
                       alpha=1,                                                      
                       edgecolor='k',                                             
                       linewidth=0.02)                                               
    ax2.add_patch(rectangle)                                                       
left=plt.Polygon(xy=[[-0.75,0.31],[-0.75, 0.69], [-2.5,0.5]],            
facecolor=cmaps[0],edgecolor='k',linewidth=0.02)           
right=plt.Polygon(xy=[[27.75,0.31],[27.75,0.69],[29.5,0.5]],              
facecolor=cmaps[-1],edgecolor='k',linewidth=0.02)              
ax2.add_patch(left)                                                              
ax2.add_patch(right)                                                            
for s in['top','bottom','left','right']:                                                
    ax2.spines[s].set_linewidth(0.5)                                            
ax2.axis('off')

3. 双刻度列colorbar

使colorbar拥有两条ticks,用于表现不一样的量度。

代码语言:javascript
复制
import numpy asnp                                                            
importmatplotlib as mpl                                                         
importmatplotlib.pyplot as plt                                                  
importmatplotlib.colors as mcolors                                               
plt.rcParams['font.sans-serif']=['SimHei']                                  
##第一步,制作雨量色条                                                       
fig=plt.figure(figsize=(1.5,0.2),dpi=500)                                        
ax=fig.add_axes([0,0,1,0.5])                                                 
colorlevel=[0.1,10.0,25.0,50.0,100.0,250.0,500.0]#雨量等级               
colordict=['#A6F28F','#3DBA3D','#61BBFF','#0000FF','#FA00FA','#800040']#颜色列表                                                                     
cmap=mcolors.ListedColormap(colordict)#产生颜色映射                    
norm=mcolors.BoundaryNorm(colorlevel,cmap.N)#生成索引                       
fc=fig.colorbar(mpl.cm.ScalarMappable(norm=norm,cmap=cmap),                
             cax=ax,orientation='horizontal',extend='both')                   
fc.ax.tick_params(which='major',labelsize=3,                                   
direction='out',width=0.5,length=1)                           
fc.outline.set_linewidth(0.3)                                                 
##第二步,生成双刻度列##                                                      
ax2=fc.ax#召唤出fc的ax属性并省称为ax2,这时ax2即视为一个子图            
ax2.xaxis.set_ticks_position('top')#将数值刻度移动到上边                        
ax2.tick_params(labelsize=3,top=True,width=0.5,length=1)#修改刻度样          
式,并使上有刻度                                                                   
ax3=ax2.secondary_xaxis('bottom')                                                                           
ax3.tick_params(labelsize=3,width=0.5,length=1)                              
ax3.spines['bottom'].set_bounds(0.1,500)#截去多余的部分                         
ax3.set_xticks([40,120,210,290,380,460])                                   
ax3.set_xticklabels(['小雨','中雨','大雨','暴雨','大暴雨','特大暴雨'])                    
ax3.spines['bottom'].set_linewidth(0.3)#修改底部到框线粗细

4. 刻度列与colorbar主体分离

在使用中,实现刻度列于colorbar主体分离的视觉效果。

代码语言:javascript
复制
import numpy asnp                                                               
importmatplotlib as mpl                                                       
importmatplotlib.pyplot as plt                                             
importmatplotlib.colors as mcolors                                           
plt.rcParams['font.sans-serif']=['SimHei']                                  
##第一步步,制作色条##                                                       
fig=plt.figure(figsize=(1.5,0.2),dpi=500)                                     
ax=fig.add_axes([0,0,1,0.5])                                                  
cmap =mpl.cm.cool                                                             
norm = mpl.colors.Normalize(vmin=5,vmax=10)                              
fc=fig.colorbar(mpl.cm.ScalarMappable(norm=norm,cmap=cmap),           
             cax=ax, orientation='horizontal')                               
fc.ax.tick_params(which='major',labelsize=3,                                 
direction='out',width=0.5,length=1)                         
fc.outline.set_linewidth(0.3)                                                 
##第二步,生成双刻度列##                                                     
ax2=fc.ax#召唤出fc的ax属性并省称为ax2,这时ax2即视为一个子图              
ax2.xaxis.set_ticks_position('top')#将数值刻度移动到左侧                           
ax2.tick_params(labelsize=3,top=True,width=0.5,length=1)                  
ax3=ax2.secondary_xaxis('bottom')#新建ax3,使ax3与ax2完全相同            
ax3.tick_params(labelsize=3,width=0.5,length=3)                              
ax3.spines['bottom'].set_bounds(5,10)#截去多余的部分                         
ax3.set_xticks([5,10])                                                         
ax3.set_xticklabels(['0.1mm','500mm'])#将ax3上的定量数值转化为定性文字     
ax3.spines['bottom'].set_linewidth(0.3)#修改底部到框线粗细                    
labels=fc.ax.get_xticklabels()                                                    
ax2.axis('off')                                                                   
fc.outline.set_color('none')                                                      
ax3.spines['bottom'].set_position(('outward',10))

5.使用Legend命令生成仿colorbar

在绘制图片时,我们还可以使用Legend命令来生成仿colorbar,在一些气象预报降雨量图和文献数值范围参考比较常见。在levels较少的情况下,且已经知道全部颜色色号,可以手动添加图例,如:

代码语言:javascript
复制
larger1=mpatches.Rectangle((0,0), 1, 1, facecolor="#A6F28F")             
larger2=mpatches.Rectangle((0,0), 1, 1, facecolor="#3DBA3D")               
larger3=mpatches.Rectangle((0,0), 1, 1, facecolor="#61BBFF")              
larger4=mpatches.Rectangle((0,0), 1, 1, facecolor="#0000FF")               
larger5=mpatches.Rectangle((0,0), 1, 1, facecolor="#FA00FA")               
labels=['0-10','10-25','25-50','50-100','100-250']                              
ax.legend([larger1,larger2,larger3,larger4,larger5],labels,                     
               fontsize=12,frameon=False,                           
               title='图例(mm)',facecolor='none',                   
               loc='lower left', bbox_to_anchor=(-0.01,-0.02),                  
               fancybox=False)

Legend的详细参数在基础绘图命令章节,请互相参阅。

在不清楚每节具体色号时,我们可以使用循环的方法添加图例,如:

代码语言:javascript
复制
cs=ax.contourf(cycle_LON,cycle_LAT, cycle_t,                                   
levels=np.arange(-60,60,10),cmap='RdBu_r',extend='both')                  
cmap=cm.get_cmap('RdBu_r',len(cs.levels)-1)                                 
color_list=(cmap(range(len(cs.levels)-1)))                                 
Rectangles=[ ]                                                                    
text=[ ]                                                                       
for l incs.levels[::-1]:                                                       
    text.append(f'{int(l-10)}~{int(l)}')                                          
for color incolor_list[::-1]:                                                   
    Rectangles.append(mpatches.Rectangle((0,0), 1, 1, facecolor=color)) 
ax.legend(handles=Rectangles,labels=text,loc='lowerright', fontsize=3,      
                             title='温度',                                      
                              frameon=False,                                 
                              fancybox=False,                                
                              handletextpad=2,                                  
                              handlelength=3,                                 
                              handleheight=1.5,                                
                              title_fontsize=5,                                 
                              bbox_to_anchor=(1.25,0),                        
                              labelspacing=0.5)                           

这里使用的是直接操作colormap方式生成新的legend,你也可以使用get_facecolor来获取当前contourf的颜色:

6. 一个数值间隔对应多个颜色问题的处理

这个问题是在不正确的使用颜色映射表时发生的,发生问题有两个原因:一、不恰当的使用了颜色映射规则,如在定制化cmap与colorbar章节的norm中使用了Normalize规则,则必然使一个数值间隔对应多个颜色;二、由于colormap中存储的颜色数组长度少于levels数组长度。其中后一个原因在使用cmaps引入NCL中的cmap时比较容易发生。例如:

代码语言:javascript
复制
pc1=ax1.contourf(olon,olat,rain_new,                  
                  levels=np.arange(0,500,           
                  cmap=cmaps.sunshine_9lev)                                     
pc2=ax2.contourf(olon,olat,rain_new,             
                  levels=np.array([0,50,100,250,300,500]),
                  cmap=cmaps.sunshine_9lev)

这个时候,只能将levels的长度缩短,使其短于cmap存储的颜色数组长度。NCL的颜色条存储的颜色是不一致的,例如上面引入的sunshine_9lev,该cmap只存储了10种颜色,而你设定的levels竟然达到100,则必然出现错位的情况。

Matplotlib的colormap基本不存在这种情况,因为其每个cmap都存有256个颜色。

定制化cmap与colorbar

在实际数据可视化使用中,cmap其实只是一连串的存储的代表颜色的数组,与数值没有确实的联系。例如在contourf绘制填色等值线图时,只要修改levels参数,就会使某个颜色代表的数值出现变化,为了体现这种变化,matplotlib的colors模块提供了规范化类语句,以实现我们的客观需求。读者可以去官网参考全部的规范化类语句。这里我们仅仅深入研讨几种常用类语句。

1.LinearSegmentedColormap

混色分割类函数,该命令可以针对使用者输入的颜色列表和切割N值,调配出新的colormap。该命令类似美术中的调色板,同过原始颜色列表中的临近颜色混合出新的颜色。而随着颜色列表与N值的改变,生成的新cmap也会改变,如下面,给出两个颜色'tab:red','tab:blue'和不同的N值来生成新cmap:

代码语言:javascript
复制
import matplotlib.colorsas mcolors                                            
color_list=['tab:red','tab:blue']                                             
new_cmap=mcolors.LinearSegmentedColormap.from_list('new_cmap',             
                                       color_list,N=2)                    
norm=mpl.colors.Normalize(vmin=5,vmax=10)                            
fc=fig.colorbar(mpl.cm.ScalarMappable(norm=norm,cmap=new_cmap),         
                cax=ax, orientation='horizontal')

这里我们只要求新生成的cmap只有两个颜色即可,所以我们输入的两个颜色不经过调色,直接被输出出来。当我们修改N为5,与数值间隔数量相同时:

代码语言:javascript
复制
new_cmap=mcolors.LinearSegmentedColormap.from_list('new_cmap',        
                                               color_list,N=5)

这时,colorbar中间部分明显体现出两种颜色混交出新的颜色。通过更改原始颜色列表和N值,我们可以调配出更多的colormap,如:

代码语言:javascript
复制
color_list=['tab:red','tab:blue','tab:green','#000000']                             
new_cmap=mcolors.LinearSegmentedColormap.from_list('new_cmap',         
                color_list,50)                       
norm=mpl.colors.Normalize(vmin=5,vmax=10)                              
fc=fig.colorbar(mpl.cm.ScalarMappable(norm=norm,cmap=new_cmap),      
                 cax=ax, orientation='horizontal')                                 

这个命令默认只使临近的两个颜色之间调配出新的颜色。

2.ListedColormap

拼接颜色列表类函数,该命令只能单纯拼接颜色列表里的全部颜色,颜色分割线明显,不会出现混调色。最常使用的是气象降水量色条的定制,以及其他需要使用者自定义颜色条的情况。

代码语言:javascript
复制
colorlevel=[0.1,10.0,25.0,50.0,100.0,250.0,500.0]                                  
colorlist=['#A6F28F','#3DBA3D','#61BBFF',                                  
           '#0000FF','#FA00FA','#800040']                                    
cmap=mcolors.ListedColormap(colorlist)                                    
norm=mcolors.BoundaryNorm(colorlevel,cmap.N)                             
fc=fig.colorbar(mpl.cm.ScalarMappable(norm=norm,cmap=cmap),             
             cax=ax, orientation='horizontal')                                

3.norm

正如之前提到的,我们需要针对颜色与数值来形成映射,这种映射规则在colors模块中叫做norm。常规绘制contourf时,我们输入的levels就叫做一种norm,此时的映射规则拥有最高的优先性,使用levels参数将会掩盖其他norm命令。这是最常见的情况,在使用默认cmap时常用这种方式。当然在我们之后的使用中,因为绘图的特殊需求,我们还需要其他的映射规则。

Matplotlib的开发者并没有将设定等级的levels命令进行普遍化,造成该便捷命令只能在contour、contourf里使用。而像其他的常见颜色映射命令scatter、pcolormesh是无法使用的,只有设定上下线的vmin、vmax可以限制范围,这时我们可以使用norm来指定数值与颜色的映射规则,例如:

代码语言:javascript
复制
import numpy asnp                                                               
import pandas aspd                                                        
importmatplotlib.pyplot as plt                                                  
importmatplotlib.colors as mcolors                                            
importmatplotlib.ticker as mticker                                             
importcartopy.crs as ccrs                                                        
importcartopy.io.shapereader as shpreader                                   
importcartopy.mpl.ticker as cmt                                             
import cmaps                                                               
importmatplotlib as mpl                                                        
fromscipy.interpolate import Rbf                                             
plt.rcParams['font.sans-serif']=['FangSong']                                     
plt.rcParams['axes.unicode_minus']=False                                     
pro=ccrs.PlateCarree()                                                         
extent=[108.3,109.4,29.7,30.7]                                               
def create_map(ax,extent):                                                    
    ax.add_geometries(shpreader.Reader(r'E:\利川.shp').geometries(),        
                      crs=ccrs.PlateCarree(),linewidth=0.5,                 
                      edgecolor='k',facecolor='none')                            
    ax.set_extent([108.3,109.35,29.7,30.7],crs=pro)                              
    ax.set_xticks(np.arange(extent[0],extent[1], 0.2))                             
    ax.set_yticks(np.arange(extent[2],extent[3]+0.2, 0.2))                       ax.tick_params(which='both',labelsize=4,top=True,                              
    ax.tick_params(which='both',labelsize=4,top=True,                              
                       right=True,width=0.5)   
    ax.xaxis.set_major_formatter(cmt.LongitudeFormatter())                   
    ax.yaxis.set_major_formatter(cmt.LatitudeFormatter())                   
    ax.grid(axis='both',which='major',linewidth=0.6,color='w',                      
                       alpha=0.45,linestyle='--',zorder=5)                           
    ax.spines['geo'].set_linewidth(0.5)                                            
    ax.minorticks_on()                                                            
fig=plt.figure(figsize=(5,4.2),dpi=700)                                        
ax1=plt.subplot(221,projection=pro,facecolor='#E8E6E7')                    
ax2=plt.subplot(222,projection=pro,facecolor='#E8E6E7')                       
ax3=plt.subplot(223,projection=pro,facecolor='#E8E6E7')                        
ax4=plt.subplot(224,projection=pro,facecolor='#E8E6E7')                          
for ax in [ax1,ax2,ax3,ax4]:                                                
    create_map(ax,extent)                                                    
filename=r'C:\Users\lenovo\Desktop\3.xlsx'                                      
df=pd.read_excel(filename)                                                    
lon=df['经度']                                                                   
lat=df['纬度']                                                                    
rain=df['rain']                                                                
as1=ax1.scatter(lon,lat,c=rain,s=24,cmap='plasma_r')                            
cb1=fig.colorbar(as1,ax=[ax1],shrink=0.9)                                    
ax1.set_title('默认norm的scatter',fontsize=7)                                       
cmap =mpl.cm.plasma_r                                                     
colorlevel=[0.1,10,25,50,100,200,250,500]                                       
norm=mcolors.BoundaryNorm(colorlevel,cmap.N)                               
as2=ax2.scatter(lon,lat,c=rain,s=24,cmap='plasma_r',norm=norm)                       
cb2=fig.colorbar(as2,ax=[ax2],shrink=0.9)                                
ax2.set_title('指定norm的scatter',fontsize=7)                                        
olon=np.linspace(108,111,110)                                              
olat=np.linspace(29,32,110)                                                   
olon,olat=np.meshgrid(olon,olat)                                               
func=Rbf(lon,lat,rain,function='linear')                                                
rain_new=func(olon,olat)                                                      
pc1=ax3.pcolormesh(olon,olat,rain_new,cmap='viridis_r')                    
cb3=fig.colorbar(pc1,ax=[ax3],shrink=0.9)                                  
ax3.set_title('默认norm的pcolormesh',fontsize=7)                                 
cmap2 =mpl.cm.viridis_r                                                                  
norm2=mcolors.BoundaryNorm(colorlevel,cmap2.N)                           
pc2=ax4.pcolormesh(olon,olat,rain_new,norm=norm2,cmap='viridis_r')                
cb4=fig.colorbar(pc2,ax=[ax4],shrink=0.9)                                      
ax4.set_title('指定norm的pcolormesh',fontsize=7)                            
for cb in[cb1,cb2,cb3,cb4]:                                                        
    cb.ax.tick_params(length=2,labelsize=4)                                       

通过比较两个默认norm的scatter、pcolormesh图片我们可以看出,由于严格的线性映射,色阶与数值的关系劣化严重,尤其是低数值面积对应的单色调区域占图片幅面面积较大。然后通过我们指定新的norm,使颜色与数值的对应映射关系得到改善。下面介绍常见的几种norm:

3.1 Normalize

即默认的norm,即所有颜色均匀排列,与数值间隔大小无关系,数值之间没有清晰的分界线,同一个数值间隔可以对应若干个颜色间隔,如:

3.2 BoundaryNorm

顾名思义,该命令下的颜色与数值间隔一一对应,拥有明显的分界线,一个数值间隔只有一个颜色与之相匹配。这是我们常见的使用方法,如:

3.3 CenteredNorm

偏度中心映射规则,使我们的中心值附近钝化,默认中心值为0,如:

该命令在matplotlib3.4.2版本中被注释掉,暂时无法使用。

3.4 LogNorm

指数化数值颜色对应规则,详参官网文档。

3.5 PowerNorm

Power law映射规则,通过改变gamma值,来改变原本的Normalize对应关系,改变后,一个数值间隔仍然可能包含多个颜色,但比原来默认效果稍好,如:

3.6 TwoSlopeNorm

该命令使色条围绕中心产生相同的刻度比例,如果是拼接色条,vcenter给出的值的位置就是拼接位置,如:

在上两条colorbar中,设定vcenter=300时,则两色条拼接处铆定300;设定vcenter=0时,则两色条拼接处铆定0。

从这个特性来看,我们在绘制类似铆定0值时的colormap时有特别的用处,如:

在使用上图3颜色条时,我们需要使红蓝阴阳色关于0值对称,但是可以发现,蓝色的赋值其实都在0~-0.5的范围,深蓝色的部分实际上是没有被使用过的。这时如果如图1单纯只改变vmin、vmax范围,则会造成0刻度掉入蓝色区,红蓝阴阳色不再关于0值对称。如果使用TwoSlopeNorm,则可以铆定0值为中心值,如图二:

代码语言:javascript
复制
Reds=mpl.cm.get_cmap('Reds')                                                    
Blues_r=mpl.cm.get_cmap('Blues_r')                                           
Redslist=Reds(np.linspace(0,1, 256))                                              
Bluerlist=Blues_r(np.linspace(0.25,1,256))                                   
new_list=np.vstack((Bluerlist,Redslist))                                      
ncmap=mcolors.LinearSegmentedColormap(colors=new_list,                    
                                      name='newcmap')                    
norm=mcolors.TwoSlopeNorm(vmin=-0.5,vcenter=0,vmax=1)             

3.7 FuncNorm

使用者自定义函数来映射颜色,详参官网文档,该命令matplotlib3.4.2版本中被注释掉,暂时无法使用。


本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-08-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 自学气象人 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档