content: from __future__ import annotations
  import math
  import numpy as np
  from toolz import memoize
  
  from datashader.antialias import two_stage_agg
  from datashader.glyphs.points import _PointLike, _GeometryLike
  from datashader.utils import isnull, isreal, ngjit
  from numba import cuda
  import numba.types as nb_types
  
  
  try:
      import cudf
      import cupy as cp
      from ..transfer_functions._cuda_utils import cuda_args
  except ImportError:
      cudf = None
      cp = None
      cuda_args = None
  
  try:
      import spatialpandas
  except Exception:
      spatialpandas = None
  
  
  class _AntiAliasedLine:
      """ Methods common to all lines. """
      _line_width = 0  # Use antialiasing if > 0.
  
      def set_line_width(self, line_width):
          self._line_width = line_width
          if hasattr(self, "antialiased"):
              self.antialiased = (line_width > 0)
  
      def _build_extend(self, x_mapper, y_mapper, info, append, antialias_stage_2,
                        antialias_stage_2_funcs):
          return self._internal_build_extend(
                  x_mapper, y_mapper, info, append, self._line_width, antialias_stage_2,
                  antialias_stage_2_funcs)
  
  
  def _line_internal_build_extend(
      x_mapper, y_mapper, append, line_width, antialias_stage_2, antialias_stage_2_funcs,
      expand_aggs_and_cols,
  ):
      antialias = line_width > 0
      map_onto_pixel = _build_map_onto_pixel_for_line(x_mapper, y_mapper, antialias)
      overwrite, use_2_stage_agg = two_stage_agg(antialias_stage_2)
      if not use_2_stage_agg:
          antialias_stage_2_funcs = None
      draw_segment = _build_draw_segment(
          append, map_onto_pixel, expand_aggs_and_cols, line_width, overwrite,
      )
      return draw_segment, antialias_stage_2_funcs
  
  
  class LineAxis0(_PointLike, _AntiAliasedLine):
      """A line, with vertices defined by ``x`` and ``y``.
  
      Parameters
      ----------
      x, y : str
          Column names for the x and y coordinates of each vertex.
      """
      @memoize
      def _internal_build_extend(
              self, x_mapper, y_mapper, info, append, line_width, antialias_stage_2,
              antialias_stage_2_funcs):
          expand_aggs_and_cols = self.expand_aggs_and_cols(append)
          draw_segment, antialias_stage_2_funcs = _line_internal_build_extend(
              x_mapper, y_mapper, append, line_width, antialias_stage_2, antialias_stage_2_funcs,
              expand_aggs_and_cols,
          )
          extend_cpu, extend_cuda = _build_extend_line_axis0(
              draw_segment, expand_aggs_and_cols, antialias_stage_2_funcs,
          )
  
          x_name = self.x
          y_name = self.y
  
          def extend(aggs, df, vt, bounds, plot_start=True):
              sx, tx, sy, ty = vt
              xmin, xmax, ymin, ymax = bounds
              aggs_and_cols = aggs + info(df, aggs[0].shape[:2])
  
              if cudf and isinstance(df, cudf.DataFrame):
                  xs = self.to_cupy_array(df, x_name)
                  ys = self.to_cupy_array(df, y_name)
                  do_extend = extend_cuda[cuda_args(xs.shape)]
              else:
                  xs = df.loc[:, x_name].to_numpy()
                  ys = df.loc[:, y_name].to_numpy()
                  do_extend = extend_cpu
  
              # line may be clipped, then mapped to pixels
              do_extend(
                  sx, tx, sy, ty, xmin, xmax, ymin, ymax,
                  xs, ys, plot_start, antialias_stage_2, *aggs_and_cols
              )
  
          return extend
  
  
  class LineAxis0Multi(_PointLike, _AntiAliasedLine):
      """
      """
  
      def validate(self, in_dshape):
          if not all([isreal(in_dshape.measure[str(xcol)]) for xcol in self.x]):
              raise ValueError('x columns must be real')
          elif not all([isreal(in_dshape.measure[str(ycol)]) for ycol in self.y]):
              raise ValueError('y columns must be real')
  
          if len(self.x) != len(self.y):
              raise ValueError(
                  f'x and y coordinate lengths do not match: {len(self.x)} != {len(self.y)}')
  
      @property
      def x_label(self):
          return 'x'
  
      @property
      def y_label(self):
          return 'y'
  
      def required_columns(self):
          return self.x + self.y
  
      def compute_x_bounds(self, df):
          bounds_list = [self._compute_bounds(df[x])
                         for x in self.x]
          mins, maxes = zip(*bounds_list)
          return self.maybe_expand_bounds((min(mins), max(maxes)))
  
      def compute_y_bounds(self, df):
          bounds_list = [self._compute_bounds(df[y])
                         for y in self.y]
          mins, maxes = zip(*bounds_list)
          return self.maybe_expand_bounds((min(mins), max(maxes)))
  
      @memoize
      def compute_bounds_dask(self, ddf):
  
          r = ddf.map_partitions(lambda df: np.array([[
              np.nanmin([np.nanmin(df[c].values).item() for c in self.x]),
              np.nanmax([np.nanmax(df[c].values).item() for c in self.x]),
              np.nanmin([np.nanmin(df[c].values).item() for c in self.y]),
              np.nanmax([np.nanmax(df[c].values).item() for c in self.y])]]
          )).compute()
  
          x_extents = np.nanmin(r[:, 0]), np.nanmax(r[:, 1])
          y_extents = np.nanmin(r[:, 2]), np.nanmax(r[:, 3])
  
          return (self.maybe_expand_bounds(x_extents),
                  self.maybe_expand_bounds(y_extents))
  
      @memoize
      def _internal_build_extend(
              self, x_mapper, y_mapper, info, append, line_width, antialias_stage_2,
              antialias_stage_2_funcs):
          expand_aggs_and_cols = self.expand_aggs_and_cols(append)
          draw_segment, antialias_stage_2_funcs = _line_internal_build_extend(
              x_mapper, y_mapper, append, line_width, antialias_stage_2, antialias_stage_2_funcs,
              expand_aggs_and_cols,
          )
          extend_cpu, extend_cuda = _build_extend_line_axis0_multi(
              draw_segment, expand_aggs_and_cols, antialias_stage_2_funcs,
          )
  
          x_names = self.x
          y_names = self.y
  
          def extend(aggs, df, vt, bounds, plot_start=True):
              sx, tx, sy, ty = vt
              xmin, xmax, ymin, ymax = bounds
              aggs_and_cols = aggs + info(df, aggs[0].shape[:2])
  
              if cudf and isinstance(df, cudf.DataFrame):
                  xs = self.to_cupy_array(df, x_names)
                  ys = self.to_cupy_array(df, y_names)
                  do_extend = extend_cuda[cuda_args(xs.shape)]
              else:
                  xs = df.loc[:, list(x_names)].to_numpy()
                  ys = df.loc[:, list(y_names)].to_numpy()
                  do_extend = extend_cpu
  
              # line may be clipped, then mapped to pixels
              do_extend(
                  sx, tx, sy, ty, xmin, xmax, ymin, ymax,
                  xs, ys, plot_start, antialias_stage_2, *aggs_and_cols,
              )
  
          return extend
  
  
  class LinesAxis1(_PointLike, _AntiAliasedLine):
      """A collection of lines (on line per row) with vertices defined
      by the lists of columns in ``x`` and ``y``
  
      Parameters
      ----------
      x, y : list
          Lists of column names for the x and y coordinates
      """
  
      def validate(self, in_dshape):
          if not all([isreal(in_dshape.measure[str(xcol)])
                      for xcol in self.x]):
              raise ValueError('x columns must be real')
          elif not all([isreal(in_dshape.measure[str(ycol)])
                        for ycol in self.y]):
              raise ValueError('y columns must be real')
  
          unique_x_measures = set(in_dshape.measure[str(xcol)]
                                  for xcol in self.x)
          if len(unique_x_measures) > 1:
              raise ValueError('x columns must have the same data type')
  
          unique_y_measures = set(in_dshape.measure[str(ycol)]
                                  for ycol in self.y)
          if len(unique_y_measures) > 1:
              raise ValueError('y columns must have the same data type')
  
          if len(self.x) != len(self.y):
              raise ValueError(
                  f'x and y coordinate lengths do not match: {len(self.x)} != {len(self.y)}')
  
      def required_columns(self):
          return self.x + self.y
  
      @property
      def x_label(self):
          return 'x'
  
      @property
      def y_label(self):
          return 'y'
  
      def compute_x_bounds(self, df):
          xs = tuple(df[xlabel] for xlabel in self.x)
  
          bounds_list = [self._compute_bounds(xcol) for xcol in xs]
          mins, maxes = zip(*bounds_list)
  
          return self.maybe_expand_bounds((min(mins), max(maxes)))
  
      def compute_y_bounds(self, df):
          ys = tuple(df[ylabel] for ylabel in self.y)
  
          bounds_list = [self._compute_bounds(ycol) for ycol in ys]
          mins, maxes = zip(*bounds_list)
  
          return self.maybe_expand_bounds((min(mins), max(maxes)))
  
      @memoize
      def compute_bounds_dask(self, ddf):
  
          r = ddf.map_partitions(lambda df: np.array([[
              np.nanmin([np.nanmin(df[c].values).item() for c in self.x]),
              np.nanmax([np.nanmax(df[c].values).item() for c in self.x]),
              np.nanmin([np.nanmin(df[c].values).item() for c in self.y]),
              np.nanmax([np.nanmax(df[c].values).item() for c in self.y])]]
          )).compute()
  
          x_extents = np.nanmin(r[:, 0]), np.nanmax(r[:, 1])
          y_extents = np.nanmin(r[:, 2]), np.nanmax(r[:, 3])
  
          return (self.maybe_expand_bounds(x_extents),
                  self.maybe_expand_bounds(y_extents))
  
      @memoize
      def _internal_build_extend(
              self, x_mapper, y_mapper, info, append, line_width, antialias_stage_2,
              antialias_stage_2_funcs):
          expand_aggs_and_cols = self.expand_aggs_and_cols(append)
          draw_segment, antialias_stage_2_funcs = _line_internal_build_extend(
              x_mapper, y_mapper, append, line_width, antialias_stage_2, antialias_stage_2_funcs,
              expand_aggs_and_cols,
          )
          extend_cpu, extend_cuda = _build_extend_line_axis1_none_constant(
              draw_segment, expand_aggs_and_cols, antialias_stage_2_funcs,
          )
          x_names = self.x
          y_names = self.y
  
          def extend(aggs, df, vt, bounds, plot_start=True):
              sx, tx, sy, ty = vt
              xmin, xmax, ymin, ymax = bounds
              aggs_and_cols = aggs + info(df, aggs[0].shape[:2])
  
              if cudf and isinstance(df, cudf.DataFrame):
                  xs = self.to_cupy_array(df, x_names)
                  ys = self.to_cupy_array(df, y_names)
                  do_extend = extend_cuda[cuda_args(xs.shape)]
              else:
                  xs = df.loc[:, list(x_names)].to_numpy()
                  ys = df.loc[:, list(y_names)].to_numpy()
                  do_extend = extend_cpu
  
              do_extend(
                  sx, tx, sy, ty, xmin, xmax, ymin, ymax, xs, ys, antialias_stage_2, *aggs_and_cols
              )
  
          return extend
  
  
  class LinesAxis1XConstant(LinesAxis1):
      """
      """
      def validate(self, in_dshape):
          if not all([isreal(in_dshape.measure[str(ycol)]) for ycol in self.y]):
              raise ValueError('y columns must be real')
  
          unique_y_measures = set(in_dshape.measure[str(ycol)]
                                  for ycol in self.y)
          if len(unique_y_measures) > 1:
              raise ValueError('y columns must have the same data type')
  
          if len(self.x) != len(self.y):
              raise ValueError(
                  f'x and y coordinate lengths do not match: {len(self.x)} != {len(self.y)}')
  
      def required_columns(self):
          return self.y
  
      def compute_x_bounds(self, *args):
          x_min = np.nanmin(self.x)
          x_max = np.nanmax(self.x)
          return self.maybe_expand_bounds((x_min, x_max))
  
      @memoize
      def compute_bounds_dask(self, ddf):
  
          r = ddf.map_partitions(lambda df: np.array([[
              np.nanmin([np.nanmin(df[c].values).item() for c in self.y]),
              np.nanmax([np.nanmax(df[c].values).item() for c in self.y])]]
          )).compute()
  
          y_extents = np.nanmin(r[:, 0]), np.nanmax(r[:, 1])
  
          return (self.compute_x_bounds(),
                  self.maybe_expand_bounds(y_extents))
  
      @memoize
      def _internal_build_extend(
              self, x_mapper, y_mapper, info, append, line_width, antialias_stage_2,
              antialias_stage_2_funcs):
          expand_aggs_and_cols = self.expand_aggs_and_cols(append)
          draw_segment, antialias_stage_2_funcs = _line_internal_build_extend(
              x_mapper, y_mapper, append, line_width, antialias_stage_2, antialias_stage_2_funcs,
              expand_aggs_and_cols,
          )
          extend_cpu, extend_cuda = _build_extend_line_axis1_x_constant(
              draw_segment, expand_aggs_and_cols, antialias_stage_2_funcs,
          )
  
          x_values = self.x
          y_names = self.y
  
          def extend(aggs, df, vt, bounds, plot_start=True):
              sx, tx, sy, ty = vt
              xmin, xmax, ymin, ymax = bounds
              aggs_and_cols = aggs + info(df, aggs[0].shape[:2])
  
              if cudf and isinstance(df, cudf.DataFrame):
                  xs = cp.asarray(x_values)
                  ys = self.to_cupy_array(df, y_names)
                  do_extend = extend_cuda[cuda_args(ys.shape)]
              else:
                  xs = x_values
                  ys = df.loc[:, list(y_names)].to_numpy()
                  do_extend = extend_cpu
  
              do_extend(
                  sx, tx, sy, ty, xmin, xmax, ymin, ymax, xs, ys, antialias_stage_2, *aggs_and_cols
              )
  
          return extend
  
  
  class LinesAxis1YConstant(LinesAxis1):
      """
      """
      def validate(self, in_dshape):
          if not all([isreal(in_dshape.measure[str(xcol)]) for xcol in self.x]):
              raise ValueError('x columns must be real')
  
          unique_x_measures = set(in_dshape.measure[str(xcol)]
                                  for xcol in self.x)
          if len(unique_x_measures) > 1:
              raise ValueError('x columns must have the same data type')
  
          if len(self.x) != len(self.y):
              raise ValueError(
                  f'x and y coordinate lengths do not match: {len(self.x)} != {len(self.y)}')
  
      def required_columns(self):
          return self.x
  
      def compute_y_bounds(self, *args):
          y_min = np.nanmin(self.y)
          y_max = np.nanmax(self.y)
          return self.maybe_expand_bounds((y_min, y_max))
  
      @memoize
      def compute_bounds_dask(self, ddf):
  
          r = ddf.map_partitions(lambda df: np.array([[
              np.nanmin([np.nanmin(df[c].values).item() for c in self.x]),
              np.nanmax([np.nanmax(df[c].values).item() for c in self.x])]]
          )).compute()
  
          x_extents = np.nanmin(r[:, 0]), np.nanmax(r[:, 1])
  
          return (self.maybe_expand_bounds(x_extents),
                  self.compute_y_bounds())
  
      @memoize
      def _internal_build_extend(
              self, x_mapper, y_mapper, info, append, line_width, antialias_stage_2,
              antialias_stage_2_funcs):
          expand_aggs_and_cols = self.expand_aggs_and_cols(append)
          draw_segment, antialias_stage_2_funcs = _line_internal_build_extend(
              x_mapper, y_mapper, append, line_width, antialias_stage_2, antialias_stage_2_funcs,
              expand_aggs_and_cols,
          )
          extend_cpu, extend_cuda = _build_extend_line_axis1_y_constant(
              draw_segment, expand_aggs_and_cols, antialias_stage_2_funcs,
          )
  
          x_names = self.x
          y_values = self.y
  
          def extend(aggs, df, vt, bounds, plot_start=True):
              sx, tx, sy, ty = vt
              xmin, xmax, ymin, ymax = bounds
              aggs_and_cols = aggs + info(df, aggs[0].shape[:2])
  
              if cudf and isinstance(df, cudf.DataFrame):
                  xs = self.to_cupy_array(df, x_names)
                  ys = cp.asarray(y_values)
                  do_extend = extend_cuda[cuda_args(xs.shape)]
              else:
                  xs = df.loc[:, list(x_names)].to_numpy()
                  ys = y_values
                  do_extend = extend_cpu
  
              do_extend(
                  sx, tx, sy, ty, xmin, xmax, ymin, ymax, xs, ys, antialias_stage_2, *aggs_and_cols
              )
  
          return extend
  
  
  class LinesAxis1Ragged(_PointLike, _AntiAliasedLine):
      def validate(self, in_dshape):
          try:
              from datashader.datatypes import RaggedDtype
          except ImportError:
              RaggedDtype = type(None)
  
          if not isinstance(in_dshape[str(self.x)], RaggedDtype):
              raise ValueError('x must be a RaggedArray')
          elif not isinstance(in_dshape[str(self.y)], RaggedDtype):
              raise ValueError('y must be a RaggedArray')
  
      def required_columns(self):
          return (self.x,) + (self.y,)
  
      def compute_x_bounds(self, df):
          bounds = self._compute_bounds(df[self.x].array.flat_array)
          return self.maybe_expand_bounds(bounds)
  
      def compute_y_bounds(self, df):
          bounds = self._compute_bounds(df[self.y].array.flat_array)
          return self.maybe_expand_bounds(bounds)
  
      @memoize
      def compute_bounds_dask(self, ddf):
  
          r = ddf.map_partitions(lambda df: np.array([[
              np.nanmin(df[self.x].array.flat_array).item(),
              np.nanmax(df[self.x].array.flat_array).item(),
              np.nanmin(df[self.y].array.flat_array).item(),
              np.nanmax(df[self.y].array.flat_array).item()]]
          )).compute()
  
          x_extents = np.nanmin(r[:, 0]), np.nanmax(r[:, 1])
          y_extents = np.nanmin(r[:, 2]), np.nanmax(r[:, 3])
  
          return (self.maybe_expand_bounds(x_extents),
                  self.maybe_expand_bounds(y_extents))
  
      @memoize
      def _internal_build_extend(
              self, x_mapper, y_mapper, info, append, line_width, antialias_stage_2,
              antialias_stage_2_funcs):
          expand_aggs_and_cols = self.expand_aggs_and_cols(append)
          draw_segment, antialias_stage_2_funcs = _line_internal_build_extend(
              x_mapper, y_mapper, append, line_width, antialias_stage_2, antialias_stage_2_funcs,
              expand_aggs_and_cols,
          )
          extend_cpu = _build_extend_line_axis1_ragged(
              draw_segment, expand_aggs_and_cols, antialias_stage_2_funcs,
          )
          x_name = self.x
          y_name = self.y
          def extend(aggs, df, vt, bounds, plot_start=True):
              sx, tx, sy, ty = vt
              xmin, xmax, ymin, ymax = bounds
  
              xs = df[x_name].array
              ys = df[y_name].array
  
              aggs_and_cols = aggs + info(df, aggs[0].shape[:2])
              # line may be clipped, then mapped to pixels
              extend_cpu(
                  sx, tx, sy, ty, xmin, xmax, ymin, ymax, xs, ys, antialias_stage_2, *aggs_and_cols
              )
  
          return extend
  
  
  class LineAxis1Geometry(_GeometryLike, _AntiAliasedLine):
      # spatialpandas must be available if a LineAxis1Geometry object is created.
  
      @property
      def geom_dtypes(self):
          from spatialpandas.geometry import (
              LineDtype, MultiLineDtype, RingDtype, PolygonDtype,
              MultiPolygonDtype
          )
          return (LineDtype, MultiLineDtype, RingDtype,
                  PolygonDtype, MultiPolygonDtype)
  
      @memoize
      def _internal_build_extend(
              self, x_mapper, y_mapper, info, append, line_width, antialias_stage_2,
              antialias_stage_2_funcs):
          from spatialpandas.geometry import (
              PolygonArray, MultiPolygonArray, RingArray
          )
          expand_aggs_and_cols = self.expand_aggs_and_cols(append)
          draw_segment, antialias_stage_2_funcs = _line_internal_build_extend(
              x_mapper, y_mapper, append, line_width, antialias_stage_2, antialias_stage_2_funcs,
              expand_aggs_and_cols,
          )
          perform_extend_cpu = _build_extend_line_axis1_geometry(
              draw_segment, expand_aggs_and_cols, antialias_stage_2_funcs,
          )
          geometry_name = self.geometry
  
          def extend(aggs, df, vt, bounds, plot_start=True):
              sx, tx, sy, ty = vt
              xmin, xmax, ymin, ymax = bounds
              aggs_and_cols = aggs + info(df, aggs[0].shape[:2])
              geom_array = df[geometry_name].array
  
              # Use type to decide whether geometry represents a closed .
              # We skip for closed geometries so as not to double count the first/last
              # pixel
              if isinstance(geom_array, (PolygonArray, MultiPolygonArray)):
                  # Convert polygon array to multi line of boundary
                  geom_array = geom_array.boundary
                  closed_rings = True
              elif isinstance(geom_array, RingArray):
                  closed_rings = True
              else:
                  closed_rings = False
  
              perform_extend_cpu(
                  sx, tx, sy, ty, xmin, xmax, ymin, ymax,
                  geom_array, closed_rings, antialias_stage_2, *aggs_and_cols
              )
  
          return extend
  
  
  class LineAxis1GeoPandas(_GeometryLike, _AntiAliasedLine):
      # geopandas must be available for a GeoPandasLine to be created.
      @property
      def geom_dtypes(self):
          from geopandas.array import GeometryDtype
          return (GeometryDtype,)
  
      @memoize
      def _internal_build_extend(
          self, x_mapper, y_mapper, info, append, line_width, antialias_stage_2,
          antialias_stage_2_funcs,
      ):
          expand_aggs_and_cols = self.expand_aggs_and_cols(append)
          draw_segment, antialias_stage_2_funcs = _line_internal_build_extend(
              x_mapper, y_mapper, append, line_width, antialias_stage_2, antialias_stage_2_funcs,
              expand_aggs_and_cols,
          )
          perform_extend_cpu = _build_extend_line_axis1_geopandas(
              draw_segment, expand_aggs_and_cols, antialias_stage_2_funcs,
          )
          geometry_name = self.geometry
  
          def extend(aggs, df, vt, bounds, plot_start=True):
              sx, tx, sy, ty = vt
              xmin, xmax, ymin, ymax = bounds
              aggs_and_cols = aggs + info(df, aggs[0].shape[:2])
              geom_array = df[geometry_name].array
  
              perform_extend_cpu(
                  sx, tx, sy, ty, xmin, xmax, ymin, ymax,
                  geom_array, antialias_stage_2, *aggs_and_cols,
              )
  
          return extend
  
  
  class LinesXarrayCommonX(LinesAxis1):
      def __init__(self, x, y, x_dim_index: int):
          super().__init__(x, y)
          self.x_dim_index = x_dim_index
  
      def __hash__(self):
          # This ensures that @memoize below caches different functions for different x_dim_index.
          return hash((type(self), self.x_dim_index))
  
      def compute_x_bounds(self, dataset):
          bounds = self._compute_bounds(dataset[self.x])
          return self.maybe_expand_bounds(bounds)
  
      def compute_y_bounds(self, dataset):
          bounds = self._compute_bounds(dataset[self.y])
          return self.maybe_expand_bounds(bounds)
  
      def compute_bounds_dask(self, xr_ds):
          return self.compute_x_bounds(xr_ds), self.compute_y_bounds(xr_ds)
  
      def validate(self, in_dshape):
          if not isreal(in_dshape.measure[str(self.x)]):
              raise ValueError('x column must be real')
  
          if not isreal(in_dshape.measure[str(self.y)]):
              raise ValueError('y column must be real')
  
      @memoize
      def _internal_build_extend(
          self, x_mapper, y_mapper, info, append, line_width, antialias_stage_2,
          antialias_stage_2_funcs,
      ):
          expand_aggs_and_cols = self.expand_aggs_and_cols(append)
          draw_segment, antialias_stage_2_funcs = _line_internal_build_extend(
              x_mapper, y_mapper, append, line_width, antialias_stage_2, antialias_stage_2_funcs,
              expand_aggs_and_cols,
          )
          swap_dims = self.x_dim_index == 0
          extend_cpu, extend_cuda = _build_extend_line_axis1_x_constant(
              draw_segment, expand_aggs_and_cols, antialias_stage_2_funcs, swap_dims,
          )
  
          x_name = self.x
          y_name = self.y
  
          def extend(aggs, df, vt, bounds, plot_start=True):
              sx, tx, sy, ty = vt
              xmin, xmax, ymin, ymax = bounds
              aggs_and_cols = aggs + info(df, aggs[0].shape[:2])
  
              if cudf and isinstance(df, cudf.DataFrame):
                  xs = cp.asarray(df[x_name])
                  ys = cp.asarray(df[y_name])
                  do_extend = extend_cuda[cuda_args(ys.shape)]
              elif cp and isinstance(df[y_name].data, cp.ndarray):
                  xs = cp.asarray(df[x_name])
                  ys = df[y_name].data
                  shape = ys.shape[::-1] if swap_dims else ys.shape
                  do_extend = extend_cuda[cuda_args(shape)]
              else:
                  xs = df[x_name].to_numpy()
                  ys = df[y_name].to_numpy()
                  do_extend = extend_cpu
  
              do_extend(
                  sx, tx, sy, ty, xmin, xmax, ymin, ymax, xs, ys, antialias_stage_2, *aggs_and_cols
              )
  
          return extend
  
  
  def _build_map_onto_pixel_for_line(x_mapper, y_mapper, want_antialias=False):
      @ngjit
      def map_onto_pixel_snap(sx, tx, sy, ty, xmin, xmax, ymin, ymax, x, y):
          """Map points onto pixel grid.
  
          Points falling on upper bound are mapped into previous bin.
  
          If the line has been clipped, x and y will have been
          computed to lie on the bounds; we compare point and bounds
          in integer space to avoid fp error. In contrast, with
          auto-ranging, a point on the bounds will be the same
          floating point number as the bound, so comparison in fp
          representation of continuous space or in integer space
          doesn't change anything.
          """
          xx = int(x_mapper(x) * sx + tx)
          yy = int(y_mapper(y) * sy + ty)
  
          # Note that sx and tx were designed so that
          # x_mapper(xmax) * sx + tx equals the width of the canvas in pixels
          #
          # Likewise, sy and ty were designed so that
          # y_mapper(ymax) * sy + ty equals the height of the canvas in pixels
          #
          # We round these results to integers (rather than casting to integers
          # with the int constructor) to handle cases where floating-point
          # precision errors results in a value just under the integer number
          # of pixels.
          xxmax = round(x_mapper(xmax) * sx + tx)
          yymax = round(y_mapper(ymax) * sy + ty)
  
          return (xx - 1 if xx == xxmax else xx,
                  yy - 1 if yy == yymax else yy)
  
      @ngjit
      def map_onto_pixel_no_snap(sx, tx, sy, ty, xmin, xmax, ymin, ymax, x, y):
          xx = x_mapper(x)*sx + tx - 0.5
          yy = y_mapper(y)*sy + ty - 0.5
          return xx, yy
  
      if want_antialias:
          return map_onto_pixel_no_snap
      else:
          return map_onto_pixel_snap
  
  
  @ngjit
  def _liang_barsky(xmin, xmax, ymin, ymax, x0, x1, y0, y1, skip):
      """ An implementation of the Liang-Barsky line clipping algorithm.
  
      https://en.wikipedia.org/wiki/Liang%E2%80%93Barsky_algorithm
  
      """
      # Check if line is fully outside viewport
      if x0 < xmin and x1 < xmin:
          skip = True
      elif x0 > xmax and x1 > xmax:
          skip = True
      elif y0 < ymin and y1 < ymin:
          skip = True
      elif y0 > ymax and y1 > ymax:
          skip = True
  
      t0, t1 = 0, 1
      dx1 = x1 - x0
      t0, t1, accept = _clipt(-dx1, x0 - xmin, t0, t1)
      if not accept:
          skip = True
      t0, t1, accept = _clipt(dx1, xmax - x0, t0, t1)
      if not accept:
          skip = True
      dy1 = y1 - y0
      t0, t1, accept = _clipt(-dy1, y0 - ymin, t0, t1)
      if not accept:
          skip = True
      t0, t1, accept = _clipt(dy1, ymax - y0, t0, t1)
      if not accept:
          skip = True
      if t1 < 1:
          clipped_end = True
          x1 = x0 + t1 * dx1
          y1 = y0 + t1 * dy1
      else:
          clipped_end = False
      if t0 > 0:
          # If x0 is clipped, we need to plot the new start
          clipped_start = True
          x0 = x0 + t0 * dx1
          y0 = y0 + t0 * dy1
      else:
          clipped_start = False
  
      return x0, x1, y0, y1, skip, clipped_start, clipped_end
  
  
  @ngjit
  def _clipt(p, q, t0, t1):
      accept = True
      if p < 0 and q < 0:
          r = q / p
          if r > t1:
              accept = False
          elif r > t0:
              t0 = r
      elif p > 0 and q < p:
          r = q / p
          if r < t0:
              accept = False
          elif r < t1:
              t1 = r
      elif q < 0:
          accept = False
      return t0, t1, accept
  
  
  @ngjit
  def _clamp(x, low, high):
      # Clamp ``x`` in the range ``low`` to ``high``.
      return max(low, min(x, high))
  
  
  @ngjit
  def _linearstep(edge0, edge1, x):
      t = _clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0)
      return t
  
  
  @ngjit
  def _x_intercept(y, cx0, cy0, cx1, cy1):
      # Return x value of intercept between line at constant y and line
      # between corner points.
      if cy0 == cy1:
          # Line is horizontal, return the "upper", i.e. right-hand, end of it.
          return cx1
      frac = (y - cy0) / (cy1 - cy0)  # In range 0..1
      return cx0 + frac*(cx1 - cx0)
  
  
  def _build_full_antialias(expand_aggs_and_cols):
      """Specialize antialiased line drawing algorithm for a given append/axis combination"""
      @ngjit
      @expand_aggs_and_cols
      def _full_antialias(line_width, overwrite, i, x0, x1, y0, y1,
                          segment_start, segment_end, xm, ym, append,
                          nx, ny, buffer, *aggs_and_cols):
          """Draw an antialiased line segment.
  
          If overwrite=True can overwrite each pixel multiple times because
          using max for the overwriting.  If False can only write each pixel
          once per segment and its previous segment.
          Argument xm, ym are only valid if overwrite and segment_start are False.
          """
          if x0 == x1 and y0 == y1:
              return
  
          # Scan occurs in y-direction. But wish to scan in the shortest direction,
          # so if |x0-x1| < |y0-y1| then flip (x,y) coords for maths and flip back
          # again before setting pixels.
          flip_xy = abs(x0-x1) < abs(y0-y1)
          if flip_xy:
              x0, y0 = y0, x0
              x1, y1 = y1, x1
              xm, ym = ym, xm
  
          scale = 1.0
  
          # line_width less than 1 is rendered as 1 but with lower intensity.
          if line_width < 1.0:
              scale *= line_width
              line_width = 1.0
  
          aa = 1.0
          halfwidth = 0.5*(line_width + aa)
  
          # Want y0 <= y1, so switch vertical direction if this is not so.
          flip_order = y1 < y0 or (y1 == y0 and x1 < x0)
  
          # Start (x0, y0), end (y0, y1)
          #       c1 +-------------+ c2          along    | right
          # (x0, y0) | o         o | (x1, y1)    vector   | vector
          #       c0 +-------------+ c3          ---->    v
  
          alongx = float(x1 - x0)
          alongy = float(y1 - y0)  # Always +ve
          length = math.sqrt(alongx**2 + alongy**2)
          alongx /= length
          alongy /= length
  
          rightx = alongy
          righty = -alongx
  
          # 4 corners, x and y.  Uses buffer, which must have length 8.  Order of coords is
          # (x0, x1, x2, x3, y0, y1, y2, y3).  Each CPU/GPU thread has its own local buffer
          # so there is no cross-talk.  Contents of buffer are written and read within the
          # lifetime of this function, so it doesn't matter what they are before this
          # function is called or after it returns.
          if flip_order:
              buffer[0] = x1 - halfwidth*( rightx - alongx)
              buffer[1] = x1 - halfwidth*(-rightx - alongx)
              buffer[2] = x0 - halfwidth*(-rightx + alongx)
              buffer[3] = x0 - halfwidth*( rightx + alongx)
              buffer[4] = y1 - halfwidth*( righty - alongy)
              buffer[5] = y1 - halfwidth*(-righty - alongy)
              buffer[6] = y0 - halfwidth*(-righty + alongy)
              buffer[7] = y0 - halfwidth*( righty + alongy)
          else:
              buffer[0] = x0 + halfwidth*( rightx - alongx)
              buffer[1] = x0 + halfwidth*(-rightx - alongx)
              buffer[2] = x1 + halfwidth*(-rightx + alongx)
              buffer[3] = x1 + halfwidth*( rightx + alongx)
              buffer[4] = y0 + halfwidth*( righty - alongy)
              buffer[5] = y0 + halfwidth*(-righty - alongy)
              buffer[6] = y1 + halfwidth*(-righty + alongy)
              buffer[7] = y1 + halfwidth*( righty + alongy)
  
          xmax = nx-1
          ymax = ny-1
          if flip_xy:
              xmax, ymax = ymax, xmax
  
          # Index of lowest-y point.
          if flip_order:
              lowindex = 0 if x0 > x1 else 1
          else:
              lowindex = 0 if x1 > x0 else 1
  
          if not overwrite and not segment_start:
              prev_alongx = x0 - xm
              prev_alongy = y0 - ym
              prev_length = math.sqrt(prev_alongx**2 + prev_alongy**2)
              if prev_length > 0.0:
                  prev_alongx /= prev_length
                  prev_alongy /= prev_length
                  prev_rightx = prev_alongy
                  prev_righty = -prev_alongx
              else:
                  overwrite = True
  
  