Search code examples
pythonpython-3.xmatplotlibcolorbar

How to create non-linear colorbar ticks in matplotlib


I have created a custom diverging colormap using 'hot_r' and 'kbc' from colorcet, like so:

def lin_to_diverge(cmap1, cmap2, cmapname):

    in1 = plt.cm.get_cmap(cmap1)(np.linspace(0, 1, 129))
    in2 = plt.cm.get_cmap(cmap2)(np.linspace(0, 1, 129))

    combine = np.vstack((in1, in2))
    outmap = mcolors.LinearSegmentedColormap.from_list(cmapname, combine)
    return outmap

I am plotting some data on a global contourplot. The guts of this operation is below:

    cmap = lin_to_diverge(cc.cm.kbc, 'hot_r', 'colorcet')

    # plot a contourplot of trends on a global map
    ax.set_global()
    ax.coastlines(linewidth=0.5)
    cbarticks = np.arange(-6.0, 7.0, 1)
    ax3.set_xticks([0, 90, 180, -90, -180], crs=ccrs.PlateCarree())
    ax4.set_xticks([0, 90, 180, -90, -180], crs=ccrs.PlateCarree())
    ax1.set_yticks([-90, -60, -30, 0, 30, 60, 90], crs=ccrs.PlateCarree())
    ax3.set_yticks([-90, -60, -30, 0, 30, 60, 90], crs=ccrs.PlateCarree())
    ax.contourf(xx, yy, trends, cbarticks, cmap=cmap, levels=levels_def, vmin=-12, vmax=12,
                transform=ccrs.PlateCarree(), extend='both')  # ,norm=colors.SymLogNorm(linthresh=0.03, linscale=0.03,vmin=-12, vmax=12)

    def_levels = [np.nanmin(insignificant1), 0, np.nanmax(insignificant1)]
    ax.contourf(xx, yy, insignificant1, cbarticks, levels=def_levels, hatches=["XXXXXX", ""], linewidth='0', alpha=0,
                transform=ccrs.PlateCarree(), vmin=-12, vmax=12)
    def_levels2 = [np.nanmin(insignificant2), 0, np.nanmax(insignificant2)]
    ax.contourf(xx, yy, insignificant2, cbarticks, levels=def_levels2, hatches=["//////", ""], alpha=0,
                transform=ccrs.PlateCarree(), vmin=-12, vmax=12)
    # plt.savefig(outdir + file+"_global_day_"+str(day)+".pdf", bbox_inches='tight', dpi=500)
    # plt.savefig(outdir + file+"_global_day_"+str(day)+".png")

fig.text(0.02, 0.5, 'Latitude', ha='center', va='center', rotation='vertical')
fig.text(0.48, 0.04, 'Longitude', ha='center', va='center')
ax1.set_title('Day 90')
ax2.set_title('Day 180')
ax3.set_title('Day 270')
ax4.set_title('Day 360')

# orig_cmap = mpl.cm.seismic
# shrunk_cmap = scm(orig_cmap, start=-12, midpoint=0.75, stop=12, name='shrunk')

m = plt.cm.ScalarMappable(cmap=cmap)
m.set_array(trends)
m.set_clim(-12, 12)
fig.subplots_adjust(bottom=0.07, top=1, left=0.1, right=0.9,
                    wspace=0.11, hspace=-0.1)
cb_ax = fig.add_axes([0.9, 0.05, 0.02, 0.92])

# cbarticks = [-12, -6., -5., -4., -3., -2., -1.,  0.,  1.,  2.,  3.,  4.,  5.,  6., 12]
ticks = np.linspace(-12, 12, 9)

cbar = fig.colorbar(m, cax=cb_ax, ticks=ticks)
# cbar.ax.set_yticklabels(cbarticks)
cbar.set_label('Trend [DU/year]')

plt.show()
plt.close()

I wish to add non-linear ticks to the colorbar, specifically without modifying the colormap, as the current distribution of colour on the plot is correct. What is the best way to do this? Should I create a new colormap specifically for the colorbar and derive the ticks from that? I don't want to change the way the colorbar looks at the moment though. I wish to have more values that are around zero, i.e. [3, 2, 1.5, 0.5, 0, -0.5, -1.5, -2, -3], but these values should be spread out more, while the location of where 12 and -12 currently are should remain the same. As a result, the ticks close to zero should be spread out more.

Here are the figures produced from the above script:


Solution

  • Figured my question out- The solution to this problem is to use SymLogNorm to scale the number values closer to zero and therefore achieved the desired outcome of more colorbar ticks near the zero mark. SymLogNorm can be used on negative values as well, so it answers this question.

    Further parameters can be changed using using the vmin and vmax arguments of the function as below:

    matplotlib.colors.SymLogNorm(linthresh, linscale=1.0, vmin=None, vmax=None, clip=False)
    

    I have implemented this into the above code by adding in a "norm=" argument, like so:

    ax.contourf(xx, yy, trends, cbarticks, cmap=cmap, levels=levels_def,
                    norm=mpcol.SymLogNorm(linthresh=0.03, linscale=0.03),
                    transform=ccrs.PlateCarree(), extend='both')
    

    I then edited the tick parameters of the colorbar as the logarithmic scale scrunched them up:

    cbarticks = [-12, -4, -2, -1, -0.5, -0.3, -0.2, -0.1, 0, 0.1, 0.2, 0.3, 0.5, 1, 2, 4, 12]
    ticks = np.linspace(-12, 12, 9)
    cbar = fig.colorbar(m, cax=cb_ax, ticks=cbarticks)
    cbar.ax.set_yticklabels(cbarticks)
    

    This produced these plots:

    SymLogNorm scaling of first plot in question

    SymLogNorm scaling of second plot in question