遵循给定线的3D栏图

发布于 2025-01-31 18:14:08 字数 818 浏览 2 评论 0原文

我想在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。 sstatic.net/kk2if.png

但是,我的目标是使栏图遵循我作为参数的行。例如,这里的参数zdir ='y'使图具有其当前方向。理想情况下,我想传递一个使图遵循给定线路的参数,例如y = 2x+1。

有人可以帮助达到所需的结果吗?

I want to draw a bar plot in 3d. I know how to do that using the following code:

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()

This will render something like this: https://i.sstatic.net/KK2If.png

However, my goal is to make the bar plot follows a line that I give as parameter. For example here, the parameter zdir='y' makes the plot have its current direction. Ideally I want to pass a parameter that makes the plot follows a given line for example y=2x+1.

Could someone help arrive at the desired result?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

星光不落少年眉 2025-02-07 18:14:08

实现这一目标的一种方法是使用poly3dcollection:想法是计算每个条的坐标和方向,然后将其添加到图中。

可以从3D空间中的矩形开始并应用适当的转换矩阵开始计算每个条的位置和方向。

如果要更改曲线,也需要更改bar 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个顶点的通用矩形:左下,右下,右下,右上,左上,左上。为简单起见,让我们修复宽度=高度= 1。然后,我们考虑一个参考系统X,Y,Z,然后绘制此矩形。顶点的坐标为:左下(-0.5,0,0),右下(0.5,0,0),右上(0.5,0,1)和左上(-0.5,0,1)。请注意,该矩形以X方向为中心。如果我们将其移至x = 2,则将其以该位置为中心。您可以在rect中看到上述坐标:为什么此变量的第四列填充了?这是能够将翻译矩阵应用于顶点的数学技巧。

让我们来谈谈变换矩阵(Wikipedia对此有一个不错的页面))。再次考虑我们的通用矩形:我们可以将其缩放,旋转并翻译它以在我们想要的位置和方向上获得一个新的矩形。

因此,上面的代码定义了每个转换的功能,转换,比例,旋转。事实证明,我们可以将多个转换矩阵乘以总体转换:这就是transformation_matrix做的,它将上述转换结合到一个矩阵中。

最后,我使用apply_transform将转换矩阵应用于通用矩形:这将在指定的位置/方向上计算新矩形顶点的坐标,该位置/方向,指定的大小(宽度,高度) 。

One way to achieve that is by using Poly3DCollection: the idea is to compute the coordinates and orientation of each bar, then add it to the plot.

The position and orientation of each bar can be computed starting from a rectangle in 3D space and applying the appropriate transformation matrix.

If you are going to change the curve, you will also need to change the bar 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()

enter image description here

EDIT to explain what is going on:

Consider a generic rectangle with 4 vertices: bottom left, bottom right, top right, top left. For simplicity, let's fix width=height=1. Then we consider a reference system x,y,z and we draw this rectangle. The coordinates of vertices are: bottom left (-0.5, 0, 0), bottom right (0.5, 0, 0), top right (0.5, 0, 1) and top left (-0.5, 0, 1). Note that this rectangle is centered around the zero in the x direction. If we move it to x=2, then it will be centered at that location. You can see the above coordinates in rect: why does this variable has a fourth column filled with ones? That's a mathematical trick to be able to apply a translation matrix to the vertices.

Let's talk about transformation matrices (wikipedia has a nice page about it). Consider again our generic rectangle: we can scale it, rotate it and translate it to get a new rectangle in the position and orientation we want.

So, the code above defines a function for each transformation, translate, scale, rotate. Turns out that we can multiply together multiple transformation matrices to get an overall transformation: that's what transformation_matrix does, it combines the aforementioned transformations into a single matrix.

Finally, I used apply_transform to apply the transformation matrix to the generic rectangle: this will compute the coordinates of the vertices of the new rectangle, in the specified position/orientation with the specified size (width, height).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文