Skip to content

Commit e151b40

Browse files
committed
Release v2.0.0
1 parent ff5dc36 commit e151b40

File tree

4 files changed

+397
-0
lines changed

4 files changed

+397
-0
lines changed

bower.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"name": "chartjs-plugin-deferred",
3+
"description": "Chart.js plugin to defer initial chart updates",
4+
"homepage": "https://chartjs-plugin-deferred.netlify.app",
5+
"license": "MIT",
6+
"version": "2.0.0",
7+
"main": "dist/chartjs-plugin-deferred.js"
8+
}
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
/*!
2+
* chartjs-plugin-deferred v2.0.0
3+
* https://chartjs-plugin-deferred.netlify.app
4+
* (c) 2016-2022 chartjs-plugin-deferred contributors
5+
* Released under the MIT license
6+
*/
7+
import { requestAnimFrame, getStyle } from 'chart.js/helpers';
8+
9+
var STUB_KEY = '$chartjs_deferred';
10+
var MODEL_KEY = '$deferred';
11+
12+
/**
13+
* Plugin based on discussion from Chart.js issue #2745.
14+
* @see https://github.com/chartjs/Chart.js/issues/2745
15+
*/
16+
17+
function defer(fn, delay) {
18+
if (delay) {
19+
window.setTimeout(fn, delay);
20+
} else {
21+
requestAnimFrame.call(window, fn);
22+
}
23+
}
24+
25+
function computeOffset(value, base) {
26+
var number = parseInt(value, 10);
27+
if (isNaN(number)) {
28+
return 0;
29+
} else if (typeof value === 'string' && value.indexOf('%') !== -1) {
30+
return number / 100 * base;
31+
}
32+
return number;
33+
}
34+
35+
function chartInViewport(chart) {
36+
var options = chart[MODEL_KEY].options;
37+
var canvas = chart.canvas;
38+
39+
// https://stackoverflow.com/a/21696585
40+
if (!canvas || canvas.offsetParent === null) {
41+
return false;
42+
}
43+
44+
var rect = canvas.getBoundingClientRect();
45+
var dy = computeOffset(options.yOffset || 0, rect.height);
46+
var dx = computeOffset(options.xOffset || 0, rect.width);
47+
48+
return rect.right - dx >= 0
49+
&& rect.bottom - dy >= 0
50+
&& rect.left + dx <= window.innerWidth
51+
&& rect.top + dy <= window.innerHeight;
52+
}
53+
54+
function onScroll(event) {
55+
var node = event.target;
56+
var stub = node[STUB_KEY];
57+
if (stub.ticking) {
58+
return;
59+
}
60+
61+
stub.ticking = true;
62+
defer(function() {
63+
var charts = stub.charts.slice();
64+
var ilen = charts.length;
65+
var chart, i;
66+
67+
for (i = 0; i < ilen; ++i) {
68+
chart = charts[i];
69+
if (chartInViewport(chart)) {
70+
unwatch(chart); // eslint-disable-line
71+
chart[MODEL_KEY].appeared = true;
72+
chart.update();
73+
}
74+
}
75+
76+
stub.ticking = false;
77+
});
78+
}
79+
80+
function isScrollable(node) {
81+
var type = node.nodeType;
82+
if (type === Node.ELEMENT_NODE) {
83+
var overflowX = getStyle(node, 'overflow-x');
84+
var overflowY = getStyle(node, 'overflow-y');
85+
return overflowX === 'auto' || overflowX === 'scroll'
86+
|| overflowY === 'auto' || overflowY === 'scroll';
87+
}
88+
89+
return node.nodeType === Node.DOCUMENT_NODE;
90+
}
91+
92+
function watch(chart) {
93+
var canvas = chart.canvas;
94+
var parent = canvas.parentElement;
95+
var stub, charts;
96+
97+
while (parent) {
98+
if (isScrollable(parent)) {
99+
stub = parent[STUB_KEY] || (parent[STUB_KEY] = {});
100+
charts = stub.charts || (stub.charts = []);
101+
if (charts.length === 0) {
102+
parent.addEventListener('scroll', onScroll);
103+
}
104+
105+
charts.push(chart);
106+
chart[MODEL_KEY].elements.push(parent);
107+
}
108+
109+
parent = parent.parentElement || parent.ownerDocument;
110+
}
111+
}
112+
113+
function unwatch(chart) {
114+
chart[MODEL_KEY].elements.forEach(function(element) {
115+
var charts = element[STUB_KEY].charts;
116+
charts.splice(charts.indexOf(chart), 1);
117+
if (!charts.length) {
118+
element.removeEventListener('scroll', onScroll);
119+
delete element[STUB_KEY];
120+
}
121+
});
122+
123+
chart[MODEL_KEY].elements = [];
124+
}
125+
126+
var plugin = {
127+
id: 'deferred',
128+
129+
defaults: {
130+
xOffset: 0,
131+
yOffset: 0,
132+
delay: 0
133+
},
134+
135+
beforeInit: function(chart, _, options) {
136+
chart[MODEL_KEY] = {
137+
options: options,
138+
appeared: false,
139+
delayed: false,
140+
loaded: false,
141+
elements: []
142+
};
143+
144+
watch(chart);
145+
},
146+
147+
beforeDatasetsUpdate: function(chart, _, options) {
148+
var model = chart[MODEL_KEY];
149+
if (!model.loaded) {
150+
if (!model.appeared && !chartInViewport(chart)) {
151+
// cancel the datasets update
152+
return false;
153+
}
154+
155+
model.appeared = true;
156+
model.loaded = true;
157+
unwatch(chart);
158+
159+
if (options.delay > 0) {
160+
model.delayed = true;
161+
defer(function() {
162+
// Ensure the chart instance is still alive. It may have been destroyed
163+
// during a delay and calling `chart.update()` will fail. The most common
164+
// reason for such scenario is user navigation.
165+
// https://github.com/chartjs/chartjs-plugin-deferred/pull/14
166+
if (chart.ctx) {
167+
model.delayed = false;
168+
chart.update();
169+
}
170+
}, options.delay);
171+
172+
return false;
173+
}
174+
}
175+
176+
if (model.delayed) {
177+
// in case of delayed update, ensure to block external requests, such
178+
// as interacting with the legend label, or direct calls to update()
179+
return false;
180+
}
181+
},
182+
183+
destroy: function(chart) {
184+
unwatch(chart);
185+
}
186+
};
187+
188+
export { plugin as default };

dist/chartjs-plugin-deferred.js

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
/*!
2+
* chartjs-plugin-deferred v2.0.0
3+
* https://chartjs-plugin-deferred.netlify.app
4+
* (c) 2016-2022 chartjs-plugin-deferred contributors
5+
* Released under the MIT license
6+
*/
7+
(function (global, factory) {
8+
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('chart.js/helpers')) :
9+
typeof define === 'function' && define.amd ? define(['chart.js/helpers'], factory) :
10+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.ChartDeferred = factory(global.Chart.helpers));
11+
})(this, (function (helpers) { 'use strict';
12+
13+
var STUB_KEY = '$chartjs_deferred';
14+
var MODEL_KEY = '$deferred';
15+
16+
/**
17+
* Plugin based on discussion from Chart.js issue #2745.
18+
* @see https://github.com/chartjs/Chart.js/issues/2745
19+
*/
20+
21+
function defer(fn, delay) {
22+
if (delay) {
23+
window.setTimeout(fn, delay);
24+
} else {
25+
helpers.requestAnimFrame.call(window, fn);
26+
}
27+
}
28+
29+
function computeOffset(value, base) {
30+
var number = parseInt(value, 10);
31+
if (isNaN(number)) {
32+
return 0;
33+
} else if (typeof value === 'string' && value.indexOf('%') !== -1) {
34+
return number / 100 * base;
35+
}
36+
return number;
37+
}
38+
39+
function chartInViewport(chart) {
40+
var options = chart[MODEL_KEY].options;
41+
var canvas = chart.canvas;
42+
43+
// https://stackoverflow.com/a/21696585
44+
if (!canvas || canvas.offsetParent === null) {
45+
return false;
46+
}
47+
48+
var rect = canvas.getBoundingClientRect();
49+
var dy = computeOffset(options.yOffset || 0, rect.height);
50+
var dx = computeOffset(options.xOffset || 0, rect.width);
51+
52+
return rect.right - dx >= 0
53+
&& rect.bottom - dy >= 0
54+
&& rect.left + dx <= window.innerWidth
55+
&& rect.top + dy <= window.innerHeight;
56+
}
57+
58+
function onScroll(event) {
59+
var node = event.target;
60+
var stub = node[STUB_KEY];
61+
if (stub.ticking) {
62+
return;
63+
}
64+
65+
stub.ticking = true;
66+
defer(function() {
67+
var charts = stub.charts.slice();
68+
var ilen = charts.length;
69+
var chart, i;
70+
71+
for (i = 0; i < ilen; ++i) {
72+
chart = charts[i];
73+
if (chartInViewport(chart)) {
74+
unwatch(chart); // eslint-disable-line
75+
chart[MODEL_KEY].appeared = true;
76+
chart.update();
77+
}
78+
}
79+
80+
stub.ticking = false;
81+
});
82+
}
83+
84+
function isScrollable(node) {
85+
var type = node.nodeType;
86+
if (type === Node.ELEMENT_NODE) {
87+
var overflowX = helpers.getStyle(node, 'overflow-x');
88+
var overflowY = helpers.getStyle(node, 'overflow-y');
89+
return overflowX === 'auto' || overflowX === 'scroll'
90+
|| overflowY === 'auto' || overflowY === 'scroll';
91+
}
92+
93+
return node.nodeType === Node.DOCUMENT_NODE;
94+
}
95+
96+
function watch(chart) {
97+
var canvas = chart.canvas;
98+
var parent = canvas.parentElement;
99+
var stub, charts;
100+
101+
while (parent) {
102+
if (isScrollable(parent)) {
103+
stub = parent[STUB_KEY] || (parent[STUB_KEY] = {});
104+
charts = stub.charts || (stub.charts = []);
105+
if (charts.length === 0) {
106+
parent.addEventListener('scroll', onScroll);
107+
}
108+
109+
charts.push(chart);
110+
chart[MODEL_KEY].elements.push(parent);
111+
}
112+
113+
parent = parent.parentElement || parent.ownerDocument;
114+
}
115+
}
116+
117+
function unwatch(chart) {
118+
chart[MODEL_KEY].elements.forEach(function(element) {
119+
var charts = element[STUB_KEY].charts;
120+
charts.splice(charts.indexOf(chart), 1);
121+
if (!charts.length) {
122+
element.removeEventListener('scroll', onScroll);
123+
delete element[STUB_KEY];
124+
}
125+
});
126+
127+
chart[MODEL_KEY].elements = [];
128+
}
129+
130+
var plugin = {
131+
id: 'deferred',
132+
133+
defaults: {
134+
xOffset: 0,
135+
yOffset: 0,
136+
delay: 0
137+
},
138+
139+
beforeInit: function(chart, _, options) {
140+
chart[MODEL_KEY] = {
141+
options: options,
142+
appeared: false,
143+
delayed: false,
144+
loaded: false,
145+
elements: []
146+
};
147+
148+
watch(chart);
149+
},
150+
151+
beforeDatasetsUpdate: function(chart, _, options) {
152+
var model = chart[MODEL_KEY];
153+
if (!model.loaded) {
154+
if (!model.appeared && !chartInViewport(chart)) {
155+
// cancel the datasets update
156+
return false;
157+
}
158+
159+
model.appeared = true;
160+
model.loaded = true;
161+
unwatch(chart);
162+
163+
if (options.delay > 0) {
164+
model.delayed = true;
165+
defer(function() {
166+
// Ensure the chart instance is still alive. It may have been destroyed
167+
// during a delay and calling `chart.update()` will fail. The most common
168+
// reason for such scenario is user navigation.
169+
// https://github.com/chartjs/chartjs-plugin-deferred/pull/14
170+
if (chart.ctx) {
171+
model.delayed = false;
172+
chart.update();
173+
}
174+
}, options.delay);
175+
176+
return false;
177+
}
178+
}
179+
180+
if (model.delayed) {
181+
// in case of delayed update, ensure to block external requests, such
182+
// as interacting with the legend label, or direct calls to update()
183+
return false;
184+
}
185+
},
186+
187+
destroy: function(chart) {
188+
unwatch(chart);
189+
}
190+
};
191+
192+
return plugin;
193+
194+
}));

0 commit comments

Comments
 (0)