plotly.py icon indicating copy to clipboard operation
plotly.py copied to clipboard

Plotly: huge html file size

Open Westlife1002 opened this issue 2 years ago • 1 comments

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.

image

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

Westlife1002 avatar May 21 '22 02:05 Westlife1002

any help? I guess the big size is caused by the number of traces being created... one for each packing box.

Westlife1002 avatar Jun 05 '22 09:06 Westlife1002

im facing a similar issue with my scatter plots. I generate a 72MB file which always crashes when I try to open it in the browser.

shawnesquivel avatar Jul 07 '23 22:07 shawnesquivel