Skip to content

Commit

Permalink
Detect attach/detach from any level (#9557)
Browse files Browse the repository at this point in the history
  • Loading branch information
kurkle committed Aug 18, 2021
1 parent 4bf42f4 commit fca0309
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 33 deletions.
21 changes: 15 additions & 6 deletions src/core/core.controller.js
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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();
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
};

Expand Down
9 changes: 4 additions & 5 deletions src/helpers/helpers.extras.js
Expand Up @@ -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
Expand Down
31 changes: 11 additions & 20 deletions src/platform/platform.dom.js
Expand Up @@ -116,40 +116,31 @@ 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;
}

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;
}

Expand Down
47 changes: 45 additions & 2 deletions test/specs/core.controller.tests.js
Expand Up @@ -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
Expand Down Expand Up @@ -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() {
Expand Down

0 comments on commit fca0309

Please sign in to comment.