繁体   English   中英

Plotly:如何避免巨大的 html 文件大小

[英]Plotly: how to avoid huge html file size

我有一个 3D 装箱模型,它使用 plotly 来绘制输出图。 我注意到绘制了 600 多个项目,生成 html 文件需要很长时间,文件大小为 89M,这很疯狂(我怀疑可能有一些巨大的重复,或者是由单个项目的“add_trace”方法引起的阴谋)。 为什么它会制作这么大的文件? 如何将大小控制在可接受的水平(不超过 5M,因为我需要在我的网站中呈现它)。 非常感谢您的帮助。

在此处输入图像描述

以下是我的完整代码(请跳过模型代码并从情节代码中查看)

from py3dbp import Packer, Bin, Item, Painter
import time
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import plotly
import pandas as pd

start = time.time()
import numpy as np

# -----------this part is about calculating the 3D bin packing problem to get x,y,z for each items of a bin/container--------------
###library reference: https://github.com/jerry800416/3D-bin-packing

# init packing function
packer = Packer()
#  init bin
# box = Bin('40HC-1', (1203, 235, 259), 18000.0,0,0)
box = Bin('40HC-1', (1202.4, 235, 269.7), 18000.0, 0, 0)
packer.addBin(box)


# add item
# for num in range(10):
#   packer.addItem(Item(f"BoxA_{num}", f"BoxA_{num}", 'cube', (120, 120, 120), 8.20, 1, 100, True, 'red'))
# for num in range(55):
#   packer.addItem(Item(f"BoxB_{num}", f"BoxB_{num}", 'cube', (65, 38, 90), 14, 1, 100, True, 'blue'))
# for num in range(50):
#   packer.addItem(Item(f"BoxC_{num}", f"BoxC_{num}", 'cube', (143, 52, 47), 10, 1, 100, True, 'gray'))


# add item
# for num in range(12):
#   packer.addItem(Item(f"BoxA_{num}", f"BoxA_{num}", 'cylinder', (120, 120, 120), 8.20, 1, 100, True, 'red'))
# for num in range(120):
#   packer.addItem(Item(f"BoxB_{num}", f"BoxB_{num}", 'cube', (65, 38, 90), 14, 1, 100, True, 'blue'))
# for num in range(60):
#   packer.addItem(Item(f"BoxC_{num}", f"BoxC_{num}", 'cube', (143, 52, 47), 10, 1, 100, True, 'gray'))


# for num in range(12):
#   packer.addItem(Item(f"BoxA_{num}", f"BoxA_{num}", 'cylinder', (120, 120, 120), 8.20, 1, 100, True, 'red'))
# for num in range(33):
#   packer.addItem(Item(f"BoxB_{num}", f"BoxB_{num}", 'cube', (65, 38, 90), 14, 1, 100, True, 'blue'))
# for num in range(32):
#   packer.addItem(Item(f"BoxC_{num}", f"BoxC_{num}", 'cube', (143, 52, 47), 10, 1, 100, True, 'gray'))


for num in range(252):
    packer.addItem(Item(f"BoxA_{num}", f"BoxA_{num}", 'cube', (65, 33, 26), 2.06, 1, 100, True, 'red'))
for num in range(222):
    packer.addItem(Item(f"BoxB_{num}", f"BoxB_{num}", 'cube', (84, 42.5, 33), 2.72, 1, 100, True, 'blue'))
for num in range(270):
    packer.addItem(Item(f"BoxC_{num}", f"BoxC_{num}", 'cube', (48, 48, 38), 2.17, 1, 100, True, 'gray'))



# calculate packing
packer.pack(bigger_first=True, distribute_items=False, fix_point=True, number_of_decimals=0)

# print result
b = packer.bins[0]
volume = b.width * b.height * b.depth
print(":::::::::::", b.string())

print("FITTED ITEMS:")
volume_t = 0
volume_f = 0
unfitted_name = ''
for item in b.items:
    print("partno : ", item.partno)
    print("color : ", item.color)
    print("position : ", item.position)
    print("type of : ", item.typeof)
    print("rotation type : ", item.rotation_type)
    print("W*H*D : ", str(item.width) + '*' + str(item.height) + '*' + str(item.depth))
    print("volume : ", float(item.width) * float(item.height) * float(item.depth))
    print("weight : ", float(item.weight))
    volume_t += float(item.width) * float(item.height) * float(item.depth)
    print("***************************************************")
print("***************************************************")
print("UNFITTED ITEMS:")
for item in b.unfitted_items:
    print("partno : ", item.partno)
    print("color : ", item.color)
    print("W*H*D : ", str(item.width) + '*' + str(item.height) + '*' + str(item.depth))
    print("volume : ", float(item.width) * float(item.height) * float(item.depth))
    print("weight : ", float(item.weight))
    volume_f += float(item.width) * float(item.height) * float(item.depth)
    unfitted_name += '{},'.format(item.partno)
    print("***************************************************")
print("***************************************************")
print('space utilization : {}%'.format(round(volume_t / float(volume) * 100, 2)))
print('residual volumn : ', float(volume) - volume_t)
print('unpack item : ', unfitted_name)
print('unpack item volumn : ', volume_f)
print("gravity distribution : ", b.gravity)
stop = time.time()
print('used time : ', stop - start)


# draw results
# painter = Painter(b)
# painter.plotBoxAndItems()

# ----------------------------------end---------------------------------------------


############################### PLOTLY ############################################
# https://plotly.com/python/3d-mesh/#mesh-cube
def vertices(xmin=0, ymin=0, zmin=0, xmax=1, ymax=1, zmax=1):
    return {
        "x": [xmin, xmin, xmax, xmax, xmin, xmin, xmax, xmax],
        "y": [ymin, ymax, ymax, ymin, ymin, ymax, ymax, ymin],
        "z": [zmin, zmin, zmin, zmin, zmax, zmax, zmax, zmax],
        "i": [7, 0, 0, 0, 4, 4, 6, 1, 4, 0, 3, 6],
        "j": [3, 4, 1, 2, 5, 6, 5, 2, 0, 1, 6, 3],
        "k": [0, 7, 2, 3, 6, 7, 1, 6, 5, 5, 7, 2],
    }


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


def slice_triangles(z, n, i, j, k, l):
    """Create the triangles of a single slice"""
    return [[z, j, i], [i, j, l], [l, j, k], [k, n, l]]


def cylinder_mesh(r, xs, ys, zs, h, n_slices=40):
    """Create a cylindrical mesh"""
    theta = np.linspace(0, 2 * np.pi, n_slices + 1)
    x = xs + r * np.cos(theta)
    y = ys + r * np.sin(theta)
    z1 = zs + 0 * np.ones_like(x)
    z2 = (zs + h) * np.ones_like(x)

    # index of the final point in the mesh
    n = n_slices * 2 + 1

    # build triangulation
    triangles = []
    for s in range(1, n_slices + 1):
        j = (s + 1) if (s <= n_slices - 1) else 1
        k = j + n_slices if (s <= n_slices - 1) else n_slices + 1
        l = s + n_slices
        triangles += slice_triangles(0, n, s, j, k, l)
    triangles = np.array(triangles)

    # coordinates of the vertices
    x_coords = np.hstack([xs, x[:-1], x[:-1], xs])
    y_coords = np.hstack([ys, y[:-1], y[:-1], ys])
    z_coords = np.hstack([zs, z1[:-1], z2[:-1], (zs + h)])
    vertices = np.stack([x_coords, y_coords, z_coords]).T

    return vertices, triangles, x, y, z1, z2

# def cylinder_traces(r, xs, ys, zs, h, n_slices=40, show_mesh=True, n_sub=4, surface_kw={}, line_kw={}):
def cylinder_traces(r, xs, ys, zs, h, color, name, n_slices=40, show_mesh=True, n_sub=4, line_kw={}):
    """
    r : radius
    xs, ys, zs : start position of the cylinder
    h : height of the cylinder
    n_slices : number of slices in the circumferential direction
    show_mesh : whether to display pseudo-wireframe
    n_sub : number of subdivision in along the height for the pseudo-wireframe
    surface_kw : customize the appearance of the surface
    line_kw : customize the appearance of the wireframe
    """
    vertices, triangles, x, y, z1, z2 = cylinder_mesh(r, xs, ys, zs, h, n_slices)
    # surface = go.Mesh3d(
    #   x=vertices[:, 0], y=vertices[:, 1], z=vertices[:, 2],
    #   i=triangles[:, 0], j=triangles[:, 1], k=triangles[:, 2],
    #   **surface_kw)
    # print("box_id: ", name)
    surface = go.Mesh3d(
        x=vertices[:, 0], y=vertices[:, 1], z=vertices[:, 2],
        i=triangles[:, 0], j=triangles[:, 1], k=triangles[:, 2],
        color=color, name=name)

    traces = [surface]
    if not show_mesh:
        return traces

    line_kw.setdefault("showlegend", False)
    # horizontal mesh lines
    zsubs = np.linspace(zs, zs + h, n_sub + 1)
    for zc in zsubs:
        traces.append(go.Scatter3d(x=x, y=y, z=zc * np.ones_like(x), mode="lines",name=name, **line_kw))
    # vertical mesh lines
    for _x, _y in zip(x, y):
        traces.append(go.Scatter3d(x=[_x, _x], y=[_y, _y], z=[zs, zs + h], mode="lines", name=name, **line_kw))
        # print("traces: ", traces)
    return traces


# take a packer item and build parameters to a plotly mesh3d cube
def packer_to_plotly(item):
    colors = ["crimson", "limegreen", "green", "red", "cyan", "magenta", "yellow"]
    ret = vertices(
        *item.position, *[sum(x) for x in zip(item.position, item.getDimension())]
    )
    ret["name"] = item.name
    ret["color"] = colors[ord(item.name.split("_")[0][-1]) - ord("A")]
    return ret


# create a figure for each bin
fig = go.Figure()

# add a trace for each packer item
for row, pbin in enumerate(packer.bins):
    for item in pbin.items:
        fig.add_trace(go.Mesh3d(packer_to_plotly(item)))

    # some first attempts at sorting out layout, prmarily aspect ratio
    fig.update_layout(
        margin={"l": 0, "r": 0, "t": 0, "b": 0},
        autosize=False,
        scene=dict(
            camera=dict(
                # eye=dict(x=0.1, y=0.1, z=1.5)
            ),
            aspectratio=dict(x=1, y=.2, z=0.2),
            aspectmode="manual",
        ),
    )

# push data into a data frame to enable more types of analysis
df = pd.DataFrame(
    [
        {
            "bin_name": b.partno,
            "bin_index": i,
            **packer_to_plotly(item),
            "item_typeof": item.typeof,
            **{d: v for v, d in zip(item.getDimension(), list("hwl"))},
            **{d + d: v for v, d in zip(item.position, list("xyz"))},
        }
        for i, b in enumerate(packer.bins)
        for item in b.items
    ]
)
# print("dataframe: \n", df['item_typeof'])

# create a figure for each container (bin)
for pbin, d in df.groupby("bin_name"):
    fig = go.Figure()
    xx = []
    yy = []
    zz = []

    # create a trace for each box (bin)
    for _, r in d.iterrows():
        # print("_, ", _,)
        # print("r ", r)
        if r["item_typeof"] == 'cube':
            fig.add_trace(
                go.Mesh3d(r[["x", "y", "z", "i", "j", "k", "name", "color"]].to_dict())
            )
            xx += [r.xx, r.xx + r.h, r.xx + r.h, r.xx, r.xx, None] * 2 + [r.xx] * 5 + [None]
            yy += [r.yy, r.yy, r.yy + r.w, r.yy + r.w, r.yy, None] * 2 + [
                r.yy,
                r.yy + r.w,
                r.yy + r.w,
                r.yy,
                r.yy,
                None,
            ]
            zz += (
                    [r.zz] * 5
                    + [None]
                    + [r.zz + r.l] * 5
                    + [None]
                    + [r.zz, r.zz, r.zz + r.l, r.zz + r.l, r.zz, None]
            )

            fig.add_trace(
                go.Scatter3d(
                    x=xx,
                    y=yy,
                    z=zz,
                    mode="lines",
                    line_color="black",
                    line_width=2,
                    hoverinfo="skip",
                )
            )
        else:
            name = r["name"]
            color = r["color"]
            radius = float(r["w"])/2
            height = float(r["l"])
            x_list = r["x"]
            # print("x_list: ", x_list)
            y_list = r["y"]
            # print("y_list: ", y_list)
            z_list = r["z"]
            x_min = float(min(x_list))
            # print("x_min ", x_min)
            x_max = float(max(x_list))
            # print("x_max ", x_max)
            y_min = float(min(y_list))
            y_max = float(max(y_list))
            x_cor = x_min + (x_max - x_min)/2
            y_cor = y_min + (y_max - y_min)/2
            z_cor = float(min(z_list))
            # print("xyz! ", x_cor,y_cor,z_cor)
            # colorscale = [[0, '#636EFA'], [1, '#636EFA']]
            # print("colorscale ", colorscale)
            fig.add_traces(
            #   cylinder_traces(radius, x_cor, y_cor, z_cor, height, n_sub=1, line_kw={"line_color": "#202020", "line_width": 3})
            # )
            cylinder_traces(radius, x_cor, y_cor, z_cor, height, color, name, n_sub=1,
                            line_kw={"line_color": "#202020", "line_width": 3}))


    x, y, z = parallelipipedic_frame(0, 1202.4, 0, 235, 0, 269.7)

    fig.add_trace(
        go.Scatter3d(
            x=x,
            y=y,
            z=z,
            mode="lines",
            line_color="blue",
            line_width=2,
            hoverinfo="skip",
        )
    )

    # -----------------newly added code to test plotting cylinder
    # fig.add_traces(
    #   cylinder_traces(50, 0, 0, 0, 80, n_sub=1, line_kw={"line_color": "#202020", "line_width": 3})
    # )

    # -----------------end for newly added code to test plotting cylinder-------------------

    ar = 4
    xr = max(d["x"].max()) - min(d["x"].min())
    # fig.update_layout(
    #   showlegend=False,
    #   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)),
    #       aspectmode="data",
    #   ),
    # )
    fig.update_layout(
        showlegend=False,
        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",
        ),
    )



    plotly.offline.plot(fig, filename='C:/Users/mike/Desktop/3D_BinPack_' + str(row) + '.html', auto_open=False,
                        config={'displaylogo': False})
    # fig.write_html('C:/Users/mike/Desktop/3D_BinPack_' + str(row) + '.html', auto_open=False,
    #              include_plotlyjs="cdn",config={'displaylogo': False})
    fig.show(config={'displaylogo': False})

您使用 plotly 的.to_html()函数来创建 html 文件。 在函数中,有一个参数决定是否包含 plotlyjs 源代码 - 通过包含代码,.html 文件在没有 Internet 连接的情况下变得可用,但文件大小相当大。 但是,您可以选择使用参数include_plotlyjs='cdn'排除 plotlyjs 源代码。 这将使文件更小,但意味着该文件只能在线查看。

您应该能够将您的图形 (fig) 保存到 .html 文件中,如下所示:

f = open('filename_here.html', "w")
f.close()
with open('filename_here.html', 'a') as f:
    f.write(fig.to_html(full_html=False, include_plotlyjs='cdn'))
f.close()

同样,这会将您的 fig 写入一个需要 Internet 连接才能查看的文件,但与包含 js 源代码的文件相比,它会非常小。

如果您想创建一个可离线使用的文件(权衡更大的文件大小),您可以将include_plotlyjs参数更改为True ,如下所示:

f = open('filename_here.html', "w")
f.close()
with open('filename_here.html', 'a') as f:
    f.write(fig.to_html(full_html=False, include_plotlyjs=True))
f.close()

注意:我已经包含了第一个open.close()调用来展示如何确保你正在写入的文件首先是空的。 这样做将清除文件,然后添加您的图形。 在没有先清除文件的情况下,如果您使用同一个输出文件多次运行该程序,您最终可能会“附加”到文件中,每次都使其变得越来越大。

请让我知道这是否适合您。


作为奖励:如果您确实想将多个图形写入单个 .html 文件,您可以创建一个绘图图形列表或字典,然后在打开文件的情况下遍历列表/字典,在每次迭代之前写入新图形关闭文件:

dictionary_of_figures = {} ### add your figures to this dictionary
   
f = open(filename_here.html,"w")
f.close()
with open(filename_here.html, 'a') as f:
    for key in dictionary_of_figures:
        f.write(dictionary_of_figures[key].to_html(full_html=False, include_plotlyjs='cdn'))
f.close()

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM