]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Resize on devicePixelRatio changes (#7717)
authorJukka Kurkela <jukka.kurkela@gmail.com>
Wed, 19 Aug 2020 11:08:41 +0000 (14:08 +0300)
committerGitHub <noreply@github.com>
Wed, 19 Aug 2020 11:08:41 +0000 (07:08 -0400)
* Resize on devicePixelRatio changes

package-lock.json
src/helpers/helpers.dom.js
src/helpers/helpers.extras.js
src/platform/platform.dom.js

index 8eb50210ab5f8c26c9cbc93d46d618b4bbf00264..374f637ee0659763daac3a6567d41c162f3caad2 100644 (file)
         },
         "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": {
         },
         "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
         }
index a0c29a7c4936cc30ec8ba90f30d2ebc9107b4b5a..91d9a388281af298bd2317e800cdf641c91c07dd 100644 (file)
@@ -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;
+}
index fa0bdffa8c4fa859344e5a70336a3c67d0b8969c..0bfec3e091a20de9e3e90d27e23a5af70da3ba17 100644 (file)
@@ -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);
+                       });
+               }
+       };
+}
index e994e6d6dc5a662ed7677b4e5f171a1a8b65eb7e..1b2eb28810e56f7341db5f681e6b644ab41e8d45 100644 (file)
@@ -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;
        }