From 007321e08f7382190d77ae036cc4c75a16139e65 Mon Sep 17 00:00:00 2001 From: kirthi-b Date: Thu, 16 Jul 2026 00:50:50 -0600 Subject: [PATCH] Only clamp responsive height when container height is independent of the canvas Since #10646, getMaximumSize clamps the computed height to the container height when maintainAspectRatio is true. When the container has no explicit height, its height is derived from the canvas itself, so the chart's current height becomes a ceiling: the chart can shrink but can never grow back. Measure the container with the canvas removed from the layout and only apply the clamp when the container height does not collapse, i.e. when it is defined independently of the canvas. Containers with an explicit height keep the documented v4 fit-within behavior. Fixes #11005 --- src/helpers/helpers.dom.ts | 33 +++++++++++++++++++++++++++- test/specs/helpers.dom.tests.js | 39 +++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) 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';