【问题标题】:How to plot 3D axis-origin figure using python如何使用python绘制3D轴原点图
【发布时间】:2020-11-16 06:51:25
【问题描述】:

我想绘制一个轴位于图中间的 3-D 曲面。

我使用以下代码绘制图形:

%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(-1, 1, 10)
y = np.linspace(-1, 1, 10)

X, Y = np.meshgrid(x, y)

Z = np.array([[2.58677481, 3.22528864, 3.65334814, 3.86669336, 3.86399048,
        3.64525411, 3.21186215, 2.56819809, 1.72989472, 0.78569291],
       [2.58677481, 3.22528864, 3.65334814, 3.86669336, 3.86399048,
        3.64525411, 3.21186215, 2.56819809, 1.72989472, 0.78569291],
       [2.58677481, 3.22528864, 3.65334814, 3.86669336, 3.86399048,
        3.64525411, 3.21186215, 2.56819809, 1.72989472, 0.78569291],
       [2.58677481, 3.22528864, 3.65334814, 3.86669336, 3.86399048,
        3.64525411, 3.21186215, 2.56819809, 1.72989472, 0.78569291],
       [2.58677481, 3.22528864, 3.65334814, 3.86669336, 3.86399048,
        3.64525411, 3.21186215, 2.56819809, 1.72989472, 0.78569291],
       [2.58677481, 3.22528864, 3.65334814, 3.86669336, 3.86399048,
        3.64525411, 3.21186215, 2.56819809, 1.72989472, 0.78569291],
       [2.58677481, 3.22528864, 3.65334814, 3.86669336, 3.86399048,
        3.64525411, 3.21186215, 2.56819809, 1.72989472, 0.78569291],
       [2.58677481, 3.22528864, 3.65334814, 3.86669336, 3.86399048,
        3.64525411, 3.21186215, 2.56819809, 1.72989472, 0.78569291],
       [2.58677481, 3.22528864, 3.65334814, 3.86669336, 3.86399048,
        3.64525411, 3.21186215, 2.56819809, 1.72989472, 0.78569291],
       [2.58677481, 3.22528864, 3.65334814, 3.86669336, 3.86399048,
        3.64525411, 3.21186215, 2.56819809, 1.72989472, 0.78569291]])


fig = plt.figure()
ax = plt.axes(projection='3d')
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, linewidth=0,
                cmap='viridis', edgecolor='none', antialiased=False)
ax.set_xlim(-1.01, 1.01)

fig.colorbar(surf, shrink=0.5, aspect=5)

# Plot the axis in the middle of the figure
xline=((min(X[:,0]),max(X[:,0])),(0,0),(0,0))
ax.plot(xline[0],xline[1],xline[2],'grey')
yline=((0,0),(min(Y[:,1]),max(Y[:,1])),(0,0))
ax.plot(yline[0],yline[1],yline[2],'grey')
zline=((0,0),(0,0),(min(Z[:,2]),max(Z[:,2])))
ax.plot(zline[0],zline[1],zline[2],'grey')

ax.view_init(30,220) # Camera angel

ax.set_title('surface');

通过上面的代码,我得到这样的图

我真正想要的是绘制一个 3-D 轴原点图,如下所示:

如何消除边距,把轴放在图的中间?

【问题讨论】:

  • 这似乎是 Python 3d plot - axis centered 的副本。不过,那里没有赞成的答案。
  • 这是一个更好的问题,因为这里有示例数据和起点。我宁愿将另一个关闭为重复项(但不确定是否重复项必须始终是最新的)。虽然,这是表面图和另一个散点图的意义上有所不同。
  • “如何消除边距”是什么意思?删除彩条?还是别的什么?

标签: python python-3.x matplotlib


【解决方案1】:

这是使用plotly 的解决方案。

简短说明

代码如下所示,但在这里我想给出最重要的说明

  • 没有办法(?)将实际的轴箭头移动到中心。这里所做的是,我从三个部分创建了轴箭头:向量和向量末尾的3d cone,然后将轴标签添加为annotation。我把它的字面意思写成“在图的中间”,但位置和箭头的外观可以通过修改get_arrow() 很容易地改变。
  • 该图周围的“框”也有点骇人听闻:我更改了layout.scene.xaxislayout.scene.yaxislayout.scene.zaxistickvalsrange 参数。只有两个值,以便绘制网格将显示这样的框。如果您还想显示普通网格,也应该使用矢量来完成(如箭头)。
  • 我不确定是否要包含色标,但只需将 showscale=True 更改为 go.Surface 即可添加。
  • 您可能要考虑的另一个选项是移动 数据 而不是轴(尽管您仍然需要绘制箭头)。这在某些用例中可能更有意义。

代码

import numpy as np
import matplotlib.pyplot as plt
import plotly.graph_objects as go


def get_data():
    x = np.linspace(-1, 1, 10)
    y = np.linspace(-1, 1, 10)

    X, Y = np.meshgrid(x, y)

    Z = np.array([[2.58677481, 3.22528864, 3.65334814, 3.86669336, 3.86399048,
            3.64525411, 3.21186215, 2.56819809, 1.72989472, 0.78569291],
        [2.58677481, 3.22528864, 3.65334814, 3.86669336, 3.86399048,
            3.64525411, 3.21186215, 2.56819809, 1.72989472, 0.78569291],
        [2.58677481, 3.22528864, 3.65334814, 3.86669336, 3.86399048,
            3.64525411, 3.21186215, 2.56819809, 1.72989472, 0.78569291],
        [2.58677481, 3.22528864, 3.65334814, 3.86669336, 3.86399048,
            3.64525411, 3.21186215, 2.56819809, 1.72989472, 0.78569291],
        [2.58677481, 3.22528864, 3.65334814, 3.86669336, 3.86399048,
            3.64525411, 3.21186215, 2.56819809, 1.72989472, 0.78569291],
        [2.58677481, 3.22528864, 3.65334814, 3.86669336, 3.86399048,
            3.64525411, 3.21186215, 2.56819809, 1.72989472, 0.78569291],
        [2.58677481, 3.22528864, 3.65334814, 3.86669336, 3.86399048,
            3.64525411, 3.21186215, 2.56819809, 1.72989472, 0.78569291],
        [2.58677481, 3.22528864, 3.65334814, 3.86669336, 3.86399048,
            3.64525411, 3.21186215, 2.56819809, 1.72989472, 0.78569291],
        [2.58677481, 3.22528864, 3.65334814, 3.86669336, 3.86399048,
            3.64525411, 3.21186215, 2.56819809, 1.72989472, 0.78569291],
        [2.58677481, 3.22528864, 3.65334814, 3.86669336, 3.86399048,
            3.64525411, 3.21186215, 2.56819809, 1.72989472, 0.78569291]])
    return X, Y, Z


# One-color arrows & arrowheads
colorscale = [
    [0, "rgb(84,48,5)"],
    [1, "rgb(84,48,5)"],
]

X, Y, Z = get_data()

data = {
    key: {
        "min": v.min(),
        "max": v.max(),
        "mid": (v.max() + v.min()) / 2,
        "range": v.max() - v.min(),
    }
    for (key, v) in dict(x=X, y=Y, z=Z).items()
}


def get_arrow(axisname="x"):

    # Create arrow body
    body = go.Scatter3d(
        marker=dict(size=1, color=colorscale[0][1]),
        line=dict(color=colorscale[0][1], width=3),
        showlegend=False,  # hide the legend
    )

    head = go.Cone(
        sizeref=0.1,
        autocolorscale=None,
        colorscale=colorscale,
        showscale=False,  # disable additional colorscale for arrowheads
        hovertext=axisname,
    )
    for ax, direction in zip(("x", "y", "z"), ("u", "v", "w")):
        if ax == axisname:
            body[ax] = data[ax]["min"], data[ax]["max"]
            head[ax] = [data[ax]["max"]]
            head[direction] = [1]
        else:
            body[ax] = data[ax]["mid"], data[ax]["mid"]
            head[ax] = [data[ax]["mid"]]
            head[direction] = [0]

    return [body, head]


def add_axis_arrows(fig):
    for ax in ("x", "y", "z"):
        for item in get_arrow(ax):
            fig.add_trace(item)


def get_annotation_for_ax(ax):
    d = dict(showarrow=False, text=ax, xanchor="left", font=dict(color="#1f1f1f"))
    for ax_ in ("x", "y", "z"):
        if ax_ == ax:
            d[ax_] = data[ax]["max"] - data[ax]["range"] * 0.05
        else:
            d[ax_] = data[ax_]["mid"]

    if ax in {"x", "y"}:
        d["xshift"] = 15

    return d


def get_axis_names():
    return [get_annotation_for_ax(ax) for ax in ("x", "y", "z")]


def get_scene_axis(axisname="x"):

    return dict(
        title="",  # remove axis label (x,y,z)
        showbackground=False,
        visible=True,
        showticklabels=False,  # hide numeric values of axes
        showgrid=True,  # Show box around plot
        gridcolor="grey",  # Box color
        tickvals=[data[axisname]["min"], data[axisname]["max"]],  # Set box limits
        range=[
            data[axisname]["min"],
            data[axisname]["max"],
        ],  # Prevent extra lines around box
    )


fig = go.Figure(
    data=[
        go.Surface(
            z=Z,
            x=X,
            y=Y,
            opacity=0.6,
            showscale=False,  # Set to True to show colorscale
        ),
    ],
    layout=dict(
        title="surface",
        autosize=True,
        width=700,
        height=500,
        margin=dict(l=20, r=20, b=25, t=25),
        scene=dict(
            xaxis=get_scene_axis("x"),
            yaxis=get_scene_axis("y"),
            zaxis=get_scene_axis("z"),
            annotations=get_axis_names(),
        ),
    ),
)

add_axis_arrows(fig)
fig.show()

【讨论】:

    【解决方案2】:

    我不知道正确的方法,但只有一种解决方法。

    对2D案例here有详细的很好的描述,选项有

    • ax.axhline(y=0, color='k')
    • 使用样条线:ax.spines['left'].set_position('zero')
    • 或使用seaborn包seaborn.despine(ax=ax, offset=0)

    但是,恐怕它们在 3D 中并不那么容易。

    我只知道this 解决方法,其中(外)轴被关闭(ax.set_axis_off())并从原点绘制箭头(ax.quiver())。

    所以你添加

    x, y, z = np.array([[-1,0,0],[0,-1,0],[0,0,0]])
    u, v, w = np.array([[2,0,0],[0,2,0],[0,0,5]])
    ax.quiver(x,y,z,u,v,w,arrow_length_ratio=0.1, color="black")
    ax.set_axis_off()
    plt.show()
    

    你的代码,你会得到这张照片:

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-01-18
      • 2023-03-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-22
      • 1970-01-01
      • 2022-11-26
      相关资源
      最近更新 更多