export function getXAxisSize()

in packages/charts/src/chart_types/heatmap/state/utils/axis.ts [77:211]


export function getXAxisSize(
  isCategoricalScale: boolean,
  style: HeatmapStyle['xAxisLabel'],
  formatter: HeatmapSpec['xAxisLabelFormatter'],
  labels: (string | number)[],
  textMeasure: TextMeasure,
  containerWidth: number,
  surroundingSpace: [left: number, right: number],
): Size & { right: number; left: number; tickCadence: number; minRotation: Radian } {
  if (!style.visible) {
    return {
      height: 0,
      width: Math.max(containerWidth - surroundingSpace[0] - surroundingSpace[1], 0),
      left: surroundingSpace[0],
      right: surroundingSpace[1],
      tickCadence: NaN,
      minRotation: 0,
    };
  }
  const isRotated = style.rotation !== 0;
  const normalizedScale = scaleBand<NonNullable<PrimitiveValue>>().domain(labels).range([0, 1]);

  const alignment = isRotated ? 'right' : isCategoricalScale ? 'center' : 'left';
  const alignmentOffset = isCategoricalScale ? normalizedScale.bandwidth() / 2 : 0;
  const scale = (d: NonNullable<PrimitiveValue>) => (normalizedScale(d) ?? 0) + alignmentOffset;

  // use positive angle from 0 to 90 only
  const rotationRad = degToRad(style.rotation);

  const measuredLabels = labels.map((label) => ({
    ...textMeasure(formatter(label), style, style.fontSize),
    label,
  }));

  // don't filter ticks if categorical scale or with rotated labels
  if (isCategoricalScale || isRotated) {
    const maxLabelBBox = measuredLabels.reduce(
      (acc, curr) => {
        return {
          height: Math.max(acc.height, curr.height),
          width: Math.max(acc.width, curr.width),
        };
      },
      { height: 0, width: 0 },
    );
    const compressedScale = computeCompressedScale(
      style,
      scale,
      measuredLabels,
      containerWidth,
      surroundingSpace,
      alignment,
      rotationRad,
    );
    const scaleStep = compressedScale.width / labels.length;
    // this optimal rotation is computed on a suboptimal compressed scale, it can be further enhanced with a monotonic hill climber
    const optimalRotation =
      scaleStep > maxLabelBBox.width ? 0 : Math.asin(Math.min(maxLabelBBox.height / scaleStep, 1));
    // if the current requested rotation is not at least bigger then the optimal one, recalculate the compression
    // using the optimal one forcing the rotation to be without overlaps
    const { width, height, left, right, minRotation } = {
      ...(rotationRad !== 0 && optimalRotation > rotationRad
        ? computeCompressedScale(
            style,
            scale,
            measuredLabels,
            containerWidth,
            surroundingSpace,
            alignment,
            optimalRotation,
          )
        : compressedScale),
      minRotation: isRotated ? Math.max(optimalRotation, rotationRad) : 0,
    };

    const validCompression = isFiniteNumber(width);
    return {
      height: validCompression ? height : 0,
      width: validCompression ? width : Math.max(containerWidth - surroundingSpace[0] - surroundingSpace[1], 0),
      left: validCompression ? left : surroundingSpace[0],
      right: validCompression ? right : surroundingSpace[1],
      tickCadence: validCompression ? 1 : NaN,
      minRotation,
    };
  }

  // TODO refactor and move to monotonic hill climber and no mutations
  // reduce the tick cadence on time scale to avoid overlaps and overflows
  let tickCadence = 1;
  let dimension = computeCompressedScale(
    style,
    scale,
    measuredLabels,
    containerWidth,
    surroundingSpace,
    alignment,
    rotationRad,
  );

  for (let i = 1; i < measuredLabels.length; i++) {
    if ((!dimension.overlaps && !dimension.overflow.right) || !isFiniteNumber(dimension.width)) {
      break;
    }
    dimension = computeCompressedScale(
      style,
      scale,
      measuredLabels.filter((_, index) => index % (i + 1) === 0),
      containerWidth,
      surroundingSpace,
      alignment,
      rotationRad,
    );
    tickCadence++;
  }

  // hide the axis because there is no space for labels
  if (!isFiniteNumber(dimension.width)) {
    return {
      // hide the whole axis
      height: 0,
      width: Math.max(containerWidth - surroundingSpace[0] - surroundingSpace[1], 0),
      left: surroundingSpace[0],
      right: surroundingSpace[1],
      // hide all ticks
      tickCadence: NaN,
      minRotation: rotationRad,
    };
  }

  return {
    ...dimension,
    tickCadence,
    minRotation: rotationRad,
  };
}