matplotlib colorbar alternating top bottom labels

2019-09-04 08:07发布

问题:

First of all, this was intended as a self-answer question, because I believe it would be helpful in certain situations, e.g. in this post the author tried to hide every other label to avoid texts overlapping, one alternative might be to alternate the label position so that one retains all labels and avoids overlapping (if there isn't a crazy number of labels) as well, which is what this post aims to solve:

How to make matplotlib colorbar with alternating top and bottom labels?

回答1:

Jump to a simple working example:

import numpy
import matplotlib.pyplot as plt

#------------------Get some data------------------
X = numpy.arange(100)
Y = numpy.arange(100)
Z = numpy.arange(100**2).reshape((100,100))

levels=numpy.arange(0,100**2,1000)
ltop=levels[::2]           # labels appear on top
lbot=levels[1:][::2]       # labels appear at bottom

#-----------------------Plot-----------------------
f = plt.figure()
ax = f.gca()

cf = ax.contourf(X,Y,Z,100)
cbar=plt.colorbar(cf,orientation='horizontal',ticks=lbot,drawedges=True)

vmin=cbar.norm.vmin
vmax=cbar.norm.vmax

#-------------Print bottom tick labels-------------
cbar.ax.set_xticklabels(lbot)

#--------------Print top tick labels--------------
for ii in ltop:
    cbar.ax.text((ii-vmin)/(vmax-vmin), 1.5, str(ii), transform=cbar.ax.transAxes, va='bottom', ha='center')

plt.show(block=False)

Basically the bottom labels are plotted using the default method cbar.ax.set_xticklabels(lbot). For the top labels, I added them manually using cbar.ax.text().

The plot looks like this:

EDIT: IMPORTANT UPDATE TO MY ANSWER:

When the colorbar has extend/overflow, a triangle is used on the relevant end to indicate value overflow. In such cases the top line tick labels need some adjustment to properly align with colorbar sections.

By default the triangle size is 5% of the colorbar axis, this is used to get the proper offset and scaling to align the labels.

See an example below which has extends on both ends. Using my previous method, the result looks like this:

The 2 end numbers at top line are aligned with the tip of the triangles. If only one end has extend and the number of contour levels are big (>=10 or so), the misalignment will get worse.

The plot after correction:

And this is the code to generate the correct plot:

import numpy
import matplotlib.pyplot as plt

#------------------Get some data------------------
X = numpy.linspace(-1,1,100)
Y = numpy.linspace(-1,1,100)
X,Y=numpy.meshgrid(X,Y)
Z=numpy.sin(X**2)

levels=numpy.linspace(-0.8,0.8,9)

ltop=levels[::2]           # labels appear on top
lbot=levels[1:][::2]       # labels appear at bottom

#-----------------------Plot-----------------------
f = plt.figure()
ax = f.gca()

cf = ax.contourf(X,Y,Z,levels,extend='both')
cbar=plt.colorbar(cf,orientation='horizontal',ticks=lbot,drawedges=True)

#------------Compute top tick label locations------------
vmin=cbar.norm.vmin
vmax=cbar.norm.vmax

if cbar.extend=='min':
    shift_l=0.05
    scaling=0.95
elif cbar.extend=='max':
    shift_l=0.
    scaling=0.95
elif cbar.extend=='both':
    shift_l=0.05
    scaling=0.9
else:
    shift_l=0.
    scaling=1.0

#-------------Print bottom tick labels-------------
cbar.ax.set_xticklabels(lbot)

#--------------Print top tick labels--------------
for ii in ltop:
    cbar.ax.text(shift_l + scaling*(ii-vmin)/(vmax-vmin),
        1.5, str(ii), transform=cbar.ax.transAxes,
        va='bottom', ha='center')

plt.show(block=False)


回答2:

You could add a twin Axes object and set every odd ticks there while setting every even ticks on the original Axes.

import numpy as np
import matplotlib.pyplot as plt

# Make the plot
fig, ax = plt.subplots(1,1, figsize=(5,5))
fig.subplots_adjust(bottom=0.2)
## Trick to have the colorbar of the same size as the plot
box = ax.get_position() 
cax = fig.add_axes([box.xmin, box.ymin - 0.1, box.width, 0.03])
m = ax.matshow(np.random.random(100).reshape(10,10), aspect="auto") # Don't forget auto or the size of the heatmap will change.
cb = plt.colorbar(m, cax=cax, orientation="horizontal")

# Add twin axes 
cax2 = cax.twiny()

# get current positions and values of the ticks.
# OR you can skip this part and set your own ticks instead.
xt = cax.get_xticks()
xtl = [i.get_text() for i in cax.get_xticklabels()]

# set odd ticks on top (twin axe)
cax2.set_xticks(xt[1::2])
cax2.set_xticklabels(xtl[1::2])
# set even ticks on  original axes (note the different object : cb != cax)
cb.set_ticks(xt[::2])
cb.set_ticklabels(xtl[::2])

HTH