]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Detect attach/detach from any level (#9557)
authorJukka Kurkela <jukka.kurkela@gmail.com>
Wed, 18 Aug 2021 22:14:09 +0000 (01:14 +0300)
committerGitHub <noreply@github.com>
Wed, 18 Aug 2021 22:14:09 +0000 (18:14 -0400)
src/core/core.controller.js
src/helpers/helpers.extras.js
src/platform/platform.dom.js
test/specs/core.controller.tests.js

index 9d7983887b3af648effe07b118b302101a97e63b..4162ac8f198af40c3b519e44dac573948364b112 100644 (file)
@@ -123,7 +123,7 @@ class Chart {
     this.attached = false;
     this._animationsDisabled = undefined;
     this.$context = undefined;
-    this._doResize = debounce(() => this.update('resize'), options.resizeDelay || 0);
+    this._doResize = debounce(mode => this.update(mode), options.resizeDelay || 0);
 
     // Add the chart instance to the global namespace
     instances[me.id] = me;
@@ -231,6 +231,7 @@ class Chart {
     const aspectRatio = options.maintainAspectRatio && me.aspectRatio;
     const newSize = me.platform.getMaximumSize(canvas, width, height, aspectRatio);
     const newRatio = options.devicePixelRatio || me.platform.getDevicePixelRatio();
+    const mode = me.width ? 'resize' : 'attach';
 
     me.width = newSize.width;
     me.height = newSize.height;
@@ -244,7 +245,7 @@ class Chart {
     callCallback(options.onResize, [me, newSize], me);
 
     if (me.attached) {
-      if (me._doResize()) {
+      if (me._doResize(mode)) {
         // The resize update is delayed, only draw without updating.
         me.render();
       }
@@ -831,19 +832,22 @@ class Chart {
     }
   }
 
-  destroy() {
+  _stop() {
     const me = this;
-    const {canvas, ctx} = me;
     let i, ilen;
-
     me.stop();
     animator.remove(me);
 
-    // dataset controllers need to cleanup associated data
     for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
       me._destroyDatasetMeta(i);
     }
+  }
+
+  destroy() {
+    const me = this;
+    const {canvas, ctx} = me;
 
+    me._stop();
     me.config.clearCache();
 
     if (canvas) {
@@ -940,6 +944,11 @@ class Chart {
       me.attached = false;
 
       _remove('resize', listener);
+
+      // Stop animating and remove metasets, so when re-attached, the animations start from begining.
+      me._stop();
+      me._resize(0, 0);
+
       _add('attach', attached);
     };
 
index 09739ce8671037bd860f5a3c1b30e0a5ede71d8d..fd7608b9cc55d93a3745c886d3b4449a27ac26f7 100644 (file)
@@ -42,24 +42,23 @@ export function throttled(fn, thisArg, updateFn) {
 
 /**
  * Debounces calling `fn` for `delay` ms
- * @param {function} fn - Function to call. No arguments are passed.
+ * @param {function} fn - Function to call.
  * @param {number} delay - Delay in ms. 0 = immediate invocation.
  * @returns {function}
  */
 export function debounce(fn, delay) {
   let timeout;
-  return function() {
+  return function(...args) {
     if (delay) {
       clearTimeout(timeout);
-      timeout = setTimeout(fn, delay);
+      timeout = setTimeout(fn, delay, args);
     } else {
-      fn();
+      fn.apply(this, args);
     }
     return delay;
   };
 }
 
-
 /**
  * Converts 'start' to 'left', 'end' to 'right' and others to 'center'
  * @param {string} align start, end, center
index 8e7858f750a561c2085cbd4259f860590c47c0c4..03844deea81f89b3b1f8b33bcc436560feb23888 100644 (file)
@@ -116,18 +116,14 @@ function fromNativeEvent(event, chart) {
 
 function createAttachObserver(chart, type, listener) {
   const canvas = chart.canvas;
-  const container = canvas && _getParentNode(canvas);
-  const element = container || canvas;
   const observer = new MutationObserver(entries => {
-    const parent = _getParentNode(element);
-    entries.forEach(entry => {
-      for (let i = 0; i < entry.addedNodes.length; i++) {
-        const added = entry.addedNodes[i];
-        if (added === element || added === parent) {
-          listener(entry.target);
+    for (const entry of entries) {
+      for (const node of entry.addedNodes) {
+        if (node === canvas || node.contains(canvas)) {
+          return listener();
         }
       }
-    });
+    }
   });
   observer.observe(document, {childList: true, subtree: true});
   return observer;
@@ -135,21 +131,16 @@ function createAttachObserver(chart, type, listener) {
 
 function createDetachObserver(chart, type, listener) {
   const canvas = chart.canvas;
-  const container = canvas && _getParentNode(canvas);
-  if (!container) {
-    return;
-  }
   const observer = new MutationObserver(entries => {
-    entries.forEach(entry => {
-      for (let i = 0; i < entry.removedNodes.length; i++) {
-        if (entry.removedNodes[i] === canvas) {
-          listener();
-          break;
+    for (const entry of entries) {
+      for (const node of entry.removedNodes) {
+        if (node === canvas || node.contains(canvas)) {
+          return listener();
         }
       }
-    });
+    }
   });
-  observer.observe(container, {childList: true});
+  observer.observe(document, {childList: true, subtree: true});
   return observer;
 }
 
index 5072537e97575ee644d43ac118f4198095488c35..809b721d9d4cf2c93b9e65d3c19e1c956b5f3988 100644 (file)
@@ -1029,8 +1029,10 @@ describe('Chart', function() {
       });
 
       parent.removeChild(wrapper);
-      parent.appendChild(wrapper);
-      wrapper.style.height = '355px';
+      setTimeout(() => {
+        parent.appendChild(wrapper);
+        wrapper.style.height = '355px';
+      }, 0);
     });
 
     // https://github.com/chartjs/Chart.js/issues/4737
@@ -1075,6 +1077,47 @@ describe('Chart', function() {
         canvas.parentNode.style.width = '455px';
       });
     });
+
+    it('should resize the canvas if attached to the DOM after construction with mutiple parents', function(done) {
+      var canvas = document.createElement('canvas');
+      var wrapper = document.createElement('div');
+      var wrapper2 = document.createElement('div');
+      var wrapper3 = document.createElement('div');
+      var body = window.document.body;
+
+      var chart = new Chart(canvas, {
+        type: 'line',
+        options: {
+          responsive: true,
+          maintainAspectRatio: false
+        }
+      });
+
+      expect(chart).toBeChartOfSize({
+        dw: 0, dh: 0,
+        rw: 0, rh: 0,
+      });
+      expect(chart.chartArea).toBeUndefined();
+
+      waitForResize(chart, function() {
+        expect(chart).toBeChartOfSize({
+          dw: 455, dh: 355,
+          rw: 455, rh: 355,
+        });
+
+        expect(chart.chartArea).not.toBeUndefined();
+
+        body.removeChild(wrapper3);
+        chart.destroy();
+        done();
+      });
+
+      wrapper3.appendChild(wrapper2);
+      wrapper2.appendChild(wrapper);
+      wrapper.style.cssText = 'width: 455px; height: 355px';
+      wrapper.appendChild(canvas);
+      body.appendChild(wrapper3);
+    });
   });
 
   describe('config.options.responsive: true (maintainAspectRatio: true)', function() {