docs: def trace3D(self):
          """Give a 3D representation of the traceroute.
          right button: rotate the scene
          middle button: zoom
          left button: move the scene
          left button on a ball: toggle IP displaying
          ctrl-left button on a ball: scan ports 21,22,23,25,80 and 443 and display the result"""
          trace = self.get_trace()
          import visual
  
          class IPsphere(visual.sphere):
              def __init__(self, ip, **kargs):
                  visual.sphere.__init__(self, **kargs)
                  self.ip = ip
                  self.label = None
                  self.setlabel(self.ip)
  
              def setlabel(self, txt, visible=None):
                  if self.label is not None:
                      if visible is None:
                          visible = self.label.visible
                      self.label.visible = 0
                  elif visible is None:
                      visible = 0
                  self.label = visual.label(text=txt, pos=self.pos, space=self.radius, xoffset=10, yoffset=20, visible=visible)
  
              def action(self):
                  self.label.visible ^= 1
  
          visual.scene = visual.display()
          visual.scene.exit = True
          start = visual.box()
          rings = {}
          tr3d = {}
          for i in trace:
              tr = trace[i]
              tr3d[i] = []
              ttl = tr.keys()
              for t in range(1, max(ttl) + 1):
                  if t not in rings:
                      rings[t] = []
                  if t in tr:
                      if tr[t] not in rings[t]:
                          rings[t].append(tr[t])
                      tr3d[i].append(rings[t].index(tr[t]))
                  else:
                      rings[t].append(("unk", -1))
                      tr3d[i].append(len(rings[t]) - 1)
          for t in rings:
              r = rings[t]
              l = len(r)
              for i in range(l):
                  if r[i][1] == -1:
                      col = (0.75, 0.75, 0.75)
                  elif r[i][1]:
                      col = visual.color.green
                  else:
                      col = visual.color.blue
  
                  s = IPsphere(pos=((l - 1) * visual.cos(2 * i * visual.pi / l), (l - 1) * visual.sin(2 * i * visual.pi / l), 2 * t),
                               ip=r[i][0],
                               color=col)
                  for trlst in tr3d.values():
                      if t <= len(trlst):
                          if trlst[t - 1] == i:
                              trlst[t - 1] = s
          forecol = colgen(0.625, 0.4375, 0.25, 0.125)
          for trlst in tr3d.values():
              col = next(forecol)
              start = (0, 0, 0)
              for ip in trlst:
                  visual.cylinder(pos=start, axis=ip.pos - start, color=col, radius=0.2)
                  start = ip.pos
  
          movcenter = None
          while 1:
              visual.rate(50)
              if visual.scene.kb.keys:
                  k = visual.scene.kb.getkey()
                  if k == "esc" or k == "q":
                      break
              if visual.scene.mouse.events:
                  ev = visual.scene.mouse.getevent()
                  if ev.press == "left":
                      o = ev.pick
                      if o:
                          if ev.ctrl:
                              if o.ip == "unk":
                                  continue
                              savcolor = o.color
                              o.color = (1, 0, 0)
                              a, _ = sr(IP(dst=o.ip) / TCP(dport=[21, 22, 23, 25, 80, 443]), timeout=2)
                              o.color = savcolor
                              if len(a) == 0:
                                  txt = "%s:\nno results" % o.ip
                              else:
                                  txt = "%s:\n" % o.ip
                                  for s, r in a:
                                      txt += r.sprintf("{TCP:%IP.src%:%TCP.sport% %TCP.flags%}{TCPerror:%IPerror.dst%:%TCPerror.dport% %IP.src% %ir,ICMP.type%}\n")
                              o.setlabel(txt, visible=1)
                          else:
                              if hasattr(o, "action"):
                                  o.action()
                  elif ev.drag == "left":
                      movcenter = ev.pos
                  elif ev.drop == "left":
                      movcenter = None
              if movcenter:
                  visual.scene.center -= visual.scene.mouse.pos - movcenter
                  movcenter = visual.scene.mouse.pos