user3517167
user3517167

Reputation: 155

Plotting Many Colorful Rectangles

I have data that I'd like to plot. I think the best way to do this is with a series of rectangles. I'd like each rectangle to span a width delta_t (each time interval is the same) and a height delta_f (the frequency intervals may differ) and each rectangle's color is given by the log(z). This example seems to have some hints, but I'm not able to put it all together. Here's what I've gotten so far

from matplotlib.collections import PatchCollection
from matplotlib.patches import Rectangle

def make_rectangle(t_min, f_min, delta_t, delta_f, z):
    return Rectangle(xy = (t_min, f_min), width = delta_t, height = delta_f, edgecolor = 'k')

timeex=[0,1,2,3,4]
frequencyex=[0,.1,1,10,100]
zex=[[1,1.2,1.1,1.5,1.6], [.01,120,.11,1.6,1.5], 
[.1,.12,1.1e-6,15,16], [1,1.2,1.1,1.5,1.6], [.01,120,.11,1.6,1.5]]

tiles = []

for i in range(len(timeex) - 1):
    t_min = timeex[i]
    f_min = frequencyex[i]
    t_max = timeex[i + 1]
    f_max = frequencyex[i + 1]
    for j in range(len(zex[i])):
        rect = make_rectangle(t_min, f_min, t_max - t_min, f_max - f_min, zex[i][j])
        tiles.append(rect)
        
fig = plt.figure()
ax = fig.add_subplot(111)        
p = PatchCollection(tiles, cmap=plt.cm.jet)
ax.add_collection(p)
fig.colorbar(p)
fig.show()

This produces the attached image. This plot is lacking in a few ways: the rectangle heights aren't what I would expect. Logscaleing the axis doesm't help. I don't know how to logarithmically color the rectangeles. I'd like to normalize the colorscale between something like 10e-6 and 10.

The end product I'm envisioning looks somewhat like the outputs shown here.

I'm using Python 3.6 and matplotlib 3.3.2.

A misbehaving plot.

Edit: the fantastic answer below solved most of my graphing problems. I turned myself around with the loops, but I sorted that out quickly. I appreciate your help.

Upvotes: 1

Views: 512

Answers (1)

Guimoute
Guimoute

Reputation: 4629

There are a few things that I added or changed to obtain this:

enter image description here

  • changed the y-axis scale to log.

  • manually changed the x- and y-limits so that the rectangles would fit.

  • specified a norm for your PatchCollection so that it knows how to turn values into colors. Without this, you can only use the 0-1 range which is not what you want.

  • specified the array of your PatchCollection so that it knows which values to turn into colors. We store the list of provided zex[i][j] values for this purpose. No need to provide those values to make_rectangle (they were unused anyway).

In theory you could calculate automatically the min and max values of the norm as well as the limits of the ax from the data. Here I went with the norm you gave in the OP (1e-6, 10) and manual limits.


# Imports.
import matplotlib.pyplot as plt
from matplotlib.collections import PatchCollection
from matplotlib.patches import Rectangle
from matplotlib.colors import LogNorm
import numpy as np

def make_rectangle(t_min, f_min, delta_t, delta_f):
    return Rectangle(xy = (t_min, f_min), width = delta_t, height = delta_f, edgecolor = 'k')

timeex = [0, 1, 2, 3, 4]
frequencyex = [0, 0.1, 1, 10, 100]
zex = [[1, 1.2, 1.1, 1.5, 1.6],
       [0.01, 120, 0.11, 1.6, 1.5], 
       [0.1, 0.12, 1.1e-6, 15, 16],
       [1, 1.2, 1.1, 1.5, 1.6],
       [0.01, 120, 0.11, 1.6, 1.5]]

tiles = []
values = []
for i in range(len(timeex) - 1):
    t_min = timeex[i]
    f_min = frequencyex[i]
    t_max = timeex[i + 1]
    f_max = frequencyex[i + 1]
    for j in range(len(zex[i])):
        rect = make_rectangle(t_min, f_min, t_max - t_min, f_max - f_min)
        tiles.append(rect)
        values.append(zex[i][j])
        
# Create figure and ax.
fig = plt.figure()
ax = fig.add_subplot(111)    

# Normalize entry values to 0-1 for the colormap, and add the colorbar.
norm = LogNorm(vmin=1e-6, vmax=10)
p = PatchCollection(tiles, cmap=plt.cm.jet, norm=norm, match_original=True) # You need `match_original=True` otherwise you lose the black edgecolor.
fig.colorbar(p)
ax.add_collection(p)

# Set the "array" of the patch collection which is in turn used to give the appropriate colors.
p.set_array(np.array(values))

# Scale the axis so that the rectangles show properly. This can be done automatically
# from the data of the patches but I leave this to you.
ax.set_yscale("log")
ax.set_xlim(0, 8)
ax.set_ylim(0, 100)

fig.show()

Upvotes: 2

Related Questions