本節提要:關於一些不常見的colorbar的仿製:彎曲與環形的colorbar、兩端分離的colorbar、收縮colorbar的主副刻度、雙刻度列colorbar、截取與拼接cmap、外部顏色引入cmaps與palettable庫包、特別的格式定製、levels等距而colorbar刻度距離不等距、其他類型的偽colorbar、使刻度側的框線與colorbar柱體分離。
一、彎曲與環形的colorbar
這是我很久之前在氣象家園上看到一個朋友提的問題了。也不知他解決沒有,我又去翻找了下,大概帖子已經沉底,只能在這裡寫一下了。
colorbar函數好像並沒有給出能夠改變其樣式的參數,所以,想要直接簡單的通過修改參數來讓他變為彎曲的樣式就非常困難了。(也不排除我眼瞎官網手冊沒看到)所以我們只能取巧了,假造一個colorbar(以下省稱cbar)。這一節推送裡的很多cbar其實都是偽cbar,怎麼理解呢?
我們通常生成cbar,一般要將等值線圖的代號傳進去,比如:
ac=ax.contourf(...)fig.colorbar(ac)這種直接傳入的方法我稱之為有源cbar,指的是顏色映射直接指向原圖,不存在任何偏差,數值與顏色絕對與原圖相匹配。但是顯然,我們不能這樣掰彎一個有源的cbar,原因見前。所以我們要按照原圖的colormap和levels來偽造一個cbar,使其在視覺效果上變成一個彎曲的cbar。
首先前面的程序直到畫等值線都是走流程。這裡只擷取核心代碼段:
from matplotlib import cmac=ax.contourf(levels=...,cmap='RdBu_r') #省略部分內容ac.levelscmap=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]*(len(ac.levels)-1))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 in zip(cs.levels,angle,radius): ax1.text(x-0.1,y+0.17,i,fontsize=3)ax1.text(0.9,2.6,'氣溫:℃',fontsize=4)ax1是個極坐標的子圖,在最底層,我們疊加的地圖是另一個ax,在上層,利用上層的地圖遮住下面。然後在視覺上形成彎曲的cbar。在生成angle時我們只用了0.5π,一個圓的周長是2π,所以我們的彎曲cbar只有四分之一。
利用這個方式還可以完成下面這個圖的cbar:
不過要修改x軸的劃分區域,變為2π,還要限制ylim使其中心被掏空。
另外,還可以用劉大成《matplotlib精進》p28裡提到的用楔形繪製圓環的辦法完成彎曲cbar的繪製。
二、兩端分離的colorbar
這個的仿製的緣起是另一個公號的編輯給我看了一張圖,我覺得還比較好看,所以專門取出來看看。這個圖的兩端的尖角與主體是分離的。我嘗試翻了官網文檔,好像不能實現。(也有可能我過年豬油吃多了)那就是又在逼我偽造cbar了。
完整代碼如下:
import numpy as npimport xarray as xrimport matplotlib.pyplot as pltimport matplotlib.path as mpathimport matplotlib as mplfrom matplotlib import cm,colorsfrom matplotlib.patches import Rectangleimport cartopy.crs as ccrsfrom cartopy.util import add_cyclic_pointfrom cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTERimport cartopy.feature as cfimport cartopy.io.shapereader as shpreaderplt.rcParams['font.sans-serif']=['SimHei']file=r'E:\aaaa\datanc\fnl_20190620_00_00.grib2'data=xr.open_dataset(file,engine='cfgrib',backend_kwargs={'filter_by_keys': {'typeOfLevel': 'surface'}})t=data['t']-278.15lat=data['latitude']lon=data['longitude']proj=ccrs.LambertConformal(central_longitude=105, central_latitude=35)reader = shpreader.Reader( r'F:\BaiduNetdiskDownload\MeteoInfo\MeteoInfo_Software\MeteoInfo\map\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.spines['geo'].set_linewidth(0.5)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())ax2.set(xlim=(-3,30),ylim=(0,1))num=ac.levelscolormap=cm.get_cmap('Spectral_r',len(num)-1)cmaps=colormap(range(len(num)-1))camps=cmaps.tolist()for i,x,y,color in 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='none') ax2.add_patch(rectangle)left=plt.Polygon(xy=[[-0.75,0.31], [-0.75, 0.69], [-2.5,0.5]], color=cmaps[0])right=plt.Polygon(xy=[[27.75,0.31],[27.75,0.69],[29.5,0.5]],color=cmaps[-1])ax2.add_patch(left)ax2.add_patch(right)ax2.axis('off')ax.set_title('分離colorbar',fontsize=7)因為想這個辦法只花了一個小時,肯定有些能簡略的地方或者更好的辦法,我這裡只是一個造假cbar的思路。
通過中間的一個for循環,我們將每個等級的色條以顏色polygon的方法按順序從左往右排列,變成一個視覺上的cbar,其實不是我們常規意義上的cbar。然後通過最後left、right兩個polygon來添加分離的兩頭小三角。
在for循環中cmap[ 1:-1]表示顏色列表中的[ 0 ]和[ -1 ]即首尾兩個顏色沒有參與中間柱形polygon的添加工作。這兩個顏色被賦予到left和right兩個polygon中了。
這一節指的是對colorbar的刻度進行修飾,一般來說,自動生成的cbar是黑色邊框的,刻度尺朝外的,字體大小也存在一定的問題。這裡簡單對其做個美化。我們以仿製plotnine庫包的cbar為例子。
cax=fig.add_axes([0.95,0.6,0.05,0.4])cmap=mpl.cm.viridisbounds=np.arange(0,100,1)norm=mpl.colors.BoundaryNorm(bounds, cmap.N)cbar=fig.colorbar(mpl.cm.ScalarMappable(norm=norm, cmap=cmap),cax=cax)cbar.outline.set_color('none')cbar.ax.tick_params(which='both',direction='in',length=1,width=0.25,color='white',labelsize=2,left=True,pad=0.5)cax這一步代表強制指定cbar擺放的位置,在傳入參數後,cbar只能擺放在這個子圖上,不會調參時東跑西跑了。
cbar.outline.set_color('none')表示將cbar的邊框顏色變為無。
ax.tick_parmas這句話,可以參考前面的推送文章中的關於刻度軸的內容:氣象繪圖加強版(四)—坐標名、刻度、軸。通過which這個參數,我們可以將主副刻度分別設置為相反的朝向等。這裡就不在贅述。
這裡提到了前面文章推送過的雙刻度列的cbar,上次推送很多地方沒有講完,這裡再提一次。並且用我們常用的雨量量級來做一個雙刻度cbar。首先製作一個單側刻度列的cbar:
import numpy as npimport matplotlib as mplimport matplotlib.pyplot as pltimport matplotlib.colors as mcolorsfig=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.axax2.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)這樣我們一方面就可以定量的分析各個雨級的mm值,又可以定性的分析各個雨級。還是比較實用的,當然,不一定只到500mm,這只是生成cbar時的限制的,你也可以改到1000。
請注意,在自己定製降水量色條時,間距是不相等的,但是新生成的ax3間距是相等的,所以會出現錯位,不過這不重要,只要視覺上過關即可。如果是自動生成的cbar,則不存在這樣的問題。
這個不是我想出來的,但也是一個比較少用的功能。即提取一個colormap的某一部分,或者將兩個colormap拼到一起。主要基於Creating Colormaps in Matplotlib這個Tutorials裡的一些demo。還參考了公眾號DQS小王的文章,以及氣象家園論壇ID為f_idled的用戶總結的經驗。
這裡只講講為什麼能截取和拼接。在第一和第二小節中,我們就提取過colormap,並將其劃分為levels的對應片段,並對每個polygon填色,實際上colormap就是一系列的色號拼接而成的一個數組。而在我們學習python中,列表list是切片操作的始祖。是數組就可以切片。數組可以切片,那麼colormap就可以截取與拼接。colormap的操作轉化為列表的操作。
這裡用DQS的例子做說明:
import numpy as npimport matplotlib as mplimport matplotlib.pyplot as pltfrom matplotlib.colors import ListedColormapcmap=mpl.cm.jet_rnewcolors=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)可以看出,暖色部分基本被截去了。更多的內容大家可以參考提到的作者的原文章,這裡不再展開(其實是我實在打不動字了)。
六、外部顏色引入cmaps與palettable庫包
cmaps與palettable不是matplotlib裡的東西,所以要先conda安裝。cmaps他可以使你在matplotlib中使用NCL裡的顏色條。matplotlib中自帶的顏色條實在是比較少的,也難看。NCL中有許多經典的大氣科學繪圖配色可供使用。大牛寫個包實在是嘉惠學林。palettable也差不多,可以讓你擁有超出matplotlib的享受。這裡簡單寫個實例:
import cmaps#首先導入這個包cmap=cmaps.CBR_wet_r#CBR_wet_r#這個顏色條就不是matplotlib裡的,而是通過cmaps轉引ncl的bounds=np.arange(0,100,10)norm=mpl.colors.BoundaryNorm(bounds, cmap.N)cbar=fig.colorbar(mpl.cm.ScalarMappable(norm=norm, cmap=cmap),cax=cax)而且請注意,導入之後,你就可以對ncl的顏色條進行matplotlib裡的操作了,比如截取和拼接等。(你敢信第五第六節幾百個字打了兩天)
七、特別的格式定製
在matplotlib中可以使用format參數對cbar的刻度的格式修改,但是有時候會有些不一樣的需求。比如下面一個色條:
上面數字格式使用的是Times New Roman,下面中文的字體是SimHei。
fc=fig.colorbar(mpl.cm.ScalarMappable(norm=norm, cmap=cmap), cax=ax, orientation='horizontal',extend='both')labels=fc.ax.get_xticklabels()[label.set_fontname('Times New Roman')for label in labels]八、levels等距而colorbar刻度距離不等距
參照FuncNorm: Arbitrary function normalization這個例子,實現levels等距而cbar不等距。但是有個問題是我現在安裝的matplotlib中的源碼中,該FuncNorm類下面只有注釋,沒有內容。打開本地的matplotlib文件下的colors.py,我從頭讀到尾,確實沒有這個功能的定義。應該是開發者更新的時候出問題了,所以即便你粘貼demo過去運行也不行。
這是為了仿製前面提到的一張圖裡的cbar時涉及到的問題。可以看出,一側的標籤框線是和柱體分離的。但是當我使用cbar.ax.spines['right'].set_position(('outward',10))來修改時,卻會只有刻度尺離開柱體的樣子。所以我嘗試第四節雙刻度列的辦法提到的新建一根坐標軸進行處理。cmap = mpl.cm.coolnorm = 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.axax2.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=3)ax3.spines['bottom'].set_bounds(5,10)ax3.set_xticks([5,10])ax3.set_xticklabels(['0.1mm','500mm'])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))噹噹當,還是比較像的,後續可以通過設置label和rotation的角度來美化。不過大頭已經解決了,其他的細節我就不再贅述。(其實是打不動字了)