Reputation: 16508
I have a binary numpy
array that contains filling data, and two grids that define the bounding box for the data:
data = np.random.choice([0, 1], size=12).reshape((3, 4))
xGrid = np.linspace(1, 4, 4)
yGrid = np.linspace(0.1, 0.3, 3)
I would like to plot a particular color, with a particular alpha, on any grid point where data is 1
, and nothing when data is 0
. The two closest matplotlib functions are
fill
, which requires (x, y) coordinates and can't work with this dataimshow
or matshow
, which unfortunately will plot some color everywhere. That is, it will also plot some color drawn from the color map wherever data == 0
. Now, I could fiddle around to make that color be the background color of the ax, but that's rather nasty.The bounding boxes are expected to behave as follows: xGrid
contains three values, and there are three data points on the x-dimension. Each value in xGrid
denotes the location of the center point for each of the data points, and similar for yGrid
. "Filling a data point" then corresponds to filling the rectangle defined by the center coordinates (x, y
).
What's the best way of achieving this?
Upvotes: 2
Views: 550
Reputation: 25043
With the understanding that the filled areas are to be drawn using the grid intersections as central points, we have
In [27]: import numpy as np
...: import matplotlib.pyplot as plt
...: np.random.seed(2018)
...: data = np.random.choice([0, 1], size=12).reshape((3, 4))
...: xGrid = np.linspace(1, 4, 4)
...: yGrid = np.linspace(0.1, 0.3, 3)
In [28]: print(data)
[[0 0 0 1]
[1 0 0 0]
[1 1 1 1]]
In [29]: dx, dy = (xGrid[1]-xGrid[0])/2, (yGrid[1]-yGrid[0])/2
In [30]: xli, yli = [], []
...: for y in yGrid:
...: for x in xGrid: # the x's in data are changing faster, so inner loop
...: xli.append([x-dx, x+dx, x+dx, x-dx, x-dx])
...: yli.append([y-dy, y-dy, y+dy, y+dy, y-dy])
In [31]: for xs, ys, Bool in zip(xli, yli, data.flatten()):
...: if Bool : plt.fill(xs, ys, color='red')
...: plt.gca().set_facecolor('yellow')
Executing the code above gives me
It's worth mentioning that only the filled rectangles are drawn, as shown by filling the background of the plotting area with a different color.
plt.fill
is documented here and the lists created in the first for
loop are simply the x, y coordinates of the corners of a rectangle that could possibly be drawn by plt.fill
.
A Note on Efficiency
If one has to draw a few hundreds of rectangles the simple approach above is OK, if we go into the tens of thousands maybe we want to loop over the data points with enumerate
, if it's needed construct the x, y lists and draw the rectangle on the fly or, even better for performance, create a Rectangle
patch, put it in a PatchCollection
and use the ax.add_collection
method when we have finished the loop on data
— an example is available in the Matplotlib docs that can be easily adapted to the scope and another example is this new answer of mine.
Upvotes: 2
Reputation: 25043
In another answer I mentioned that possible efficiency issues can be solved using Rectangle
patches and a PatchCollection
— here it is an implementation of this approach. First the initialization, note the imports of Rectangle
and PatchCollection
In [99]: import numpy as np
...: import matplotlib.pyplot as plt
...: from matplotlib.collections import PatchCollection
...: from matplotlib.patches import Rectangle
...:
...: np.random.seed(2018)
...: data = np.random.choice([0, 1], size=12).reshape((3, 4))
...: xGrid = np.linspace(1, 4, 4)
...: yGrid = np.linspace(0.1, 0.3, 3)
...: dx, dy = (xGrid[1]-xGrid[0])/2, (yGrid[1]-yGrid[0])/2
...: print(data)
[[0 0 0 1]
[1 0 0 0]
[1 1 1 1]]
Next we construct the PatchCollection
: we need a provisional list of patches, we loop on the rows of data
AND the y coordinates and on the columns in each row AND the x coordinates, if we have to we add a Rectangle
to the list of patches and finally we instantiate it
In [100]: patches = []
...: for y, row in zip(yGrid, data):
...: for x, col in zip(xGrid, row):
...: if col: patches.append(Rectangle((x-dx, y-dy), 2*dx, 2*dy))
...: pc = PatchCollection(patches)
And in the end the plotting, we need two axis' methods so plt.gca()
is needed, we modify the rectangles using methods of the path collection, committ the collection to the ax
and finally we call explicitly the autoscale_view
method that is required to have the correct axes limits.
In [101]: ax = plt.gca()
...: pc.set_facecolor('yellow')
...: pc.set_edgecolor('black')
...: ax.add_collection(pc)
...: ax.autoscale_view()
And this is the result
Upvotes: 1
Reputation: 18658
You can manage color with the colormap parameter. Here a fast solution using imshow, with total control on all parameters, in particular custom colors:
from pylab import imshow,show,cm
from matplotlib.colors import LinearSegmentedColormap
alpha=.7
cdict = {'blue': ((0.0, 0.0, 0.0), (1.0, 0.0, 0.0)),
'green': ((0.0, 0.0, 0.0), (1.0, 0.0, 0.0)),
'red': ((0.0, 0.0, 0.0), (1.0, alpha,alpha))}
mycolors = LinearSegmentedColormap("my_colors",cdict,N=2)
ax=imshow(data,cmap=mycolors)
ax.axes.set_xticks(np.arange(len(xGrid)))
ax.axes.set_xticklabels([str(a) for a in xGrid])
ax.axes.set_yticks(np.arange(len(yGrid)))
ax.axes.set_yticklabels([str(a) for a in yGrid])
ax.axes.set_xbound(-.5,3.5)
ax.axes.set_ybound(-.5,2.5)
ax.axes.set_aspect(.2/3)
Upvotes: 1
Reputation: 16966
Using imshow() based on this example for using the alpha
.
I am using set_ticks code given by @B. M.
def make_rgb_transparent(rgb, bg_rgb, alpha):
return [alpha * c1 + (1 - alpha) * c2
for (c1, c2) in zip(rgb, bg_rgb)]
import matplotlib
from matplotlib import colors
alpha =1.0
white = np.ones((1,3))
rgb = colors.colorConverter.to_rgb('red')
rgb_new = make_rgb_transparent(rgb, (1, 1, 1), alpha)
red_white_map = colors.LinearSegmentedColormap.from_list('map_white', np.vstack((white, rgb_new)),2)
ax=plt.imshow(data,cmap=red_white_map)
ax.axes.set_xticks(np.arange(len(xGrid)))
ax.axes.set_xticklabels([str(a) for a in xGrid])
ax.axes.set_yticks(np.arange(len(yGrid)))
ax.axes.set_yticklabels([str(a) for a in yGrid])
Upvotes: 1