content: from __future__ import annotations
  import numpy as np
  from toolz import memoize
  
  from datashader.glyphs.glyph import Glyph
  from datashader.glyphs.line import _build_map_onto_pixel_for_line, _clipt
  from datashader.glyphs.points import _PointLike
  from datashader.utils import isnull, isreal, ngjit
  from numba import cuda
  
  try:
      import cudf
      import cupy as cp
      from ..transfer_functions._cuda_utils import cuda_args
  except ImportError:
      cudf = None
      cp = None
      cuda_args = None
  
  
  class _AreaToLineLike(Glyph):
      """Shared methods between Point and Line"""
      def __init__(self, x, y, y_stack):
          self.x = x
          self.y = y
          self.y_stack = y_stack
  
      @property
      def ndims(self):
          return 1
  
      @property
      def inputs(self):
          return (self.x, self.y, self.y_stack)
  
      def validate(self, in_dshape):
          if not isreal(in_dshape.measure[str(self.x)]):
              raise ValueError('x must be real')
          elif not isreal(in_dshape.measure[str(self.y)]):
              raise ValueError('y must be real')
          elif not isreal(in_dshape.measure[str(self.y_stack)]):
              raise ValueError('y_stack must be real or None')
  
      @property
      def x_label(self):
          return self.x
  
      @property
      def y_label(self):
          return self.y
  
      def required_columns(self):
          return self.x, self.y, self.y_stack
  
      def compute_x_bounds(self, df):
          bounds = self._compute_bounds(df[self.x])
          return self.maybe_expand_bounds(bounds)
  
  
  class AreaToZeroAxis0(_PointLike):
      """A filled area glyph.
      The area to be filled is the region from the line defined by ``x`` and
      ``y`` and the y=0 line
  
      Parameters
      ----------
      x, y
          Column names for the x and y coordinates of each vertex.
      """
  
      def compute_y_bounds(self, df):
          # Compute bounds of curve
          bounds = self._compute_bounds(df[self.y])
  
          # Make sure bounds include zero
          bounds = min(bounds[0], 0), max(bounds[1], 0)
  
          # Expand bounds if needed
          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].values).item(),
              np.nanmax(df[self.x].values).item(),
              np.nanmin(df[self.y].values).item(),
              np.nanmax(df[self.y].values).item()]]
          )).compute()
  
          x_extents = np.nanmin(r[:, 0]), np.nanmax(r[:, 1])
          y_extents = np.nanmin(r[:, 2]), np.nanmax(r[:, 3])
  
          # Make sure y_extents include zero
          y_extents = min(y_extents[0], 0), max(y_extents[1], 0)
  
          return (self.maybe_expand_bounds(x_extents),
                  self.maybe_expand_bounds(y_extents))
  
      @memoize
      def _build_extend(self, x_mapper, y_mapper, info, append, _antialias_stage_2,
                        _antialias_stage_2_funcs):
          expand_aggs_and_cols = self.expand_aggs_and_cols(append)
          map_onto_pixel = _build_map_onto_pixel_for_line(x_mapper, y_mapper)
          draw_trapezoid_y = _build_draw_trapezoid_y(
              append, map_onto_pixel, expand_aggs_and_cols
          )
  
          extend_cpu, extend_cuda = _build_extend_area_to_zero_axis0(
              draw_trapezoid_y, expand_aggs_and_cols
          )
          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[x_name].values
                  ys = df[y_name].values
                  do_extend = extend_cpu
  
              do_extend(
                  sx, tx, sy, ty,
                  xmin, xmax, ymin, ymax,
                  plot_start, xs, ys, *aggs_and_cols
              )
  
          return extend
  
  
  class AreaToLineAxis0(_AreaToLineLike):
      """A filled area glyph
      The area to be filled is the region from the line defined by ``x`` and
      ``y[0]`` and the line defined by ``x`` and ``y[1]``.
  
      Parameters
      ----------
      x
          Column names for the x and y coordinates of each vertex.
      y
          List or tuple of length two containing the column names of the
          y-coordinates of the two curves that define the area region.
      """
  
      def compute_y_bounds(self, df):
          # Compute bounds of each curve
          bounds0 = self._compute_bounds(df[self.y])
          bounds1 = self._compute_bounds(df[self.y_stack])
  
          # Combine bounds from the two curves
          bounds = min(bounds0[0], bounds1[0]), max(bounds0[1], bounds1[1])
  
          # Expand bounds if needed
          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].values).item(),
              np.nanmax(df[self.x].values).item(),
              np.nanmin(df[self.y].values).item(),
              np.nanmax(df[self.y].values).item(),
              np.nanmin(df[self.y_stack].values).item(),
              np.nanmax(df[self.y_stack].values).item()]]
          )).compute()
  
          x_extents = np.nanmin(r[:, 0]), np.nanmax(r[:, 1])
          y0_extents = np.nanmin(r[:, 2]), np.nanmax(r[:, 3])
          y1_extents = np.nanmin(r[:, 4]), np.nanmax(r[:, 5])
  
          # Make sure y_extents include 0
          y_extents = (min(y0_extents[0], y1_extents[0]),
                       max(y0_extents[1], y1_extents[1]))
  
          return (self.maybe_expand_bounds(x_extents),
                  self.maybe_expand_bounds(y_extents))
  
      @memoize
      def _build_extend(self, x_mapper, y_mapper, info, append, _antialias_stage_2,
                        _antialias_stage_2_funcs):
          expand_aggs_and_cols = self.expand_aggs_and_cols(append)
          map_onto_pixel = _build_map_onto_pixel_for_line(x_mapper, y_mapper)
          draw_trapezoid_y = _build_draw_trapezoid_y(
              append, map_onto_pixel, expand_aggs_and_cols
          )
          extend_cpu, extend_cuda = _build_extend_area_to_line_axis0(
              draw_trapezoid_y, expand_aggs_and_cols
          )
          x_name = self.x
          y_name = self.y
          y_stack_name = self.y_stack
  
          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)
                  ys0 = self.to_cupy_array(df, y_name)
                  ys1 = self.to_cupy_array(df, y_stack_name)
                  do_extend = extend_cuda[cuda_args(xs.shape)]
              else:
                  xs = df[x_name].values
                  ys0 = df[y_name].values
                  ys1 = df[y_stack_name].values
                  do_extend = extend_cpu
  
              do_extend(
                  sx, tx, sy, ty, xmin, xmax, ymin, ymax,
                  plot_start, xs, ys0, ys1, *aggs_and_cols
              )
  
          return extend
  
  
  class AreaToZeroAxis0Multi(_PointLike):
      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')
  
      @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)
  
          # Make sure min/max range includes zero
          mn = min(0, min(mins))
          mx = max(0, max(maxes))
          return self.maybe_expand_bounds((mn, mx))
  
      @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])
  
          # Makes sure y_extents include 0
          y_extents = min(0, y_extents[0]), max(0, y_extents[1])
  
          return (self.maybe_expand_bounds(x_extents),
                  self.maybe_expand_bounds(y_extents))
  
      @memoize
      def _build_extend(self, x_mapper, y_mapper, info, append, _antialias_stage_2,
                        _antialias_stage_2_funcs):
          expand_aggs_and_cols = self.expand_aggs_and_cols(append)
          map_onto_pixel = _build_map_onto_pixel_for_line(x_mapper, y_mapper)
          draw_trapezoid_y = _build_draw_trapezoid_y(
              append, map_onto_pixel, expand_aggs_and_cols
          )
  
          extend_cpu, extend_cuda = _build_extend_area_to_zero_axis0_multi(
              draw_trapezoid_y, expand_aggs_and_cols
          )
          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,
                  plot_start, xs, ys, *aggs_and_cols
              )
  
          return extend
  
  
  class AreaToLineAxis0Multi(_AreaToLineLike):
      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')
          elif not all(
                  [isreal(in_dshape.measure[str(ycol)])
                   for ycol in self.y_stack]):
              raise ValueError('y_stack columns must be real')
  
      @property
      def x_label(self):
          return 'x'
  
      @property
      def y_label(self):
          return 'y'
  
      def required_columns(self):
          return self.x + self.y + self.y_stack
  
      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 + self.y_stack]
          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]),
              np.nanmin([np.nanmin(df[c].values).item() for c in self.y_stack]),
              np.nanmax([np.nanmax(df[c].values).item() for c in self.y_stack]),
          ]]
          )).compute()
  
          x_extents = np.nanmin(r[:, 0]), np.nanmax(r[:, 1])
          y_extents = np.nanmin(r[:, [2, 4]]), np.nanmax(r[:, [3, 5]])
  
          return (self.maybe_expand_bounds(x_extents),
                  self.maybe_expand_bounds(y_extents))
  
      @memoize
      def _build_extend(self, x_mapper, y_mapper, info, append, _antialias_stage_2,
                        _antialias_stage_2_funcs):
          expand_aggs_and_cols = self.expand_aggs_and_cols(append)
          map_onto_pixel = _build_map_onto_pixel_for_line(x_mapper, y_mapper)
          draw_trapezoid_y = _build_draw_trapezoid_y(
              append, map_onto_pixel, expand_aggs_and_cols
          )
          extend_cpu, extend_cuda = _build_extend_area_to_line_axis0_multi(
              draw_trapezoid_y, expand_aggs_and_cols
          )
          x_names = self.x
          y_names = self.y
          y_stack_names = self.y_stack
  
          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)
                  ys0 = self.to_cupy_array(df, y_names)
                  ys1 = self.to_cupy_array(df, y_stack_names)
                  do_extend = extend_cuda[cuda_args(xs.shape)]
              else:
                  xs = df.loc[:, list(x_names)].to_numpy()
                  ys0 = df.loc[:, list(y_names)].to_numpy()
                  ys1 = df.loc[:, list(y_stack_names)].to_numpy()
                  do_extend = extend_cpu
  
              do_extend(
                  sx, tx, sy, ty, xmin, xmax, ymin, ymax,
                  plot_start, xs, ys0, ys1, *aggs_and_cols
              )
  
          return extend
  
  
  class AreaToZeroAxis1(_PointLike):
      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')
  
      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)
  
          # Make sure min/max range includes zero
          mn = min(0, min(mins))
          mx = max(0, max(maxes))
  
          return self.maybe_expand_bounds((mn, mx))
  
      @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])
  
          # Makes sure y_extents include 0
          y_extents = min(0, y_extents[0]), max(0, y_extents[1])
  
          return (self.maybe_expand_bounds(x_extents),
                  self.maybe_expand_bounds(y_extents))
  
      @memoize
      def _build_extend(self, x_mapper, y_mapper, info, append, _antialias_stage_2,
                        _antialias_stage_2_funcs):
          expand_aggs_and_cols = self.expand_aggs_and_cols(append)
          map_onto_pixel = _build_map_onto_pixel_for_line(x_mapper, y_mapper)
          draw_trapezoid_y = _build_draw_trapezoid_y(
              append, map_onto_pixel, expand_aggs_and_cols
          )
  
          extend_cpu, extend_cuda = _build_extend_area_to_zero_axis1_none_constant(
              draw_trapezoid_y, expand_aggs_and_cols
          )
  
          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,list(x_names))
                  ys = self.to_cupy_array(df,list(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, *aggs_and_cols
              )
  
          return extend
  
  
  class AreaToLineAxis1(_AreaToLineLike):
      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')
          elif not all([isreal(in_dshape.measure[str(ycol)])
                        for ycol in self.y_stack]):
              raise ValueError('y_stack 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')
  
          unique_y_stack_measures = set(in_dshape.measure[str(ycol)]
                                        for ycol in self.y_stack)
          if len(unique_y_stack_measures) > 1:
              raise ValueError('y_stack columns must have the same data type')
  
      def required_columns(self):
          return self.x + self.y + self.y_stack
  
      @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):
          bounds_list = [self._compute_bounds(df[y])
                         for y in self.y + self.y_stack]
          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]),
              np.nanmin([np.nanmin(df[c].values).item() for c in self.y_stack]),
              np.nanmax([np.nanmax(df[c].values).item() for c in self.y_stack]),
          ]]
          )).compute()
  
          x_extents = np.nanmin(r[:, 0]), np.nanmax(r[:, 1])
          y_extents = np.nanmin(r[:, [2, 4]]), np.nanmax(r[:, [3, 5]])
  
          return (self.maybe_expand_bounds(x_extents),
                  self.maybe_expand_bounds(y_extents))
  
      @memoize
      def _build_extend(self, x_mapper, y_mapper, info, append, _antialias_stage_2,
                        _antialias_stage_2_funcs):
          expand_aggs_and_cols = self.expand_aggs_and_cols(append)
          map_onto_pixel = _build_map_onto_pixel_for_line(x_mapper, y_mapper)
          draw_trapezoid_y = _build_draw_trapezoid_y(
              append, map_onto_pixel, expand_aggs_and_cols
          )
          extend_cpu, extend_cuda = _build_extend_area_to_line_axis1_none_constant(
              draw_trapezoid_y, expand_aggs_and_cols
          )
          x_names = self.x
          y_names = self.y
          y_stack_names = self.y_stack
  
          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,list(x_names))
                  ys = self.to_cupy_array(df,list(y_names))
                  y_stacks = self.to_cupy_array(df,list(y_stack_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()
                  y_stacks = df.loc[:, list(y_stack_names)].to_numpy()
                  do_extend = extend_cpu
  
              do_extend(
                  sx, tx, sy, ty, xmin, xmax, ymin, ymax,
                  xs, ys, y_stacks, *aggs_and_cols
              )
  
          return extend
  
  
  class AreaToZeroAxis1XConstant(AreaToZeroAxis1):
      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')
  
      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 _build_extend(self, x_mapper, y_mapper, info, append, _antialias_stage_2,
                        _antialias_stage_2_funcs):
          expand_aggs_and_cols = self.expand_aggs_and_cols(append)
          map_onto_pixel = _build_map_onto_pixel_for_line(x_mapper, y_mapper)
          draw_trapezoid_y = _build_draw_trapezoid_y(
              append, map_onto_pixel, expand_aggs_and_cols
          )
          extend_cpu, extend_cuda = _build_extend_area_to_zero_axis1_x_constant(
              draw_trapezoid_y, expand_aggs_and_cols
          )
  
          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):
                  ys = self.to_cupy_array(df,list(y_names))
                  do_extend = extend_cuda[cuda_args(ys.shape)]
              else:
                  ys = df.loc[:, list(y_names)].to_numpy()
                  do_extend = extend_cpu
  
              do_extend(
                  sx, tx, sy, ty,
                  xmin, xmax, ymin, ymax,
                  x_values, ys, *aggs_and_cols
              )
  
          return extend
  
  
  class AreaToLineAxis1XConstant(AreaToLineAxis1):
      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')
  
          if not all([isreal(in_dshape.measure[str(ycol)])
                      for ycol in self.y_stack]):
              raise ValueError('y_stack 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')
  
          unique_y_stack_measures = set(in_dshape.measure[str(ycol)]
                                        for ycol in self.y)
          if len(unique_y_stack_measures) > 1:
              raise ValueError('y_stack columns must have the same data type')
  
      def required_columns(self):
          return self.y + self.y_stack
  
      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]),
              np.nanmin([np.nanmin(df[c].values).item() for c in self.y_stack]),
              np.nanmax([np.nanmax(df[c].values).item() for c in self.y_stack]),
          ]]
          )).compute()
  
          y_extents = np.nanmin(r[:, [0, 2]]), np.nanmax(r[:, [1, 3]])
  
          return (self.compute_x_bounds(),
                  self.maybe_expand_bounds(y_extents))
  
      @memoize
      def _build_extend(self, x_mapper, y_mapper, info, append, _antialias_stage_2,
                        _antialias_stage_2_funcs):
          expand_aggs_and_cols = self.expand_aggs_and_cols(append)
          map_onto_pixel = _build_map_onto_pixel_for_line(x_mapper, y_mapper)
          draw_trapezoid_y = _build_draw_trapezoid_y(
              append, map_onto_pixel, expand_aggs_and_cols
          )
          extend_cpu, extend_cuda = _build_extend_area_to_line_axis1_x_constant(
              draw_trapezoid_y, expand_aggs_and_cols
          )
  
          x_values = self.x
          y_names = self.y
          y_stack_names = self.y_stack
  
          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):
                  ys = self.to_cupy_array(df,list(y_names))
                  y_stacks = self.to_cupy_array(df,list(y_stack_names))
                  do_extend = extend_cuda[cuda_args(ys.shape)]
              else:
                  ys = df.loc[:, list(y_names)].to_numpy()
                  y_stacks = df.loc[:, list(y_stack_names)].to_numpy()
                  do_extend = extend_cpu
  
              do_extend(
                  sx, tx, sy, ty,
                  xmin, xmax, ymin, ymax,
                  x_values, ys, y_stacks, *aggs_and_cols
              )
  
          return extend
  
  
  class AreaToZeroAxis1YConstant(AreaToZeroAxis1):
      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')
  
      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 _build_extend(self, x_mapper, y_mapper, info, append, _antialias_stage_2,
                        _antialias_stage_2_funcs):
          expand_aggs_and_cols = self.expand_aggs_and_cols(append)
          map_onto_pixel = _build_map_onto_pixel_for_line(x_mapper, y_mapper)
          draw_trapezoid_y = _build_draw_trapezoid_y(
              append, map_onto_pixel, expand_aggs_and_cols
          )
          extend_cpu, extend_cuda = _build_extend_area_to_zero_axis1_y_constant(
              draw_trapezoid_y, expand_aggs_and_cols
          )
  
          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,list(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, *aggs_and_cols
              )
  
          return extend
  
  
  class AreaToLineAxis1YConstant(AreaToLineAxis1):
      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')
  
      def required_columns(self):
          return self.x
  
      def compute_y_bounds(self, *args):
          y_min = min(np.nanmin(self.y), np.nanmin(self.y_stack))
          y_max = max(np.nanmax(self.y), np.nanmax(self.y_stack))
          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 _build_extend(self, x_mapper, y_mapper, info, append, _antialias_stage_2,
                        _antialias_stage_2_funcs):
          expand_aggs_and_cols = self.expand_aggs_and_cols(append)
          map_onto_pixel = _build_map_onto_pixel_for_line(x_mapper, y_mapper)
          draw_trapezoid_y = _build_draw_trapezoid_y(
              append, map_onto_pixel, expand_aggs_and_cols
          )
          extend_cpu, extend_cuda = _build_extend_area_to_line_axis1_y_constant(
              draw_trapezoid_y, expand_aggs_and_cols
          )
          x_names = self.x
          y_values = self.y
          y_stack_values = self.y_stack
  
          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,list(x_names))
                  ys = cp.asarray(y_values)
                  ysv = cp.asarray(y_stack_values)
                  do_extend = extend_cuda[cuda_args(xs.shape)]
              else:
                  xs = df.loc[:, list(x_names)].to_numpy()
                  ys = y_values
                  ysv = y_stack_values
                  do_extend = extend_cpu
  
              do_extend(
                  sx, tx, sy, ty,
                  xmin, xmax, ymin, ymax,
                  xs, ys, ysv, *aggs_and_cols
              )
  
          return extend
  
  
  class AreaToZeroAxis1Ragged(_PointLike):
      def validate(self, in_dshape):
          try:
  