-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ff5dc36
commit e151b40
Showing
4 changed files
with
397 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"name": "chartjs-plugin-deferred", | ||
"description": "Chart.js plugin to defer initial chart updates", | ||
"homepage": "https://chartjs-plugin-deferred.netlify.app", | ||
"license": "MIT", | ||
"version": "2.0.0", | ||
"main": "dist/chartjs-plugin-deferred.js" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
/*! | ||
* chartjs-plugin-deferred v2.0.0 | ||
* https://chartjs-plugin-deferred.netlify.app | ||
* (c) 2016-2022 chartjs-plugin-deferred contributors | ||
* Released under the MIT license | ||
*/ | ||
import { requestAnimFrame, getStyle } from 'chart.js/helpers'; | ||
|
||
var STUB_KEY = '$chartjs_deferred'; | ||
var MODEL_KEY = '$deferred'; | ||
|
||
/** | ||
* Plugin based on discussion from Chart.js issue #2745. | ||
* @see https://github.com/chartjs/Chart.js/issues/2745 | ||
*/ | ||
|
||
function defer(fn, delay) { | ||
if (delay) { | ||
window.setTimeout(fn, delay); | ||
} else { | ||
requestAnimFrame.call(window, fn); | ||
} | ||
} | ||
|
||
function computeOffset(value, base) { | ||
var number = parseInt(value, 10); | ||
if (isNaN(number)) { | ||
return 0; | ||
} else if (typeof value === 'string' && value.indexOf('%') !== -1) { | ||
return number / 100 * base; | ||
} | ||
return number; | ||
} | ||
|
||
function chartInViewport(chart) { | ||
var options = chart[MODEL_KEY].options; | ||
var canvas = chart.canvas; | ||
|
||
// https://stackoverflow.com/a/21696585 | ||
if (!canvas || canvas.offsetParent === null) { | ||
return false; | ||
} | ||
|
||
var rect = canvas.getBoundingClientRect(); | ||
var dy = computeOffset(options.yOffset || 0, rect.height); | ||
var dx = computeOffset(options.xOffset || 0, rect.width); | ||
|
||
return rect.right - dx >= 0 | ||
&& rect.bottom - dy >= 0 | ||
&& rect.left + dx <= window.innerWidth | ||
&& rect.top + dy <= window.innerHeight; | ||
} | ||
|
||
function onScroll(event) { | ||
var node = event.target; | ||
var stub = node[STUB_KEY]; | ||
if (stub.ticking) { | ||
return; | ||
} | ||
|
||
stub.ticking = true; | ||
defer(function() { | ||
var charts = stub.charts.slice(); | ||
var ilen = charts.length; | ||
var chart, i; | ||
|
||
for (i = 0; i < ilen; ++i) { | ||
chart = charts[i]; | ||
if (chartInViewport(chart)) { | ||
unwatch(chart); // eslint-disable-line | ||
chart[MODEL_KEY].appeared = true; | ||
chart.update(); | ||
} | ||
} | ||
|
||
stub.ticking = false; | ||
}); | ||
} | ||
|
||
function isScrollable(node) { | ||
var type = node.nodeType; | ||
if (type === Node.ELEMENT_NODE) { | ||
var overflowX = getStyle(node, 'overflow-x'); | ||
var overflowY = getStyle(node, 'overflow-y'); | ||
return overflowX === 'auto' || overflowX === 'scroll' | ||
|| overflowY === 'auto' || overflowY === 'scroll'; | ||
} | ||
|
||
return node.nodeType === Node.DOCUMENT_NODE; | ||
} | ||
|
||
function watch(chart) { | ||
var canvas = chart.canvas; | ||
var parent = canvas.parentElement; | ||
var stub, charts; | ||
|
||
while (parent) { | ||
if (isScrollable(parent)) { | ||
stub = parent[STUB_KEY] || (parent[STUB_KEY] = {}); | ||
charts = stub.charts || (stub.charts = []); | ||
if (charts.length === 0) { | ||
parent.addEventListener('scroll', onScroll); | ||
} | ||
|
||
charts.push(chart); | ||
chart[MODEL_KEY].elements.push(parent); | ||
} | ||
|
||
parent = parent.parentElement || parent.ownerDocument; | ||
} | ||
} | ||
|
||
function unwatch(chart) { | ||
chart[MODEL_KEY].elements.forEach(function(element) { | ||
var charts = element[STUB_KEY].charts; | ||
charts.splice(charts.indexOf(chart), 1); | ||
if (!charts.length) { | ||
element.removeEventListener('scroll', onScroll); | ||
delete element[STUB_KEY]; | ||
} | ||
}); | ||
|
||
chart[MODEL_KEY].elements = []; | ||
} | ||
|
||
var plugin = { | ||
id: 'deferred', | ||
|
||
defaults: { | ||
xOffset: 0, | ||
yOffset: 0, | ||
delay: 0 | ||
}, | ||
|
||
beforeInit: function(chart, _, options) { | ||
chart[MODEL_KEY] = { | ||
options: options, | ||
appeared: false, | ||
delayed: false, | ||
loaded: false, | ||
elements: [] | ||
}; | ||
|
||
watch(chart); | ||
}, | ||
|
||
beforeDatasetsUpdate: function(chart, _, options) { | ||
var model = chart[MODEL_KEY]; | ||
if (!model.loaded) { | ||
if (!model.appeared && !chartInViewport(chart)) { | ||
// cancel the datasets update | ||
return false; | ||
} | ||
|
||
model.appeared = true; | ||
model.loaded = true; | ||
unwatch(chart); | ||
|
||
if (options.delay > 0) { | ||
model.delayed = true; | ||
defer(function() { | ||
// Ensure the chart instance is still alive. It may have been destroyed | ||
// during a delay and calling `chart.update()` will fail. The most common | ||
// reason for such scenario is user navigation. | ||
// https://github.com/chartjs/chartjs-plugin-deferred/pull/14 | ||
if (chart.ctx) { | ||
model.delayed = false; | ||
chart.update(); | ||
} | ||
}, options.delay); | ||
|
||
return false; | ||
} | ||
} | ||
|
||
if (model.delayed) { | ||
// in case of delayed update, ensure to block external requests, such | ||
// as interacting with the legend label, or direct calls to update() | ||
return false; | ||
} | ||
}, | ||
|
||
destroy: function(chart) { | ||
unwatch(chart); | ||
} | ||
}; | ||
|
||
export { plugin as default }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
/*! | ||
* chartjs-plugin-deferred v2.0.0 | ||
* https://chartjs-plugin-deferred.netlify.app | ||
* (c) 2016-2022 chartjs-plugin-deferred contributors | ||
* Released under the MIT license | ||
*/ | ||
(function (global, factory) { | ||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('chart.js/helpers')) : | ||
typeof define === 'function' && define.amd ? define(['chart.js/helpers'], factory) : | ||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.ChartDeferred = factory(global.Chart.helpers)); | ||
})(this, (function (helpers) { 'use strict'; | ||
|
||
var STUB_KEY = '$chartjs_deferred'; | ||
var MODEL_KEY = '$deferred'; | ||
|
||
/** | ||
* Plugin based on discussion from Chart.js issue #2745. | ||
* @see https://github.com/chartjs/Chart.js/issues/2745 | ||
*/ | ||
|
||
function defer(fn, delay) { | ||
if (delay) { | ||
window.setTimeout(fn, delay); | ||
} else { | ||
helpers.requestAnimFrame.call(window, fn); | ||
} | ||
} | ||
|
||
function computeOffset(value, base) { | ||
var number = parseInt(value, 10); | ||
if (isNaN(number)) { | ||
return 0; | ||
} else if (typeof value === 'string' && value.indexOf('%') !== -1) { | ||
return number / 100 * base; | ||
} | ||
return number; | ||
} | ||
|
||
function chartInViewport(chart) { | ||
var options = chart[MODEL_KEY].options; | ||
var canvas = chart.canvas; | ||
|
||
// https://stackoverflow.com/a/21696585 | ||
if (!canvas || canvas.offsetParent === null) { | ||
return false; | ||
} | ||
|
||
var rect = canvas.getBoundingClientRect(); | ||
var dy = computeOffset(options.yOffset || 0, rect.height); | ||
var dx = computeOffset(options.xOffset || 0, rect.width); | ||
|
||
return rect.right - dx >= 0 | ||
&& rect.bottom - dy >= 0 | ||
&& rect.left + dx <= window.innerWidth | ||
&& rect.top + dy <= window.innerHeight; | ||
} | ||
|
||
function onScroll(event) { | ||
var node = event.target; | ||
var stub = node[STUB_KEY]; | ||
if (stub.ticking) { | ||
return; | ||
} | ||
|
||
stub.ticking = true; | ||
defer(function() { | ||
var charts = stub.charts.slice(); | ||
var ilen = charts.length; | ||
var chart, i; | ||
|
||
for (i = 0; i < ilen; ++i) { | ||
chart = charts[i]; | ||
if (chartInViewport(chart)) { | ||
unwatch(chart); // eslint-disable-line | ||
chart[MODEL_KEY].appeared = true; | ||
chart.update(); | ||
} | ||
} | ||
|
||
stub.ticking = false; | ||
}); | ||
} | ||
|
||
function isScrollable(node) { | ||
var type = node.nodeType; | ||
if (type === Node.ELEMENT_NODE) { | ||
var overflowX = helpers.getStyle(node, 'overflow-x'); | ||
var overflowY = helpers.getStyle(node, 'overflow-y'); | ||
return overflowX === 'auto' || overflowX === 'scroll' | ||
|| overflowY === 'auto' || overflowY === 'scroll'; | ||
} | ||
|
||
return node.nodeType === Node.DOCUMENT_NODE; | ||
} | ||
|
||
function watch(chart) { | ||
var canvas = chart.canvas; | ||
var parent = canvas.parentElement; | ||
var stub, charts; | ||
|
||
while (parent) { | ||
if (isScrollable(parent)) { | ||
stub = parent[STUB_KEY] || (parent[STUB_KEY] = {}); | ||
charts = stub.charts || (stub.charts = []); | ||
if (charts.length === 0) { | ||
parent.addEventListener('scroll', onScroll); | ||
} | ||
|
||
charts.push(chart); | ||
chart[MODEL_KEY].elements.push(parent); | ||
} | ||
|
||
parent = parent.parentElement || parent.ownerDocument; | ||
} | ||
} | ||
|
||
function unwatch(chart) { | ||
chart[MODEL_KEY].elements.forEach(function(element) { | ||
var charts = element[STUB_KEY].charts; | ||
charts.splice(charts.indexOf(chart), 1); | ||
if (!charts.length) { | ||
element.removeEventListener('scroll', onScroll); | ||
delete element[STUB_KEY]; | ||
} | ||
}); | ||
|
||
chart[MODEL_KEY].elements = []; | ||
} | ||
|
||
var plugin = { | ||
id: 'deferred', | ||
|
||
defaults: { | ||
xOffset: 0, | ||
yOffset: 0, | ||
delay: 0 | ||
}, | ||
|
||
beforeInit: function(chart, _, options) { | ||
chart[MODEL_KEY] = { | ||
options: options, | ||
appeared: false, | ||
delayed: false, | ||
loaded: false, | ||
elements: [] | ||
}; | ||
|
||
watch(chart); | ||
}, | ||
|
||
beforeDatasetsUpdate: function(chart, _, options) { | ||
var model = chart[MODEL_KEY]; | ||
if (!model.loaded) { | ||
if (!model.appeared && !chartInViewport(chart)) { | ||
// cancel the datasets update | ||
return false; | ||
} | ||
|
||
model.appeared = true; | ||
model.loaded = true; | ||
unwatch(chart); | ||
|
||
if (options.delay > 0) { | ||
model.delayed = true; | ||
defer(function() { | ||
// Ensure the chart instance is still alive. It may have been destroyed | ||
// during a delay and calling `chart.update()` will fail. The most common | ||
// reason for such scenario is user navigation. | ||
// https://github.com/chartjs/chartjs-plugin-deferred/pull/14 | ||
if (chart.ctx) { | ||
model.delayed = false; | ||
chart.update(); | ||
} | ||
}, options.delay); | ||
|
||
return false; | ||
} | ||
} | ||
|
||
if (model.delayed) { | ||
// in case of delayed update, ensure to block external requests, such | ||
// as interacting with the legend label, or direct calls to update() | ||
return false; | ||
} | ||
}, | ||
|
||
destroy: function(chart) { | ||
unwatch(chart); | ||
} | ||
}; | ||
|
||
return plugin; | ||
|
||
})); |
Oops, something went wrong.