how to get matplotlib physical (not data) scale - python

I have this simple code:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_pdf import PdfPages
from matplotlib.patches import Ellipse
PlotFileName="test.pdf"
pdf = PdfPages(PlotFileName)
fig=plt.figure(1)
ax1=fig.add_subplot(111)
plt.xlim([0,10])
plt.ylim([0,10])
ax1.plot([0,10],[0,10])
e=0.0
theta=0
maj_ax=2
min_ax=maj_ax*np.sqrt(1-e**2)
const=1
ax1.add_artist(Ellipse((5, 5), maj_ax, const*min_ax, angle=theta, facecolor="green", edgecolor="black",zorder=2, alpha=0.5))
plt.grid()
pdf.savefig(fig)
pdf.close()
plt.close()
Here is how it looks:
As you see from the code, it should be a circle, but it isn't! I have narrowed the problem down to the const term in line 16. I don't want to use ax1.axis("equal") because my data don't have the same scales on the vertical and horizontal. Could any one tell me how I can ask matplotlib to tell me what aspect ratio it is using so I can set the const term correctly so I have a circle in the end?
In other words I want to know the ratio of the horizontal to the vertical axis "physical" length (for example, what is printed out).
I would really appreciate any suggestions, thanks in advance

One option is to explicitly define the figure size... you may also need to specify the subplot parameters if you are using non-default settings. Adjust figsize and subplot parameters as needed for non-equal horizontal and vertical scales. For example:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Ellipse
fig = plt.figure(figsize=(6,4))
fig.subplots_adjust(left=0.1, right=0.9, bottom=0.1, top=0.9)
ax1 = fig.add_subplot(111, xlim=(-2.5,12.5), ylim=(0,10))
ax1.plot((0,10), (0,10))
maj_ax, e, theta = 2, 0, 0
min_ax = maj_ax * np.sqrt(1 - e**2)
ax1.add_artist(Ellipse((5, 5), maj_ax, min_ax, angle=theta,
fc="green", ec="black", zorder=2, alpha=0.5))
plt.grid()
plt.show()

Related

How do I put a watermark behind plotted data using matplotlib

I found this tutorial on how to do a watermark but I cannot figure out how to put it behind my plotted data.
https://www.tutorialspoint.com/how-to-plot-a-watermark-image-in-matplotlib
Changing zorder has no impact because I think it is being drawn on the entire figure. I would like to have a subdued logo behind my data which is always centered in the figure so I don't really want to plot an image as a data point because then it would move as it is panned/zoomed.
Setting the zorder to a negative value works for me. However, you also need to make the facecolor of the axes transparent:
import numpy as np
import matplotlib.cbook as cbook
import matplotlib.image as image
import matplotlib.pyplot as plt
with cbook.get_sample_data('logo2.png') as file:
im = image.imread(file)
fig, ax = plt.subplots()
fig.figimage(im, 10, 10, zorder=-1, alpha=.5)
ax.plot(np.sin(10 * np.linspace(0, 1)), '-o', ms=20,
alpha=0.7, mfc='orange')
ax.set_facecolor('none')
plt.show()

How can I use SymLogNorm with matplotlib but still have colorbar appear linear?

Is there a way to use SymLogNorm with imshow, but make the colorbar basically stretch the colors so that the colorbar actually appears linear?
Below is a short code
from pylab import *
import numpy as np
from matplotlib.colors import SymLogNorm
data = np.random.uniform(low=-10, high=10, size=(10,10))
norm = SymLogNorm(2,vmin=-10,vmax=10)
fig, axes = plt.subplots()
im = axes.imshow(data,extent=[-10,10,-10,10],cmap=plt.cm.jet,norm=norm)
cb = fig.colorbar(im)
that produces this
I basically want this image, but want to stretch the colorbar so the ticks appear linear, not log.

How to change the frequency of labeling the x and y axis in matplotlib in python?

I am trying to plot a circle with a grid being shown. I wrote the following script which gives the below picture. However, the labels on the axes are interfering together. How to make the label appear (..,-10,-5,0,5,10,...) KEEPING the grid as it appears in the below figure?. I want to keep the dimension of the grid cell as 1*1 dimension.
I tried to use plt.locator_params(), but the dimension of the grid cell changed and became bigger.
import numpy as np
import matplotlib.pyplot as plt
import math
from matplotlib.pyplot import figure
R1=28
n=64
t=np.linspace(0, 2*np.pi, n)
x1=R1*np.cos(t)
y1=R1*np.sin(t)
plt.axis("square")
plt.grid(True, which='both', axis='both')
plt.xticks(np.arange(min(x1)-2,max(x1)+2, step=1))
plt.yticks(np.arange(min(y1)-2,max(y1)+2, step=1))
#plt.locator_params(axis='x', nbins=5)
#plt.locator_params(axis='y', nbins=5)
plt.plot(x1,y1)
plt.legend()
plt.show()
Not a matplotlib expert, so there may be a better way to do this, but perhaps like the following:
from matplotlib.ticker import MultipleLocator
...
fig, ax = plt.subplots(figsize=(6, 6))
ax.plot(x1,y1)
ax.xaxis.set_minor_locator(MultipleLocator())
ax.xaxis.set_major_locator(MultipleLocator(5))
ax.yaxis.set_minor_locator(MultipleLocator())
ax.yaxis.set_major_locator(MultipleLocator(5))
ax.grid(True, which='both', axis='both')
plt.show()

How do I add a colorbar with log data in an image/matrix

I have a 2D matrix I want to plot. The plotting itself works, but I need
a colorbar with it. The figure only makes sense when the data is
log-tranformed. And I want the colorbar show the original values. How
do I do this?
A search provided
A logarithmic colorbar in matplotlib scatter plot
but I cannot make this work.
The code below gives an idea of what I attempt to do. Only the revevant
lines are included (as far as I could see).
import matplotlib.pyplot as plt
from matplotlib import cm
import numpy as np
my_speed=np.ones(shape=(no,no))
fig=plt.figure(2)
ax=fig.add_subplot(1,1,1)
my_speed=np.log10(my_speed)
ax.imshow(my_speed, interpolation='bilinear', cmap=cm.jet)
plt.colorbar() #this does not work
plt.savefig('myspeedplot.png')
plt.close(2)
Thank you for any help
The idea is not to transform your data, but let the visualization do the trick for you.
pylot.imshow[1] has an optional parameter norm that can do the log transformation for you.
my_speed=np.ones(shape=(no,no))
fig = plt.figure(2)
ax = fig.add_subplot(1,1,1)
# my_speed=np.log10(my_speed)
img = ax.imshow(my_speed, interpolation='bilinear', cmap=cm.jet,
norm=mpl.colors.LogNorm())
fig.colorbar(img)
As far as I see, there are two problems with your code.
First, you are trying to have the ticks on colorbar show original values. For this you should not transform the data, but just normalize the plot.
And second, you are using the ax.imshow and this is why the colorbar does not see it. You should use plt.imshow or use im=ax.imshow and then colorbar(im)
Here is a working solution:
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
my_speed = np.random.rand(20, 20)
fig = plt.figure(2)
ax = fig.add_subplot(1, 1, 1)
im = ax.imshow(my_speed, interpolation='bilinear',
norm=mpl.colors.LogNorm(),
cmap=plt.cm.jet)
cb = plt.colorbar(im, orientation='vertical')
plt.show()

Aspect ratio in subplots with various y-axes

I would like the following code to produce 4 subplots of the same size with a common aspect ratio between the size of x-axis and y-axis set by me. Referring to the below example, I would like all of the subplots look exactly like the first one (upper left). What is wrong right now is that the size of the y-axis is correlated with its largest value. That is the behaviour I want to avoid.
import matplotlib.pyplot as plt
import numpy as np
def main():
fig = plt.figure(1, [5.5, 3])
for i in range(1,5):
fig.add_subplot(221+i-1, adjustable='box', aspect=1)
plt.plot(np.arange(0,(i)*4,i))
plt.show()
if __name__ == "__main__":
main()
Surprisingly, matplotlib produces the right thing by default (picture below):
import matplotlib.pyplot as plt
import numpy as np
def main():
fig = plt.figure(1, [5.5, 3])
for i in range(1,5):
fig.add_subplot(221+i-1)
plt.plot(np.arange(0,(i)*4,i))
plt.show()
I just want to add to this an ability to control the aspect ratio between lengths of x and y-axes.
I can't quite tell what you want from your question.
Do you want all of the plots to have the same data limits?
If so, use shared axes (I'm using subplots here, but you can avoid it if you want to stick to matlab-style code):
import matplotlib.pyplot as plt
import numpy as np
fig, axes = plt.subplots(nrows=2, ncols=2, sharey=True, sharex=True)
for i, ax in enumerate(axes.flat, start=1):
ax.set(aspect=1)
ax.plot(np.arange(0, i * 4, i))
plt.show()
If you want them all to share their axes limits, but to have adjustable='box' (i.e. non-square axes boundaries), use adjustable='box-forced':
import matplotlib.pyplot as plt
import numpy as np
fig, axes = plt.subplots(nrows=2, ncols=2, sharey=True, sharex=True)
for i, ax in enumerate(axes.flat, start=1):
ax.set(aspect=1, adjustable='box-forced', xticks=range(i))
ax.plot(np.arange(0, i * 4, i))
plt.show()
Edit: Sorry, I'm still a bit confused. Do you want something like this?
import matplotlib.pyplot as plt
import numpy as np
fig, axes = plt.subplots(nrows=2, ncols=2)
for i, ax in enumerate(axes.flat, start=1):
ax.set(adjustable='datalim', aspect=1)
ax.plot(np.arange(0, i * 4, i))
plt.show()
Okay, I think I finally understand your question. We both meant entirely different things by "aspect ratio".
In matplotlib, the aspect ratio of the plot refers to the relative scales of the data limits. In other words, if the aspect ratio of the plot is 1, a line with a slope of one will appear at 45 degrees. You were assuming that the aspect ratio applied to the outline of the axes and not the data plotted on the axes.
You just want the outline of the subplots to be square. (In which case, they all have different aspect ratios, as defined by matplotlib.)
In that case, you need a square figure. (There are other ways, but just making a square figure is far simpler. Matplotlib axes fill up a space that is proportional to the size of the figure they're in.)
import matplotlib.pyplot as plt
import numpy as np
# The key here is the figsize (it needs to be square). The position and size of
# axes in matplotlib are defined relative to the size of the figure.
fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(8,8))
for i, ax in enumerate(axes.flat, start=1):
ax.plot(np.arange(0, i * 4, i))
# By default, subplots leave a bit of room for tick labels on the left.
# We'll remove it so that the axes are perfectly square.
fig.subplots_adjust(left=0.1)
plt.show()
Combing the answer of Joe Kington with new pythonic style for shared axes square subplots in matplotlib?
and another post that I am afraid I cannot find it again, I made a code for precisely setting the ratio of the box to a given value.
Let desired_box_ratioN indicate the desired ratio between y and x sides of the box.
temp_inverse_axis_ratioN is the ratio between x and y sides of the current plot; since 'aspect' is the ratio between y and x scale (and not axes), we need to set aspect to desired_box_ratioN * temp_inverse_axis_ratioN.
import matplotlib.pyplot as plt
import numpy as np
fig, axes = plt.subplots(nrows=2, ncols=2)
desired_box_ratioN = 1
for i, ax in enumerate(axes.flat, start=1):
ax.plot(np.arange(0, i * 4, i))
temp_inverse_axis_ratioN = abs( (ax.get_xlim()[1] - ax.get_xlim()[0])/(ax.get_ylim()[1] - ax.get_ylim()[0]) )
ax.set(aspect = desired_box_ratioN * temp_inverse_axis_ratioN, adjustable='box-forced')
plt.show()
The theory
Different coordinate systems exists in matplotlib. The differences between different coordinate systems can really confuse a lot of people. What the OP want is aspect ratio in display coordinate but ax.set_aspect() is setting the aspect ratio in data coordinate. Their relationship can be formulated as:
aspect = 1.0/dataRatio*dispRatio
where, aspect is the argument to use in set_aspect method, dataRatio is aspect ratio in data coordinate and dispRatio is your desired aspect ratio in display coordinate.
The practice
There is a get_data_ratio method which we can use to make our code more concise. A work code snippet is shown below:
import matplotlib.pyplot as plt
import numpy as np
fig, axes = plt.subplots(nrows=2, ncols=2)
dispRatio = 0.5
for i, ax in enumerate(axes.flat, start=1):
ax.plot(np.arange(0, i * 4, i))
ax.set(aspect=1.0/ax.get_data_ratio()*dispRatio, adjustable='box-forced')
plt.show()
I have also written a detailed post about all this stuff here.

Categories

Resources