使用plotly绘制具有正确纵横比的交互式3D图
我正在使用 matplotlib 绘制 3D 图像(即 3D 装箱问题,如装载容器)。绘图时,长/宽/高会自动缩放,与实际值不成比例,即长度是高度的6倍,但图片显示三个轴的比例几乎相同(见下面第一张图片) 。我知道 matplot3D 在以正确的纵横比绘制 3D 绘图方面有其局限性。
我需要的是以更现实的方式绘制一个图,如下图所示。我们可以很容易地看到集装箱的空间和装载的物品。很多人推荐使用plotly,它支持很好的交互式3D绘图。我从来没有使用过这样的工具来绘制 3D 绘图。有人可以帮忙提供这样做的代码示例吗?谢谢
下面是我的代码:
from py3dbp import Packer, Bin, Item
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
import numpy as np
import matplotlib.pyplot as plt
import random
def cuboid_data2(o, size=(1, 1, 1)):
X = [[[0, 1, 0], [0, 0, 0], [1, 0, 0], [1, 1, 0]],
[[0, 0, 0], [0, 0, 1], [1, 0, 1], [1, 0, 0]],
[[1, 0, 1], [1, 0, 0], [1, 1, 0], [1, 1, 1]],
[[0, 0, 1], [0, 0, 0], [0, 1, 0], [0, 1, 1]],
[[0, 1, 0], [0, 1, 1], [1, 1, 1], [1, 1, 0]],
[[0, 1, 1], [0, 0, 1], [1, 0, 1], [1, 1, 1]]]
X = np.array(X).astype(float)
for i in range(3):
X[:, :, i] *= size[i]
X += np.array(o)
return X
def plotCubeAt2(positions, sizes=None, colors=None, **kwargs):
if not isinstance(colors, (list, np.ndarray)): colors = ["C0"] * len(positions)
if not isinstance(sizes, (list, np.ndarray)): sizes = [(1, 1, 1)] * len(positions)
g = []
for p, s, c in zip(positions, sizes, colors):
g.append(cuboid_data2(p, size=s))
return Poly3DCollection(np.concatenate(g),
facecolors=np.repeat(colors, 6), **kwargs)
containers = [
[1203, 235, 259],
[1203, 235, 259],
# [1202.4, 235, 269],
# [12.024, 2.350, 2.69],
# [12.024, 2.350, 2.69],
# [12.024, 2.350, 2.69],
]
packer = Packer()
containerX = 0
containerY = 0
containerZ = 0
for i, t in enumerate(range(len(containers))):
containerX = containers[t][0]
containerY = containers[t][1]
containerZ = containers[t][2]
i += 1
packer.add_bin(Bin('40HC-' + str(i), containerX, containerY, containerZ, 18000.0))
for i in range(50):
packer.add_item(Item('BoxA_' + str(i), 44, 39, 70, 8.20))
for i in range(35):
packer.add_item(Item('BoxB_' + str(i), 65, 38, 40, 14))
for i in range(31):
packer.add_item(Item('BoxC_' + str(i), 43, 52, 47, 10))
for i in range(38):
packer.add_item(Item('BoxD_' + str(i), 60, 45, 40, 14))
for i in range(11):
packer.add_item(Item('BoxE_' + str(i), 42, 46, 54, 9.70))
for i in range(525):
packer.add_item(Item('BoxF_' + str(i), 62, 45, 35, 14.5))
# packer.pack()
# packer.pack(bigger_first=False)
packer.pack(bigger_first=False, distribute_items=True, number_of_decimals=3)
for b in packer.bins:
positions = []
sizes = []
colors = []
print(":::::::::::", b.string())
print("FITTED ITEMS:")
for item in b.items:
print("====> ", item.string())
x = float(item.position[0])
y = float(item.position[1])
z = float(item.position[2])
positions.append((x, y, z))
sizes.append(
(float(item.get_dimension()[0]), float(item.get_dimension()[1]), float(item.get_dimension()[2])))
colorList = ["crimson", "limegreen", "g", "r", "c", "m", "y", "k"]
if item.width == 44:
colors.append(colorList[0])
if item.width == 65:
colors.append(colorList[1])
if item.width == 43:
colors.append(colorList[2])
if item.width == 60:
colors.append(colorList[3])
if item.width == 42:
colors.append(colorList[4])
if item.width == 62:
colors.append(colorList[5])
print("UNFITTED ITEMS:")
for item in b.unfitted_items:
print("====> ", item.string())
print("***************************************************")
print("***************************************************")
# colorList = ["crimson", "limegreen", "g", "r", "c", "m", "y", "k"]
#
# for i in range(len(b.items)):
# f = random.randint(0, 7)
# colors.append(colorList[f])
if len(colors) > 0:
fig = plt.figure()
fig.canvas.set_window_title(b.string().split("(")[0])
ax = fig.gca(projection='3d')
ax.set_aspect('auto')
pc = plotCubeAt2(positions, sizes, colors=colors, edgecolor="k")
ax.add_collection3d(pc)
ax.set_xlim([0, float(b.string().split(",")[0].split("(")[1].split("x")[0])])
ax.set_ylim([0, float(b.string().split(",")[0].split("(")[1].split("x")[1])])
ax.set_zlim([0, float(b.string().split(",")[0].split("(")[1].split("x")[2])])
plt.show()
上面代码的 3D 装箱计算输出如下所示,其中“pos”应该是 3D 位置数据:
====> BoxC_16(64.000x37.000x52.000, weight: 0.000) pos([Decimal('1024.000'), 0, 0]) rt(0) vol(123136.000)
====> BoxC_17(64.000x37.000x52.000, weight: 0.000) pos([Decimal('1088.000'), 0, 0]) rt(0) vol(123136.000)
====> BoxC_18(64.000x37.000x52.000, weight: 0.000) pos([Decimal('1152.000'), 0, 0]) rt(0) vol(123136.000)
====> BoxC_19(64.000x37.000x52.000, weight: 0.000) pos([Decimal('1216.000'), 0, 0]) rt(0) vol(123136.000)
====> BoxC_20(64.000x37.000x52.000, weight: 0.000) pos([Decimal('1280.000'), 0, 0]) rt(0) vol(123136.000)
====> BoxC_21(64.000x37.000x52.000, weight: 0.000) pos([0, Decimal('37.000'), 0]) rt(0) vol(123136.000)
====> BoxC_22(64.000x37.000x52.000, weight: 0.000) pos([Decimal('64.000'), Decimal('37.000'), 0]) rt(0) vol(123136.000)
====> BoxC_23(64.000x37.000x52.000, weight: 0.000) pos([Decimal('128.000'), Decimal('37.000'), 0]) rt(0) vol(123136.000)
====> BoxC_24(64.000x37.000x52.000, weight: 0.000) pos([Decimal('192.000'), Decimal('37.000'), 0]) rt(0) vol(123136.000)
====> BoxC_25(64.000x37.000x52.000, weight: 0.000) pos([Decimal('256.000'), Decimal('37.000'), 0]) rt(0) vol(123136.000)
====> BoxC_26(64.000x37.000x52.000, weight: 0.000) pos([Decimal('320.000'), Decimal('37.000'), 0]) rt(0) vol(123136.000)
UPADTE:(绘制外容器框架)
def parallelipipedic_frame(xm, xM, ym, yM, zm, zM):
# defines the coords of each segment followed by None, if the line is
# discontinuous
x = [xm, xM, xM, xm, xm, None, xm, xM, xM, xm, xm, None, xm, xm, None, xM, xM,
None, xM, xM, None, xm, xm]
y = [ym, ym, yM, yM, ym, None, ym, ym, yM, yM, ym, None, ym, ym, None, ym, ym,
None, yM, yM, None, yM, yM]
z = [zm, zm, zm, zm, zm, None, zM, zM, zM, zM, zM, None, zm, zM, None, zm, zM,
None, zm, zM, None, zm, zM]
return x, y, z
x, y, z = parallelipipedic_frame(0, 1202.4, 0, 235, 0, 269.7)
# fig = go.Figure(go.Scatter3d(x=x, y=y, z=z, mode="lines", line_width=4))
fig.add_trace(
go.Scatter3d(
x=x,
y=y,
z=z,
mode="lines",
line_color="blue",
line_width=2,
hoverinfo="skip",
)
)
ar = 4
xr = max(d["x"].max()) - min(d["x"].min())
fig.update_layout(
title={"text": pbin, "y": 0.9, "x": 0.5, "xanchor": "center", "yanchor": "top"},
margin={"l": 0, "r": 0, "t": 0, "b": 0},
# autosize=False,
scene=dict(
camera=dict(eye=dict(x=2, y=2, z=2)),
aspectratio={
**{"x": ar},
**{
c: ((max(d[c].max()) - min(d[c].min())) / xr) * ar
for c in list("yz")
},
},
aspectmode="manual",
),
)
I am using matplotlib to plot 3D image (i.e 3D bin packing problem like loading containers). when plotting, the length/width/height is automatically scaled which is not proportional to its actual value, i.e. the length is 6 times bigger than the height but the picture shows almost the same scale for the three axis (see below first one pic). I understood that matplot3D has its limitations on drawing 3D plot with right aspect ratio.
What I need is to draw a plot in more realistic manner like the below graph. we could easily see the space of the container and the items loaded in it. Many people recommand using plotly and it supports nice interactive 3D plotting. I have never used such tool to draw 3D plot. Can someone help to provide an example of code doing so? thanks
below is my code:
from py3dbp import Packer, Bin, Item
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
import numpy as np
import matplotlib.pyplot as plt
import random
def cuboid_data2(o, size=(1, 1, 1)):
X = [[[0, 1, 0], [0, 0, 0], [1, 0, 0], [1, 1, 0]],
[[0, 0, 0], [0, 0, 1], [1, 0, 1], [1, 0, 0]],
[[1, 0, 1], [1, 0, 0], [1, 1, 0], [1, 1, 1]],
[[0, 0, 1], [0, 0, 0], [0, 1, 0], [0, 1, 1]],
[[0, 1, 0], [0, 1, 1], [1, 1, 1], [1, 1, 0]],
[[0, 1, 1], [0, 0, 1], [1, 0, 1], [1, 1, 1]]]
X = np.array(X).astype(float)
for i in range(3):
X[:, :, i] *= size[i]
X += np.array(o)
return X
def plotCubeAt2(positions, sizes=None, colors=None, **kwargs):
if not isinstance(colors, (list, np.ndarray)): colors = ["C0"] * len(positions)
if not isinstance(sizes, (list, np.ndarray)): sizes = [(1, 1, 1)] * len(positions)
g = []
for p, s, c in zip(positions, sizes, colors):
g.append(cuboid_data2(p, size=s))
return Poly3DCollection(np.concatenate(g),
facecolors=np.repeat(colors, 6), **kwargs)
containers = [
[1203, 235, 259],
[1203, 235, 259],
# [1202.4, 235, 269],
# [12.024, 2.350, 2.69],
# [12.024, 2.350, 2.69],
# [12.024, 2.350, 2.69],
]
packer = Packer()
containerX = 0
containerY = 0
containerZ = 0
for i, t in enumerate(range(len(containers))):
containerX = containers[t][0]
containerY = containers[t][1]
containerZ = containers[t][2]
i += 1
packer.add_bin(Bin('40HC-' + str(i), containerX, containerY, containerZ, 18000.0))
for i in range(50):
packer.add_item(Item('BoxA_' + str(i), 44, 39, 70, 8.20))
for i in range(35):
packer.add_item(Item('BoxB_' + str(i), 65, 38, 40, 14))
for i in range(31):
packer.add_item(Item('BoxC_' + str(i), 43, 52, 47, 10))
for i in range(38):
packer.add_item(Item('BoxD_' + str(i), 60, 45, 40, 14))
for i in range(11):
packer.add_item(Item('BoxE_' + str(i), 42, 46, 54, 9.70))
for i in range(525):
packer.add_item(Item('BoxF_' + str(i), 62, 45, 35, 14.5))
# packer.pack()
# packer.pack(bigger_first=False)
packer.pack(bigger_first=False, distribute_items=True, number_of_decimals=3)
for b in packer.bins:
positions = []
sizes = []
colors = []
print(":::::::::::", b.string())
print("FITTED ITEMS:")
for item in b.items:
print("====> ", item.string())
x = float(item.position[0])
y = float(item.position[1])
z = float(item.position[2])
positions.append((x, y, z))
sizes.append(
(float(item.get_dimension()[0]), float(item.get_dimension()[1]), float(item.get_dimension()[2])))
colorList = ["crimson", "limegreen", "g", "r", "c", "m", "y", "k"]
if item.width == 44:
colors.append(colorList[0])
if item.width == 65:
colors.append(colorList[1])
if item.width == 43:
colors.append(colorList[2])
if item.width == 60:
colors.append(colorList[3])
if item.width == 42:
colors.append(colorList[4])
if item.width == 62:
colors.append(colorList[5])
print("UNFITTED ITEMS:")
for item in b.unfitted_items:
print("====> ", item.string())
print("***************************************************")
print("***************************************************")
# colorList = ["crimson", "limegreen", "g", "r", "c", "m", "y", "k"]
#
# for i in range(len(b.items)):
# f = random.randint(0, 7)
# colors.append(colorList[f])
if len(colors) > 0:
fig = plt.figure()
fig.canvas.set_window_title(b.string().split("(")[0])
ax = fig.gca(projection='3d')
ax.set_aspect('auto')
pc = plotCubeAt2(positions, sizes, colors=colors, edgecolor="k")
ax.add_collection3d(pc)
ax.set_xlim([0, float(b.string().split(",")[0].split("(")[1].split("x")[0])])
ax.set_ylim([0, float(b.string().split(",")[0].split("(")[1].split("x")[1])])
ax.set_zlim([0, float(b.string().split(",")[0].split("(")[1].split("x")[2])])
plt.show()
The 3D bin packing calculation output from above code looks like, where "pos" should be the 3D position data:
====> BoxC_16(64.000x37.000x52.000, weight: 0.000) pos([Decimal('1024.000'), 0, 0]) rt(0) vol(123136.000)
====> BoxC_17(64.000x37.000x52.000, weight: 0.000) pos([Decimal('1088.000'), 0, 0]) rt(0) vol(123136.000)
====> BoxC_18(64.000x37.000x52.000, weight: 0.000) pos([Decimal('1152.000'), 0, 0]) rt(0) vol(123136.000)
====> BoxC_19(64.000x37.000x52.000, weight: 0.000) pos([Decimal('1216.000'), 0, 0]) rt(0) vol(123136.000)
====> BoxC_20(64.000x37.000x52.000, weight: 0.000) pos([Decimal('1280.000'), 0, 0]) rt(0) vol(123136.000)
====> BoxC_21(64.000x37.000x52.000, weight: 0.000) pos([0, Decimal('37.000'), 0]) rt(0) vol(123136.000)
====> BoxC_22(64.000x37.000x52.000, weight: 0.000) pos([Decimal('64.000'), Decimal('37.000'), 0]) rt(0) vol(123136.000)
====> BoxC_23(64.000x37.000x52.000, weight: 0.000) pos([Decimal('128.000'), Decimal('37.000'), 0]) rt(0) vol(123136.000)
====> BoxC_24(64.000x37.000x52.000, weight: 0.000) pos([Decimal('192.000'), Decimal('37.000'), 0]) rt(0) vol(123136.000)
====> BoxC_25(64.000x37.000x52.000, weight: 0.000) pos([Decimal('256.000'), Decimal('37.000'), 0]) rt(0) vol(123136.000)
====> BoxC_26(64.000x37.000x52.000, weight: 0.000) pos([Decimal('320.000'), Decimal('37.000'), 0]) rt(0) vol(123136.000)
UPADTE: (drawing outer container frame)
def parallelipipedic_frame(xm, xM, ym, yM, zm, zM):
# defines the coords of each segment followed by None, if the line is
# discontinuous
x = [xm, xM, xM, xm, xm, None, xm, xM, xM, xm, xm, None, xm, xm, None, xM, xM,
None, xM, xM, None, xm, xm]
y = [ym, ym, yM, yM, ym, None, ym, ym, yM, yM, ym, None, ym, ym, None, ym, ym,
None, yM, yM, None, yM, yM]
z = [zm, zm, zm, zm, zm, None, zM, zM, zM, zM, zM, None, zm, zM, None, zm, zM,
None, zm, zM, None, zm, zM]
return x, y, z
x, y, z = parallelipipedic_frame(0, 1202.4, 0, 235, 0, 269.7)
# fig = go.Figure(go.Scatter3d(x=x, y=y, z=z, mode="lines", line_width=4))
fig.add_trace(
go.Scatter3d(
x=x,
y=y,
z=z,
mode="lines",
line_color="blue",
line_width=2,
hoverinfo="skip",
)
)
ar = 4
xr = max(d["x"].max()) - min(d["x"].min())
fig.update_layout(
title={"text": pbin, "y": 0.9, "x": 0.5, "xanchor": "center", "yanchor": "top"},
margin={"l": 0, "r": 0, "t": 0, "b": 0},
# autosize=False,
scene=dict(
camera=dict(eye=dict(x=2, y=2, z=2)),
aspectratio={
**{"x": ar},
**{
c: ((max(d[c].max()) - min(d[c].min())) / xr) * ar
for c in list("yz")
},
},
aspectmode="manual",
),
)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
附加要求
如何制作单独的图(不要将子图合并在同一图中,因为它们太小而看不到);
如何解决非满容器的纵横比问题?如果未满,我希望它显示空白空间供用户查看;
如何制作不同的边缘颜色以区分每个单独的立方体
Scatter3d()` 以及构建立方体顶点的坐标
additional requirements
how to make individual plot (not merge subplot in the same graph as they too small to see);
how to solve the problem of aspect ratio for non-full container? if not full, I want it to display the empty space for user to see;
how to make a different edge color in order to distinguish from each individual cube
Scatter3d()` as well build co-ordinates of vertices of cube