Matplotlib 3D plot with image - image is not visible - python

I have a simple plot, where I want to insert image of UAV, but it doesn't show up. I believe that the annotation box is somewhere out of area of plot, but can't figure out where to move it. Currently I want to have it at [2,4], just to test.
Here is my code:
from mpl_toolkits import mplot3d
import numpy as np
import matplotlib.pyplot as plt
import random
from matplotlib.offsetbox import (OffsetImage, AnnotationBbox)
import matplotlib.image as image
fig = plt.figure()
ax = plt.axes(projection="3d")
num_bars = 3
x_pos = random.sample(range(20), num_bars)
y_pos = random.sample(range(20), num_bars)
z_pos = [0] * num_bars
x_size = np.ones(num_bars)
y_size = np.ones(num_bars)
z_size = random.sample(range(20), num_bars)
#ax.bar3d(x_pos, y_pos, z_pos, x_size, y_size, z_size, color='grey')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
ax.set_xlim(0,20)
ax.set_ylim(0,20)
ax.set_zlim(0,30)
"""
ax.set_xticks([])
ax.set_yticks([])
ax.set_zticks([])
"""
img="./UAV.png"
uav = image.imread(img)
arr_img = plt.imread("./UAV.png", format='png')
imagebox = OffsetImage(arr_img, zoom = .15)
imagebox.image.axes = ax
#ab = AnnotationBbox(imagebox, (5, 10), xybox = (5.0, 10.0), box_alignment=(1, 0))
ab = AnnotationBbox(imagebox, [2., 4.],
xycoords='data',
boxcoords="offset points",
pad=0
)
ax.add_artist(ab)
ax.bar3d(0,0,0,4,4,25,color="grey")
ax.bar3d(16,16,0,4,4,27,color="grey")
ax.bar3d(0,16,0,4,4,23,color="grey")
plt.tight_layout()
plt.show()

I could not find the problem with annotation box, but I have managed to fix this by adding the image to the plot by imshow instead. See the code:
arr_img = plt.imread("./UAV.png", format='png')
newax = fig.add_axes([0.45,0.5,0.2,0.2], anchor='NE', zorder=1)
newax.imshow(arr_img)
newax.patch.set_alpha(0.01)
newax.get_xaxis().set_ticks([])
newax.get_yaxis().set_ticks([])
newax.spines['top'].set_visible(False)
newax.spines['right'].set_visible(False)
newax.spines['bottom'].set_visible(False)
newax.spines['left'].set_visible(False)
output:

Related

moving AnnotationBbox after intital draw in matplotlib in jupyter notebook

I would like to draw annotation images in a matplotlib plot, and be able to move them after plotting.
To that end, I started from the demo:
https://matplotlib.org/3.2.1/gallery/text_labels_and_annotations/demo_annotation_box.html
with the notebook backend:
%matplotlib notebook
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Circle
from matplotlib.offsetbox import (TextArea, DrawingArea, OffsetImage,
AnnotationBbox)
from matplotlib.cbook import get_sample_data
fig, ax = plt.subplots()
# Define a 1st position to annotate (display it with a marker)
xy = (0.5, 0.7)
ax.plot(xy[0], xy[1], ".r")
# Annotate the 1st position with a text box ('Test 1')
offsetbox = TextArea("Test 1", minimumdescent=False)
ab = AnnotationBbox(offsetbox, xy,
xybox=(-20, 40),
xycoords='data',
boxcoords="offset points",
arrowprops=dict(arrowstyle="->"))
ax.add_artist(ab)
# Annotate the 1st position with another text box ('Test')
offsetbox = TextArea("Test", minimumdescent=False)
ab = AnnotationBbox(offsetbox, xy,
xybox=(1.02, xy[1]),
xycoords='data',
boxcoords=("axes fraction", "data"),
box_alignment=(0., 0.5),
arrowprops=dict(arrowstyle="->"))
ax.add_artist(ab)
# Define a 2nd position to annotate (don't display with a marker this time)
xy = [0.3, 0.55]
# Annotate the 2nd position with a circle patch
da = DrawingArea(20, 20, 0, 0)
p = Circle((10, 10), 10)
da.add_artist(p)
ab = AnnotationBbox(da, xy,
xybox=(1.02, xy[1]),
xycoords='data',
boxcoords=("axes fraction", "data"),
box_alignment=(0., 0.5),
arrowprops=dict(arrowstyle="->"))
ax.add_artist(ab)
# Annotate the 2nd position with an image (a generated array of pixels)
arr = np.arange(100).reshape((10, 10))
im = OffsetImage(arr, zoom=2)
im.image.axes = ax
ab = AnnotationBbox(im, xy,
xybox=(-50., 50.),
xycoords='data',
boxcoords="offset points",
pad=0.3,
arrowprops=dict(arrowstyle="->"))
ax.add_artist(ab)
# Annotate the 2nd position with another image (a Grace Hopper portrait)
with get_sample_data("grace_hopper.png") as file:
arr_img = plt.imread(file, format='png')
imagebox = OffsetImage(arr_img, zoom=0.2)
imagebox.image.axes = ax
ab = AnnotationBbox(imagebox, xy,
xybox=(120., -80.),
xycoords='data',
boxcoords="offset points",
pad=0.5,
arrowprops=dict(
arrowstyle="->",
connectionstyle="angle,angleA=0,angleB=90,rad=3")
)
ax.add_artist(ab)
# Fix the display limits to see everything
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
plt.show()
To test how to move stuff, I execute in the next cell:
rnd = fig.canvas.renderer
print(rnd)
print(im.get_window_extent(rnd))
print(im.get_window_extent(rnd).get_points())
print(ab.xybox)
ab.xybox = (130, -70)
im._offset = (135.46666666666667, 301.6355555555555)
im.set_zoom(.5)
ab.update_positions(rnd)
print(ab.xybox)
print(im.get_window_extent(rnd).get_points())
ab.draw(rnd)
im.draw(rnd)
fig.draw(rnd)
ax.plot([0, 1], [0, 1])
plt.show()
fig.canvas.draw_idle()
and what I see is that I do get a break in the plotted line, showing that the annotationbbox is shifted, but all its content isn't. (here the line has been plotted first (blue), then all the shifting has been executed, with another line plot (orange))
Any suggestions on how I can actually move the image of Grace Hopper are highly appreciated.
This is not a direct answer to your question, but I was wondering a similar thing (and the matplotlib documentation is lacking), so I hacked up this example, which appears to work. The key thing seems to be using fig.canvas.draw() after every change. Also, I'm using matplotlib version 3.1.3
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.offsetbox import OffsetImage, AnnotationBbox
def onpick(evt):
x = (evt.xdata, evt.ydata)
ab.xybox = x
print(ab.xybox)
fig.canvas.draw()
pix = np.linspace(0, 1, 20)
x, y = np.meshgrid(pix, pix)
p = np.cos(2*np.pi*x)*np.sin(8*np.pi*y)
fig = plt.figure()
ax = fig.add_subplot(111)
im = OffsetImage(p, zoom=1, cmap = 'gray')
ab = AnnotationBbox(im, (0.5, 0.5), xycoords='data', frameon=False)
ax.add_artist(ab)
fig.canvas.mpl_connect('button_press_event', onpick)
plt.show()

Reduce horizontal colorbar padding

I'm trying to generate a plot with several axis, each one with their own colorbar (code below). If I use the default colorbar plotting I get too much horizontal spacing between the plot and the colorbar:
If I try to use the make_axes_locatable() method I get this horrible result:
What is going on and how can I fix this?
import numpy as np
from matplotlib import pyplot as plt
import matplotlib.gridspec as gridspec
from mpl_toolkits.axes_grid1 import make_axes_locatable
# Random data to plot
data = np.random.uniform(0., 1., (2, 100))
z = np.random.uniform(0., 10., 100)
# Define figure
fig = plt.figure(figsize=(30, 30))
gs = gridspec.GridSpec(12, 12)
for i in range(2):
ax = plt.subplot(gs[2 * i:2 + (2 * i), 0:8])
SC = plt.scatter(*data, c=z)
# Colorbar 1
cbar = plt.colorbar()
# Colorbar 2
# the_divider = make_axes_locatable(ax)
# color_axis = the_divider.append_axes("right", size="1%", pad=0.)
# cbar = plt.colorbar(SC, cax=color_axis)
cbar.set_label("test", fontsize=15, labelpad=10)
fig.tight_layout()
plt.savefig('test.png', dpi=300, bbox_inches='tight')
Use the pad argument of colorbar to set the padding between the axes and the colorbar. pad is given in units of the fraction of the original axes' size to use as space. Here e.g. pad=0.01 might make sense.
import numpy as np
from matplotlib import pyplot as plt
# Random data to plot
data = np.random.uniform(0., 1., (2, 100))
z = np.random.uniform(0., 10., 100)
# Define figure
fig, axes = plt.subplots(nrows=2, figsize=(30, 30))
for i, ax in enumerate(axes.flat):
sc = ax.scatter(*data, c=z)
cbar = fig.colorbar(sc, ax=ax, pad=0.01)
cbar.set_label("test", fontsize=15, labelpad=10)
fig.tight_layout()
plt.savefig('test.png', dpi=300, bbox_inches='tight')

How to make an animation over different values of n here?

I have written a code that plot some points and lines on the xy plane. It plots everything for a given value of n. So for different n I get my desired plots. But I want to animate these plots for different values of n, say, for n=1, 2, ..., 100. But I cannot do this animation.
Can anyone here help me to do this? Thank you.. I paste my code here:
My Code
import matplotlib as mpl
mpl.rc('text', usetex = True)
mpl.rc('font', family = 'serif')
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Circle
fig = plt.subplots()
ax = plt.axes(xlim=(-1.2, 1.2), ylim=(-1.2, 1.2))
plt.gca().set_aspect('equal', adjustable='box')
plt.style.use(['ggplot','dark_background'])
plt.rcParams['figure.figsize'] = (12, 8)
n = 10 #I want to animate this n.
p = 2
for k in range(0,n,1):
theta1 = np.pi + 2*k*np.pi / n
theta2 = np.pi + 2*p*k*np.pi / n
x, y = np.cos(theta1), np.sin(theta1)
x1, y1 = np.cos(theta2), np.sin(theta2)
plt.scatter(x, y, s=50, c='violet', zorder=3)
plt.plot([x,x1], [y,y1], color = 'w')
circle = plt.Circle((0, 0), 1, color='c', fill=False, lw = 1)
ax.add_artist(circle)
#Customize the axes and gridlines:
ax.grid(False)
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
ax.spines['left'].set_visible(False)
ax.spines['bottom'].set_visible(False)
#TickMarks Customization:
ax.set_xticks([])
ax.set_yticks([])
#plt.savefig('nthRoots.png', format='png', dpi=1000,bbox_inches='tight')
plt.show()
Output
Is it possible to animate n over different values?
EDIT: Here I donot have only scatter plots ...so I cannot understand how to do this job using those links..!
My Attempt
#Animation.
import matplotlib as mpl
mpl.rc('text', usetex = True) #for LaTex notation in the Plot
mpl.rc('font', family = 'serif')
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Circle
from matplotlib import animation, rc
rc('animation', html='html5')
fig = plt.subplots()
ax = plt.axes(xlim=(-1.2, 1.2), ylim=(-1.2, 1.2))
plt.gca().set_aspect('equal', adjustable='box')
plt.style.use(['ggplot','dark_background'])
plt.rcParams['figure.figsize'] = (12, 8)
p = 2
#Plotting Function:
def f(n):
for k in range(0,n,1):
theta1 = np.pi + 2*k*np.pi / n
theta2 = np.pi + 2*p*k*np.pi / n
x, y = np.cos(theta1), np.sin(theta1)
x1, y1 = np.cos(theta2), np.sin(theta2)
plt.scatter(x, y, s=50, c='violet', zorder=3)
plt.plot([x,x1], [y,y1], color = 'w')
circle = plt.Circle((0, 0), 1, color='c', fill=False, lw = 1)
ax.add_artist(circle)
#Customize the axes and gridlines:
ax.grid(False)
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
ax.spines['left'].set_visible(False)
ax.spines['bottom'].set_visible(False)
#TickMarks Customization:
ax.set_xticks([])
ax.set_yticks([])
plt.show()
#Now I try to define a function for animating n in f(n)
def animate(n):
f(n)
anim = animation.FuncAnimation(fig, animate,
frames=100, interval=100, blit=True)
#anim.save('Wave.mp4', writer = 'ffmpeg', fps = 2, dpi=500,extra_args=['-vcodec', 'libx264'])
That's all I had... But this idea didn't work...I think I have to properly define animate(n).
Any suggestion...! thanks.
Several problems in your code (most are unrelated to animations)
rcParams need to be defined before creating the figure
plt.subplots returns a tuple of figure and axes.
The animation must return a sequence of artist objects when blitting is used. You might turn it off though
plt.show() should be called once at the end of the script.
Correcting for those you get
import matplotlib as mpl
mpl.rc('font', family = 'serif')
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Circle
from matplotlib import animation, rc
plt.rcParams['figure.figsize'] = (12, 8)
plt.style.use(['ggplot','dark_background'])
fig, ax = plt.subplots()
p = 2
#Plotting Function:
def f(n):
ax.clear()
ax.set(xlim=(-1.2, 1.2), ylim=(-1.2, 1.2))
ax.set_aspect('equal', adjustable='box')
for k in range(0,n,1):
theta1 = np.pi + 2*k*np.pi / n
theta2 = np.pi + 2*p*k*np.pi / n
x, y = np.cos(theta1), np.sin(theta1)
x1, y1 = np.cos(theta2), np.sin(theta2)
plt.scatter(x, y, s=50, c='violet', zorder=3)
plt.plot([x,x1], [y,y1], color = 'w')
circle = Circle((0, 0), 1, color='c', fill=False, lw = 1)
ax.add_artist(circle)
#Customize the axes and gridlines:
ax.grid(False)
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
ax.spines['left'].set_visible(False)
ax.spines['bottom'].set_visible(False)
#TickMarks Customization:
ax.set_xticks([])
ax.set_yticks([])
anim = animation.FuncAnimation(fig, f, frames=100, interval=100, blit=False)
plt.show()

Set equal aspect in plot with colorbar

I need to generate a plot with equal aspect in both axis and a colorbar to the right. I've tried setting aspect='auto', aspect=1, and aspect='equal' with no good results. See below for examples and the MWE.
Using aspect='auto' the colorbars are of the correct height but the plots are distorted:
Using aspect=1 or aspect='equal' the plots are square (equal aspect in both axis) but the colorbars are distorted:
In both plots the colorbars are positioned too far to the right for some reason. How can I get a square plot with colorbars of matching heights?
MWE
import numpy as np
import matplotlib.gridspec as gridspec
import matplotlib.pyplot as plt
def col_plot(params):
gs, i, data = params
xarr, yarr, zarr = zip(*data)[0], zip(*data)[1], zip(*data)[2]
xmin, xmax = min(xarr), max(xarr)
ymin, ymax = min(yarr), max(yarr)
#plt.subplot(gs[i], aspect='auto')
plt.subplot(gs[i], aspect=1)
#plt.subplot(gs[i], aspect='equal')
plt.xlim(xmin, xmax)
plt.ylim(xmin, xmax)
plt.xlabel('$x axis$', fontsize=20)
plt.ylabel('$y axis$', fontsize=20)
# Scatter plot.
cm = plt.cm.get_cmap('RdYlBu_r')
SC = plt.scatter(xarr, yarr, marker='o', c=zarr, s=60, lw=0.25, cmap=cm,
zorder=3)
# Colorbar.
ax0 = plt.subplot(gs[i + 1])
cbar = plt.colorbar(SC, cax=ax0)
cbar.set_label('$col bar$', fontsize=21, labelpad=-2)
# Generate data.
data0 = np.random.uniform(0., 1., size=(50, 3))
data1 = np.random.uniform(0., 1., size=(50, 3))
# Create the top-level container
fig = plt.figure(figsize=(14, 25))
gs = gridspec.GridSpec(4, 4, width_ratios=[1, 0.05, 1, 0.05])
# Generate plots.
par_lst = [[gs, 0, data0], [gs, 2, data1]]
for pl_params in par_lst:
col_plot(pl_params)
# Output png file.
fig.tight_layout()
plt.savefig('colorbar_aspect.png', dpi=300)
You can use an AxesDivider to do that. I have modified your code a bit to make use of an AxesDivider.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
def col_plot(data):
xarr, yarr, zarr = zip(*data)[0], zip(*data)[1], zip(*data)[2]
xarr = [2*x for x in xarr]
xmin, xmax = min(xarr), max(xarr)
ymin, ymax = min(yarr), max(yarr)
fig = plt.figure()
ax0 = fig.add_subplot(111, aspect='equal')
plt.xlim(xmin, xmax)
plt.ylim(ymin, ymax)
plt.xlabel('$x axis$', fontsize=20)
plt.ylabel('$y axis$', fontsize=20)
# Scatter plot.
cm = plt.cm.get_cmap('RdYlBu_r')
SC = ax0.scatter(xarr, yarr, marker='o', c=zarr, s=60, lw=0.25, cmap=cm,
zorder=3)
the_divider = make_axes_locatable(ax0)
color_axis = the_divider.append_axes("right", size="5%", pad=0.1)
# Colorbar.
cbar = plt.colorbar(SC, cax=color_axis)
cbar.set_label('$col bar$', fontsize=21, labelpad=-2)
# Generate data.
data0 = np.random.uniform(0., 1., size=(20, 3))
col_plot(data0)
And here is the result (I changed your data so it spans a range of [0, 2] in the x-direction for demonstration purposes):
On Joseph Long's blog there is the following nice solution.
1) Define a colorbar function as:
from mpl_toolkits.axes_grid1 import make_axes_locatable
def colorbar(mappable):
ax = mappable.axes
fig = ax.figure
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.05)
return fig.colorbar(mappable, cax=cax)
2) Call colorbar(thing) when you want to make a colorbar. In your case:
SC = ax0.scatter(xarr, yarr, marker='o', c=zarr, s=60, lw=0.25, cmap=cm,
zorder=3)
colorbar(SC)
3) And you get:

Matplotlib - Annotations overlapping on chart, how do I evenly distribute them vertically?

I have a chart, with 95% confidence intervals as patches. Naturally some of the data points overlap. Because of this, I need the point labels to be dynamically spaced so they are human readable. I have the following code below. As you can see the labels currently overlap. Any suggestions how to get them not to overlap?
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import matplotlib.transforms as transforms
from matplotlib.font_manager import FontProperties
from matplotlib.pyplot import *
mypath = ['1,0.025','1.01,0.05','1.02,0.035','1.03,0.040']
fig = plt.figure()
ax = fig.add_subplot(111)
Distances = []
Confidence_Intervals = []
for line in mypath:
Distances.append(float(line.split(',')[0].strip()))
Confidence_Intervals.append(float(line.split(',')[1].strip()))
ind = np.arange(len(Distances))
data = np.array(Distances)
y_error = np.array(Confidence_Intervals)
circles = []
plt.xlim(-1,1)
plt.ylim(0.8,1.1)
for a in range(len(ind)):
ax.scatter(0, data[a], s=60, color='Black')
trans = transforms.blended_transform_factory(ax.transData, ax.transData)
circles.append(patches.Circle((0,data[a]),y_error[a], transform=trans, facecolor='yellow', alpha=0.5))
fig.set_size_inches(24,12)
for circle in circles:
ax.add_patch(circle)
labels = ['{0}'.format(i) for i in range(len(data))]
for label, x, y in zip(labels, ind, data):
plt.annotate(
label,
xy = (0, y), xytext = (100, 0),
textcoords = 'offset points', ha = 'right', va = 'bottom',
bbox = dict(boxstyle = 'round,pad=0.5', fc = 'yellow', alpha = 0.5),
arrowprops = dict(arrowstyle = '->', connectionstyle = 'arc3,rad=0'))
plt.grid(True)
plt.legend(loc=0, scatterpoints = 1)
plt.ylabel('Pairwise distance (FastTree)')
plt.xlabel('Clade pairing')
plt.tick_params(axis='both', which='minor', labelsize=8)
plt.title('Sample Patch Chart')
axes().set_aspect('equal', 'datalim')
plt.show()

Categories

Resources