Repository: chartjs/Chart.js
File: src/helpers/helpers.dom.ts (TypeScript)

Code before:
    height = round1(width / 2);
  }

  const maintainHeight = bbWidth !== undefined || bbHeight !== undefined;

  if (maintainHeight && aspectRatio && containerSize.height && height > containerSize.height) {
    height = containerSize.height;
    width = round1(Math.floor(height * aspectRatio));
  }

  return {width, height};
}

/**
 * @param chart
 * @param forceRatio
 * @param forceStyle
 * @returns True if the canvas context size or transformation has changed.
 */
export function retinaScale(
  chart: Chart,
  forceRatio: number,
  forceStyle?: boolean
): boolean | void {
  const pixelRatio = forceRatio || 1;
  const deviceHeight = Math.floor(chart.height * pixelRatio);
  const deviceWidth = Math.floor(chart.width * pixelRatio);

  const canvas = chart.canvas;

  // If no style has been set on the canvas, the render size is used as display size,
  // making the chart visually bigger, so let's enforce it to the "correct" values.
  // See https://github.com/chartjs/Chart.js/issues/3575
  if (canvas.style && (forceStyle || (!canvas.style.height && !canvas.style.width))) {
    canvas.style.height = `${chart.height}px`;
    canvas.style.width = `${chart.width}px`;
  }

  if (chart.currentDevicePixelRatio !== pixelRatio
      || canvas.height !== deviceHeight
      || canvas.width !== deviceWidth) {
    chart.currentDevicePixelRatio = pixelRatio;
    canvas.height = deviceHeight;
    canvas.width = deviceWidth;
    chart.ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
    return true;
  }
  return false;
}

/**

Review comment (suggestion):
The deviceHeight could end up being a little bit smaller now. For example if devicePixelRatio is 1.75, and the `newSize` in core.controller has a width of 15.9, the deviceWidth will end up being 26. (`Math.floor((Math.floor(15.9)*1.75)`), when with current implementation it ends up being 27 (`Math.floor(15.9*1.75)`).

For this reason, I would revert the change in core.controller and replace the `chart.height = deviceHeight / pixelRatio` with `chart.height = Math.floor(chart.height)` below. (width obviously too)

Code after:
    height = round1(width / 2);
  }

  const maintainHeight = bbWidth !== undefined || bbHeight !== undefined;

  if (maintainHeight && aspectRatio && containerSize.height && height > containerSize.height) {
    height = containerSize.height;
    width = round1(Math.floor(height * aspectRatio));
  }

  return {width, height};
}

/**
 * @param chart
 * @param forceRatio
 * @param forceStyle
 * @returns True if the canvas context size or transformation has changed.
 */
export function retinaScale(
  chart: Chart,
  forceRatio: number,
  forceStyle?: boolean
): boolean | void {
  const pixelRatio = forceRatio || 1;
  const deviceHeight = Math.floor(chart.height * pixelRatio);
  const deviceWidth = Math.floor(chart.width * pixelRatio);

  chart.height = Math.floor(chart.height);
  chart.width = Math.floor(chart.width);

  const canvas = chart.canvas;

  // If no style has been set on the canvas, the render size is used as display size,
  // making the chart visually bigger, so let's enforce it to the "correct" values.
  // See https://github.com/chartjs/Chart.js/issues/3575
  if (canvas.style && (forceStyle || (!canvas.style.height && !canvas.style.width))) {
    canvas.style.height = `${chart.height}px`;
    canvas.style.width = `${chart.width}px`;
  }

  if (chart.currentDevicePixelRatio !== pixelRatio
      || canvas.height !== deviceHeight
      || canvas.width !== deviceWidth) {
    chart.currentDevicePixelRatio = pixelRatio;
    canvas.height = deviceHeight;
    canvas.width = deviceWidth;
    chart.ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
    return true;
  }
  return false;