Skip to content

Commit

Permalink
Upgrade to Chart.js 2.6 (breaking change)
Browse files Browse the repository at this point in the history
Deprecate `options.deferred.*` in favor of the reserved plugin options: `options.plugins.deferred.*`, and thus `deferred.enabled` has been removed (disabling the plugin per chart can now be done using: `options.plugins.deferred: false`). Global default options are now accessible via `Chart.defaults.global.plugins.deferred`.
  • Loading branch information
simonbrunel committed May 26, 2017
1 parent 89d4c6b commit 7dd5bd9
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 107 deletions.
2 changes: 1 addition & 1 deletion LICENSE.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
**The MIT License (MIT)**
The MIT License (MIT)

Copyright (c) 2017 Simon Brunel

Expand Down
25 changes: 13 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

[Chart.js](http://www.chartjs.org/) plugin to defer initial chart updates until the user scrolls and the canvas appears inside the viewport, and thus trigger the initial chart animations when the user is likely to see them.

Requires [Chart.js](/chartjs/Chart.js/releases) **2.1.5** or later.
Requires [Chart.js](https://github.com/chartjs/Chart.js/releases) **2.6.0** or later.

## Usage

Expand All @@ -16,26 +16,27 @@ To configure this plugin, you can simply add the following entries to your chart

| Name | Type | Default | Description |
| ---- | ---- | ------- | ----------- |
| `deferred` | `Object/Boolean` | `true` | The deferred options (see `deferred.*` options). Also accepts a boolean, in which case if `true`, the chart will be deferred using the default options, else if `false`, the chart will not be deferred.
| `deferred.enabled` | `Boolean` | `true` | `true` to enable this plugin, else `false` to disable it for the associated chart.
| `deferred.xOffset` | `Number/String` | `0` | Number of pixels (or percent of the canvas width) from which the chart is considered inside the viewport.
| `deferred.yOffset` | `Number/String` | `0` | Number of pixels (or percent of the canvas height) from which the chart is considered inside the viewport.
| `deferred.delay` | `Number` | `0` | Number of milliseconds to delay the loading after the chart is considered inside the viewport.
| `plugins.deferred` | `Object/Boolean` | `true` | The deferred options (see `plugins.deferred.*` options). Also accepts a boolean, in which case if `true`, the chart will be deferred using the **global options**, else if `false`, the chart will not be deferred.
| `plugins.deferred.xOffset` | `Number/String` | `0` | Number of pixels (or percent of the canvas width) from which the chart is considered inside the viewport.
| `plugins.deferred.yOffset` | `Number/String` | `0` | Number of pixels (or percent of the canvas height) from which the chart is considered inside the viewport.
| `plugins.deferred.delay` | `Number` | `0` | Number of milliseconds to delay the loading after the chart is considered inside the viewport.

> **Global options** can be change through `Chart.defaults.global.plugins.deferred`, which by default defer the chart loading until the first line of pixels of the canvas appears in the viewport.
For example:

```
{
deferred: { // enabled by default
xOffset: 150, // defer until 150px of the canvas width are inside the viewport
yOffset: '50%', // defer until 50% of the canvas height are inside the viewport
delay: 500 // delay of 500 ms after the canvas is considered inside the viewport
plugins: {
deferred: { // enabled by default
xOffset: 150, // defer until 150px of the canvas width are inside the viewport
yOffset: '50%', // defer until 50% of the canvas height are inside the viewport
delay: 500 // delay of 500 ms after the canvas is considered inside the viewport
}
}
}
```

Note that default options will defer the chart loading until the first line of pixels of the canvas appears in the viewport.

## Development

You first need to install node dependencies (requires [Node.js](https://nodejs.org/)):
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@
"yargs": "^4.7.1"
},
"peerDependencies": {
"chart.js": ">= 2.5.0 < 3"
"chart.js": ">= 2.6.0 < 3"
}
}
7 changes: 4 additions & 3 deletions samples/deferred-horizontal.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<html>
<head>
<title>Horizontal Scrolling Example | chartjs-plugin-deferred</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.1.5/Chart.bundle.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.6.0/Chart.bundle.min.js"></script>
<script src="../dist/chartjs-plugin-deferred.min.js"></script>
<script src="utils.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/latest/css/font-awesome.min.css">
Expand Down Expand Up @@ -61,17 +61,18 @@ <h2><b>Horizontal</b> Scrolling Example</h2>
];

window.onload = function() {
var options = Samples.utils.defaultBarOptions();
var data = Samples.utils.generateBarData();
for (i = 0, ilen = configs.length; i<ilen; ++i) {
var text = document.getElementById("config_" + i);
var canvas = document.getElementById("canvas_" + i);
var options = Samples.utils.defaultBarOptions();

text.textContent = 'options: ' + JSON.stringify(configs[i], null, ' ');
options.plugins = configs[i];

new Chart(canvas.getContext("2d"), {
type: [ 'bar' ][i%1],
options: Chart.helpers.extend(options, configs[i]),
options: options,
data: data
});
}
Expand Down
7 changes: 4 additions & 3 deletions samples/deferred-vertical.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<html>
<head>
<title>Vertical Scrolling Example | chartjs-plugin-deferred</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.1.5/Chart.bundle.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.6.0/Chart.bundle.min.js"></script>
<script src="../dist/chartjs-plugin-deferred.min.js"></script>
<script src="utils.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/latest/css/font-awesome.min.css">
Expand Down Expand Up @@ -57,17 +57,18 @@ <h2><b>Vertical</b> Scrolling Example</h2>
];

window.onload = function() {
var options = Samples.utils.defaultBarOptions();
var data = Samples.utils.generateBarData();
for (i = 0, ilen = configs.length; i<ilen; ++i) {
var text = document.getElementById("config_" + i);
var canvas = document.getElementById("canvas_" + i);
var options = Samples.utils.defaultBarOptions();

text.textContent = 'options: ' + JSON.stringify(configs[i], null, ' ');
options.plugins = configs[i];

new Chart(canvas.getContext("2d"), {
type: [ 'bar' ][i%1],
options: Chart.helpers.extend(options, configs[i]),
options: options,
data: data
});
}
Expand Down
143 changes: 56 additions & 87 deletions src/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,26 @@ import Chart from 'chart.js';

(function() {
var helpers = Chart.helpers;
var STUB_KEY = '_chartjs_deferred';
var MODEL_KEY = '_deferred_model';
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
*/
Chart.Deferred = Chart.Deferred || {};
Chart.Deferred.defaults = {
enabled: true,
Chart.defaults.global.plugins.deferred = {
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 defer(fn, delay) {
if (delay) {
window.setTimeout(fn, delay);
} else {
helpers.requestAnimFrame.call(window, fn);
}
});
}

function computeOffset(value, base) {
var number = parseInt(value, 10);
Expand All @@ -44,49 +35,25 @@ import Chart from 'chart.js';
return number;
}

function chartInViewport(instance) {
var model = instance[MODEL_KEY];
var canvas = instance.chart.canvas;
function chartInViewport(chart) {
var options = chart[MODEL_KEY].options;
var canvas = chart.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);
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 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];
Expand All @@ -95,17 +62,17 @@ import Chart from 'chart.js';
}

stub.ticking = true;
Chart.platform.defer(function() {
var instances = stub.instances.slice();
var ilen = instances.length;
var instance, i;
defer(function() {
var charts = stub.charts.slice();
var ilen = charts.length;
var chart, 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();
chart = charts[i];
if (chartInViewport(chart)) {
unwatch(chart); // eslint-disable-line
chart[MODEL_KEY].appeared = true;
chart.update();
}
}

Expand All @@ -125,70 +92,73 @@ import Chart from 'chart.js';
return node.nodeType === Node.DOCUMENT_NODE;
}

function watch(instance) {
var canvas = instance.chart.canvas;
function watch(chart) {
var canvas = chart.chart.canvas;
var parent = canvas.parentElement;
var stub, instances;
var stub, charts;

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

instances.push(instance);
instance[MODEL_KEY].elements.push(parent);
charts.push(chart);
chart[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) {
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) {
helpers.removeEvent(element, 'scroll', onScroll);
delete element[STUB_KEY];
}
});

instance[MODEL_KEY].elements = [];
chart[MODEL_KEY].elements = [];
}

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

beforeInit: function(chart, options) {
chart[MODEL_KEY] = {
options: options,
appeared: false,
delayed: false,
loaded: false,
elements: []
};

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

beforeDatasetsUpdate: function(chart, options) {
var model = chart[MODEL_KEY];
if (!model.loaded) {
if (!model.appeared && !chartInViewport(instance)) {
if (!model.appeared && !chartInViewport(chart)) {
// cancel the datasets update
return false;
}

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

if (model.delay > 0) {
if (options.delay > 0) {
model.delayed = true;
Chart.platform.defer(function() {
defer(function() {
model.delayed = false;
instance.update();
}, model.delay);
chart.update();
}, options.delay);

return false;
}
Expand All @@ -205,5 +175,4 @@ import Chart from 'chart.js';
unwatch(chart);
}
});

}());

0 comments on commit 7dd5bd9

Please sign in to comment.