I have the following script:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits import mplot3d
nn = 400 # number of points along circle's perimeter
theta = np.linspace(0, 2*np.pi, nn)
rho = np.ones(nn)
# (x,y) represents points on circle's perimeter
x = np.ravel(rho*np.cos(theta))
y = np.ravel(rho*np.sin(theta))
fig, ax = plt.subplots()
plt.rcParams["figure.figsize"] = [6, 10]
ax = plt.axes(projection='3d') # set the axes for 3D plot
ax.azim = -90 # y rotation (default=270)
ax.elev = 21 # x rotation (default=0)
# low, high values of z for plotting 2 circles at different elev.
loz, hiz = -15, 15
# Plot two circles
ax.plot(x, y, hiz)
ax.plot(x, y, loz)
# set some indices to get proper (x,y) for line plotting
lo1,hi1 = 15, 15+nn//2
lo2,hi2 = lo1+nn//2-27, hi1-nn//2-27
# plot 3d lines using coordinates of selected points
ax.plot([x[lo1], x[hi1]], [y[lo1], y[hi1]], [loz, hiz])
ax.plot([x[lo2], x[hi2]], [y[lo2], y[hi2]], [loz, hiz])
ax.plot([0, 0, 0], [0, 0, 10])
ax.plot([0, 0, 0], [9, 0, 0])
ax.plot([0, 0, 0], [0, 8, 0])
plt.show()
At the end of the script, I would like to plot three lines in three directions. How to do that? Why this:
ax.plot([0, 0, 0], [0, 0, 10])
ax.plot([0, 0, 0], [9, 0, 0])
ax.plot([0, 0, 0], [0, 8, 0])
gives the line in same direction?
And I have a second question, please. How to make the cone more narrower (the base more similar to circle)?
Output now:
ax.plot([0, 0, 0], [0, 0, 10]) is giving plot the x and y coordinates of 3 points, but you haven't given any coordinates in the z direction. Remember the inputs to plot are x, y, z, not, as you seem to have assumed, (x0,y0,z0), (x1,y1,z1)
So this is drawing 3 "lines" where two of them start and end at x=y=z=0, and one of them extends to y=10. The other two ax.plot calls you have are doing similar things.
To draw three lines that start at the origin and each extend along one of the x, y, or z directions, you perhaps meant to use:
ax.plot([0, 0], [0, 0], [0, 10]) # extend in z direction
ax.plot([0, 0], [0, 8], [0, 0]) # extend in y direction
ax.plot([0, 9], [0, 0], [0, 0]) # extend in x direction
Note that this also makes your circles look more like circles
After commenting the last 3 lines of your code, the image is the output I am getting
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits import mplot3d
nn = 400 # number of points along circle's perimeter
theta = np.linspace(0, 2*np.pi, nn)
rho = np.ones(nn)
# (x,y) represents points on circle's perimeter
x = np.ravel(rho*np.cos(theta))
y = np.ravel(rho*np.sin(theta))
fig, ax = plt.subplots()
plt.rcParams["figure.figsize"] = [6, 10]
ax = plt.axes(projection='3d') # set the axes for 3D plot
ax.azim = -90 # y rotation (default=270)
ax.elev = 21 # x rotation (default=0)
# low, high values of z for plotting 2 circles at different elev.
loz, hiz = -15, 15
# Plot two circles
ax.plot(x, y, hiz)
ax.plot(x, y, loz)
# set some indices to get proper (x,y) for line plotting
lo1,hi1 = 15, 15+nn//2
lo2,hi2 = lo1+nn//2-27, hi1-nn//2-27
# plot 3d lines using coordinates of selected points
ax.plot([x[lo1], x[hi1]], [y[lo1], y[hi1]], [loz, hiz])
ax.plot([x[lo2], x[hi2]], [y[lo2], y[hi2]], [loz, hiz])
#ax.plot([0, 0, 0], [0, 0, 10])
#ax.plot([0, 0, 0], [9, 0, 0])
#ax.plot([0, 0, 0], [0, 8, 0])
plt.show()
You can see that the base is almost a perfect circle. Because you are also plotting lines in your figure, it is giving an illusion that the base in not a circle.
And regarding the lines in 3 different directions. Since this part of code
ax.plot([0, 0, 0], [0, 0, 10])
ax.plot([0, 0, 0], [9, 0, 0])
ax.plot([0, 0, 0], [0, 8, 0])
has all zeroes in X-Axis, it is essentially plotting the lines on Y-Axis only.
When I give some values in the X-Axis part, like this
ax.plot([1, 0, 0], [0, 0, 10])
ax.plot([0, 0, 5], [9, 0, 0])
ax.plot([0, 8, 0], [0, 8, 0])
The output is
I hope this is what you were asking.
Related
I have a code for plotting a cone with some text and arrows like axes. How to change this code to get the bases of the cone looking like circles? Is the problem in the settings of margins? Or is it necessary to define circles in another way?
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits import mplot3d
from matplotlib.patches import FancyArrowPatch
from mpl_toolkits.mplot3d import proj3d
class Arrow3D(FancyArrowPatch):
def __init__(self, xs, ys, zs, *args, **kwargs):
FancyArrowPatch.__init__(self, (0, 0), (0, 0), *args, **kwargs)
self._verts3d = xs, ys, zs
def draw(self, renderer):
xs3d, ys3d, zs3d = self._verts3d
xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, renderer.M)
self.set_positions((xs[0], ys[0]), (xs[1], ys[1]))
FancyArrowPatch.draw(self, renderer)
nn = 400 # number of points along circle's perimeter
theta = np.linspace(0, 2*np.pi, nn)
rho = np.ones(nn)
# (x,y) represents points on circle's perimeter
x = np.ravel(rho*np.cos(theta))
y = np.ravel(rho*np.sin(theta))
fig, ax = plt.subplots()
plt.rcParams["figure.figsize"] = [5, 5]
figsize = (5, 5)
ax = plt.axes(projection='3d') # set the axes for 3D plot
ax.azim = -88 # y rotation (default=270)
ax.elev = 13 # x rotation (default=0)
# Low, high values of z for plotting 2 circles at different elevation
loz, hiz = -15, 15
# Plot two circles
ax.plot(x, y, hiz)
ax.plot(x, y, loz)
# Set some indices to get proper (x,y) for line plotting
lo1,hi1 = 15, 15+nn//2
lo2,hi2 = lo1+nn//2-27, hi1-nn//2-27
# Plot 3d lines using coordinates of selected points
ax.plot([x[lo1], x[hi1]], [y[lo1], y[hi1]], [loz, hiz])
ax.plot([x[lo2], x[hi2]], [y[lo2], y[hi2]], [loz, hiz])
eps = 0.005
ax.plot([0, 0], [0, 0], [0, 20]) # extend in z direction
ax.plot([0-eps, 0], [0-eps, -5], [0-eps, 0]) # extend in y direction
ax.plot([0, 1.3], [0, 0], [0, 0]) # extend in x direction
ax.plot([0+eps, 0.6], [0+eps, -4], [0+eps, 16]) # v vector
ax.plot([0.63, 0.63], [-4, -4], [16, -0.005]) # vertical projection
ax.plot([0+eps, 0.6], [0+eps, -4], [0+eps, -0.005]) # to the beginning
ax.scatter(0, 0, 20, marker=(3, 0, 0), s=100, clip_on=False)
ax.scatter(0, -5, 0, marker=(3, 0, 43), s=100, clip_on=False)
ax.scatter(1.3, 0, 0, marker=(3, 0, 30), s=100, clip_on=False)
ax.scatter(0.6, -4, 16, marker=(3, 0, 80), s=100, clip_on=False)
ax.scatter(0.6, -4, -0.005, marker=(3, 0, 0), s=100, clip_on=False)
a2 = Arrow3D([0.14, -0.515], [-5.581, 1.358], [14.73, 4.983], mutation_scale=20, arrowstyle="-|>", color="k", connectionstyle="arc3,rad=0.3")
ax.add_artist(a2)
ax.text3D(0.23, -5.23, 23.33, r'$A$')
ax.text3D(1.41, 1.29, -2.7, r'$B$')
ax.text3D(-0.31, 1.46, -12.6, r'$C$')
ax.text3D(0.4, -5.48, 17, r'$D$')
ax.text3D(0.64, 1.57, -9.95, r'$E$')
ax.text3D(-0.2, -5.5, 15.73, r'$F$')
# Hide axes
ax._axis3don = False
# Save the figure (.pdf)
margins = { # vvv margin in inches
"left" : 1 / figsize[0],
"bottom" : -2.45 / figsize[1],
"right" : 1 - 0.5 / figsize[0],
"top" : 1 + 1.8 / figsize[1]
}
fig.subplots_adjust(**margins)
plt.savefig('output.pdf')
plt.show()
From this code I got the following output:
The desired output is bases looking like a circle.
This is the view from above:
It is not a circle but an ellipse.
Desired shape of the cone:
I am struggling with tweaking a plot, I have been working on.
I am facing to two problems:
The plots should be adjacent and with 0 wspace and hspace. I set both values to zero but still there are some spaces between the plots.
I would like to have one colorbar for all the subplots (they all the same range). Right now, the code adds a colorbar to the last subplot as i understand that it needs the third return value of hist2D.
Here is my code so far:
def plot_panel(pannel_plot):
fig, ax = plt.subplots(3, 2, figsize=(7, 7), gridspec_kw={'hspace': 0.0, 'wspace': 0.0}, sharex=True, sharey=True)
fig.subplots_adjust(wspace=0.0)
ax = ax.flatten()
xmin = 0
ymin = 0
xmax = 0.19
ymax = 0.19
hist2_num = 0
h =[]
for i, j in zip(pannel_plot['x'].values(), pannel_plot['y'].values()):
h = ax[hist2_num].hist2d(i, j, bins=50, norm=LogNorm(vmin=1, vmax=5000), range=[[xmin, xmax], [ymin, ymax]])
ax[hist2_num].set_aspect('equal', 'box')
ax[hist2_num].tick_params(axis='both', top=False, bottom=True, left=True, right=False,
labelsize=10, direction='in')
ax[hist2_num].set_xticks(np.arange(xmin, xmax, 0.07))
ax[hist2_num].set_yticks(np.arange(ymin, ymax, 0.07))
hist2_num += 1
fig.colorbar(h[3], orientation='vertical', fraction=.1)
plt.show()
And the corrsiponding result:
Result
I would be glad for any heads up that i am missing!
You can use ImageGrid, which was designed to make this kind of things easier
data = np.vstack([
np.random.multivariate_normal([10, 10], [[3, 2], [2, 3]], size=100000),
np.random.multivariate_normal([30, 20], [[2, 3], [1, 3]], size=1000)
])
from mpl_toolkits.axes_grid1 import ImageGrid
fig = plt.figure(figsize=(4, 6))
grid = ImageGrid(fig, 111, # similar to subplot(111)
nrows_ncols=(3, 2), # creates 2x2 grid of axes
axes_pad=0.1, # pad between axes in inch.
cbar_mode="single",
cbar_location="right",
cbar_pad=0.1
)
for ax in grid:
h = ax.hist2d(data[:, 0], data[:, 1], bins=100)
fig.colorbar(h[3], cax=grid.cbar_axes[0], orientation='vertical')
or
data = np.vstack([
np.random.multivariate_normal([10, 10], [[3, 2], [2, 3]], size=100000),
np.random.multivariate_normal([30, 20], [[2, 3], [1, 3]], size=1000)
])
from mpl_toolkits.axes_grid1 import ImageGrid
fig = plt.figure(figsize=(4, 6))
grid = ImageGrid(fig, 111, # similar to subplot(111)
nrows_ncols=(3, 2), # creates 2x2 grid of axes
axes_pad=0.1, # pad between axes in inch.
cbar_mode="single",
cbar_location="top",
cbar_pad=0.1
)
for ax in grid:
h = ax.hist2d(data[:, 0], data[:, 1], bins=100)
fig.colorbar(h[3], cax=grid.cbar_axes[0], orientation='horizontal')
grid.cbar_axes[0].xaxis.set_ticks_position('top')
I would like to have names of axes as in the figure.
This could be a good starter. Try experiment with it.
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
fig = plt.figure(figsize=[8,8])
ax = fig.gca(projection = '3d')
# some settings
vleng = 4
aleng = vleng/3.
p = np.array([vleng, 0, 0])
q = np.array([0, vleng, 0])
r = np.array([0, 0, vleng])
ax.plot(*np.vstack([[0,0,0], p]).T, color='b')
ax.plot(*np.vstack([[0,0,0], q]).T, color='g')
ax.plot(*np.vstack([[0,0,0], r]).T, color='r')
# plotting arrow at terminal of the lines
ax.quiver(vleng, 0, 0, aleng, 0, 0, \
length=0.5, arrow_length_ratio=0.5, color='r')
ax.quiver(0, vleng, 0, 0, aleng, 0, \
length=0.5, arrow_length_ratio=0.5, color='m')
ax.quiver(0, 0, vleng, 0, 0, aleng, \
length=0.5, arrow_length_ratio=0.5, color='k')
ax.text3D(vleng+1.5, 0, 0, 'X')
ax.text3D(0, vleng+1.0, 0, 'y')
ax.text3D(0, 0, vleng+1.0, 'z')
ax.azim = 35 # y rotation (default=270)
ax.elev = 20 # x rotation (default=0)
ax.dist = 15 # zoom (define perspective)
ax.set_axis_off( ) # hide all grid
ax.set_aspect('equal')
# plot poly1
ax.plot3D( [3.5, 0.25, 2, 3.5], [1, 0.25, 2.5, 1], [1.9, 3.2, 3.8, 1.9], label = 'one line', color='pink' )
# projection of poly1 on xy-plane
ax.plot3D( [3.5, 0.25, 2, 3.5], [1, 0.25, 2.5, 1], [0, 0, 0, 0], label = 'one line', color='gray' )
#ax.legend()
plt.show()
I would like to use nested circles as legend in Python in a seaborn scatterplot where I used their size to indicate a quantity.
For now I managed, using the legend's labels and handles, to get two circles for the two extremes. Would you know how I can nest them?
I have in mind something like this:
I tried to draw stacked circles in legend by writing Handlers. Here is the implementation.
This will give the figure like output image and would be modified to what you need.
import matplotlib.pyplot as plt
from matplotlib.lines import Line2D
from matplotlib.collections import PathCollection
from matplotlib.legend_handler import HandlerLine2D, HandlerPathCollection
class HandlerPath(HandlerPathCollection):
"""
Custom Handler for HandlerPathCollection instances.
"""
def create_artists(self, legend, orig_handle,
xdescent, ydescent, width, height, fontsize, trans):
xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent,
width, height, fontsize)
leglines = []
legline_1 = Line2D([10], [5], marker='o', markerfacecolor='darkred', markersize=10, alpha=0.3)
legline_2 = Line2D([10], [5], marker='o', markerfacecolor='darkred', markersize=20, alpha=0.3)
legline_1.set_color("darkred")
legline_2.set_color("darkred")
leglines.append(legline_1)
leglines.append(legline_2)
return leglines
fig, ax = plt.subplots()
l = ax.plot([0.5, 0, -0.5], [0.5, 0, -0.5], linestyle = '--', color='darkred', marker='o', label="TinyTL")
c = ax.scatter([0.5, 0, -0.5], [0.5, 0, -0.5] , s = 1e3, alpha=0.3, color="darkred")
ax.legend(
l+[c], ["line", "circles"],
handler_map={
Line2D: HandlerLine2D(),
PathCollection: HandlerPath()
},
handlelength=2.5, handleheight=3
)
plt.savefig('test.png')
Hope this helps.
Recently, I want to achieve the same legend of the nested circles. The following is my implementation:
import matplotlib.pyplot as plt
import matplotlib.legend_handler as mhandler
import pandas as pd
# Sample data
data = [[ 0, 2, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 2, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 2, 0],
[ 2, 0, 6, 0, 0, 6, 0, 0],
[ 2, 2, 2, 2, 3, 0, 0, 2],
[ 8, 0, 8, 0, 0, 0, 0, 0],
[ 6, 6, 6, 2, 0, 0, 0, 0],
[10, 6, 2, 0, 0, 2, 0, 0],
[10, 10, 2, 0, 4, 2, 0, 0],
[ 8, 9, 8, 20, 10, 0, 8, 2]]
df = pd.DataFrame(data).reset_index().melt(id_vars=['index'])
fig, ax = plt.subplots(figsize=(7, 7), dpi=100)
bubbles = ax.scatter(
df['index'], df['variable'],
s=df['value']*70, # So that the marker is larger
marker='o',
c='#FFB24D', edgecolor='#FF6A1C', lw=1.5,
)
# Set markers' properties before they are used as the legend handles.
def set_marker_color(handle, orig_handle):
handle.update_from(orig_handle)
handle.set_facecolor('w')
handle.set_edgecolor('k')
legend = ax.legend([bubbles], ['value'], handletextpad=2,
scatterpoints=3, # Choose three marker points for a legend entry
handler_map={
type(bubbles):
mhandler.HandlerPathCollection(
sizes=[30*70, 10*70, 2*70], # Choose the corresponding size.
marker_pad=1, # So that all points have the same x coordinate.
yoffsets=[0, -1.2, -2.1], # Offset in the y direction to get the expected layout.
update_func=set_marker_color)}, # If not, the legend handles will be same as the original handles.
frameon=False,
loc='lower left',
bbox_to_anchor=(0.01, 1.05))
After that, you can use ax.text or ax.annotate to add some labels in the legend handles.
However, I haven't figured out any way to add the number labels automatically, or to get the marker_pad and yoffsets automatically.
I hope this can be a start, and someone can find a more generic way to achieve this kind of legend.
I'm getting the following error when calling .backward():
Encounter the RuntimeError: one of the variables needed for gradient computation has been modified by an inplace operation
Here's the code:
for i, j, k in zip(X, Y, Z):
A[:, i, j] = A[:, i, j] + k
I've tried .clone(), torch.add(), and so on.
Please help!
After the comments I'm a bit confused about what you want to accomplish. The code you gave gives me an error using the dimensions you provided in the comments
Traceback (most recent call last):
A[:, i, j] = A[:, i, j] + k
RuntimeError: The size of tensor a (32) must match the size of tensor b (200) at non-singleton dimension 0
But here's what I think you want to do, please correct me in the comments if this is wrong...
Given tensors X, Y, and Z, each entry of X, Y, and Z correspond to a coordinate (x,y) and a value z. What you want is to add z to A at coordinate (x,y). For most cases the batch dimension is kept independent, although its not clear that's the case in the code you posted. For now that's what I'll assume you want to do.
For example lets say A contains all zeros and has shape 3x4x5 and X,Y are shape 3x3 and Z is shape 3x3x1. For this example let's assume A contains all zeros to start, and X, Y, and Z have the following values
X = tensor([[1, 2, 3],
[1, 2, 3],
[2, 2, 2]])
Y = tensor([[1, 2, 3],
[1, 2, 3],
[1, 1, 1]])
Z = tensor([[[0.1], [0.2], [0.3]],
[[0.4], [0.5], [0.6]],
[[0.7], [0.8], [0.9]]])
Then we would expect A to have the following values after the operation
A = tensor([[[0, 0, 0, 0, 0],
[0, 0.1, 0, 0, 0],
[0, 0, 0.2, 0, 0],
[0, 0, 0, 0.3, 0]],
[[0, 0, 0, 0, 0],
[0, 0.4, 0, 0, 0],
[0, 0, 0.5, 0, 0],
[0, 0, 0, 0.6, 0]],
[[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 2.4, 0, 0, 0],
[0, 0, 0, 0, 0]]])
In order to accomplish this we can make use to the index_add function which allows us to add to a list of indices. Since this only supports 1-dimensional operations we first need to convert X,Y to a linear index for flattened tensor A. Afterwards we can un-flatten to the original shape.
layer_size = A.shape[1] * A.shape[2]
index_offset = torch.arange(0, A.shape[0] * layer_size, layer_size).unsqueeze(1)
indices = (X * A.shape[2] + Y) + index_offset
A = A.view(-1).index_add(0, indices.view(-1), Z.view(-1)).view(A.shape)