Get X,Y Position in Figure (not in Plot) - python

I have a question about the X and Y Position in figures. How you can see I am working with gridspec for a better layout and adding Text to a figure. The problem is that I am trying to get the exact Position manually. Which means I am changing the X and Y in fig.text(0.2, 0.5, 'matplotlib') until I get the final figure.
import matplotlib.pylab as plt
import numpy as np
vector = np.arange(0,100)
time = np.arange(0,vector.shape[0])
fig = plt.figure(figsize=(10,10))
plt.rcParams['axes.grid'] = True
gs = fig.add_gridspec(2, 2)
ax1 = fig.add_subplot(gs[0, :])
ax1.plot(time,vector)
fig.text(0.2, 0.5, 'matplotlib')
At Link I already found an interactive solution but its only working for the Plot.
Does someone have an idea how to manage this?

You can create a blended transform, where the y-coordinates have a figure transform. And the x-coordinates have a axes transform. The figure transform is measured 0 at the left/bottom and 1 at the right/top of the figure. The axes transform is similar, but regarding the axes. The parameter clip_on=False allows to draw outside the axes region (text allows this by default).
import matplotlib.transforms as mtransforms
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
fig, ax = plt.subplots(gridspec_kw={})
# the x coords of this transformation are axes, and the y coord are fig
trans = mtransforms.blended_transform_factory(ax.transAxes, fig.transFigure)
x, w = 1, -0.3 # axes coordinates
y, h = 0.04, 0.06 # figure coordinates
ax.text(x + w / 2, y + h / 2, 'hello', transform=trans, ha='center', va='center')
rect = mpatches.Rectangle((x, y), w, h, transform=trans, edgecolor='crimson', facecolor='yellow', clip_on=False)
ax.add_patch(rect)
fig.tight_layout(pad=2)
plt.show()
PS: You can set the vertical alignment va='right' to have the right margin of the text align with the right axis. You can also use transform=ax.transAxes with negative y-coordinates to plot everything relative to the axes.

Related

Set the size and location of a subplot within a window of specific size in matplotlib

I want to exactly define the location and size of a single subplot within an entire plot. I define the subplot like below:
plt.figure(figsize=(12, 12)) # ignored
fig, ax = plt.subplots(1, 1, figsize=get_figsize(min_bounds, max_bounds), dpi=150)
It seems to me that the code above does NOT affect the size of the subplot, but of the entire plot (I want to set the size of the entire plot independently). Ideally I would like to say: "I want to place my subplot of size (x, y) at the position (e.g. top-left coordinate of subplot) (u, v) within my window of bigger size". How could I achieve that?
Sounds like you may want Figure.add_axes This method takes a list of 4 values as its argument to specify the coordinate of the lower left hand corner of the Axes as well as its width and height: [x, y, width, height]
The coordinate system on the Figure is fractional meaning its horizontal axis spans from 0 to 1, and its vertical axis also spans from 0 to 1.
import matplotlib.pyplot as plt
from numpy import linspace, sin
from numpy.random import default_rng
rng = default_rng(0)
xs = linspace(0, 5)
sin_ys = sin(xs)
linear_ys = xs + rng.normal(scale=4, size=xs.size)
fig = plt.figure()
# add axes whose lower left corner is at (.2, .7) with width of .4 and height of .2
ax0 = fig.add_axes([.2, .7, .4, .2])
ax0.plot(xs, sin_ys)
# add axes whose lower left corner is at (.7, .1) with width of .2 and height of .5
ax1 = fig.add_axes([.7, .1, .2, .5])
ax1.scatter(xs, linear_ys, s=8)
plt.show()
As a note- if you're working in a Jupyter Notebook, make sure you are either saving the plot manually and viewing it, or using a GUI backend (like %matplotlib nbagg) or else the default for Jupyter (%matplotlib inline) will tighten your layout for you by default, removing empty whitespace from your figure.
update with set_aspect('equal') Note that when using this method, your width or height will change in accordance with the plotted data. This is unavoidable.
import matplotlib.pyplot as plt
from numpy import linspace, sin
from numpy.random import default_rng
fig = plt.figure(figsize=(12, 6))
y = 0
w, h = .2, 1
ax0 = fig.add_axes([0, y, w, h])
ax0.set_title('original')
ax1 = fig.add_axes([.25, y, w, h])
# anchor='C' is the default
ax1.set_aspect('equal', anchor='C')
ax1.set_title('equal aspect\nCentered anchor')
ax2 = fig.add_axes([.5, y, w, h])
# Resize & move plot to the lower right (South East; SE)
# corner of the original Axes area
ax2.set_aspect('equal', anchor='SE')
ax2.set_title('equal aspect\nSE anchor')
ax3 = fig.add_axes([.75, y, w, h])
# Resize & move plot to the lower right (South East; SE)
# corner of the original Axes area
ax3.set_aspect('equal', anchor='NW')
ax3.set_title('equal aspect\nNW anchor')
for ax in fig.axes:
ax.yaxis.set_visible(False)
ax.xaxis.set_visible(False)
plt.show()

matplotlib set_data() not updating plot on next draw()

I have a 2D plot placed on one of the walls of a 3D plot that doesn't seem to reflect any changes from set_data(), I would like to understand what I'm doing wrong here.
Here is a sample code showing the 3D plot with the 2D 'projection' plot in question.
The output is shown here:
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['legend.fontsize'] = 10
fig = plt.figure()
ax = fig.gca(projection='3d')
# Test data for projection onto xz plane
t = linspace(0,10, num=20)
z = np.sin(t)
# Plot projection
projx, = ax.plot(np.linspace(-1,0, num=len(z)), z, 'r', zdir='y', zs=1)
# Labels and scaling
ax.set_xlabel('M_x')
ax.set_ylabel('M_y')
ax.set_zlabel('M_z')
ax.set_xlim(-1, 1)
ax.set_ylim(-1, 1)
ax.set_zlim(-1, 1)
# Update projection data
projx.set_data([0],[0])
# See if actually updated data
print(projx.get_xdata())
# Draw and display window
plt.draw()
ax.legend()
plt.show()
I imagine that this line:
projx.set_data([0],[0])
would make the projection plot virtually empty. Instead, the sine wave remains.
Furthermore, the printout yields [0] as expected, so the set_data() call was successful, but for some reason the plot doesn't get drawn with the new data.
Shouldn't the set_data() changes be reflected when drawn afterwards?
There is a way to update a Line3D object by directly setting its vertices. Not sure, if this might have any negative side effects, though.
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.gca(projection='3d')
# Test data for projection onto xz plane
t = np.linspace(0,10, num=20)
z = np.sin(t)
# Plot projections
projx, = ax.plot(np.linspace(-1,0, num=len(z)), z, 'r', zdir='y', zs=1, label="changed")
projy, = ax.plot(np.linspace(-1,0, num=len(z)), z, 'b', zdir='x', zs=-1, label="not changed")
# Labels and scaling
ax.set_xlabel('M_x')
ax.set_ylabel('M_y')
ax.set_zlabel('M_z')
ax.set_xlim(-1, 1)
ax.set_ylim(-1, 1)
ax.set_zlim(-1, 1)
#update vertices of one Line3D object
projx._verts3d = [0, 0.2, 0.7], [1, 1, 1], [0.5, 0.2, 0.7]
ax.legend()
plt.show()
Sample output:
However, since one cannot omit any of the x, y, and z arrays, there is no real advantage over plotting it as a 3D array with one array being a constant.

Matplotlib overlaying imshow and pie chart

I am trying to overlay a pie chart over radial gradient as a background. It does work, but I want the figure to show the entire radial gradient (without the figure boundary being cut as a square).
How do I acheive this?
I want something like this to this
Thank you.
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
delta = 0.02
x = y = np.arange(-1.0, 1.0, delta)
X, Y = np.meshgrid(x, y)
plt.imshow(-X**2+-Y**2, interpolation='bicubic', cmap=plt.cm.Blues,
origin='lower',extent=[-1.2, 1.2, -1.2, 1.2])
plt.pie([1] , explode=None, radius=1.2 \
, labels=['H'] \
, labeldistance=0.9 \
, startangle=270 \
, colors=[g(0.70)])
plt.show()
It will be hard to get this to work in the same axes. Better create two axes, one for the background image and one for the pie. This allows to tweak them individually, i.e. the background axes should range over the complete figure and have an adjustable aspect ratio, while the pie axes should have some margins and an equal aspect.
The following is an example that positions the pie chart (ax) on top of the imshow plot (ax2).
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
fig.subplots_adjust(.1,.1,.9,.9)
ax.set_aspect("equal")
ax.pie([1,2], radius=1.2 , labels=['H',"A"] , labeldistance=0.9,
startangle=270 , colors=[plt.cm.Blues(1.), plt.cm.Blues(0.5)])
ax2 = fig.add_axes([0,0,1,1], zorder=-1)
ax2.axis("off")
w,h = fig.get_size_inches()
x = np.linspace(-1, 1)*w/h
y = np.linspace(-1, 1)
X, Y = np.meshgrid(x, y)
ax2.imshow(-X**2-Y**2, interpolation='bicubic', cmap=plt.cm.Blues,
origin='lower', aspect="auto", vmin=-0.99)
plt.show()

Legend specifying 3d position in 3D axes matplotlib

I have a scatter 3d plot using matplotlib.
What I'm trying to do is to position the legend inside the plot. I have read the documentation and it seems that is only possible to select predefined positions or only specify x and y coordinates.
Is there a way to position the legend specifying the 3 coordinates?
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
#more code
ax.legend(loc=(0.5,0.5,0.5), frameon=0)
The last line is what I thought might work but obviously is not working.
This is what I've now:
I'm trying to position the legend inside the axes, sort of like:
I reached that position by trial and error using ax.legend(loc=(0.15,0.65),frameon=0) because the legend doesn't move as the axes are rotated. The issue is that I'll be doing several plots thus I'm trying to avoid the trial and error approach.
Thanks.
To place the legend in a 3D plot using data coordinates, one may first get the projected coordinates of a point in 3D space using
mpl_toolkits.mplot3d.proj3d.proj_transform(x,y,z, ax.get_proj())
and provide those to the bbox_to_anchor argument of the legend. Than changing the bbox_transform to the data coordinate system produces the desired plot.
The following places the lower left corner of the legend at position (70,1000,80) in data coordinates.
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D, proj3d
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
theta = np.linspace(-4 * np.pi, 4 * np.pi, 100)
z = np.linspace(-2, 2, 100)
r = z**2 + 1
x = 25 * r * np.sin(theta)
y = 350* r * np.cos(theta)
ax.plot(x, y, 70*z, label='parametric curve')
ax.plot(x*.6, y*0.5, 70*z, label='parametric curve 2')
f = lambda x,y,z: proj3d.proj_transform(x,y,z, ax.get_proj())[:2]
ax.legend(loc="lower left", bbox_to_anchor=f(70,1000,80),
bbox_transform=ax.transData)
plt.show()

Matplotlib Half color axis

I am using matplotlib to make some plots and I have run into a few difficulties that I need help with.
problem 1) In order to keep a consistent colorscheme I need to only use half of the color axis. There are only positive values, so I want the zero values to be green, the mid values to be yellow and the highest values to be red. The color scheme that most closely matches this is gist_rainbow_r, but I only want the top half of it.
problem 2) I can't seem to figure out how to get the colorbar on the right hand side of the plot to show up or how to get it to let me label the axes.
If it helps, I am using the latest version of Anaconda wth the latext version of matplotlib
cmap = plt.get_cmap('gist_rainbow_r')
edosfig2 = plt.figure(2)
edossub2 = edosfig.add_subplot(1,1,1)
edossub2 = plt.contourf(eVec,kints,smallEDOS,cmap=cmap)
edosfig2.show()
If you have a specific set of colors that you want to use for you colormap, you can build it based on those. For example:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
cmap = LinearSegmentedColormap.from_list('name', ['green', 'yellow', 'red'])
# Generate some data similar to yours
y, x = np.mgrid[-200:1900, -300:2000]
z = np.cos(np.hypot(x, y) / 100) + 1
fig, ax = plt.subplots()
cax = ax.contourf(x, y, z, cmap=cmap)
cbar = fig.colorbar(cax)
cbar.set_label('Z-Values')
plt.show()
However, if you did just want the top half of some particularly complex colormap, you can copy a portion of it by evaluating the colormap over the range you're interested in. For example, if you wanted the "top" half, you'd evaluate it from 0.5 to 1:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
# Evaluate an existing colormap from 0.5 (midpoint) to 1 (upper end)
cmap = plt.get_cmap('gist_earth')
colors = cmap(np.linspace(0.5, 1, cmap.N // 2))
# Create a new colormap from those colors
cmap2 = LinearSegmentedColormap.from_list('Upper Half', colors)
y, x = np.mgrid[-200:1900, -300:2000]
z = np.cos(np.hypot(x, y) / 100) + 1
fig, axes = plt.subplots(ncols=2)
for ax, cmap in zip(axes.flat, [cmap, cmap2]):
cax = ax.imshow(z, cmap=cmap, origin='lower',
extent=[x.min(), x.max(), y.min(), y.max()])
cbar = fig.colorbar(cax, ax=ax, orientation='horizontal')
cbar.set_label(cmap.name)
plt.show()

Categories

Resources