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

Jac*_*ack 3 python plotly

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

\n

在此输入图像描述

\n

下面是我的完整代码(请跳过模型代码并从绘图代码中查看)

\n
from py3dbp import Packer, Bin, Item, Painter\nimport time\nimport plotly.graph_objects as go\nfrom plotly.subplots import make_subplots\nimport plotly\nimport pandas as pd\n\nstart = time.time()\nimport numpy as np\n\n# -----------this part is about calculating the 3D bin packing problem to get x,y,z for each items of a bin/container--------------\n###library reference: https://github.com/jerry800416/3D-bin-packing\n\n# init packing function\npacker = Packer()\n#  init bin\n# box = Bin(\'40HC-1\', (1203, 235, 259), 18000.0,0,0)\nbox = Bin(\'40HC-1\', (1202.4, 235, 269.7), 18000.0, 0, 0)\npacker.addBin(box)\n\n\n# add item\n# for num in range(10):\n#   packer.addItem(Item(f"BoxA_{num}", f"BoxA_{num}", \'cube\', (120, 120, 120), 8.20, 1, 100, True, \'red\'))\n# for num in range(55):\n#   packer.addItem(Item(f"BoxB_{num}", f"BoxB_{num}", \'cube\', (65, 38, 90), 14, 1, 100, True, \'blue\'))\n# for num in range(50):\n#   packer.addItem(Item(f"BoxC_{num}", f"BoxC_{num}", \'cube\', (143, 52, 47), 10, 1, 100, True, \'gray\'))\n\n\n# add item\n# for num in range(12):\n#   packer.addItem(Item(f"BoxA_{num}", f"BoxA_{num}", \'cylinder\', (120, 120, 120), 8.20, 1, 100, True, \'red\'))\n# for num in range(120):\n#   packer.addItem(Item(f"BoxB_{num}", f"BoxB_{num}", \'cube\', (65, 38, 90), 14, 1, 100, True, \'blue\'))\n# for num in range(60):\n#   packer.addItem(Item(f"BoxC_{num}", f"BoxC_{num}", \'cube\', (143, 52, 47), 10, 1, 100, True, \'gray\'))\n\n\n# for num in range(12):\n#   packer.addItem(Item(f"BoxA_{num}", f"BoxA_{num}", \'cylinder\', (120, 120, 120), 8.20, 1, 100, True, \'red\'))\n# for num in range(33):\n#   packer.addItem(Item(f"BoxB_{num}", f"BoxB_{num}", \'cube\', (65, 38, 90), 14, 1, 100, True, \'blue\'))\n# for num in range(32):\n#   packer.addItem(Item(f"BoxC_{num}", f"BoxC_{num}", \'cube\', (143, 52, 47), 10, 1, 100, True, \'gray\'))\n\n\nfor num in range(252):\n    packer.addItem(Item(f"BoxA_{num}", f"BoxA_{num}", \'cube\', (65, 33, 26), 2.06, 1, 100, True, \'red\'))\nfor num in range(222):\n    packer.addItem(Item(f"BoxB_{num}", f"BoxB_{num}", \'cube\', (84, 42.5, 33), 2.72, 1, 100, True, \'blue\'))\nfor num in range(270):\n    packer.addItem(Item(f"BoxC_{num}", f"BoxC_{num}", \'cube\', (48, 48, 38), 2.17, 1, 100, True, \'gray\'))\n\n\n\n# calculate packing\npacker.pack(bigger_first=True, distribute_items=False, fix_point=True, number_of_decimals=0)\n\n# print result\nb = packer.bins[0]\nvolume = b.width * b.height * b.depth\nprint(":::::::::::", b.string())\n\nprint("FITTED ITEMS:")\nvolume_t = 0\nvolume_f = 0\nunfitted_name = \'\'\nfor item in b.items:\n    print("partno : ", item.partno)\n    print("color : ", item.color)\n    print("position : ", item.position)\n    print("type of : ", item.typeof)\n    print("rotation type : ", item.rotation_type)\n    print("W*H*D : ", str(item.width) + \'*\' + str(item.height) + \'*\' + str(item.depth))\n    print("volume : ", float(item.width) * float(item.height) * float(item.depth))\n    print("weight : ", float(item.weight))\n    volume_t += float(item.width) * float(item.height) * float(item.depth)\n    print("***************************************************")\nprint("***************************************************")\nprint("UNFITTED ITEMS:")\nfor item in b.unfitted_items:\n    print("partno : ", item.partno)\n    print("color : ", item.color)\n    print("W*H*D : ", str(item.width) + \'*\' + str(item.height) + \'*\' + str(item.depth))\n    print("volume : ", float(item.width) * float(item.height) * float(item.depth))\n    print("weight : ", float(item.weight))\n    volume_f += float(item.width) * float(item.height) * float(item.depth)\n    unfitted_name += \'{},\'.format(item.partno)\n    print("***************************************************")\nprint("***************************************************")\nprint(\'space utilization : {}%\'.format(round(volume_t / float(volume) * 100, 2)))\nprint(\'residual volumn : \', float(volume) - volume_t)\nprint(\'unpack item : \', unfitted_name)\nprint(\'unpack item volumn : \', volume_f)\nprint("gravity distribution : ", b.gravity)\nstop = time.time()\nprint(\'used time : \', stop - start)\n\n\n# draw results\n# painter = Painter(b)\n# painter.plotBoxAndItems()\n\n# ----------------------------------end---------------------------------------------\n\n\n############################### PLOTLY ############################################\n# https://plotly.com/python/3d-mesh/#mesh-cube\ndef vertices(xmin=0, ymin=0, zmin=0, xmax=1, ymax=1, zmax=1):\n    return {\n        "x": [xmin, xmin, xmax, xmax, xmin, xmin, xmax, xmax],\n        "y": [ymin, ymax, ymax, ymin, ymin, ymax, ymax, ymin],\n        "z": [zmin, zmin, zmin, zmin, zmax, zmax, zmax, zmax],\n        "i": [7, 0, 0, 0, 4, 4, 6, 1, 4, 0, 3, 6],\n        "j": [3, 4, 1, 2, 5, 6, 5, 2, 0, 1, 6, 3],\n        "k": [0, 7, 2, 3, 6, 7, 1, 6, 5, 5, 7, 2],\n    }\n\n\ndef parallelipipedic_frame(xm, xM, ym, yM, zm, zM):\n    # defines the coords of each segment followed by None, if the line is\n    # discontinuous\n    x = [xm, xM, xM, xm, xm, None, xm, xM, xM, xm, xm, None, xm, xm, None, xM, xM,\n         None, xM, xM, None, xm, xm]\n    y = [ym, ym, yM, yM, ym, None, ym, ym, yM, yM, ym, None, ym, ym, None, ym, ym,\n         None, yM, yM, None, yM, yM]\n    z = [zm, zm, zm, zm, zm, None, zM, zM, zM, zM, zM, None, zm, zM, None, zm, zM,\n         None, zm, zM, None, zm, zM]\n    return x, y, z\n\n\ndef slice_triangles(z, n, i, j, k, l):\n    """Create the triangles of a single slice"""\n    return [[z, j, i], [i, j, l], [l, j, k], [k, n, l]]\n\n\ndef cylinder_mesh(r, xs, ys, zs, h, n_slices=40):\n    """Create a cylindrical mesh"""\n    theta = np.linspace(0, 2 * np.pi, n_slices + 1)\n    x = xs + r * np.cos(theta)\n    y = ys + r * np.sin(theta)\n    z1 = zs + 0 * np.ones_like(x)\n    z2 = (zs + h) * np.ones_like(x)\n\n    # index of the final point in the mesh\n    n = n_slices * 2 + 1\n\n    # build triangulation\n    triangles = []\n    for s in range(1, n_slices + 1):\n        j = (s + 1) if (s <= n_slices - 1) else 1\n        k = j + n_slices if (s <= n_slices - 1) else n_slices + 1\n        l = s + n_slices\n        triangles += slice_triangles(0, n, s, j, k, l)\n    triangles = np.array(triangles)\n\n    # coordinates of the vertices\n    x_coords = np.hstack([xs, x[:-1], x[:-1], xs])\n    y_coords = np.hstack([ys, y[:-1], y[:-1], ys])\n    z_coords = np.hstack([zs, z1[:-1], z2[:-1], (zs + h)])\n    vertices = np.stack([x_coords, y_coords, z_coords]).T\n\n    return vertices, triangles, x, y, z1, z2\n\n# def cylinder_traces(r, xs, ys, zs, h, n_slices=40, show_mesh=True, n_sub=4, surface_kw={}, line_kw={}):\ndef cylinder_traces(r, xs, ys, zs, h, color, name, n_slices=40, show_mesh=True, n_sub=4, line_kw={}):\n    """\n    r : radius\n    xs, ys, zs : start position of the cylinder\n    h : height of the cylinder\n    n_slices : number of slices in the circumferential direction\n    show_mesh : whether to display pseudo-wireframe\n    n_sub : number of subdivision in along the height for the pseudo-wireframe\n    surface_kw : customize the appearance of the surface\n    line_kw : customize the appearance of the wireframe\n    """\n    vertices, triangles, x, y, z1, z2 = cylinder_mesh(r, xs, ys, zs, h, n_slices)\n    # surface = go.Mesh3d(\n    #   x=vertices[:, 0], y=vertices[:, 1], z=vertices[:, 2],\n    #   i=triangles[:, 0], j=triangles[:, 1], k=triangles[:, 2],\n    #   **surface_kw)\n    # print("box_id: ", name)\n    surface = go.Mesh3d(\n        x=vertices[:, 0], y=vertices[:, 1], z=vertices[:, 2],\n        i=triangles[:, 0], j=triangles[:, 1], k=triangles[:, 2],\n        color=color, name=name)\n\n    traces = [surface]\n    if not show_mesh:\n        return traces\n\n    line_kw.setdefault("showlegend", False)\n    # horizontal mesh lines\n    zsubs = np.linspace(zs, zs + h, n_sub + 1)\n    for zc in zsubs:\n        traces.append(go.Scatter3d(x=x, y=y, z=zc * np.ones_like(x), mode="lines",name=name, **line_kw))\n    # vertical mesh lines\n    for _x, _y in zip(x, y):\n        traces.append(go.Scatter3d(x=[_x, _x], y=[_y, _y], z=[zs, zs + h], mode="lines", name=name, **line_kw))\n        # print("traces: ", traces)\n    return traces\n\n\n# take a packer item and build parameters to a plotly mesh3d cube\ndef packer_to_plotly(item):\n    colors = ["crimson", "limegreen", "green", "red", "cyan", "magenta", "yellow"]\n    ret = vertices(\n        *item.position, *[sum(x) for x in zip(item.position, item.getDimension())]\n    )\n    ret["name"] = item.name\n    ret["color"] = colors[ord(item.name.split("_")[0][-1]) - ord("A")]\n    return ret\n\n\n# create a figure for each bin\nfig = go.Figure()\n\n# add a trace for each packer item\nfor row, pbin in enumerate(packer.bins):\n    for item in pbin.items:\n        fig.add_trace(go.Mesh3d(packer_to_plotly(item)))\n\n    # some first attempts at sorting out layout, prmarily aspect ratio\n    fig.update_layout(\n        margin={"l": 0, "r": 0, "t": 0, "b": 0},\n        autosize=False,\n        scene=dict(\n            camera=dict(\n                # eye=dict(x=0.1, y=0.1, z=1.5)\n            ),\n            aspectratio=dict(x=1, y=.2, z=0.2),\n            aspectmode="manual",\n        ),\n    )\n\n# push data into a data frame to enable more types of analysis\ndf = pd.DataFrame(\n    [\n        {\n            "bin_name": b.partno,\n            "bin_index": i,\n            **packer_to_plotly(item),\n            "item_typeof": item.typeof,\n            **{d: v for v, d in zip(item.getDimension(), list("hwl"))},\n            **{d + d: v for v, d in zip(item.position, list("xyz"))},\n        }\n        for i, b in enumerate(packer.bins)\n        for item in b.items\n    ]\n)\n# print("dataframe: \\n", df[\'item_typeof\'])\n\n# create a figure for each container (bin)\nfor pbin, d in df.groupby("bin_name"):\n    fig = go.Figure()\n    xx = []\n    yy = []\n    zz = []\n\n    # create a trace for each box (bin)\n    for _, r in d.iterrows():\n        # print("_, ", _,)\n        # print("r ", r)\n        if r["item_typeof"] == \'cube\':\n            fig.add_trace(\n                go.Mesh3d(r[["x", "y", "z", "i", "j", "k", "name", "color"]].to_dict())\n            )\n            xx += [r.xx, r.xx + r.h, r.xx + r.h, r.xx, r.xx, None] * 2 + [r.xx] * 5 + [None]\n            yy += [r.yy, r.yy, r.yy + r.w, r.yy + r.w, r.yy, None] * 2 + [\n                r.yy,\n                r.yy + r.w,\n                r.yy + r.w,\n                r.yy,\n                r.yy,\n                None,\n            ]\n            zz += (\n                    [r.zz] * 5\n                    + [None]\n                    + [r.zz + r.l] * 5\n                    + [None]\n                    + [r.zz, r.zz, r.zz + r.l, r.zz + r.l, r.zz, None]\n            )\n\n            fig.add_trace(\n                go.Scatter3d(\n                    x=xx,\n                    y=yy,\n                    z=zz,\n                    mode="lines",\n                    line_color="black",\n                    line_width=2,\n                    hoverinfo="skip",\n                )\n            )\n        else:\n            name = r["name"]\n            color = r["color"]\n            radius = float(r["w"])/2\n            height = float(r["l"])\n            x_list = r["x"]\n            # print("x_list: ", x_list)\n            y_list = r["y"]\n            # print("y_list: ", y_list)\n            z_list = r["z"]\n            x_min = float(min(x_list))\n            # print("x_min ", x_min)\n            x_max = float(max(x_list))\n            # print("x_max ", x_max)\n            y_min = float(min(y_list))\n            y_max = float(max(y_list))\n            x_cor = x_min + (x_max - x_min)/2\n            y_cor = y_min + (y_max - y_min)/2\n            z_cor = float(min(z_list))\n            # print("xyz! ", x_cor,y_cor,z_cor)\n            # colorscale = [[0, \'#636EFA\'], [1, \'#636EFA\']]\n            # print("colorscale ", colorscale)\n            fig.add_traces(\n            #   cylinder_traces(radius, x_cor, y_cor, z_cor, height, n_sub=1, line_kw={"line_color": "#202020", "line_width": 3})\n            # )\n            cylinder_traces(radius, x_cor, y_cor, z_cor, height, color, name, n_sub=1,\n                            line_kw={"line_color": "#202020", "line_width": 3}))\n\n\n    x, y, z = parallelipipedic_frame(0, 1202.4, 0, 235, 0, 269.7)\n\n    fig.add_trace(\n        go.Scatter3d(\n            x=x,\n            y=y,\n            z=z,\n            mode="lines",\n            line_color="blue",\n            line_width=2,\n            hoverinfo="skip",\n        )\n    )\n\n    # -----------------newly added code to test plotting cylinder\n    # fig.add_traces(\n    #   cylinder_traces(50, 0, 0, 0, 80, n_sub=1, line_kw={"line_color": "#202020", "line_width": 3})\n    # )\n\n    # -----------------end for newly added code to test plotting cylinder-------------------\n\n    ar = 4\n    xr = max(d["x"].max()) - min(d["x"].min())\n    # fig.update_layout(\n    #   showlegend=False,\n    #   title={"text": pbin, "y": 0.9, "x": 0.5, "xanchor": "center", "yanchor": "top"},\n    #   margin={"l": 0, "r": 0, "t": 0, "b": 0},\n    #   # autosize=False,\n    #   scene=dict(\n    #       camera=dict(eye=dict(x=2, y=2, z=2)),\n    #       aspectmode="data",\n    #   ),\n    # )\n    fig.update_layout(\n        showlegend=False,\n        title={"text": pbin, "y": 0.9, "x": 0.5, "xanchor": "center", "yanchor": "top"},\n        margin={"l": 0, "r": 0, "t": 0, "b": 0},\n        # autosize=False,\n        scene=dict(\n            camera=dict(eye=dict(x=2, y=2, z=2)),\n            aspectratio={\n                **{"x": ar},\n                **{\n                    c: ((max(d[c].max()) - min(d[c].min())) / xr) * ar\n                    for c in list("yz")\n                },\n            },\n            aspectmode="manual",\n        ),\n    )\n\n\n\n    plotly.offline.plot(fig, filename=\'C:/Users/mike/Desktop/3D_BinPack_\' + str(row) + \'.html\', auto_open=False,\n                        config={\'displaylogo\': False})\n    # fig.write_html(\'C:/Users/mike/Desktop/3D_BinPack_\' + str(row) + \'.html\', auto_open=False,\n    #              include_plotlyjs="cdn",config={\'displaylogo\': False})\n    fig.show(config={\'displaylogo\': False})\n
Run Code Online (Sandbox Code Playgroud)\n

JaP*_*aPa 6

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

您应该能够将图形 (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()
Run Code Online (Sandbox Code Playgroud)

同样,这会将您的无花果写入一个需要互联网连接才能查看的文件,但与包含 js 源代码的文件相比会相当小。

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

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()
Run Code Online (Sandbox Code Playgroud)

注意:我已经包含了第一个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()
Run Code Online (Sandbox Code Playgroud)