docs: def cartogram(df, projection=None,
                scale=None, limits=(0.2, 1), scale_func=None, trace=True, trace_kwargs=None,
                hue=None, categorical=False, scheme=None, k=5, cmap='viridis', vmin=None, vmax=None,
                legend=False, legend_values=None, legend_labels=None, legend_kwargs=None, legend_var="scale",
                extent=None,
                figsize=(8, 6), ax=None,
                **kwargs):
      """
      Self-scaling area plot.
  
      Parameters
      ----------
      df : GeoDataFrame
          The data being plotted.
      projection : geoplot.crs object instance, optional
          A geographic projection. For more information refer to `the tutorial page on projections
          <https://nbviewer.jupyter.org/github/ResidentMario/geoplot/blob/master/notebooks/tutorials/Projections.ipynb>`_.
      scale : str or iterable, optional
          Applies scaling to the output points. Defaults to None (no scaling).
      limits : (min, max) tuple, optional
          The minimum and maximum scale limits. Ignored if ``scale`` is left specified.
      scale_func : ufunc, optional
          The function used to scale point sizes. Defaults to a linear scale. For more information see `the Gallery demo
          <examples/usa-city-elevations.html>`_.
      trace : boolean, optional
          Whether or not to include a trace of the polygon's original outline in the plot result.
      trace_kwargs : dict, optional
          If ``trace`` is set to ``True``, this parameter can be used to adjust the properties of the trace outline. This
          parameter is ignored if trace is ``False``.
      hue : None, Series, GeoSeries, iterable, or str, optional
          Applies a colormap to the output points.
      categorical : boolean, optional
          Set to ``True`` if ``hue`` references a categorical variable, and ``False`` (the default) otherwise. Ignored
          if ``hue`` is left unspecified.
      scheme : None or {"quantiles"|"equal_interval"|"fisher_Jenks"}, optional
          Controls how the colormap bin edges are determined. Ignored if ``hue`` is left unspecified.
      k : int or None, optional
          Ignored if ``hue`` is left unspecified. Otherwise, if ``categorical`` is False, controls how many colors to
          use (5 is the default). If set to ``None``, a continuous colormap will be used.
      cmap : matplotlib color, optional
          The `matplotlib colormap <http://matplotlib.org/examples/color/colormaps_reference.html>`_ to be used.
          Ignored if ``hue`` is left unspecified.
      vmin : float, optional
          Values below this level will be colored the same threshold value. Defaults to the dataset minimum. Ignored
          if ``hue`` is left unspecified.
      vmax : float, optional
          Values above this level will be colored the same threshold value. Defaults to the dataset maximum. Ignored
          if ``hue`` is left unspecified.
      legend : boolean, optional
          Whether or not to include a legend. Ignored if neither a ``hue`` nor a ``scale`` is specified.
      legend_values : list, optional
          The values to use in the legend. Defaults to equal intervals. For more information see `the Gallery demo
          <https://residentmario.github.io/geoplot/examples/largest-cities-usa.html>`_.
      legend_labels : list, optional
          The names to use in the legend. Defaults to the variable values. For more information see `the Gallery demo
          <https://residentmario.github.io/geoplot/examples/largest-cities-usa.html>`_.
      legend_kwargs : dict, optional
          Keyword arguments to be passed to `the underlying legend <http://matplotlib.org/users/legend_guide.html>`_.
      extent : None or (minx, maxx, miny, maxy), optional
          Used to control plot x-axis and y-axis limits manually.
      figsize : tuple, optional
          An (x, y) tuple passed to ``matplotlib.figure`` which sets the size, in inches, of the resultant plot.
      ax : AxesSubplot or GeoAxesSubplot instance, optional
          A ``matplotlib.axes.AxesSubplot`` or ``cartopy.mpl.geoaxes.GeoAxesSubplot`` instance. Defaults to a new axis.
      kwargs: dict, optional
          Keyword arguments to be passed to the underlying ``matplotlib`` `Polygon patches
          <http://matplotlib.org/api/patches_api.html#matplotlib.patches.Polygon>`_.
  
      Returns
      -------
      ``AxesSubplot`` or ``GeoAxesSubplot``
          The plot axis
  
      Examples
      --------
      A cartogram is a plot type which ingests a series of enclosed ``Polygon`` or ``MultiPolygon`` entities and spits
      out a view of these shapes in which area is distorted according to the size of some parameter of interest.
  
      A basic cartogram specifies data, a projection, and a ``scale`` parameter.
  
      .. code-block:: python
  
          import geoplot as gplt
          import geoplot.crs as gcrs
          gplt.cartogram(boroughs, scale='Population Density', projection=gcrs.AlbersEqualArea())
  
      .. image:: ../figures/cartogram/cartogram-initial.png
  
      The gray outline can be turned off by specifying ``trace``, and a legend can be added by specifying ``legend``.
  
      .. code-block:: python
  
          gplt.cartogram(boroughs, scale='Population Density', projection=gcrs.AlbersEqualArea(),
                         trace=False, legend=True)
  
      .. image:: ../figures/cartogram/cartogram-trace-legend.png
  
      Keyword arguments can be passed to the legend using the ``legend_kwargs`` argument. These arguments will be
      passed to the underlying ``matplotlib.legend.Legend`` instance (`ref
      <http://matplotlib.org/api/legend_api.html#matplotlib.legend.Legend>`_). The ``loc`` and ``bbox_to_anchor``
      parameters are particularly useful for positioning the legend. Other additional arguments will be passed to the
      underlying ``matplotlib`` `scatter plot <http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.scatter>`_.
  
      .. code-block:: python
  
          gplt.cartogram(boroughs, scale='Population Density', projection=gcrs.AlbersEqualArea(),
                         trace=False, legend=True, legend_kwargs={'loc': 'upper left'})
  
      .. image:: ../figures/cartogram/cartogram-legend-kwargs.png
  
      Additional arguments to ``cartogram`` will be interpreted as keyword arguments for the scaled polygons,
      using `matplotlib Polygon patch
      <http://matplotlib.org/api/patches_api.html#matplotlib.patches.Polygon>`_ rules.
  
      .. code-block:: python
  
          gplt.cartogram(boroughs, scale='Population Density', projection=gcrs.AlbersEqualArea(),
                         edgecolor='darkgreen')
  
      .. image:: ../figures/cartogram/cartogram-kwargs.png
  
      Manipulate the outlines use the ``trace_kwargs`` argument, which accepts the same `matplotlib Polygon patch
      <http://matplotlib.org/api/patches_api.html#matplotlib.patches.Polygon>`_ parameters.
  
      .. code-block:: python
  
          gplt.cartogram(boroughs, scale='Population Density', projection=gcrs.AlbersEqualArea(),
                         trace_kwargs={'edgecolor': 'lightgreen'})
  
      .. image:: ../figures/cartogram/cartogram-trace-kwargs.png
  
      Adjust the level of scaling to apply using the ``limits`` parameter.
  
      .. code-block:: python
  
          gplt.cartogram(boroughs, scale='Population Density', projection=gcrs.AlbersEqualArea(),
                         limits=(0.5, 1))
  
      .. image:: ../figures/cartogram/cartogram-limits.png
  
      The default scaling function is linear: an observations at the midpoint of two others will be exactly midway
      between them in size. To specify an alternative scaling function, use the ``scale_func`` parameter. This should
      be a factory function of two variables which, when given the maximum and minimum of the dataset,
      returns a scaling function which will be applied to the rest of the data. A demo is available in
      the `example gallery <examples/usa-city-elevations.html>`_.
  
      .. code-block:: python
  
          def trivial_scale(minval, maxval): return lambda v: 2
          gplt.cartogram(boroughs, scale='Population Density', projection=gcrs.AlbersEqualArea(),
                         limits=(0.5, 1), scale_func=trivial_scale)
  
      .. image:: ../figures/cartogram/cartogram-scale-func.png
  
      ``cartogram`` also provides the same ``hue`` visual variable parameters provided by e.g. ``pointplot``. For more
      information on ``hue``-related arguments, see the related sections in the ``pointplot`` `documentation
      <./pointplot.html>`_.
  
      .. code-block:: python
  
          gplt.cartogram(boroughs, scale='Population Density', projection=gcrs.AlbersEqualArea(),
                         hue='Population Density', k=None, cmap='Blues')
  
      .. image:: ../figures/cartogram/cartogram-hue.png
      """
      # Initialize the figure.
      fig = _init_figure(ax, figsize)
  
      # Load the projection.
      if projection:
          projection = projection.load(df, {
              'central_longitude': lambda df: np.mean(np.array([p.x for p in df.geometry.centroid])),
              'central_latitude': lambda df: np.mean(np.array([p.y for p in df.geometry.centroid]))
          })
  
          # Set up the axis.
          if not ax:
              ax = plt.subplot(111, projection=projection)
  
          # Clean up patches.
      else:
          if not ax:
              ax = plt.gca()
  
      # Clean up patches.
      _lay_out_axes(ax, projection)
  
      # Immediately return if input geometry is empty.
      if len(df.geometry) == 0:
          return ax
  
      # Set extent.
      extrema = _get_envelopes_min_maxes(df.geometry.envelope.exterior)
      _set_extent(ax, projection, extent, extrema)
  
      # Check that the ``scale`` parameter is filled, and use it to fill a ``values`` name.
      if not scale:
          raise ValueError("No scale parameter provided.")
      elif isinstance(scale, str):
          values = df[scale]
      else:
          values = scale
  
      # Compute a scale function.
      dmin, dmax = np.min(values), np.max(values)
      if not scale_func:
          dslope = (limits[1] - limits[0]) / (dmax - dmin)
          dscale = lambda dval: limits[0] + dslope * (dval - dmin)
      else:
          dscale = scale_func(dmin, dmax)
  
      # Create a legend, if appropriate.
      if legend:
          _paint_carto_legend(ax, values, legend_values, legend_labels, dscale, legend_kwargs)
  
      # Validate hue input.
      hue = _validate_hue(df, hue)
  
      # Generate the coloring information, if needed. Follows one of two schemes, categorical or continuous,
      # based on whether or not ``k`` is specified (``hue`` must be specified for either to work).
      if k is not None and hue is not None:
          # Categorical colormap code path.
          categorical, k, scheme = _validate_buckets(categorical, k, scheme)
  
          if hue is not None:
              cmap, categories, hue_values = _discrete_colorize(categorical, hue, scheme, k, cmap, vmin, vmax)
              colors = [cmap.to_rgba(v) for v in hue_values]
  
              # Add a legend, if appropriate.
              if legend and (legend_var != "scale" or scale is None):
                  _paint_hue_legend(ax, categories, cmap, legend_labels, legend_kwargs)
          else:
              colors = ['None']*len(df)
      elif k is None and hue is not None:
          # Continuous colormap code path.
          hue_values = hue
          cmap = _continuous_colormap(hue_values, cmap, vmin, vmax)
          colors = [cmap.to_rgba(v) for v in hue_values]
  
          # Add a legend, if appropriate.
          if legend and (legend_var != "scale" or scale is None):
              _paint_colorbar_legend(ax, hue_values, cmap, legend_kwargs)
      elif 'facecolor' in kwargs:
          colors = [kwargs.pop('facecolor')]*len(df)
      else:
          colors = ['None']*len(df)
  
      # Manipulate trace_kwargs.
      if trace:
          if trace_kwargs is None:
              trace_kwargs = dict()
          if 'edgecolor' not in trace_kwargs.keys():
              trace_kwargs['edgecolor'] = 'lightgray'
          if 'facecolor' not in trace_kwargs.keys():
              trace_kwargs['facecolor'] = 'None'
  
      # Draw traces first, if appropriate.
      if trace:
          if projection:
              for polygon in df.geometry:
                  features = ShapelyFeature([polygon], ccrs.PlateCarree())
                  ax.add_feature(features, **trace_kwargs)
          else:
              for polygon in df.geometry:
                  try:  # Duck test for MultiPolygon.
                      for subgeom in polygon:
                          feature = descartes.PolygonPatch(subgeom, **trace_kwargs)
                          ax.add_patch(feature)
                  except (TypeError, AssertionError):  # Shapely Polygon.
                      feature = descartes.PolygonPatch(polygon, **trace_kwargs)
                      ax.add_patch(feature)
  
      # Finally, draw the scaled geometries.
      for value, color, polygon in zip(values, colors, df.geometry):
          scale_factor = dscale(value)
          scaled_polygon = shapely.affinity.scale(polygon, xfact=scale_factor, yfact=scale_factor)
          if projection:
              features = ShapelyFeature([scaled_polygon], ccrs.PlateCarree())
              ax.add_feature(features, facecolor=color, **kwargs)
          else:
              try:  # Duck test for MultiPolygon.
                  for subgeom in scaled_polygon:
                      feature = descartes.PolygonPatch(subgeom, facecolor=color, **kwargs)
                      ax.add_patch(feature)
              except (TypeError, AssertionError):  # Shapely Polygon.
                  feature = descartes.PolygonPatch(scaled_polygon, facecolor=color, **kwargs)
                  ax.add_patch(feature)
  
      return ax