I'm trying to plot different rectangles with matplotlib which should have a little gap in between them like in following example:
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
import numpy as np
Nmax = 200
xvalues = np.arange(Nmax)
fig = plt.figure()
ax = plt.subplot(111)
for xvalue in xvalues:
rect = Rectangle(
xy=(xvalue - 0.25, xvalue),
width = 0.5,
height = 1.5,
facecolor = 'r',
edgecolor = 'r',
)
ax.add_patch(rect)
ax.autoscale_view()
plt.show()
It's working as I would like to work for Nmax = 20 rectangles. Plot of Nmax=20 below:
As the rectangle positions are always 1 'unit' apart with a width of 0.5 there is always a spacing of 0.5 between two neighbouring rectangles.
However when I try it for example with 200 rectangles the rectangles get thicker and start overlapping. Upon zooming into the graph the rectangles are separated again. But saving the original figure as pdf still yields overlapping rectangles. Zoom of pdf with Nmax=200 below:
I don't know why this is happening, as I'm specifying still their widths to 0.5. I would be glad if someone could give me a hint on this.
I'm not sure but in a vector format it should be possible to determine the rectangle position exactly, so maybe saving it as svg and converting it to pdf would do the trick?
Final solution:
alright, thanks to zephyr the solution is to turn off the rectangle edge:
edgecolor = 'none',
Changing the edgecolor to 'none' in matplotlib.finance would also solve overlapping candlestick bars which seems to be the same problem here
Assuming you do want to use an edgecolor (that is, setting edgecolor='none' is not an option), you could produce a PDF which shows the space between boxes by increasing the figsize and dpi when creating the figure:
fig = plt.figure(figsize=(12,4), dpi=600)
If the figsize and dpi are big enough, the pdf-generating backend will display the whitespace between the rectangles:
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
import numpy as np
Nmax = 200
xvalues = np.arange(Nmax)
fig = plt.figure(figsize=(12,4), dpi=600)
ax = plt.subplot(111)
for xvalue in xvalues:
rect = Rectangle(
xy=(xvalue - 0.25, xvalue),
width = 0.5,
height = 1.5,
facecolor = 'r',
edgecolor = 'r',
)
ax.add_patch(rect)
ax.autoscale_view()
# plt.show()
plt.savefig('/tmp/test.pdf')
Detail:
Another option is to reduce the linewidth when creating the Rectangle:
Rectangle(..., edgecolor='b', linewidth=0.01)
Thanks to zephyr the solution is to turn off the rectangle edge:
edgecolor = 'none',
Related
I am making figures for a paper, and I want to put a label some fixed distance from the top-left corner of the plot. ax.text(x, y, label, transform=ax.transAxes) nearly does this, but specifies the position as a fraction of the size of the plot. If I could get the absolute size of the plot area I could use that to convert. For example, in the following script how could I get the height and width?
Edit: I want the height and width just of the plot (not of the whole figure), and excluding the labels, ticks, etc.
from matplotlib import pyplot as plt
import numpy as np
data = np.random.rand(10,10)
fig, ax = plt.subplots()
ax.pcolormesh(data)
ax.set_aspect("equal")
# get width and height of plot here?
plt.show()
Your response above is good, but perhaps better:
import matplotlib.pyplot as plt
import matplotlib.transforms as mtransforms
fig, ax = plt.subplots()
trans = (fig.dpi_scale_trans +
mtransforms.ScaledTranslation(0, 1, ax.transAxes))
ax.set_aspect(1)
ax.text(0.1, -0.2, 'Boo', transform=trans)
plt.show()
You can read more at: https://matplotlib.org/tutorials/advanced/transforms_tutorial.html#plotting-in-physical-coordinates
I did manage to find a way to do this. It was made more complicated because I used set_aspect() - set_aspect() modifies the bounding box of ax, but by default doesn't apply the modification until ax is drawn (e.g. by plt.show()), which messes up attempts to get the bounding box size.
The solution is:
To avoid problems from set_aspect(), call apply_aspect() before trying to get the bounding box size. This makes the updated aspect ratio actually modify the bounding box size so we can find out what it is.
Get the size of the plot area with ax.get_window_extent() - this gets the size of just the plot area, excluding axis labels, ticks, etc.
The result of ax.get_window_extent() is in 'display units', which we can convert to inches using fig.dpi.
So adding the kind of label I wanted as an example:
from matplotlib import pyplot as plt
import numpy as np
data = np.random.rand(10,10)
fig, ax = plt.subplots()
ax.pcolormesh(data)
ax.set_aspect("equal")
ax.apply_aspect()
bbox = ax.get_window_extent()
# dpi used to convert from display units to inches
dpi = fig.dpi
height = bbox.height / dpi # in inches
width = bbox.width / dpi # in inches
x = 0.2 # in inches
y = 0.1 # in inches
ax.text(x / width, 1.0 - y / height, "(a)", verticalalignment="top", color="w", transform=ax.transAxes)
plt.show()
which gives:
Edit: I tested this solution with matplotlib-3.3.2.
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 am using NetworkX and matplotlib to draw graph with png images as nodes. Here is my code:
import networkx as nx
import matplotlib.pyplot as plt
G = nx.DiGraph()
#DRAWING EDGES ON AXIS
G.add_edges_from(([1,2],[3,4],[5,6],[7,8]))
pos = nx.circular_layout(G)
fig = plt.figure(figsize=(20, 20))
ax = plt.axes([0, 0, 15, 15])
ax.set_aspect('equal')
nx.draw_networkx_edges(G, pos, ax=ax, arrows=True)
#TRANSFORMING COORDINATES
trans = ax.transData.transform
trans2 = fig.transFigure.inverted().transform
#PUTTING IMAGE INSTEAD OF NODES
size = 0.2
p2 = size / 2.0
for n in G:
xx, yy = trans(pos[n])
xa, ya = trans2((xx, yy))
a = plt.axes([xa - p2, ya - p2, size, size])
a.set_aspect('equal')
a.imshow(image, aspect='auto')
a.axis('off')
plt.savefig('save.png')
plt.show()
Jupyter notebook displays graph. However, when I use Pycharm it shows blank white figure. Saving by plt.savefig() also do not works. I tried to play with dpi in plt.savefig() but doesn't change anything. Will be very grateful for any clues.
Adding bbox_inches='tight' while saving solved the problem:
plt.savefig('save.png',bbox_inches='tight')
This argument cuts unnecessary whitespace margins around output image. Without it only some part of whole figure is saved.
Valuable discussion about how to save pure image in matplotlib is here:
scipy: savefig without frames, axes, only content
You can find the bbox of the image inside the axis (using
get_window_extent), and use the bbox_inches parameter to save only
that portion of the image
I've been similar situation before. Can you please try this:
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
plt.axes expects a rectangle with coordinates expressed as a fraction of the figure canvas. Given figsize=(20,20),
ax = plt.axes([0, 0, 15, 15])
should really be
ax = plt.axes([0, 0, 0.75, 0.75])
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()
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