imshow top axes lables without twiny?

I have a single row of data that I want to heat-map it without the use of twiny(), because it will have alignment problems. I have searched a lot in this site and thats what I've reached till far:

imshow with single row heat map

import numpy as np
import matplotlib.pyplot as plt

x = "0.2, 0.3, 0.4, 0.5, 0.6".split(",")
y = "180, 175, 170, 169, 150".split(",")
z = [[5000, 4800, 4500, 4450, 4300]]

fig, ax1 = plt.subplots()

image = z

im = ax1.imshow(image, cmap=plt.cm.Blues, interpolation='nearest')
plt.colorbar(im)


ax1.set_xticks(np.arange(len(x)), minor=False)

ax1.set_xticklabels(x, minor=False)
#ax1.set_yticklabels(y, minor=False)

ax1.tick_params(labelbottom='on',labeltop='on', labelleft="off")



plt.show()

As you can see, the top axis has exactly the same text as the lower axes. What I want is to put y in the above axis.

Thanks in advance :)

Upvotes: 1

Views: 2271

Answers (4)

DrV
DrV

Reputation: 23540

It seems that twiny and axes with aspect ratio set to equal do not want to live on the same axes. To me it seems like a bug, but there may be an explanation.

So, let us get around this problem by plotting two axes on top of each other. This is not as trivial as it sounds, because if two subplots are at the same position, matplotlib interpretes them to be the same one. However, using add_plot there is no such problem.

import numpy as np
import matplotlib.pyplot as plt

x = "0.2, 0.3, 0.4, 0.5, 0.6".split(",")
y = "180, 175, 170, 169, 150".split(",")
z = [[5000, 4800, 4500, 4450, 4300]]

fig = plt.figure()
ax1 = fig.add_subplot(111)

image = z

im = ax1.imshow(image, cmap=plt.cm.Blues, interpolation='nearest')
plt.colorbar(im)

ax1.set_xticks(np.arange(len(x)), minor=False)
ax1.set_xticklabels(x, minor=False)
ax1.tick_params(labelbottom='on',labeltop='off', labelleft="off", 
    top='off', left='off', right='off')

# create another axes on the same position:
# - create second axes on top of the first one without background
# - make the background invisible
# - set the x scale according to that of `ax1`
# - set the top ticks on and everything else off
# - set the size according to the size of `ax1`
ax2 = fig.add_axes(ax1.get_position(), frameon=False)
ax2.tick_params(labelbottom='off',labeltop='on', labelleft="off", labelright='off',
    bottom='off', left='off', right='off')
ax2.set_xlim(ax1.get_xlim())
ax2.set_xticks(np.arange(len(y)))
ax2.set_xticklabels(y, minor=False)

plt.draw()
ax2.set_position(ax1.get_position())

plt.draw()
plt.show()

The plt.draw() is needed before the set_position as otherwise the get_position will return wrong position for ax1 due to the use of equal aspect. (This may be the very reason why twiny fails.)

enter image description here


If you need to have several rows, the solution is not that different:

import numpy as np
import matplotlib.pyplot as plt

x = "0.2, 0.3, 0.4, 0.5, 0.6".split(",")
y = "180, 175, 170, 169, 150".split(",")
z = [[5000, 4800, 4500, 4450, 4300]]

numRows = 8

fig, subaxes = plt.subplots(nrows=numRows, ncols=1)
axeslist = subaxes.flatten()

for ax in axeslist:
    im = ax.imshow(z, cmap=plt.cm.Blues, interpolation='nearest')

    ax.tick_params(labelbottom='off',labeltop='off', labelleft="off", labelright='off',
                   bottom='off', top='off', left='off', right='off')
    if ax == axeslist[0]:
        ax.set_title('Avg. (s)\n', size=13)
    elif ax == axeslist[-1]:
        ax.tick_params(bottom='on', labelbottom='on')
        ax.set_xticks(range(len(x)))
        ax.set_xticklabels(x)

# reserve some space between the subplots
fig.subplots_adjust(hspace=0.07*(numRows-1))

# create the overlay images, add them as extra properties of the original images
for ax in axeslist:
    axnew = fig.add_axes(ax.get_position(), frameon=False)
    axnew.tick_params(labelbottom='off',labeltop='on', labelleft="off", labelright='off',
                   bottom='off', top='on', left='off', right='off')
    axnew.set_xlim(ax.get_xlim())
    axnew.set_xticks(range(len(y)))
    axnew.set_xticklabels(y)
    ax.extra_axes = axnew

# update the secondary axes positions
# draw() only if there was something changed (important!)
def update_secondary(event=None):
    position_changed = False
    for ax in axeslist:
        if ax.extra_axes.get_position().bounds == ax.get_position().bounds:
            continue
        position_changed = True
        ax.extra_axes.set_position(ax.get_position())

    if position_changed:
        plt.draw()

# register the secondary axes updater as a callback
fig.canvas.mpl_connect('draw_event', update_secondary)

# make sure everything is drawn
plt.draw()    

enter image description here

As the overlay update has to be carried out after everything else has been drawn, it is here done by the draw_event from the backend. The result is that after the image is redrawn for some reason, the overlays are readjusted, and if any positions were changed, the scene is redrawn.

This works but is not beautiful.

Upvotes: 4

Use twin() and from mpl_toolkits.axes_grid1 import host_subplot

import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import host_subplot
import mpl_toolkits.axisartist as AA
import numpy as np

x = "0.2, 0.3, 0.4, 0.5, 0.6".split(",")
y = "180, 175, 170, 169, 150".split(",")
z = [[5000, 4800, 4500, 4450, 4300]]

#fig, ax1 = plt.subplots()
ax1 = host_subplot(111, axes_class=AA.Axes)

ax2 = ax1.twin()
image = z

im = ax1.imshow(image, cmap=plt.cm.Blues, interpolation='nearest')
plt.colorbar(im)


ax1.set_xticks(np.arange(len(x)), minor=False)
ax2.set_xticks(np.arange(len(y)), minor=False)

#ax1.tick_params(labelbottom='on',labeltop='on', labelleft="off")
#ax2.tick_params(labelbottom='on',labeltop='on', labelleft="off")

ax1.set_yticklabels([])
ax2.set_yticklabels([])

ax1.tick_params(labelbottom='on',labeltop='on', labelleft="off")


plt.show()

Upvotes: 1

Well, I found an alternative solution by using text instead of changing the axes values.

imshow single top and bottom labels without twiny

import numpy as np
import matplotlib.pyplot as plt

font = {'family' : 'sans-serif',
        'color'  : 'k',
        'weight' : 'normal',
        'size'   : 12,
        }

x = "0.2, 0.3, 0.4, 0.5, 0.6".split(",")
y = "180, 175, 170, 169, 150".split(",")
z = [[5000, 4800, 4500, 4450, 4300]]

fig, ax1 = plt.subplots()
#fig, ax1 = plt.subplots()

image = z

im = ax1.imshow(z, cmap=plt.cm.Blues, interpolation='nearest')
xticks = ax1.get_xticks()

top_lables_width_spacings = 0.83
top_lables_hight_spacings = -.53

for i in range(len(y)):
    ax1.text(xticks[i] + top_lables_width_spacings, top_lables_hight_spacings, y[i], fontdict=font)
#ax1.set_aspect('auto')

fig.colorbar(im, orientation='vertical')

ax1.set_xticks(np.arange(len(x)), minor=False)
ax1.set_xticklabels(x, minor=False)

ax1.tick_params(labelbottom='on',labeltop='off', labelleft="off")

ax1.set_title('$\eta$\n', size=17)      # represents the top axes label
plt.xlabel(r'$\theta$', size=17)                        # represents the bottom axes label
plt.show()

Upvotes: 1

otterb
otterb

Reputation: 2710

If I got this right, I guess you just need to change x to y:

ax1.set_xticklabels(y, minor=False)

If I do that, I see labels in list y on the top and bottom of the plot.

Upvotes: 0

Related Questions