docstr: Improved tree layout based on the Sugiyama algorithm.
  * rotate: rotates the layout by the given angle (in degrees)
docstr_tok: ["Improved","tree","layout","based","on","the","Sugiyama","algorithm",".","*","rotate",":","rotates","the","layout","by","the","given","angle","(","in","degrees",")"]
code: def tree_layout(
      self,
      level_col: Optional[str] = None,
      level_sort_values_by: Optional[Union[str, List[str]]] = None,
      level_sort_values_by_ascending: bool = True,
      width: Optional[float] = None,
      height: Optional[float] = None,
      rotate: Optional[float] = None,
      allow_cycles=True,
      root=None,
      *args,
      **kwargs,
  ):
      """
      Improved tree layout based on the Sugiyama algorithm.
      * rotate: rotates the layout by the given angle (in degrees)
      """
      g: Plottable = self
  
      if (g._edges is None) or (len(g._edges) == 0):
          return g
  
      x_col = g._point_x if g._point_x is not None else "x"
      if self._point_x is None:
          g = g.bind(point_x=x_col).layout_settings(play=0)
  
      y_col = g._point_y if g._point_y is not None else "y"
      if g._point_y is None:
          g = g.bind(point_y=y_col).layout_settings(play=0)
  
      # since the coordinates are topological
      if width is None:
          width = 1
      if height is None:
          height = 1
  
      # ============================================================
      # level and y-values
      # ============================================================
      if level_col is None:
          level_col = "level"
      g2 = g.materialize_nodes()
      # check cycles
      if not allow_cycles:
          if SugiyamaLayout.has_cycles(
              g._edges,
              source_column=g2._source,
              target_column=g2._destination,
          ):
              raise ValueError
  
      triples = SugiyamaLayout.arrange(
          g2._edges,
          topological_coordinates=True,
          source_column=g2._source,
          target_column=g2._destination,
          include_levels=True,
          root=root,
      )
      g2._nodes[level_col] = [
          triples[id][2] for id in g2._nodes[g2._node]
      ]
      g2._nodes[y_col] = [
          triples[id][1] * height for id in g2._nodes[g2._node]
      ]
  
      if (g2._nodes is None) or (len(g2._nodes) == 0):
          return g2
      # ============================================================
      #  y-values
      # ============================================================
      if level_sort_values_by is not None:
          g2 = g2.nodes(
              g2._nodes.sort_values(
                  by=level_sort_values_by,
                  ascending=level_sort_values_by_ascending,
              )
          )
  
      g2._nodes[x_col] = [
          triples[id][0] * width for id in g2._nodes[g2._node]
      ]
  
      if rotate is not None:
          g2 = cast(LayoutsMixin, g2).rotate(rotate)
  
      return g2
  
file_path: pygraphistry/graphistry/layouts.py
imports: from typing import Any, Callable, cast, Iterable, List, Optional, Set, Union, TYPE_CHECKING
  import math, pandas
  from .Plottable import Plottable
  from .layout import SugiyamaLayout, group_in_a_box_layout
  from .layout.graph import Graph
  from .util import deprecated, setup_logger
label_desc: Use image node_id 3 for calling the LayoutsMixin obj's underlying member method code with example usage: obj.tree_layout(level_col, level_sort_values_by, level_sort_values_by_ascending, width, height, rotate, allow_cycles, root) and returns: g2, g, g2