Skip to content

Commit

Permalink
Release 0.3.0
Browse files Browse the repository at this point in the history
  • Loading branch information
travis@localhost committed Apr 30, 2017
1 parent 89d4c6b commit 2908c52
Show file tree
Hide file tree
Showing 3 changed files with 242 additions and 0 deletions.
8 changes: 8 additions & 0 deletions bower.json
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": "http://www.chartjs.org",
"license": "MIT",
"version": "0.3.0",
"main": "./dist/chartjs-plugin-deferred.js"
}
224 changes: 224 additions & 0 deletions dist/chartjs-plugin-deferred.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
/*!
* chartjs-plugin-deferred
* http://chartjs.org/
* Version: 0.3.0
*
* Copyright 2017 Simon Brunel
* Released under the MIT license
* https://github.com/chartjs/chartjs-plugin-deferred/blob/master/LICENSE.md
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('chart.js')) :
typeof define === 'function' && define.amd ? define(['chart.js'], factory) :
(factory(global.Chart));
}(this, (function (Chart) { 'use strict';

Chart = 'default' in Chart ? Chart['default'] : Chart;

(function() {
var helpers = Chart.helpers;
var STUB_KEY = '_chartjs_deferred';
var MODEL_KEY = '_deferred_model';

/**
* Plugin based on discussion from Chart.js issue #2745.
* @see https://github.com/chartjs/Chart.js/issues/2745
*/
Chart.Deferred = Chart.Deferred || {};
Chart.Deferred.defaults = {
enabled: true,
xOffset: 0,
yOffset: 0,
delay: 0
};

// DOM implementation
// @TODO move it in Chart.js: src/core/core.platform.js
Chart.platform = helpers.extend(Chart.platform || {}, {
defer: function(fn, delay, scope) {
var callback = function() {
fn.call(scope);
};
if (!delay) {
helpers.requestAnimFrame.call(window, callback);
} else {
window.setTimeout(callback, delay);
}
}
});

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(instance) {
var model = instance[MODEL_KEY];
var canvas = instance.chart.canvas;

// http://stackoverflow.com/a/21696585
if (!canvas || canvas.offsetParent === null) {
return false;
}

var rect = canvas.getBoundingClientRect();
var dy = computeOffset(model.yOffset || 0, rect.height);
var dx = computeOffset(model.xOffset || 0, rect.width);

return rect.right - dx >= 0
&& rect.bottom - dy >= 0
&& rect.left + dx <= window.innerWidth
&& rect.top + dy <= window.innerHeight;
}

function buildDeferredModel(instance) {
var defaults = Chart.Deferred.defaults;
var options = instance.options.deferred;
var getValue = helpers.getValueOrDefault;

if (options === undefined) {
options = {};
} else if (typeof options === 'boolean') {
// accepting { options: { deferred: true } }
options = {enabled: options};
}

return {
enabled: getValue(options.enabled, defaults.enabled),
xOffset: getValue(options.xOffset, defaults.xOffset),
yOffset: getValue(options.yOffset, defaults.yOffset),
delay: getValue(options.delay, defaults.delay),
appeared: false,
delayed: false,
loaded: false,
elements: []
};
}

function onScroll(event) {
var node = event.target;
var stub = node[STUB_KEY];
if (stub.ticking) {
return;
}

stub.ticking = true;
Chart.platform.defer(function() {
var instances = stub.instances.slice();
var ilen = instances.length;
var instance, i;

for (i=0; i<ilen; ++i) {
instance = instances[i];
if (chartInViewport(instance)) {
unwatch(instance); // eslint-disable-line
instance[MODEL_KEY].appeared = true;
instance.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(instance) {
var canvas = instance.chart.canvas;
var parent = canvas.parentElement;
var stub, instances;

while (parent) {
if (isScrollable(parent)) {
stub = parent[STUB_KEY] || (parent[STUB_KEY] = {});
instances = stub.instances || (stub.instances = []);
if (instances.length === 0) {
parent.addEventListener('scroll', onScroll, {passive: true});
}

instances.push(instance);
instance[MODEL_KEY].elements.push(parent);
}

parent = parent.parentElement || parent.ownerDocument;
}
}

function unwatch(instance) {
instance[MODEL_KEY].elements.forEach(function(element) {
var instances = element[STUB_KEY].instances;
instances.splice(instances.indexOf(instance), 1);
if (!instances.length) {
helpers.removeEvent(element, 'scroll', onScroll);
delete element[STUB_KEY];
}
});

instance[MODEL_KEY].elements = [];
}

Chart.plugins.register({
beforeInit: function(instance) {
var model = instance[MODEL_KEY] = buildDeferredModel(instance);
if (model.enabled) {
watch(instance);
}
},

beforeDatasetsUpdate: function(instance) {
var model = instance[MODEL_KEY];
if (!model.enabled) {
return true;
}

if (!model.loaded) {
if (!model.appeared && !chartInViewport(instance)) {
// cancel the datasets update
return false;
}

model.appeared = true;
model.loaded = true;
unwatch(instance);

if (model.delay > 0) {
model.delayed = true;
Chart.platform.defer(function() {
model.delayed = false;
instance.update();
}, model.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);
}
});

}());

})));
10 changes: 10 additions & 0 deletions dist/chartjs-plugin-deferred.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 2908c52

Please sign in to comment.