export function renderPoints()

in packages/charts/src/chart_types/xy_chart/rendering/points.ts [33:156]


export function renderPoints(
  shift: number,
  dataSeries: DataSeries,
  xScale: ScaleBand | ScaleContinuous,
  yScale: ScaleContinuous,
  panel: Dimensions,
  color: Color,
  pointStyle: PointStyle,
  isolatedPointThemeStyle: Omit<PointStyle, 'radius'>,
  lineStrokeWidth: number,
  isBandedSpec: boolean,
  markSizeOptions: MarkSizeOptions,
  useSpatialIndex: boolean,
  allowIsolated: boolean,
  styleAccessor?: PointStyleAccessor,
): {
  pointGeometries: PointGeometry[];
  minDistanceBetweenPoints: number;
  indexedGeometryMap: IndexedGeometryMap;
} {
  const indexedGeometryMap = new IndexedGeometryMap();
  const getRadius = markSizeOptions.enabled
    ? getRadiusFn(dataSeries.data, pointStyle.strokeWidth, markSizeOptions.ratio)
    : () => 0;
  const geometryType = useSpatialIndex ? GeometryType.spatial : GeometryType.linear;

  const y1Fn = getY1ScaledValueFn(yScale);
  const y0Fn = getY0ScaledValueFn(yScale);
  const yDefined = isYValueDefinedFn(yScale, xScale);

  const needSorting = !markSizeOptions.enabled;

  let style = buildPointGeometryStyles(color, pointStyle);
  let isolatedPointStyle = buildPointGeometryStyles(color, isolatedPointThemeStyle);
  let styleOverrides: Partial<PointStyle> | undefined = undefined;

  const { pointGeometries, minDistanceBetweenPoints } = dataSeries.data.reduce<{
    pointGeometries: SortedArray<PointGeometry>;
    minDistanceBetweenPoints: number;
    prevX: number | undefined;
  }>(
    (acc, datum, dataIndex) => {
      const { x: xValue, mark } = datum;
      const prev = dataSeries.data[dataIndex - 1];
      const next = dataSeries.data[dataIndex + 1];
      // don't create the point if not within the xScale domain
      if (!xScale.isValueInDomain(xValue)) return acc;

      // don't create the point if it that point was filled
      const x = xScale.scale(xValue);

      if (!isFiniteNumber(x)) return acc;

      if (isFiniteNumber(acc.prevX) && !isDatumFilled(datum)) {
        acc.minDistanceBetweenPoints = Math.min(acc.minDistanceBetweenPoints, Math.abs(x - acc.prevX));
      }
      acc.prevX = x;

      const yDatumKeyNames: Array<keyof Omit<FilledValues, 'x'>> = isBandedSpec ? ['y0', 'y1'] : ['y1'];
      const seriesIdentifier: XYChartSeriesIdentifier = getSeriesIdentifierFromDataSeries(dataSeries);
      const isPointIsolated = allowIsolated && isIsolatedPoint(dataIndex, dataSeries.data.length, yDefined, prev, next);
      if (styleAccessor) {
        styleOverrides = getPointStyleOverrides(datum, seriesIdentifier, isPointIsolated, styleAccessor);
        style = buildPointGeometryStyles(color, pointStyle, styleOverrides);
        isolatedPointStyle = buildPointGeometryStyles(color, isolatedPointThemeStyle, styleOverrides);
      }
      yDatumKeyNames.forEach((yDatumKeyName, keyIndex) => {
        const valueAccessor = getYDatumValueFn(yDatumKeyName);
        const y = yDatumKeyName === 'y1' ? y1Fn(datum) : y0Fn(datum);
        const originalY = getDatumYValue(datum, keyIndex === 0, isBandedSpec, dataSeries.stackMode);

        // if radius is defined with the mark, limit the minimum radius to the theme radius value
        const radius = isPointIsolated
          ? isolatedPointRadius(lineStrokeWidth)
          : markSizeOptions.enabled
            ? Math.max(getRadius(mark), pointStyle.radius)
            : styleOverrides?.radius ?? pointStyle.radius;

        const pointGeometry: PointGeometry = {
          x,
          y: y === null ? NaN : y,
          radius,
          color,
          style: isPointIsolated ? isolatedPointStyle : style,
          value: {
            x: xValue,
            y: originalY,
            mark,
            accessor: isBandedSpec && keyIndex === 0 ? BandedAccessorType.Y0 : BandedAccessorType.Y1,
            datum: datum.datum,
          },
          transform: {
            x: shift,
            y: 0,
          },
          seriesIdentifier,
          panel,
          isolated: isPointIsolated,
        };
        indexedGeometryMap.set(pointGeometry, geometryType);
        // use the geometry only if the yDatum in contained in the current yScale domain
        if (
          isFiniteNumber(y) &&
          yDefined(datum, valueAccessor) &&
          yScale.isValueInDomain(valueAccessor(datum)) &&
          !isDatumFilled(datum)
        ) {
          if (needSorting) {
            inplaceInsertInSortedArray(acc.pointGeometries, pointGeometry, (p) => p?.radius ?? NaN);
          } else {
            acc.pointGeometries.push(pointGeometry);
          }
        }
      });
      return acc;
    },
    { pointGeometries: [], minDistanceBetweenPoints: Infinity, prevX: undefined },
  );
  return {
    pointGeometries,
    minDistanceBetweenPoints,
    indexedGeometryMap,
  };
}