3

I'm trying to plot a set of polygons with a colormap. I set up a ScalarMappable object and generate polygon colors from that ScalarMappable, but when I try to add a colorbar, I get the error:

TypeError: You must first set_array for mappable

The documentation for "set_array" doesn't really say anything, so I'm not at all clear what it is doing, whether I need to give it values, and if I do, what they will be doing.

Can anyone please explain what set_array does, and how I should deal with this?

    plt.clf()
    fig, ax  = plt.subplots(1,1)

    # Set color mappable
    range_min = df.col1.min()
    range_max = df.col1.max()
    cmap = matplotlib.cm.ScalarMappable(
          norm = mcolors.Normalize(range_min, range_max), 
          cmap = plt.get_cmap('binary'))

    for i in polygonDict.keys():
        ax.add_patch(ds.PolygonPatch(polygonDict[i], fc = cmap.to_rgba(df.col1.loc[i])))

    fig.colorbar(cmap, ax = ax)
nick_eu
  • 3,541
  • 5
  • 24
  • 38
  • 1
    Those are the values used to generate the colors. `ScalarMappale` is not normally used directly like this, but as a mix-in to other classes (like `PolygonCollection`) to allow you to map between `R^1 -> RGBA` – tacaswell Mar 02 '15 at 02:37
  • @tcaswell -- thanks. So can I just feed cmap.set_array() some arbitrary values without concern, since I've already set the vmax and vmin values in mcolors.Normalize()? – nick_eu Mar 02 '15 at 02:41
  • 1
    Not strictly sure as you are using it is such a strange way. I really think you want to be using `PolygonCollection` here as mpl will take care of all the color mapping for you, I suspect it will be more performant on draw and simpler to read the code. – tacaswell Mar 02 '15 at 02:43
  • ok, thanks @tcaswell. This is a fix for a problem outside this snippet -- I'm trying to merge values from a dataframe with polygons from a shapefile so I can color each polygon (a district) with the values of a variable computed in pandas. Doing it by order is a little challenging, but I'll give it some thought! – nick_eu Mar 02 '15 at 02:52
  • 1
    I suggest just making lists (instead of adding patches) and then making one call to `PolygonCollection` at the end which (should) take care of the ordering problem. – tacaswell Mar 02 '15 at 02:54
  • Ah, so build one list of scalar values (to become colors) and one list of the polygons, then put the list of polygons into a PolygonCollection, and assign the list of scalar values as the color values for the PolygonCollection? That's clever, thanks! – nick_eu Mar 02 '15 at 02:57

3 Answers3

4

A far easier way to do this is to send an empty array [] to set_array(). I don't really know why, but I've seen it done in this answer, and it works.

The only thing I know is that before you set the array, you get None from get_array(), which is probably why you get this error.

You can do :

plt.clf()
fig, ax  = plt.subplots(1,1)

# Set color mappable
range_min = df.col1.min()
range_max = df.col1.max()
cmap = matplotlib.cm.ScalarMappable(
      norm = mcolors.Normalize(range_min, range_max), 
      cmap = plt.get_cmap('binary'))

for i in polygonDict.keys():
    ax.add_patch(ds.PolygonPatch(polygonDict[i], fc = cmap.to_rgba(df.col1.loc[i])))

cmap.set_array([]) # or alternatively cmap._A = []

fig.colorbar(cmap, ax = ax)

After a few tests, it seems that you can send any array in the world ([df.col1], [0,1], ['hello']) to set_array(), and your colorbar will be the one you want. It just can't be None.

I've noticed that if you set the array to an number array, and then call autoscale(), the min and max values of the colorbar will be the min and max of that array.

I hope this helps.

matthieu
  • 1,412
  • 1
  • 11
  • 33
3

This may be too late, but I bumped into the same problem today.

So far as I understand it, imshow() and scatter() create a mappable object to convert from a floating point value to a colour in a two step process. First the floating point number is mapped to the range 0..1, then the the appropriate RGB for that normalised number is looked up in the colour map.

Artists such as PolygonPatch don't create this mappable, so you need to do it yourself using ColorbarBase()

Here's what I ended up doing.

def createColourbar(lwr, upr):
    """Create a colourbar with limits of lwr and upr"""
    cax, kw = matplotlib.colorbar.make_axes(mp.gca())
    norm = matplotlib.colors.Normalize(vmin = lwr, vmax = upr, clip = False)

    c = matplotlib.colorbar.ColorbarBase(cax, cmap=mp.spectral(), norm=norm)
    return c
Fergal
  • 494
  • 3
  • 12
2

This seems to have been fixed in matplotlib v3.1.0 (was not working in v3.0.2).

chris.currin
  • 151
  • 6