Reputation: 744
I want to draw a rectangle, with a gradient color fill from left to right, at an arbitrary position with arbitrary dimensions in my axes instance (ax1) coordinate system.
My first thought was to create a path patch and somehow set its fill as a color gradient. But according to THIS POST there isn't a way to do that.
Next I tried using a colorbar. I created a second axes instance ax2 using fig.add_axes([left, bottom, width, height])
and added a color bar to that.
ax2 = fig.add_axes([0, 0, width, height/8])
colors = [grad_start_color, grad_end_color]
index = [0.0, 1.0]
cm = LinearSegmentedColormap.from_list('my_colormap', zip(index, colors))
colorbar.ColorbarBase(ax2, cmap=cm, orientation='horizontal')
But the positional parameters passed to fig.add_axes()
are in the coordinate system of fig, and don't match up with the coordinate system of ax1.
How can I do this?
Upvotes: 9
Views: 10171
Reputation: 41
How about something like this?
import matplotlib as mpl
import matplotlib.pyplot as plt
def cname2hex(cname):
colors = dict(mpl.colors.BASE_COLORS, **mpl.colors.CSS4_COLORS) # dictionary. key: names, values: hex codes
try:
hex = colors[cname]
return hex
except KeyError:
print(cname, ' is not registered as default colors by matplotlib!')
return None
def hex2rgb(hex, normalize=False):
h = hex.strip('#')
rgb = np.asarray(list(int(h[i:i + 2], 16) for i in (0, 2, 4)))
return rgb
def draw_rectangle_gradient(ax, x1, y1, width, height, color1='white', color2='blue', alpha1=0.0, alpha2=0.5, n=100):
# convert color names to rgb if rgb is not given as arguments
if not color1.startswith('#'):
color1 = cname2hex(color1)
if not color2.startswith('#'):
color2 = cname2hex(color2)
color1 = hex2rgb(color1) / 255. # np array
color2 = hex2rgb(color2) / 255. # np array
# Create an array of the linear gradient between the two colors
gradient_colors = []
for segment in np.linspace(0, width, n):
interp_color = [(1 - segment / width) * color1[j] + (segment / width) * color2[j] for j in range(3)]
interp_alpha = (1 - segment / width) * alpha1 + (segment / width) * alpha2
gradient_colors.append((*interp_color, interp_alpha))
for i, color in enumerate(gradient_colors):
ax.add_patch(plt.Rectangle((x1 + width/n * i, y1), width/n, height, color=color, linewidth=0, zorder=0))
return ax
# SAMPLE
fig, ax = plt.subplots(figsize=(4, 2))
ax.set_xlim(0, 100)
ax.set_ylim(0, 40)
draw_rectangle_gradient(ax, 0, 0, 30, 10, color1='blue', color2='orange', alpha1=1, alpha2=1)
draw_rectangle_gradient(ax, 40, 15, 40, 10, color1='white', color2='pink', alpha1=1, alpha2=1)
draw_rectangle_gradient(ax, 15, 30, 80, 5, color1='gold', color2='red', alpha1=0.0, alpha2=1)
Upvotes: 2
Reputation: 2876
I have been asking myself a similar question and spent some time looking for the answer to find in the end that this can quite easily be done by imshow
:
from matplotlib import pyplot
pyplot.imshow([[0.,1.], [0.,1.]],
cmap = pyplot.cm.Greens,
interpolation = 'bicubic'
)
It is possible to specify a colormap, what interpolation to use and much more. One additional thing, I find very interesting, is the possibility to specify which part of the colormap to use. This is done by means of vmin
and vmax
:
pyplot.imshow([[64, 192], [64, 192]],
cmap = pyplot.cm.Greens,
interpolation = 'bicubic',
vmin = 0, vmax = 255
)
Inspired by this example
I chose X = [[0.,1.], [0.,1.]]
to make the gradient change from left to right. By setting the array to something like X = [[0.,0.], [1.,1.]]
, you get a gradient from top to bottom. In general, it is possible to specify the colour for each corner where in X = [[i00, i01],[i10, i11]]
, i00
, i01
, i10
and i11
specify colours for the upper-left, upper-right, lower-left and lower-right corners respectively. Increasing the size of X
obviously allows to set colours for more specific points.
Upvotes: 9
Reputation: 13206
did you ever solve this? I wanted the same thing and found the answer using the coordinate mapping from here,
#Map axis to coordinate system
def maptodatacoords(ax, dat_coord):
tr1 = ax.transData.transform(dat_coord)
#create an inverse transversion from display to figure coordinates:
fig = ax.get_figure()
inv = fig.transFigure.inverted()
tr2 = inv.transform(tr1)
#left, bottom, width, height are obtained like this:
datco = [tr2[0,0], tr2[0,1], tr2[1,0]-tr2[0,0],tr2[1,1]-tr2[0,1]]
return datco
#Plot a new axis with a colorbar inside
def crect(ax,x,y,w,h,c,**kwargs):
xa, ya, wa, ha = maptodatacoords(ax, [(x,y),(x+w,y+h)])
fig = ax.get_figure()
axnew = fig.add_axes([xa, ya, wa, ha])
cp = mpl.colorbar.ColorbarBase(axnew, cmap=plt.get_cmap("Reds"),
orientation='vertical',
ticks=[],
**kwargs)
cp.outline.set_linewidth(0.)
plt.sca(ax)
Hopefully this helps anyone in the future who needs similar functionality. I ended up using a grid of patch objects instead.
Upvotes: 2