给定线后的 3d 条形图
bar plot in 3d following a given line
我想在 3d 中绘制条形图。我知道如何使用以下代码做到这一点:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure(figsize=(10,10))
ax = fig.add_subplot(111, projection='3d')
nbins = 50
# for c, z in zip(['r', 'g', 'b', 'y'], [30, 20, 10, 0]):
ys = np.random.normal(loc=10, scale=10, size=2000)
hist, bins = np.histogram(ys, bins=nbins)
xs = (bins[:-1] + bins[1:])/2
ax.bar(xs, hist, zs=30, zdir='y', color='r', ec='r', alpha=0.8)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
plt.show()
这将呈现如下内容:https://i.stack.imgur.com/KK2If.png
但是,我的目标是使条形图遵循我作为参数给出的一条线。例如这里,参数 zdir='y' 使绘图具有当前方向。理想情况下,我想传递一个参数,使绘图遵循给定的线,例如 y=2x+1.
有人可以帮助达到预期的结果吗?
实现这一点的一种方法是使用 Poly3DCollection
:想法是计算每个条形的坐标和方向,然后将其添加到绘图中。
可以从 3D 矩形开始计算每个条形的位置和方向 space 并应用适当的变换矩阵。
如果您要更改 curve
,您还需要更改栏 width
。
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
from matplotlib.patches import Rectangle
################
# Generates data
################
nbins = 50
ys = np.random.normal(loc=10, scale=10, size=2000)
hist, bins = np.histogram(ys, bins=nbins)
xs = (bins[:-1] + bins[1:])/2
#################################################
# Create a single bar and a transformation matrix
#################################################
# rectangle of width=height=1, centered at x,y=0
# covering the z range [0, height]
rect = np.array([
[-0.5, 0, 0, 1],
[0.5, 0, 0, 1],
[0.5, 0, 1, 1],
[-0.5, 0, 1, 1],
])
def translate(x, y, z):
d = np.eye(4, dtype=float)
d[:, -1] = [x, y, z, 1]
return d
def scale(sx, sy, sz):
d = np.eye(4, dtype=float)
d[np.diag_indices(4)] = [sx, sy, sz, 1]
return d
def rotate(t):
d = np.eye(4, dtype=float)
d[:2, :2] = np.array([
[np.cos(t), -np.sin(t)],
[np.sin(t), np.cos(t)]])
return d
def transformation_matrix(t, x, y, z, w, h):
return translate(x, y, z) @ rotate(t) @ scale(w, 1, h)
def apply_transform(t, x, y, z, w, h):
"""Apply the transformation matrix to the rectangle"""
verts = transformation_matrix(t, x, y, z, w, h) @ rect.T
return verts.T
#################
# Create the plot
#################
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
curve = lambda x: 2 * x + 1
# curve = lambda x: np.sin(0.05 * x)
xstep = abs(xs[0] - xs[1])
# NOTE: chose an appropriate bar width
width = xstep * 1.5
ys = curve(xs)
# previous bar coordinates
xp = np.roll(xs, 1)
yp = np.roll(ys, 1)
xp[0] = xs[0] - xstep
yp[0] = curve(xp[0])
# compute the orientation of the bars
theta = np.arctan2((ys - yp), (xs - xp))
# customize the appearance of the bar
facecolor = "tab:red"
edgecolor = "k"
linewidth = 0
# loop to add each bar
for x, y, t, h in zip(xs, ys, theta, hist):
verts_matrix = apply_transform(t, x, y, 0, width, h)
x, y, z = verts_matrix[:, 0], verts_matrix[:, 1], verts_matrix[:, 2]
verts = [list(zip(x, y, z))]
c = Poly3DCollection(verts, facecolor=facecolor, edgecolor=edgecolor, linewidth=linewidth)
ax.add_collection3d(c)
# eventually show a legend
ax.legend([Rectangle((0, 0), 1, 1, facecolor=facecolor, edgecolor=edgecolor, linewidth=linewidth)], ["Bar Plot"])
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
ax.set_xlim(xs.min(), xs.max())
ax.set_ylim(ys.min(), ys.max())
ax.set_zlim(0, 100)
plt.show()
编辑 解释发生了什么:
考虑一个具有 4 个顶点的通用矩形:左下角、右下角、右上角、左上角。为简单起见,让我们固定 width=height=1。然后我们考虑一个参考系统 x,y,z 并绘制这个矩形。顶点的坐标是:左下角(-0.5,0,0),右下角(0.5,0,0),右上角(0.5,0,1)和左上角(-0.5,0,1)。请注意,此矩形在 x 方向上以零为中心。如果我们将它移动到 x=2,那么它将以该位置为中心。可以看到上面的坐标在rect
中:为什么这个变量第四列填的是1?这是一个能够将平移矩阵应用于顶点的数学技巧。
我们来谈谈transformation matrices (wikipedia has a nice page about it)。再次考虑我们的通用矩形:我们可以对其进行缩放、旋转和平移,以在我们想要的位置和方向上获得一个新的矩形。
因此,上面的代码为每个转换定义了一个函数,translate, scale, rotate
。事实证明,我们可以将多个变换矩阵相乘以获得整体变换:这就是 transformation_matrix
所做的,它将上述变换组合成一个矩阵。
最后,我使用 apply_transform
将变换矩阵应用于通用矩形:这将计算新矩形顶点的坐标,在指定的 position/orientation 中具有指定的大小(宽度、高度)。
我想在 3d 中绘制条形图。我知道如何使用以下代码做到这一点:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure(figsize=(10,10))
ax = fig.add_subplot(111, projection='3d')
nbins = 50
# for c, z in zip(['r', 'g', 'b', 'y'], [30, 20, 10, 0]):
ys = np.random.normal(loc=10, scale=10, size=2000)
hist, bins = np.histogram(ys, bins=nbins)
xs = (bins[:-1] + bins[1:])/2
ax.bar(xs, hist, zs=30, zdir='y', color='r', ec='r', alpha=0.8)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
plt.show()
这将呈现如下内容:https://i.stack.imgur.com/KK2If.png
但是,我的目标是使条形图遵循我作为参数给出的一条线。例如这里,参数 zdir='y' 使绘图具有当前方向。理想情况下,我想传递一个参数,使绘图遵循给定的线,例如 y=2x+1.
有人可以帮助达到预期的结果吗?
实现这一点的一种方法是使用 Poly3DCollection
:想法是计算每个条形的坐标和方向,然后将其添加到绘图中。
可以从 3D 矩形开始计算每个条形的位置和方向 space 并应用适当的变换矩阵。
如果您要更改 curve
,您还需要更改栏 width
。
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
from matplotlib.patches import Rectangle
################
# Generates data
################
nbins = 50
ys = np.random.normal(loc=10, scale=10, size=2000)
hist, bins = np.histogram(ys, bins=nbins)
xs = (bins[:-1] + bins[1:])/2
#################################################
# Create a single bar and a transformation matrix
#################################################
# rectangle of width=height=1, centered at x,y=0
# covering the z range [0, height]
rect = np.array([
[-0.5, 0, 0, 1],
[0.5, 0, 0, 1],
[0.5, 0, 1, 1],
[-0.5, 0, 1, 1],
])
def translate(x, y, z):
d = np.eye(4, dtype=float)
d[:, -1] = [x, y, z, 1]
return d
def scale(sx, sy, sz):
d = np.eye(4, dtype=float)
d[np.diag_indices(4)] = [sx, sy, sz, 1]
return d
def rotate(t):
d = np.eye(4, dtype=float)
d[:2, :2] = np.array([
[np.cos(t), -np.sin(t)],
[np.sin(t), np.cos(t)]])
return d
def transformation_matrix(t, x, y, z, w, h):
return translate(x, y, z) @ rotate(t) @ scale(w, 1, h)
def apply_transform(t, x, y, z, w, h):
"""Apply the transformation matrix to the rectangle"""
verts = transformation_matrix(t, x, y, z, w, h) @ rect.T
return verts.T
#################
# Create the plot
#################
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
curve = lambda x: 2 * x + 1
# curve = lambda x: np.sin(0.05 * x)
xstep = abs(xs[0] - xs[1])
# NOTE: chose an appropriate bar width
width = xstep * 1.5
ys = curve(xs)
# previous bar coordinates
xp = np.roll(xs, 1)
yp = np.roll(ys, 1)
xp[0] = xs[0] - xstep
yp[0] = curve(xp[0])
# compute the orientation of the bars
theta = np.arctan2((ys - yp), (xs - xp))
# customize the appearance of the bar
facecolor = "tab:red"
edgecolor = "k"
linewidth = 0
# loop to add each bar
for x, y, t, h in zip(xs, ys, theta, hist):
verts_matrix = apply_transform(t, x, y, 0, width, h)
x, y, z = verts_matrix[:, 0], verts_matrix[:, 1], verts_matrix[:, 2]
verts = [list(zip(x, y, z))]
c = Poly3DCollection(verts, facecolor=facecolor, edgecolor=edgecolor, linewidth=linewidth)
ax.add_collection3d(c)
# eventually show a legend
ax.legend([Rectangle((0, 0), 1, 1, facecolor=facecolor, edgecolor=edgecolor, linewidth=linewidth)], ["Bar Plot"])
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
ax.set_xlim(xs.min(), xs.max())
ax.set_ylim(ys.min(), ys.max())
ax.set_zlim(0, 100)
plt.show()
编辑 解释发生了什么:
考虑一个具有 4 个顶点的通用矩形:左下角、右下角、右上角、左上角。为简单起见,让我们固定 width=height=1。然后我们考虑一个参考系统 x,y,z 并绘制这个矩形。顶点的坐标是:左下角(-0.5,0,0),右下角(0.5,0,0),右上角(0.5,0,1)和左上角(-0.5,0,1)。请注意,此矩形在 x 方向上以零为中心。如果我们将它移动到 x=2,那么它将以该位置为中心。可以看到上面的坐标在rect
中:为什么这个变量第四列填的是1?这是一个能够将平移矩阵应用于顶点的数学技巧。
我们来谈谈transformation matrices (wikipedia has a nice page about it)。再次考虑我们的通用矩形:我们可以对其进行缩放、旋转和平移,以在我们想要的位置和方向上获得一个新的矩形。
因此,上面的代码为每个转换定义了一个函数,translate, scale, rotate
。事实证明,我们可以将多个变换矩阵相乘以获得整体变换:这就是 transformation_matrix
所做的,它将上述变换组合成一个矩阵。
最后,我使用 apply_transform
将变换矩阵应用于通用矩形:这将计算新矩形顶点的坐标,在指定的 position/orientation 中具有指定的大小(宽度、高度)。