diff --git a/src/helpers/helpers.dom.ts b/src/helpers/helpers.dom.ts index b0c41eec0c0..876235e2ad4 100644 --- a/src/helpers/helpers.dom.ts +++ b/src/helpers/helpers.dom.ts @@ -159,6 +159,36 @@ function getContainerSize(canvas: HTMLCanvasElement, width: number, height: numb const round1 = (v: number) => Math.round(v * 10) / 10; +/** + * Determine whether the container's height is defined independently of the + * canvas, by measuring the container with the canvas removed from the layout. + * When the container's height is derived from the canvas itself (e.g. a plain + * block wrapper with no explicit height), the current canvas height must not + * be used as a constraint, because doing so prevents the chart from ever + * growing (see #11005). + */ +function _isContainerHeightIndependent(canvas: HTMLCanvasElement, containerHeight: number): boolean { + const container = _getParentNode(canvas); + if (!container) { + return false; + } + const style = canvas.style; + const prevValue = style.getPropertyValue('display'); + const prevPriority = style.getPropertyPriority('display'); + style.setProperty('display', 'none', 'important'); + const rect = container.getBoundingClientRect(); + const containerStyle = getComputedStyle(container); + const heightWithoutCanvas = rect.height + - getPositionedStyle(containerStyle, 'padding').height + - getPositionedStyle(containerStyle, 'border', 'width').height; + if (prevValue) { + style.setProperty('display', prevValue, prevPriority); + } else { + style.removeProperty('display'); + } + return heightWithoutCanvas >= containerHeight - 0.5; +} + // eslint-disable-next-line complexity export function getMaximumSize( canvas: HTMLCanvasElement, @@ -191,7 +221,8 @@ export function getMaximumSize( const maintainHeight = bbWidth !== undefined || bbHeight !== undefined; - if (maintainHeight && aspectRatio && containerSize.height && height > containerSize.height) { + if (maintainHeight && aspectRatio && containerSize.height && height > containerSize.height + && _isContainerHeightIndependent(canvas, containerSize.height)) { height = containerSize.height; width = round1(Math.floor(height * aspectRatio)); } diff --git a/test/specs/helpers.dom.tests.js b/test/specs/helpers.dom.tests.js index 51957168422..0037cbe6ec6 100644 --- a/test/specs/helpers.dom.tests.js +++ b/test/specs/helpers.dom.tests.js @@ -512,6 +512,45 @@ describe('DOM helpers tests', function() { document.body.removeChild(container); }); + it('should not limit growth by a container height that is derived from the canvas itself', () => { + // https://github.com/chartjs/Chart.js/issues/11005 + // The container has no explicit height, so its height comes from the + // target (canvas) itself. That height must not act as a constraint, + // otherwise the chart can shrink but never grow back. + const container = document.createElement('div'); + container.style.width = '900px'; + + document.body.appendChild(container); + + const target = document.createElement('div'); + target.style.width = '600px'; + target.style.height = '300px'; + container.appendChild(target); + + expect(helpers.getMaximumSize(target, 900, 300, 2)).toEqual(jasmine.objectContaining({width: 900, height: 450})); + + document.body.removeChild(container); + }); + + it('should still restore the display style of the canvas after measuring', () => { + const container = document.createElement('div'); + container.style.width = '900px'; + + document.body.appendChild(container); + + const target = document.createElement('div'); + target.style.width = '600px'; + target.style.height = '300px'; + target.style.setProperty('display', 'block', 'important'); + container.appendChild(target); + + helpers.getMaximumSize(target, 900, 300, 2); + expect(target.style.getPropertyValue('display')).toBe('block'); + expect(target.style.getPropertyPriority('display')).toBe('important'); + + document.body.removeChild(container); + }); + it('should respect aspect ratio and skip container height', () => { const container = document.createElement('div'); container.style.width = '500px';