I am looking for a way in Python/matplotlib/pandas to create a color fill for a graph similar to this (Source: http://www.scminc.com/resources/SCM_TIPSTRICKS_Petrel_Well_Sections_2013_July14.pdf):
It uses a color map for the fill (left of the image), and based on a specific interval on the x-axis assigns a color to it. Unfortunately, I haven't found a solution, and since I am pretty new to Python in general, I am unable to find a way to do that.
Many thanks
You can plot the fill as a background with imshow, then clip it. You can use fill_betweenx to make the mask.
Here's an example using random data:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import PathPatch
# Make a random x and a y to go with it.
np.random.seed(26)
x = np.random.normal(0, 1, 200).cumsum()
y = np.arange(x.size)
# Set up the figure.
fig, ax = plt.subplots(figsize=(2, 10))
# Make the background 'image'.
im = ax.imshow(x.reshape(-1, 1),
aspect='auto',
origin='lower',
extent=[x.min(), x.max(), y.min(), y.max()]
)
# Draw the path.
paths = ax.fill_betweenx(y, x, x.min(),
facecolor='none',
lw=2,
edgecolor='b',
)
# Make the 'fill' mask and clip the background image with it.
patch = PathPatch(paths._paths[0], visible=False)
ax.add_artist(patch)
im.set_clip_path(patch)
# Finish up.
ax.invert_yaxis()
plt.show()
This yields:
Related
I wish to plot things on top of an image I insert into my figure. I'm not sure how to do that. Here is a simple example where I do my best to place scattered points in the foreground of mario: I specify the order with zorder and call the scatter command last. However, mario is in the foreground and the scattered points are in the background.
How can I make the scattered points appear in front of Mario?
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.offsetbox import OffsetImage, AnnotationBbox
# load up mario
vortexRF = plt.imread('./mario.png')
imagebox = OffsetImage(vortexRF, zoom=0.08, zorder=1)
# initiate plot
fig, ax = plt.subplots()
# place mario in plot
ab = AnnotationBbox(imagebox, (0, 0), frameon=False)
cbar_ax = fig.add_axes([0.7, .42, 0.1, 0.1])
cbar_ax.add_artist(ab)
cbar_ax.axis('off')
# add scatter plot
NPoints = 1000
ax.scatter(np.random.random(NPoints), np.random.normal(0, 1, NPoints), s=3, c='purple', zorder=2)
# comment that mario should be in the background
ax.set_title("we want the purple dots to be in front of Mario")
# save figure. Mario is behind the scattered points :(
plt.savefig('marioExample')
cbar_ax = fig.add_axes(..., zorder=-1) arranges the z-order between axes. And ax.set_facecolor('none') makes the background of the scatter plot fully transparent (the default is opaque white, hiding everything behind it).
Note that everything that uses an ax is combined into one layer. An ax is either completely in front or completely to the back of another ax. Inside each ax, the elements can have their own z-orders.
To avoid copy-right issues, and to create a standalone example, the code below uses Ada Lovelace's image that comes with matplotlib.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.offsetbox import OffsetImage, AnnotationBbox
import matplotlib.cbook as cbook
np.random.seed(1234)
# load up Ada's image
with cbook.get_sample_data('ada.png') as image_file:
vortexRF = plt.imread(image_file)
imagebox = OffsetImage(vortexRF, zoom=0.2)
# initiate plot
fig, ax = plt.subplots()
# place Ada in plot
ab = AnnotationBbox(imagebox, (0, 0), frameon=False)
cbar_ax = fig.add_axes([0.6, .42, 0.3, 0.3], zorder=-1)
cbar_ax.add_artist(ab)
cbar_ax.axis('off')
# add scatter plot
ax.scatter(np.random.normal(np.tile(np.random.uniform(0, 1, 5), 1000), .1),
np.random.normal(np.tile(np.random.uniform(0, 1, 5), 1000), .1),
c=np.tile(['fuchsia', 'gold', 'coral', 'deepskyblue', 'chartreuse'], 1000),
s=3, alpha=0.2)
# comment that Ada should be in the background
ax.set_title("we want the dots to be in front of Ada")
# make the background of the scatter plot fully transparent
ax.set_facecolor('none')
plt.show()
PS: Note that you can also add the image on the same ax as the scatter using imshow with an extent. The extent is default expressed in the same data coordinates as the plot in the order (x0, x1, y0, y1). This makes things somewhat simpler. The method using fig.add_axes, however, nicely keeps the original aspect ratio of the image.
ax.imshow(vortexRF, extent=[0.0, 0.4, 0.7, 1.1])
I have seen many examples of using annotate arrows in Matplotlib that have a single color specified. I was wondering if it is possible to instead set the color according to a colormap, so that the whole range of colors from a specified colormap is displayed on a single arrow. I know that it is possible to set the color of an arrow to a single color from a colormap, but I want to have a single arrow displaying all of the colors of a given colormap.
A simple example of using an annotate arrow is shown below. In the documentation, I have not found any method for specifying a colormap. If I naively specify a colormap, I get an error from an invalid RGBA argument.
import matplotlib.pyplot as plt
RdPu = plt.get_cmap('RdPu')
ax = plt.subplot(111)
ax.annotate("Test", xy=(0.2, 0.2), xycoords='data',
xytext=(0.8, 0.8), textcoords='data',
size=20, arrowprops=dict(color=RdPu),
)
plt.show()
Ok, let's produce The Rainbow Arrow. ;-)
There is of course no built-in way to colorize an arrow with a color gradient. Instead one needs to build the arrow manually. I can think of two options. (1) Create a color gradient and clip it with the circonference path of an arrow. (2) Produce a LineCollection with a colorgradient and then add an arrow head to it.
The following is the second option:
import matplotlib.pyplot as plt
import matplotlib.transforms
import matplotlib.path
import numpy as np
from matplotlib.collections import LineCollection
def rainbowarrow(ax, start, end, cmap="viridis", n=50,lw=3):
cmap = plt.get_cmap(cmap,n)
# Arrow shaft: LineCollection
x = np.linspace(start[0],end[0],n)
y = np.linspace(start[1],end[1],n)
points = np.array([x,y]).T.reshape(-1,1,2)
segments = np.concatenate([points[:-1],points[1:]], axis=1)
lc = LineCollection(segments, cmap=cmap, linewidth=lw)
lc.set_array(np.linspace(0,1,n))
ax.add_collection(lc)
# Arrow head: Triangle
tricoords = [(0,-0.4),(0.5,0),(0,0.4),(0,-0.4)]
angle = np.arctan2(end[1]-start[1],end[0]-start[0])
rot = matplotlib.transforms.Affine2D().rotate(angle)
tricoords2 = rot.transform(tricoords)
tri = matplotlib.path.Path(tricoords2, closed=True)
ax.scatter(end[0],end[1], c=1, s=(2*lw)**2, marker=tri, cmap=cmap,vmin=0)
ax.autoscale_view()
fig,ax = plt.subplots()
ax.axis([0,5,0,4])
ax.set_aspect("equal")
rainbowarrow(ax, (3,3), (2,2.5), cmap="viridis", n=100,lw=3)
rainbowarrow(ax, (1,1), (1.5,1.5), cmap="jet", n=50,lw=7)
rainbowarrow(ax, (4,1.3), (2.7,1.0), cmap="RdYlBu", n=23,lw=5)
plt.show()
The following is the old solution, caused by a misunderstanding
An annotation arrow is a single arrow. Hence you would need to draw any number of arrows individually. In order for each arrow to then obtain a color, you may use the arrowprops=dict(color="<some color>") argument.
To get colors from a colormap, you can call the colormap with a value. Here the length of the arrow can be taken as the quantity to encode as color.
import matplotlib.pyplot as plt
import numpy as np
RdPu = plt.get_cmap('RdPu')
ax = plt.subplot(111)
ax.axis([-6,2,-4.5,3.2])
ax.set_aspect("equal")
X = np.linspace(0,1,17, endpoint=False)
Xt =np.sin(2.5*X+3)
Yt = 3*np.cos(2.6*X+3.4)
Xh = np.linspace(-0.5,-5,17)
Yh = -1.3*Xh-5
#Distance
D = np.sqrt((Xh-Xt)**2+(Yh-Yt)**2)
norm = plt.Normalize(D.min(), D.max())
for xt, yt, xh, yh, d in zip(Xt,Yt,Xh,Yh,D):
ax.annotate("Test", xy=(xh,yh), xycoords='data',
xytext=(xt,yt), textcoords='data',
size=10, arrowprops=dict(color=RdPu(norm(d))))
plt.show()
How to use python and matplotlib to plot a picture like following?
I know how to plot the 2D heat map, but it frustrated me a lot with plotting the bar on top of the heat map, and the bar between the color bar and heat map.
How to add those two bars on the picture, and show the number in x axis or y axis belongs to which group?
Thanks very much for all the responses.
A systematic and straightforward approach, although a bit more cumbersome at the start, is to use matplotlib.gridspec.GridSpec.
First set up the grid:
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
fig = plt.figure()
gs = GridSpec(2, 3, width_ratios=[10, 1, 1], height_ratios=[1, 10])
This gives us a grid of 2 rows and 3 columns, where the lower left axis will be 10x10 and the other axes will be either 10x1 or 1x10 in relative sizes. These ratios can be tweaked to your liking. Note that the top center/right axes will be empty.
big_ax = fig.add_subplot(gs[1,0]) # bottom left
top_ax = fig.add_subplot(gs[0,0]) # top left
right_ax = fig.add_subplot(gs[1,1]) # bottom center
cbar_ax = fig.add_subplot(gs[1,2]) # bottom right
I will use a generic genome picture I found via google for the top and right image:
and will generate a random heatmap. I use imshow(aspect='auto') so that the image objects and heatmap take up the full space of their respective axes (otherwise they will override the height/width ratios set by gridspec).
im = plt.imread('/path/to/image.png')
# Plot your heatmap on big_ax and colorbar on cbar_ax
heatmap = big_ax.imshow(np.random.rand(10, 10), aspect='auto', origin='lower')
cbar = fig.colorbar(heatmap, cax=cbar_ax)
# Show your images on top_ax and right_ax
top_ax.imshow(im, aspect='auto')
# need to rotate my image.
# you may not have to if you have two different images
from scipy import ndimage
right_ax.imshow(ndimage.rotate(im, 90), aspect='auto')
# Clean up the image axes (remove ticks, etc.)
right_ax.set_axis_off()
top_ax.set_axis_off()
# remove spacing between axes
fig.subplots_adjust(wspace=0.05, hspace=0.05)
It's not super glamorous (especially with the default jet colormap), but you could easily use this to reproduce the figure your OP.
Edit: So if you want to generate that genome-like plot on the top and right, you could try something like this for the top bar:
from matplotlib.patches import Rectangle
from matplotlib.collections import PatchCollection
# draw the black line
top_ax.axhline(0, color='k', zorder=-1)
# box x-coords and text labels
boxes = zip(np.arange(0.1, 1, 0.2), np.arange(0.2, 1, 0.2))
box_text = ('A1', 'B1', 'B2', 'A2')
# color indicators for boxes
colors = (0, 1, 1, 0)
# construct Rects
patches = [Rectangle(xy=(x0, -1), width=(x1-x0), height=2) for x0,x1 in boxes]
p = PatchCollection(patches, cmap='jet')
# this maps the colors in [0,1] to the cmap above
p.set_array(np.array(colors))
top_ax.add_collection(p)
# add text
[top_ax.text((x0+x1)/2., 1.2, text, ha='center')
for (x0,x1), text in zip(boxes, box_text)]
# adjust ylims
top_ax.set_ylim(-2, 2)
For something the right axis, you can do the same thing but use axvline and swap the x-coords for y-coords.
right_ax.axvline(0, color='k', zorder=-1)
patches = [Rectangle(xy=(-1, y0), width=2, height=(y1-y0)) for y0, y1 in boxes]
p = PatchCollection(patches, cmap='jet')
p.set_array(np.array(colors))
right_ax.add_collection(p)
[right_ax.text(1.2, (y0+y1)/2., text, va='center')
for (y0, y1), text in zip(boxes, box_text)]
right_ax.set_xlim(-2,2)
These modifications lead to something like:
I am plotting rectangular patches in matplotlib in interactive mode. I want to add text to each patch. I do not want to annotate them as it decreases the speed. I am using 'label' property of patch but it is not working. Ayone know how to add 1 string to patch.
import matplotlib.pyplot as plt
import matplotlib.patches as patches
plt.ion()
plt.show()
x = y = 0.1
fig1 = plt.figure()
ax1 = fig1.add_subplot(111, aspect='equal')
patch = ax1.add_patch(patches.Rectangle((x, y), 0.5, 0.5,
alpha=0.1,facecolor='red',label='Label'))
plt.pause(0)
plt.close()
You already know where the patch is, so you can calculate where the center is and add some text there:
import matplotlib.pyplot as plt
import matplotlib.patches as patches
x=y=0.1
fig1 = plt.figure()
ax1 = fig1.add_subplot(111, aspect='equal')
patch= ax1.add_patch(patches.Rectangle((x, y), 0.5, 0.5,
alpha=0.1,facecolor='red',label='Label'))
centerx = centery = x + 0.5/2 # obviously use a different formula for different shapes
plt.text(centerx, centery,'lalala')
plt.show()
The coordinates for plt.text determine where the text begins, so you can nudge it a bit in the x direction to get the text to be more centered e.g. centerx - 0.05. obviously #JoeKington's suggestion is the proper way of achieving this
I want to make 4 imshow subplots but all of them share the same colormap. Matplotlib automatically adjusts the scale on the colormap depending on the entries of the matrices. For example, if one of my matrices has all entires as 10 and the other one has all entries equal to 5 and I use the Greys colormap then one of my subplots should be completely black and the other one should be completely grey. But both of them end up becoming completely black. How to make all the subplots share the same scale on the colormap?
To get this right you need to have all the images with the same intensity scale, otherwise the colorbar() colours are meaningless. To do that, use the vmin and vmax arguments of imshow(), and make sure they are the same for all your images.
E.g., if the range of values you want to show goes from 0 to 10, you can use the following:
import pylab as plt
import numpy as np
my_image1 = np.linspace(0, 10, 10000).reshape(100,100)
my_image2 = np.sqrt(my_image1.T) + 3
plt.subplot(1, 2, 1)
plt.imshow(my_image1, vmin=0, vmax=10, cmap='jet', aspect='auto')
plt.subplot(1, 2, 2)
plt.imshow(my_image2, vmin=0, vmax=10, cmap='jet', aspect='auto')
plt.colorbar()
When the ranges of data (data1 and data2) sets are unknown and you want to use the same colour bar for both/all plots, find the overall minimum and maximum to use as vmin and vmax in the call to imshow:
import numpy as np
import matplotlib.pyplot as plt
fig, axes = plt.subplots(nrows=1, ncols=2)
# generate randomly populated arrays
data1 = np.random.rand(10,10)*10
data2 = np.random.rand(10,10)*10 -7.5
# find minimum of minima & maximum of maxima
minmin = np.min([np.min(data1), np.min(data2)])
maxmax = np.max([np.max(data1), np.max(data2)])
im1 = axes[0].imshow(data1, vmin=minmin, vmax=maxmax,
extent=(-5,5,-5,5), aspect='auto', cmap='viridis')
im2 = axes[1].imshow(data2, vmin=minmin, vmax=maxmax,
extent=(-5,5,-5,5), aspect='auto', cmap='viridis')
# add space for colour bar
fig.subplots_adjust(right=0.85)
cbar_ax = fig.add_axes([0.88, 0.15, 0.04, 0.7])
fig.colorbar(im2, cax=cbar_ax)
It may be that you don't know beforehand the ranges of your data, but you may know that somehow they are compatible. In that case, you may prefer to let matplotlib choose those ranges for the first plot and use the same range for the remaining plots. Here is how you can do it. The key is to get the limits with properties()['clim']
import numpy as np
import matplotlib.pyplot as plt
my_image1 = np.linspace(0, 10, 10000).reshape(100,100)
my_image2 = np.sqrt(my_image1.T) + 3
fig, axes = plt.subplots(nrows=1, ncols=2)
im = axes[0].imshow(my_image1)
clim=im.properties()['clim']
axes[1].imshow(my_image2, clim=clim)
fig.colorbar(im, ax=axes.ravel().tolist(), shrink=0.5)
plt.show()