title: Plotly: how to avoid huge html file size
body: <p>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, or caused by “add_trace” method to individual item plot). 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.</p>
  <p><a href="https://i.stack.imgur.com/03Xq9.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/03Xq9.png" alt="enter image description here" /></a></p>
  <p>below is my full code (please skip the model code and see from the plotly code)</p>
  <pre><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&quot;BoxA_{num}&quot;, f&quot;BoxA_{num}&quot;, 'cube', (120, 120, 120), 8.20, 1, 100, True, 'red'))
  # for num in range(55):
  #   packer.addItem(Item(f&quot;BoxB_{num}&quot;, f&quot;BoxB_{num}&quot;, 'cube', (65, 38, 90), 14, 1, 100, True, 'blue'))
  # for num in range(50):
  #   packer.addItem(Item(f&quot;BoxC_{num}&quot;, f&quot;BoxC_{num}&quot;, 'cube', (143, 52, 47), 10, 1, 100, True, 'gray'))
  
  
  # add item
  # for num in range(12):
  #   packer.addItem(Item(f&quot;BoxA_{num}&quot;, f&quot;BoxA_{num}&quot;, 'cylinder', (120, 120, 120), 8.20, 1, 100, True, 'red'))
  # for num in range(120):
  #   packer.addItem(Item(f&quot;BoxB_{num}&quot;, f&quot;BoxB_{num}&quot;, 'cube', (65, 38, 90), 14, 1, 100, True, 'blue'))
  # for num in range(60):
  #   packer.addItem(Item(f&quot;BoxC_{num}&quot;, f&quot;BoxC_{num}&quot;, 'cube', (143, 52, 47), 10, 1, 100, True, 'gray'))
  
  
  # for num in range(12):
  #   packer.addItem(Item(f&quot;BoxA_{num}&quot;, f&quot;BoxA_{num}&quot;, 'cylinder', (120, 120, 120), 8.20, 1, 100, True, 'red'))
  # for num in range(33):
  #   packer.addItem(Item(f&quot;BoxB_{num}&quot;, f&quot;BoxB_{num}&quot;, 'cube', (65, 38, 90), 14, 1, 100, True, 'blue'))
  # for num in range(32):
  #   packer.addItem(Item(f&quot;BoxC_{num}&quot;, f&quot;BoxC_{num}&quot;, 'cube', (143, 52, 47), 10, 1, 100, True, 'gray'))
  
  
  for num in range(252):
      packer.addItem(Item(f&quot;BoxA_{num}&quot;, f&quot;BoxA_{num}&quot;, 'cube', (65, 33, 26), 2.06, 1, 100, True, 'red'))
  for num in range(222):
      packer.addItem(Item(f&quot;BoxB_{num}&quot;, f&quot;BoxB_{num}&quot;, 'cube', (84, 42.5, 33), 2.72, 1, 100, True, 'blue'))
  for num in range(270):
      packer.addItem(Item(f&quot;BoxC_{num}&quot;, f&quot;BoxC_{num}&quot;, '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(&quot;:::::::::::&quot;, b.string())
  
  print(&quot;FITTED ITEMS:&quot;)
  volume_t = 0
  volume_f = 0
  unfitted_name = ''
  for item in b.items:
      print(&quot;partno : &quot;, item.partno)
      print(&quot;color : &quot;, item.color)
      print(&quot;position : &quot;, item.position)
      print(&quot;type of : &quot;, item.typeof)
      print(&quot;rotation type : &quot;, item.rotation_type)
      print(&quot;W*H*D : &quot;, str(item.width) + '*' + str(item.height) + '*' + str(item.depth))
      print(&quot;volume : &quot;, float(item.width) * float(item.height) * float(item.depth))
      print(&quot;weight : &quot;, float(item.weight))
      volume_t += float(item.width) * float(item.height) * float(item.depth)
      print(&quot;***************************************************&quot;)
  print(&quot;***************************************************&quot;)
  print(&quot;UNFITTED ITEMS:&quot;)
  for item in b.unfitted_items:
      print(&quot;partno : &quot;, item.partno)
      print(&quot;color : &quot;, item.color)
      print(&quot;W*H*D : &quot;, str(item.width) + '*' + str(item.height) + '*' + str(item.depth))
      print(&quot;volume : &quot;, float(item.width) * float(item.height) * float(item.depth))
      print(&quot;weight : &quot;, float(item.weight))
      volume_f += float(item.width) * float(item.height) * float(item.depth)
      unfitted_name += '{},'.format(item.partno)
      print(&quot;***************************************************&quot;)
  print(&quot;***************************************************&quot;)
  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(&quot;gravity distribution : &quot;, 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 {
          &quot;x&quot;: [xmin, xmin, xmax, xmax, xmin, xmin, xmax, xmax],
          &quot;y&quot;: [ymin, ymax, ymax, ymin, ymin, ymax, ymax, ymin],
          &quot;z&quot;: [zmin, zmin, zmin, zmin, zmax, zmax, zmax, zmax],
          &quot;i&quot;: [7, 0, 0, 0, 4, 4, 6, 1, 4, 0, 3, 6],
          &quot;j&quot;: [3, 4, 1, 2, 5, 6, 5, 2, 0, 1, 6, 3],
          &quot;k&quot;: [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):
      &quot;&quot;&quot;Create the triangles of a single slice&quot;&quot;&quot;
      return [[z, j, i], [i, j, l], [l, j, k], [k, n, l]]
  
  
  def cylinder_mesh(r, xs, ys, zs, h, n_slices=40):
      &quot;&quot;&quot;Create a cylindrical mesh&quot;&quot;&quot;
      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 &lt;= n_slices - 1) else 1
          k = j + n_slices if (s &lt;= 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={}):
      &quot;&quot;&quot;
      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
      &quot;&quot;&quot;
      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(&quot;box_id: &quot;, 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(&quot;showlegend&quot;, 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=&quot;lines&quot;,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=&quot;lines&quot;, name=name, **line_kw))
          # print(&quot;traces: &quot;, traces)
      return traces
  
  
  # take a packer item and build parameters to a plotly mesh3d cube
  def packer_to_plotly(item):
      colors = [&quot;crimson&quot;, &quot;limegreen&quot;, &quot;green&quot;, &quot;red&quot;, &quot;cyan&quot;, &quot;magenta&quot;, &quot;yellow&quot;]
      ret = vertices(
          *item.position, *[sum(x) for x in zip(item.position, item.getDimension())]
      )
      ret[&quot;name&quot;] = item.name
      ret[&quot;color&quot;] = colors[ord(item.name.split(&quot;_&quot;)[0][-1]) - ord(&quot;A&quot;)]
      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={&quot;l&quot;: 0, &quot;r&quot;: 0, &quot;t&quot;: 0, &quot;b&quot;: 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=&quot;manual&quot;,
          ),
      )
  
  # push data into a data frame to enable more types of analysis
  df = pd.DataFrame(
      [
          {
              &quot;bin_name&quot;: b.partno,
              &quot;bin_index&quot;: i,
              **packer_to_plotly(item),
              &quot;item_typeof&quot;: item.typeof,
              **{d: v for v, d in zip(item.getDimension(), list(&quot;hwl&quot;))},
              **{d + d: v for v, d in zip(item.position, list(&quot;xyz&quot;))},
          }
          for i, b in enumerate(packer.bins)
          for item in b.items
      ]
  )
  # print(&quot;dataframe: \n&quot;, df['item_typeof'])
  
  # create a figure for each container (bin)
  for pbin, d in df.groupby(&quot;bin_name&quot;):
      fig = go.Figure()
      xx = []
      yy = []
      zz = []
  
      # create a trace for each box (bin)
      for _, r in d.iterrows():
          # print(&quot;_, &quot;, _,)
          # print(&quot;r &quot;, r)
          if r[&quot;item_typeof&quot;] == 'cube':
              fig.add_trace(
                  go.Mesh3d(r[[&quot;x&quot;, &quot;y&quot;, &quot;z&quot;, &quot;i&quot;, &quot;j&quot;, &quot;k&quot;, &quot;name&quot;, &quot;color&quot;]].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=&quot;lines&quot;,
                      line_color=&quot;black&quot;,
                      line_width=2,
                      hoverinfo=&quot;skip&quot;,
                  )
              )
          else:
              name = r[&quot;name&quot;]
              color = r[&quot;color&quot;]
              radius = float(r[&quot;w&quot;])/2
              height = float(r[&quot;l&quot;])
              x_list = r[&quot;x&quot;]
              # print(&quot;x_list: &quot;, x_list)
              y_list = r[&quot;y&quot;]
              # print(&quot;y_list: &quot;, y_list)
              z_list = r[&quot;z&quot;]
              x_min = float(min(x_list))
              # print(&quot;x_min &quot;, x_min)
              x_max = float(max(x_list))
              # print(&quot;x_max &quot;, 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(&quot;xyz! &quot;, x_cor,y_cor,z_cor)
              # colorscale = [[0, '#636EFA'], [1, '#636EFA']]
              # print(&quot;colorscale &quot;, colorscale)
              fig.add_traces(
              #   cylinder_traces(radius, x_cor, y_cor, z_cor, height, n_sub=1, line_kw={&quot;line_color&quot;: &quot;#202020&quot;, &quot;line_width&quot;: 3})
              # )
              cylinder_traces(radius, x_cor, y_cor, z_cor, height, color, name, n_sub=1,
                              line_kw={&quot;line_color&quot;: &quot;#202020&quot;, &quot;line_width&quot;: 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=&quot;lines&quot;,
              line_color=&quot;blue&quot;,
              line_width=2,
              hoverinfo=&quot;skip&quot;,
          )
      )
  
      # -----------------newly added code to test plotting cylinder
      # fig.add_traces(
      #   cylinder_traces(50, 0, 0, 0, 80, n_sub=1, line_kw={&quot;line_color&quot;: &quot;#202020&quot;, &quot;line_width&quot;: 3})
      # )
  
      # -----------------end for newly added code to test plotting cylinder-------------------
  
      ar = 4
      xr = max(d[&quot;x&quot;].max()) - min(d[&quot;x&quot;].min())
      # fig.update_layout(
      #   showlegend=False,
      #   title={&quot;text&quot;: pbin, &quot;y&quot;: 0.9, &quot;x&quot;: 0.5, &quot;xanchor&quot;: &quot;center&quot;, &quot;yanchor&quot;: &quot;top&quot;},
      #   margin={&quot;l&quot;: 0, &quot;r&quot;: 0, &quot;t&quot;: 0, &quot;b&quot;: 0},
      #   # autosize=False,
      #   scene=dict(
      #       camera=dict(eye=dict(x=2, y=2, z=2)),
      #       aspectmode=&quot;data&quot;,
      #   ),
      # )
      fig.update_layout(
          showlegend=False,
          title={&quot;text&quot;: pbin, &quot;y&quot;: 0.9, &quot;x&quot;: 0.5, &quot;xanchor&quot;: &quot;center&quot;, &quot;yanchor&quot;: &quot;top&quot;},
          margin={&quot;l&quot;: 0, &quot;r&quot;: 0, &quot;t&quot;: 0, &quot;b&quot;: 0},
          # autosize=False,
          scene=dict(
              camera=dict(eye=dict(x=2, y=2, z=2)),
              aspectratio={
                  **{&quot;x&quot;: ar},
                  **{
                      c: ((max(d[c].max()) - min(d[c].min())) / xr) * ar
                      for c in list(&quot;yz&quot;)
                  },
              },
              aspectmode=&quot;manual&quot;,
          ),
      )
  
  
  
      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=&quot;cdn&quot;,config={'displaylogo': False})
      fig.show(config={'displaylogo': False})
  </code></pre>