From: Jukka Kurkela Date: Wed, 19 Aug 2020 11:08:41 +0000 (+0300) Subject: Resize on devicePixelRatio changes (#7717) X-Git-Tag: v3.0.0-beta.2~9 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ae214d9fbdae6a7a879c51e463be1c64e565e8e8;p=thirdparty%2FChart.js.git Resize on devicePixelRatio changes (#7717) * Resize on devicePixelRatio changes --- diff --git a/package-lock.json b/package-lock.json index 8eb50210a..374f637ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2399,7 +2399,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -2418,7 +2418,7 @@ }, "supports-color": { "version": "2.0.0", - "resolved": "http://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", "dev": true } diff --git a/src/helpers/helpers.dom.js b/src/helpers/helpers.dom.js index a0c29a7c4..91d9a3882 100644 --- a/src/helpers/helpers.dom.js +++ b/src/helpers/helpers.dom.js @@ -176,3 +176,42 @@ export function retinaScale(chart, forceRatio) { canvas.style.width = width + 'px'; } } + +/** + * Detects support for options object argument in addEventListener. + * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support + * @private + */ +export const supportsEventListenerOptions = (function() { + let passiveSupported = false; + try { + const options = { + get passive() { // This function will be called when the browser attempts to access the passive property. + passiveSupported = true; + return false; + } + }; + // @ts-ignore + window.addEventListener('test', null, options); + // @ts-ignore + window.removeEventListener('test', null, options); + } catch (e) { + // continue regardless of error + } + return passiveSupported; +}()); + +/** + * The "used" size is the final value of a dimension property after all calculations have + * been performed. This method uses the computed style of `element` but returns undefined + * if the computed style is not expressed in pixels. That can happen in some cases where + * `element` has a size relative to its parent and this last one is not yet displayed, + * for example because of `display: none` on a parent node. + * @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value + * @returns {number=} Size in pixels or undefined if unknown. + */ +export function readUsedSize(element, property) { + const value = getStyle(element, property); + const matches = value && value.match(/^(\d+)(\.\d+)?px$/); + return matches ? +matches[1] : undefined; +} diff --git a/src/helpers/helpers.extras.js b/src/helpers/helpers.extras.js index fa0bdffa8..0bfec3e09 100644 --- a/src/helpers/helpers.extras.js +++ b/src/helpers/helpers.extras.js @@ -13,3 +13,26 @@ export const requestAnimFrame = (function() { } return window.requestAnimationFrame; }()); + +/** + * Throttles calling `fn` once per animation frame + * Latest argments are used on the actual call + * @param {function} fn + * @param {*} thisArg + */ +export function throttled(fn, thisArg) { + let ticking = false; + let args = []; + + return function(...rest) { + args = Array.prototype.slice.call(rest); + + if (!ticking) { + ticking = true; + requestAnimFrame.call(window, () => { + ticking = false; + fn.apply(thisArg, args); + }); + } + }; +} diff --git a/src/platform/platform.dom.js b/src/platform/platform.dom.js index e994e6d6d..1b2eb2881 100644 --- a/src/platform/platform.dom.js +++ b/src/platform/platform.dom.js @@ -3,8 +3,8 @@ */ import BasePlatform from './platform.base'; -import {_getParentNode, getStyle, getRelativePosition} from '../helpers/helpers.dom'; -import {requestAnimFrame} from '../helpers/helpers.extras'; +import {_getParentNode, getRelativePosition, supportsEventListenerOptions, readUsedSize} from '../helpers/helpers.dom'; +import {throttled} from '../helpers/helpers.extras'; import {isNullOrUndef} from '../helpers/helpers.core'; /** @@ -30,21 +30,7 @@ const EVENT_TYPES = { pointerout: 'mouseout' }; -/** - * The "used" size is the final value of a dimension property after all calculations have - * been performed. This method uses the computed style of `element` but returns undefined - * if the computed style is not expressed in pixels. That can happen in some cases where - * `element` has a size relative to its parent and this last one is not yet displayed, - * for example because of `display: none` on a parent node. - * @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value - * @returns {number=} Size in pixels or undefined if unknown. - */ -function readUsedSize(element, property) { - const value = getStyle(element, property); - const matches = value && value.match(/^(\d+)(\.\d+)?px$/); - return matches ? +matches[1] : undefined; -} - +const isNullOrEmpty = value => value === null || value === ''; /** * Initializes the canvas style and render size without modifying the canvas display size, * since responsiveness is handled by the controller.resize() method. The config is used @@ -80,14 +66,14 @@ function initCanvas(canvas, config) { // Include possible borders in the size style.boxSizing = style.boxSizing || 'border-box'; - if (renderWidth === null || renderWidth === '') { + if (isNullOrEmpty(renderWidth)) { const displayWidth = readUsedSize(canvas, 'width'); if (displayWidth !== undefined) { canvas.width = displayWidth; } } - if (renderHeight === null || renderHeight === '') { + if (isNullOrEmpty(renderHeight)) { if (canvas.style.height === '') { // If no explicit render height and style height, let's apply the aspect ratio, // which one can be specified by the user but also by charts as default option @@ -104,30 +90,6 @@ function initCanvas(canvas, config) { return canvas; } -/** - * Detects support for options object argument in addEventListener. - * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support - * @private - */ -const supportsEventListenerOptions = (function() { - let passiveSupported = false; - try { - const options = { - get passive() { // This function will be called when the browser attempts to access the passive property. - passiveSupported = true; - return false; - } - }; - // @ts-ignore - window.addEventListener('test', null, options); - // @ts-ignore - window.removeEventListener('test', null, options); - } catch (e) { - // continue regardless of error - } - return passiveSupported; -}()); - // Default passive to true as expected by Chrome for 'touchstart' and 'touchend' events. // https://github.com/chartjs/Chart.js/issues/4287 const eventListenerOptions = supportsEventListenerOptions ? {passive: true} : false; @@ -136,8 +98,8 @@ function addListener(node, type, listener) { node.addEventListener(type, listener, eventListenerOptions); } -function removeListener(node, type, listener) { - node.removeEventListener(type, listener, eventListenerOptions); +function removeListener(chart, type, listener) { + chart.canvas.removeEventListener(type, listener, eventListenerOptions); } function createEvent(type, chart, x, y, nativeEvent) { @@ -156,23 +118,6 @@ function fromNativeEvent(event, chart) { return createEvent(type, chart, pos.x, pos.y, event); } -function throttled(fn, thisArg) { - let ticking = false; - let args = []; - - return function(...rest) { - args = Array.prototype.slice.call(rest); - - if (!ticking) { - ticking = true; - requestAnimFrame.call(window, () => { - ticking = false; - fn.apply(thisArg, args); - }); - } - }; -} - function createAttachObserver(chart, type, listener) { const canvas = chart.canvas; const container = canvas && _getParentNode(canvas); @@ -212,6 +157,36 @@ function createDetachObserver(chart, type, listener) { return observer; } +const drpListeningCharts = new Map(); +let oldDevicePixelRatio = 0; + +function onWindowResize() { + const dpr = window.devicePixelRatio; + if (dpr === oldDevicePixelRatio) { + return; + } + oldDevicePixelRatio = dpr; + drpListeningCharts.forEach((resize, chart) => { + if (chart.currentDevicePixelRatio !== dpr) { + resize(); + } + }); +} + +function listenDevicePixelRatioChanges(chart, resize) { + if (!drpListeningCharts.size) { + window.addEventListener('resize', onWindowResize); + } + drpListeningCharts.set(chart, resize); +} + +function unlistenDevicePixelRatioChanges(chart) { + drpListeningCharts.delete(chart); + if (!drpListeningCharts.size) { + window.removeEventListener('resize', onWindowResize); + } +} + function createResizeObserver(chart, type, listener) { const canvas = chart.canvas; const container = canvas && _getParentNode(canvas); @@ -247,13 +222,18 @@ function createResizeObserver(chart, type, listener) { resize(width, height); }); observer.observe(container); + listenDevicePixelRatioChanges(chart, resize); + return observer; } -function releaseObserver(canvas, type, observer) { +function releaseObserver(chart, type, observer) { if (observer) { observer.disconnect(); } + if (type === 'resize') { + unlistenDevicePixelRatioChanges(chart); + } } function createProxyAndListen(chart, type, listener) { @@ -367,7 +347,6 @@ export default class DomPlatform extends BasePlatform { * @param {string} type */ removeEventListener(chart, type) { - const canvas = chart.canvas; const proxies = chart.$proxies || (chart.$proxies = {}); const proxy = proxies[type]; @@ -381,7 +360,7 @@ export default class DomPlatform extends BasePlatform { resize: releaseObserver }; const handler = handlers[type] || removeListener; - handler(canvas, type, proxy); + handler(chart, type, proxy); proxies[type] = undefined; }