Plotly: huge html file size

I have a 3D bin packing model which uses plotly to draw the output graph. I noticed that with 600+items being ploted, it takes a long time to generate the html file and the file size is 89M, which is crazy (I doubt there might be some huge duplications). why does it make such a big file? How to control the size to an acceptable level (no more than 5M as I need to render it in my website). many thanks for the help.

below is my full code (please skip the model code and see from the plotly code)

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: GitHub - jerry800416/3D-bin-packing: 3D Bin Packing improvements based on https://github.com/enzoruiz/3dbinpacking

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("WHD : ", 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("WHD : ", 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 ############################################

3d mesh plots in Python

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

any help regarding this issue?

Your code is formatted a bit funny, which makes it hard to read. Any chance you can make a minimal example?

My primary hack to reduce file size has been to round values before plotting, since the values are stored as plaintext in the html file. For example, if you are writing values as 0.3978238382 when 0.40 would suffice, that cuts the file size by a factor of ~3. Not sure if this is something you’re already doing/able to do here, but figured I’d mention it.

That said, I am not a plotly dev, so there may be much better approaches to compression that I haven’t yet found. (If so, would love to hear about them).

You don’t have to look at the mode code (I just copy all in case anyone want to actually run it to test). You just look at the plotly code par, the above plotly drawing codes will result in a very large html file when the quantity of items is big, e.g. 600+ items to be plotted in a container, which will result in 80MB file size. how can we avoid generating such a big size html file? I guess this is a result of “add_trace” to individual item plot.