Circles coming out as ovals - set aspect ratio in Python - python

I have a graph that plots a series circles, however, because of the axes they aren't coming out as circles, but as ovals, as you can see in the image below. I know this is a reoccurring problem, and there are many questions like this... However I can't find anything that helps me! I've tried putting in fig = plt.figure(0, figsize=(14.5, 1.75)) which does slightly help, but maybe ax.set_aspect() however, using scalars for this hasn't helped much either!
For this plot, the line marked *** is not there
my code is as follows:
fig = plt.figure(0)
ax = fig.add_subplot(111)
ax.set_aspect(???)#*** not sure if this should be here or not
plt.axis([-5, 20, -1, 1])
circle1 = plt.Circle((-1,0.25), radius=0.2, color='c')
circle2= plt.Circle((4,-0.5), radius=0.5, color='m')
plt.gcf().gca().add_artist(circle1)
plt.gcf().gca().add_artist(circle2)

You can set the aspect to be equal but you will also need to choose similar sizes for both axis as follows:
from matplotlib import pyplot as plt
fig = plt.figure(0)
ax = fig.add_subplot(111)
ax.set_aspect('equal')
plt.axis([-5, 5, -5, 5])
circle1 = plt.Circle((-1, 0.25), radius=0.2, color='c')
circle2 = plt.Circle((4, -0.5), radius=0.5, color='m')
ax.add_artist(circle1)
ax.add_artist(circle2)
plt.show()
Which would display as follows:

Related

How to add major and minor grid lines using pcolor?

My goal is to add a thick set of grid marks over the existing ones I have created using pcolor (see code below). There would be one thick grid line for every N (5 for instance) thinner grid lines. The grid lines I want to add could be analogous to major tick marks while the existing grid lines could be analogous to minor tick marks.
My code:
Z = np.random.rand(25, 25)
fig=plt.figure(figsize=(18, 16), dpi= 80, facecolor='w', edgecolor='k')
gs = gridspec.GridSpec(2, 3, width_ratios=[1,1,0.1])
ax1 = plt.subplot(gs[0,1])
plt1 = ax1.pcolor(Z, cmap=plt.cm.Blues, edgecolors='k', linewidths=1)
cbax = plt.subplot(gs[0,2])
cb = Colorbar(ax=cbax, mappable = plt1)
Output image:
random data with grid lines
Doctored image with red lines showing major grid I want: same data with doctored red grid lines
Does anyone have a good solution or work around for this?
I was able to resolve my issue by digging around in the matplotlib.pyplot.grid documentation.
Here is my updated code:
Z = np.random.rand(25, 25)
fig=plt.figure(figsize=(18, 16), dpi= 80, facecolor='w', edgecolor='k')
gs = gridspec.GridSpec(2, 3, width_ratios=[1,1,0.1])
ax1 = plt.subplot(gs[0,1])
plt1 = ax1.pcolor(Z, cmap=plt.cm.Blues, edgecolors='k', linewidths=1)
ax1.xaxis.set_major_locator(MultipleLocator(5))
ax1.yaxis.set_major_locator(MultipleLocator(5))
ax1.grid(b=True, which='major', color='r', linestyle='-')
cbax = plt.subplot(gs[0,2])
cb = Colorbar(ax=cbax, mappable = plt1)
Output figure:
random data with major and minor grid lines

matplotlib: axes border and tick mark/label locations

The end result I'm attempting to achieve is to have a "thicker" black boarder around my plot, along xmin, xmax, ymin, & ymax. I've tried a couple of different things (such as just drawing a rectangle on the plot, see below), but I have not been able to achieve the desired results for a few reasons.
Because I cannot just use the spines (I've set 2 of them to always be at 0), I need to add some other line or rectangle to create the desired border.
By default the first and last tick labels overhang the axes. I "overcame" this by changing the horizontal or vertical alignment, but they could still use some more padding. I know this is possible, but requires a transform and is a bit clunky.
Now I'd like to remove the first and last tick marks on both axis. This is because given the way the rectangle is drawn it is always inside the plot area, but the first and last tick mark are always outside it, regardless of how thick the rectangle is. Making the rectangle thicker only causes it to overlap the first and last tick label more, which the actual tick mark remains outside the rectangle.
Any other suggestions on how to achieve this kind of border while always maintaining an axis at 0, 0 would be welcomed. That is the overall desired result.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
from matplotlib.patches import Rectangle
X = np.random.randint(low=-9, high=9, size=10)
Y = np.random.randint(low=-9, high=9, size=10)
fig, ax = plt.subplots()
ax.axis([-10, 10, -10, 10])
ax.spines['left'].set_position('zero')
ax.spines['bottom'].set_position('zero')
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
plt.setp(ax.xaxis.get_majorticklabels()[0], ha='left')
plt.setp(ax.xaxis.get_majorticklabels()[-1], ha='right')
plt.setp(ax.yaxis.get_majorticklabels()[0], va='bottom')
plt.setp(ax.yaxis.get_majorticklabels()[-1], va='top')
patPlotBorder = ax.add_artist(Rectangle((-10, -10), 20, 20, fill=False, color='k', linewidth=2))
ax.grid(True)
fig.set_tight_layout(True)
ax.scatter(X, Y, c="b", marker="o", s=40)
plt.show()
Without changing much of your code, you can set the clip_on to False, such that the complete rectangle is shown.
border = Rectangle((-10, -10), 20, 20, fill=False, color='k', linewidth=3, clip_on=False)
ax.add_artist(border)
Since the gridlines are shown above the axes content, you have some grey line within the rectangle border.
Alternatively, you can use two axes. One with all the content and the modified spine positions etc., and one where you just make the spines bold and remove all the rest.
import numpy as np
import matplotlib.pyplot as plt
X = np.random.randint(low=-9, high=9, size=10)
Y = np.random.randint(low=-9, high=9, size=10)
fig, ax = plt.subplots()
ax2 = fig.add_subplot(111)
ax2.patch.set_visible(False)
ax2.tick_params(left=False, bottom=False, labelleft=False, labelbottom=False)
for _, sp in ax2.spines.items():
sp.set_linewidth(3)
ax.axis([-10, 10, -10, 10])
ax.spines['left'].set_position('zero')
ax.spines['bottom'].set_position('zero')
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
plt.setp(ax.xaxis.get_majorticklabels()[0], ha='left')
plt.setp(ax.xaxis.get_majorticklabels()[-1], ha='right')
plt.setp(ax.yaxis.get_majorticklabels()[0], va='bottom')
plt.setp(ax.yaxis.get_majorticklabels()[-1], va='top')
ax.grid(True)
fig.set_tight_layout(True)
ax.scatter(X, Y, c="b", marker="o", s=40)
plt.show()
You can access the individual grid lines by calling get_{x|y}gridlines(). Each grid line is an object of type Line2D, and you can change any of their properties, such as thickness, color, etc.
ax.get_xgridlines()[0].set_linewidth(5)
ax.get_xgridlines()[-1].set_linewidth(5)
ax.get_ygridlines()[0].set_linewidth(5)
ax.get_ygridlines()[-1].set_linewidth(5)

Circle with matplotlib with border out of the figure

I am trying to draw a circle with matplotlib, with a diameter, say, of 2 inches and a border of 10 pixels, and I want to save it in a file. This is my code:
import matplotlib.pyplot as plt
from matplotlib import patches
path = 'test.png'
fig1 = plt.figure()
fig1.dpi = 100
fig1.set_size_inches(2, 2)
ax1 = fig1.add_subplot(111, aspect='equal')
ax1.axes.get_xaxis().set_visible(False)
ax1.axes.get_yaxis().set_visible(False)
ax1.add_patch(patches.Circle((0.5, 0.5),
radius=0.5,
color='k', linewidth=10, fill=False))
fig1.tight_layout()
fig1.savefig(path, bbox_inches='tight', pad_inches=0)
and this is what I get:
As you can see, part of the border is out of the picture.
In fact, even doing something much simpler, I get similar results:
import matplotlib.pyplot as plt
from matplotlib import patches
fig1 = plt.figure()
ax1 = fig1.add_subplot(111, aspect='equal')
ax1.add_patch(patches.Circle((0.5, 0.5),
radius=0.5,
color='k', linewidth=10, fill=False))
plt.show()
so, I can't understand where is the problem.
What am I doing wrong?
Adding a patch won't automatically adjust the axes limits. You have to call ax1.autoscale_view() to adjust the limits to the content.
fig1 = plt.figure()
ax1 = fig1.add_subplot(111, aspect='equal')
ax1.add_patch(patches.Circle((0.5, 0.5),
radius=0.5,
color='k', linewidth=10, fill=False))
ax1.autoscale_view()
The limits are small, by default take the minimum position and maximum of all points without considering the thickness, I recommend that you set the limits a little bigger. You must be {axes}.set_xlim() and {axes}.set_ylim()
import matplotlib.pyplot as plt
from matplotlib import patches
path = 'test.png'
fig1 = plt.figure()
fig1.dpi = 100
fig1.set_size_inches(2, 2)
ax1 = fig1.add_subplot(111, aspect='equal')
ax1.axes.get_xaxis().set_visible(False)
ax1.axes.get_yaxis().set_visible(False)
ax1.add_patch(patches.Circle((0.5, 0.5),
radius=0.5,
color='k', linewidth=10, fill=False))
ax1.set_xlim([-0.1, 1.1])
ax1.set_ylim([-0.1, 1.1])
fig1.tight_layout()
fig1.savefig(path, bbox_inches='tight', pad_inches=0)

Matplotlib: make figure that has x and y labels square in aspect ratio

The question is bettered explained with examples. Let's say below is a figure I tried to plot:
So the figure region is square in shape, and there are axis labels explaining the meaning of the values. In MATLAB the code is simple and works as expected.
t = linspace(0, 2*pi, 101);
y = sin(t);
figure(1)
h = gcf;
set(h,'PaperUnits','inches');
set(h,'PaperSize',[3.5 3.5]);
plot(t, y)
xlim([0, 2*pi])
ylim([-1.1, 1.1])
xlabel('Time (s)')
ylabel('Voltage (V)')
axis('square')
Now let's work with Python and Matplotlib. The code is below:
from numpy import *
from matplotlib import pyplot as plt
t = linspace(0, 2*pi, 101)
y = sin(t)
plt.figure(figsize = (3.5, 3.5))
plt.plot(t, y)
plt.xlim(0, 2*pi)
plt.ylim(-1.1, 1.1)
plt.xlabel('Time (s)')
plt.ylabel('Voltage (V)')
plt.axis('equal') # square / tight
plt.show()
It does not work, see the first row of the figure below, where I tried three options of the axis command ('equal', 'square' and 'tight'). I wondered if this is due to the order of axis() and the xlim/ylim, they do affect the result, yet still I don't have what I want.
I found this really confusing to understand and frustrating to use. The relative position of the curve and axis seems go haywire. I did extensive research on stackoverflow, but couldn't find the answer. It appears to me adding axis labels would compress the canvas region, but it really should not, because the label is just an addon to a figure, and they should have separate space allocations.
I do not have a computer at hand right now but it seems this answer might work: https://stackoverflow.com/a/7968690/2768172 Another solution might be to add the axis with a fixed size manually with: ax=fig.add_axes(bottom, left, width, height). If width and height are the same the axis should be squared.
It doesn't explain the figures you obtain but here is one way to achieve square axes with the right axes limits. In this example, I just calculate the axes aspect ratio using the x and y range:
plt.figure()
ax = plt.axes()
ax.plot(t, y)
xrange = (0, 2*pi)
yrange = (-1.1, 1.1)
plt.xlim(xrange)
plt.ylim(yrange)
plt.xlabel('Time (s)')
plt.ylabel('Voltage (V)')
ax.set_aspect((xrange[1]-xrange[0])/(yrange[1]-yrange[0]))
plt.show()
Another method is to create a square figure and square axes:
plt.figure(figsize = (5, 5))
ax = plt.axes([0.15, 0.15, 0.7, 0.7])
ax.plot(t, y)
plt.xlim(0, 2*pi)
plt.ylim(-1.1, 1.1)
plt.xlabel('Time (s)')
plt.ylabel('Voltage (V)')
plt.show()

Matplotlib Pyplot logo/image in Plot

I'm struggling to achieve a simple goal in matplotlib... I want to put a small logo or indicator in the bottom right of my graph, without altering the axis or the real data that is being displayed. Here is my code now:
fig = plt.figure()
plt.rcParams.update({'font.size': 15})
img = plt.imread('./path/to/image.png')
ax1 = fig.add_subplot(111)
ax1.yaxis.tick_left()
ax1.tick_params(axis='y', colors='black', labelsize=15)
ax1.tick_params(axis='x', colors='black', labelsize=15)
plt.grid(b=True, which='major', color='#D3D3D3', linestyle='-')
plt.scatter([1,2,3,4,5],[5,4,3,2,1], alpha=1.0)
plt.autoscale(enable=True, axis=u'both')
fig.savefig('figure.png')
My output from this is below.
This is now laying the photo over the whole graph -- I'd like it scaled to 20% of width & height (if possible) and anchored to the bottom right. This also ruins my axis, because in this output I should be in the 0-100 range on both x & y. Any ideas to solve this, the scaling is the big issue.
Edit1: I've tried the solution below and linked questions here on SO. The problem is relying on the extent variable being passed to imshow() then doesn't work well when introducing new data. For example plotting a scatter plot coming from a data frame, could be from 0..1000 and 50..100 but using extent won't show the label or the position will be off.
Edit2: There seems to be some progress with getting the figure length with fig.get_size_inches() and passing the variable to extent. Apparently all of matplotlib graph calculations are done through inches, so this may be a promising lead.
import matplotlib.image as image
import matplotlib.pyplot as plt
im = image.imread('debian-swirl.png')
fig, ax = plt.subplots()
ax.imshow(im, aspect='auto', extent=(0.4, 0.6, .5, .7), zorder=-1)
ax.yaxis.tick_left()
ax.tick_params(axis='y', colors='black', labelsize=15)
ax.tick_params(axis='x', colors='black', labelsize=15)
ax.grid(b=True, which='major', color='#D3D3D3', linestyle='-')
ax.scatter([1,2,3,4,5],[5,4,3,2,1], alpha=1.0)
plt.show()
I added a png file to bottom left. Adjust the extent parameter to set the logo position.
Similar to : Scale image in matplotlib without changing the axis
The following is an adaptation of the answer by Kirubaharan J, but adapting the position of the logo to the extent of the graph (but the aspect ratio of the logo itself is not preserved)
import matplotlib.image as image
import matplotlib.pyplot as plt
im =image.imread('debian-swirl.png')
fig, ax = plt.subplots()
ax.yaxis.tick_left()
ax.tick_params(axis='y', colors='black', labelsize=15)
ax.tick_params(axis='x', colors='black', labelsize=15)
ax.grid(b=True, which='major', color='#D3D3D3', linestyle='-')
ax.scatter( [100,90,89,70], [55, 23,76,29], alpha=1.0)
plt.autoscale(enable=True, axis=u'both')
xrng=plt.xlim()
yrng=plt.ylim()
scale=.2 #the image takes this fraction of the graph
ax.imshow(im,aspect='auto',extent=(xrng[0],xrng[0] + scale*(xrng[1]-xrng[0]), yrng[0], yrng[0] + scale*(yrng[1]-yrng[0]) ), zorder=-1)
plt.xlim(xrng)
plt.ylim(yrng)
plt.show()
I've worked on a similar problem to print several pdf with a fix logo on every pages independant of the graph size. The best solution I found was using GridSpec.
fig = plt.figure(figsize = (11,8.5)) # 8.5" x 11" : letter format
G = plt.GridSpec(14,21)
I my case I'v build a grid of 14 square by 21 over an 8.5 x 11 inch template.
Then I just have to allocate a section of the grid for the logo and import it using matplotlib.image
ax = fig.add_subplot(G[2:5,5:14])
logo = mpimg.imread("logo.png")
imagebox = OffsetImage(logo, zoom=0.08)
ab = AnnotationBbox(imagebox, (0.4, 0.6), frameon = False)
ax.add_artist(ab)
You can control the scale using the zoom arg in OffsetImage
You can find the detail at the following link :
https://www.science-emergence.com/Articles/How-to-insert-an-image-a-picture-or-a-photo-in-a-matplotlib-figure/
i think it's best to simply put the image on a new axis...
in this way you have full control on where to put it without having to bother with existing axes
import matplotlib.image as image
import matplotlib.pyplot as plt
# create a plot
f, ax = plt.subplots()
im = image.imread("path-to-logo.png")
# put a new axes where you want the image to appear
# (x, y, width, height)
imax = f.add_axes([0.8, 0.75, 0.1, 0.1])
# remove ticks & the box from imax
imax.set_axis_off()
# print the logo with aspect="equal" to avoid distorting the logo
imax.imshow(im, aspect="equal")

Categories