class_docstr: Base class for Hoeffding Decision Trees.
  
  This is an **abstract class**, so it cannot be used directly. It defines base operations
  and properties that all the Hoeffding decision trees must inherit or implement according to
  their own design.
  
  Parameters
  ----------
  max_depth
      The maximum depth a tree can reach. If `None`, the tree will grow indefinitely.
  binary_split
      If True, only allow binary splits.
  max_size
      The max size of the tree, in Megabytes (MB).
  memory_estimate_period
      Interval (number of processed instances) between memory consumption checks.
  stop_mem_management
      If True, stop growing as soon as memory limit is hit.
  remove_poor_attrs
      If True, disable poor attributes to reduce memory usage.
  merit_preprune
      If True, enable merit-based tree pre-pruning.
docstr: Draw the tree using the `graphviz` library.
  
  Since the tree is drawn without passing incoming samples, classification trees
  will show the majority class in their leaves, whereas regression trees will
  use the target mean.
  
  Parameters
  ----------
  max_depth
      Only the root will be drawn when set to `0`. Every node will be drawn when
      set to `None`.
  
  Notes
  -----
  Currently, Label Combination Hoeffding Tree Classifier (for multi-label
  classification) is not supported.
  
  Examples
  --------
  >>> from river import datasets
  >>> from river import tree
  >>> model = tree.HoeffdingTreeClassifier(
  ...    grace_period=5,
  ...    delta=1e-5,
  ...    split_criterion='gini',
  ...    max_depth=10,
  ...    tau=0.05,
  ... )
  >>> for x, y in datasets.Phishing():
  ...    model.learn_one(x, y)
  >>> dot = model.draw()
  
  .. image:: ../../docs/img/dtree_draw.svg
      :align: center
code: def draw(self, max_depth: int | None = None):
      """Draw the tree using the `graphviz` library.
  
      Since the tree is drawn without passing incoming samples, classification trees
      will show the majority class in their leaves, whereas regression trees will
      use the target mean.
  
      Parameters
      ----------
      max_depth
          Only the root will be drawn when set to `0`. Every node will be drawn when
          set to `None`.
  
      Notes
      -----
      Currently, Label Combination Hoeffding Tree Classifier (for multi-label
      classification) is not supported.
  
      Examples
      --------
      >>> from river import datasets
      >>> from river import tree
      >>> model = tree.HoeffdingTreeClassifier(
      ...    grace_period=5,
      ...    delta=1e-5,
      ...    split_criterion='gini',
      ...    max_depth=10,
      ...    tau=0.05,
      ... )
      >>> for x, y in datasets.Phishing():
      ...    model.learn_one(x, y)
      >>> dot = model.draw()
  
      .. image:: ../../docs/img/dtree_draw.svg
          :align: center
      """
      try:
          import graphviz
      except ImportError as e:
          raise ValueError(
              "You have to install graphviz to use the draw method."
          ) from e
      counter = 0
  
      def iterate(node=None):
          if node is None:
              yield None, None, self._root, 0, None
              yield from iterate(self._root)
  
          nonlocal counter
          parent_no = counter
  
          if isinstance(node, DTBranch):
              for branch_index, child in enumerate(node.children):
                  counter += 1
                  yield parent_no, node, child, counter, branch_index
                  if isinstance(child, DTBranch):
                      yield from iterate(child)
  
      if max_depth is None:
          max_depth = -1
  
      dot = graphviz.Digraph(
          graph_attr={
              "splines": "ortho",
              "forcelabels": "true",
              "overlap": "false",
          },
          node_attr={
              "shape": "box",
              "penwidth": "1.2",
              "fontname": "trebuchet",
              "fontsize": "11",
              "margin": "0.1,0.0",
          },
          edge_attr={
              "penwidth": "0.6",
              "center": "true",
              "fontsize": "7  ",
          },
      )
  
      if isinstance(self, base.Classifier):
          n_colors = len(self.classes)  # type: ignore
      else:
          n_colors = 1
  
      # Pick a color palette which maps classes to colors
      new_color = functools.partial(next, iter(_color_brew(n_colors)))
      palette: collections.defaultdict = collections.defaultdict(
          new_color
      )
  
      for parent_no, parent, child, child_no, branch_index in iterate():
          if child.depth > max_depth and max_depth != -1:
              continue
  
          if isinstance(child, DTBranch):
              text = f"{child.feature}"  # type: ignore
          else:
              text = (
                  f"{repr(child)}\nsamples: {int(child.total_weight)}"
              )
  
          # Pick a color, the hue depends on the class and the transparency on the distribution
          if isinstance(self, base.Classifier):
              class_proba = normalize_values_in_dict(
                  child.stats, inplace=False
              )
              mode = max(class_proba, key=class_proba.get)
              p_mode = class_proba[mode]
              try:
                  alpha = (p_mode - 1 / n_colors) / (1 - 1 / n_colors)
                  fillcolor = str(
                      transparency_hex(color=palette[mode], alpha=alpha)
                  )
              except ZeroDivisionError:
                  fillcolor = "#FFFFFF"
          else:
              fillcolor = "#FFFFFF"
  
          dot.node(
              f"{child_no}", text, fillcolor=fillcolor, style="filled"
          )
  
          if parent_no is not None:
              dot.edge(
                  f"{parent_no}",
                  f"{child_no}",
                  xlabel=parent.repr_branch(branch_index, shorten=True),
              )
  
      return dot
  
imports: from __future__ import annotations
  import collections
  import functools
  import io
  import math
  from abc import ABC, abstractmethod
  from river import base
  from river.utils.norm import normalize_values_in_dict
  from .nodes.branch import DTBranch, NominalBinaryBranch, NominalMultiwayBranch, NumericBinaryBranch, NumericMultiwayBranch
  from .nodes.leaf import HTLeaf
  from .utils import calculate_object_size
label_desc: Use image node_id 24 for calling the HoeffdingTree obj's underlying member method code with example usage: obj.draw(max_depth) and returns: dot