code: def _get_projection(cls, obj):
          
          isoverlay = lambda x: isinstance(x, CompositeOverlay)
          element3d = obj.traverse(lambda x: x, [Element3D])
          if element3d:
              return '3d'
          opts = cls._traverse_options(obj, 'plot', ['projection'],
                                       [CompositeOverlay, Element],
                                       keyfn=isoverlay)
          from_overlay = not all(p is None for p in opts[True]['projection'])
          projections = opts[from_overlay]['projection']
          custom_projs = [p for p in projections if p is not None]
          if len(set(custom_projs)) > 1:
              raise Exception("An axis may only be assigned one projection type")
          return custom_projs[0] if custom_projs else None
docs: Uses traversal to find the appropriate projection
          for a nested object. Respects projections set on
          Overlays before considering Element based settings,
          before finally looking up the default projection on
          the plot type. If more than one non-None projection
          type is found an exception is raised.