2

I want generate a contour plot/heat map with a color bar and then add an annotation box. This figure is ugly, but gets at what I want:

example image

add_subplot() is not enough. If I try to put everything in the same subplot, the box gets covered up. I can get around this by making it dragable and then futzing with the size of the image, but this is no good. I am going to have to make several of these images, all of a standard size, and I can't fight with the size over and over again.

I tried axes() as well, putting the box in a separate axis. But that generates a new window for plotting that covers up most of my color bar. I guess there would be ways to make the window completely transparent. But when I get to that point, I think my approach must be completely wrong.

This doesn't seem like it should be so hard. Any ideas?

Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
bob.sacamento
  • 6,283
  • 10
  • 56
  • 115
  • Maybe [this answer](http://stackoverflow.com/questions/11270153/matplotlib-table-gets-cropped/11270791#11270791) can be of interest to you. – Schorsch Jun 12 '13 at 18:55

3 Answers3

5

Annotation box to contour map:

much better graph

Done like this:

import matplotlib.pyplot as plt
import numpy as np
from matplotlib import cm
from numpy.random import randn

from mpl_toolkits.axes_grid1.axes_divider import HBoxDivider
import mpl_toolkits.axes_grid1.axes_size as Size


def make_heights_equal(fig, rect, ax1, ax2, ax3, pad):
    # pad in inches

    h1, v1 = Size.AxesX(ax1), Size.AxesY(ax1)
    h2, v2 = Size.AxesX(ax2, 0.1), Size.AxesY(ax2)
    h3, v3 = Size.AxesX(ax3), Size.AxesY(ax3)

    pad_v = Size.Scaled(1)
    pad_h = Size.Fixed(pad)

    my_divider = HBoxDivider(fig, rect,
                             horizontal=[h1, pad_h, h2, pad_h, h3],
                             vertical=[v1, pad_v, v2, pad_v, v3])


    ax1.set_axes_locator(my_divider.new_locator(0))
    ax2.set_axes_locator(my_divider.new_locator(2))
    ax3.set_axes_locator(my_divider.new_locator(4))


# Make plot with vertical (default) colorbar
fig = plt.figure()
img_ax = fig.add_subplot(131)
bar_ax = fig.add_subplot(132)
ann_ax = fig.add_subplot(133)

data = np.clip(randn(250, 250), -1, 1)

im = img_ax.imshow(data, interpolation='nearest', cmap=cm.coolwarm)

# Add colorbar, make sure to specify tick locations to match desired ticklabels
cbar = fig.colorbar(im, cax=bar_ax, ticks=[-1, 0, 1])
cbar.ax.set_yticklabels(['< -1', '0', '> 1'])# vertically oriented colorbar

ann_ax.axis('off')

ann_ax.annotate("Hello, I'm an annotation", (0.5, 0.5),
                 xycoords="axes fraction", va="center", ha="center",
                 bbox=dict(boxstyle="round, pad=1", fc="w")) 

make_heights_equal(fig, 111, img_ax, bar_ax, ann_ax, 0.2)

plt.savefig("try.png")
Community
  • 1
  • 1
brice
  • 24,329
  • 7
  • 79
  • 95
  • Much obliged. My boss will never go for that honkin' big colorbar, though. Thanks. – bob.sacamento Jun 12 '13 at 19:41
  • @bob.sacamento I know! was just putting down my first attempt. Here's a much better looking graph. Not very clean way of doing it but gets the job done. – brice Jun 12 '13 at 19:48
2

Here's a rather simple solution, using the make_axes_locatable function from mpl_toolkits.axes_grid1, as this makes the colorbar the same height as the image. Further it is very easy to set the placement, width and padding of the colorbar relative to the Axes.

import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.axes_grid1 import make_axes_locatable
from matplotlib import cm
from numpy.random import randn

# Make plot with vertical (default) colorbar
fig = plt.figure()
ax = fig.add_subplot(121, aspect='equal')
ax2 = fig.add_subplot(122, aspect='equal')
ax2.axis('off')
divider = make_axes_locatable(ax)

# Specify placement, width and padding of colorbar
cax = divider.append_axes("right", size="10%", pad=0.1)

data = np.clip(randn(250, 250), -1, 1)

im = ax.imshow(data, interpolation='nearest', cmap=cm.coolwarm)
ax.set_title('Title')

# Add colorbar, make sure to specify tick locations to match desired ticklabels
cbar = fig.colorbar(im, cax=cax, ticks=[-1, 0, 1])
cbar.ax.set_yticklabels(['< -1', '0', '> 1'])# vertically oriented colorbar

# Add text
boxtext = \
"""Text box
Second line
Third line"""

props = dict(boxstyle='round, pad=1', facecolor='white', edgecolor='black')
ax2.text(0.15, 0.85, boxtext, ha='left', va='top', transform=ax2.transAxes, bbox=props)

#plt.tight_layout()
plt.savefig(r'D:\image.png', bbox_inches='tight', dpi=150)

enter image description here

sodd
  • 12,482
  • 3
  • 54
  • 62
1

Here's what I'm going with:

import matplotlib.pyplot as plt
from matplotlib import cm
import numpy as np
from numpy.random import randn
import pickle

data  = np.clip(randn(250, 250), -1, 1)

fig3 = plt.figure()

ax1 = fig3.add_axes([.1,.1,.55,.8])
ax2 = fig3.add_axes([.7,.1,.05,.8])
ax3 = fig3.add_axes([.78,.1,.1,.8])
ax3.axis('off')

hm = ax1.pcolor(data, cmap=cm.coolwarm) 

plt.colorbar(hm,cax = ax2)

bbox_args = dict(boxstyle='square',facecolor='white')
ann = ax3.annotate('More info here', xy=(.2,.8),
              xytext=(.2,.8), transform=ax3.transAxes, bbox=bbox_args)

plt.show()

Result is:

example plot from above code

Not that this is better than the other answers provided (for which I am very grateful!) but I can understand it (!) and tweak it pretty easily. I'm not going to select my own answer, because reading the others is what got me thinking along the lines I needed to make my own. (Especially, did not know about axis('off') and was not thinking about putting the colorbar in its own axis object. Did I mention I'm a noob?) Anyway, if anyone wants a third alternative, here's this one.

bob.sacamento
  • 6,283
  • 10
  • 56
  • 115