mirror of
https://github.com/chartjs/Chart.js.git
synced 2025-12-08 20:36:08 +00:00
Allow switching platforms (#6964)
Allow switching platforms Move the Chart.platform to Chart.platform.current instead, and add ways to see available platforms and set the current platform. This is necessary for adding tests that use the "basic" platform.
This commit is contained in:
parent
08110af6f1
commit
1ad5f369af
@ -63,6 +63,7 @@ Chart.js 3.0 introduces a number of breaking changes. Chart.js 2.0 was released
|
||||
* `scales.[x/y]Axes.time.max` was renamed to `scales[id].max`
|
||||
* `scales.[x/y]Axes.time.min` was renamed to `scales[id].min`
|
||||
* The dataset option `tension` was renamed to `lineTension`
|
||||
* To override the platform class used in a chart instance, pass `platform: PlatformClass` in the config object. Note that the class should be passed, not an instance of the class.
|
||||
|
||||
### Animations
|
||||
|
||||
@ -162,6 +163,7 @@ Animation system was completely rewritten in Chart.js v3. Each property can now
|
||||
|
||||
* `helpers._alignPixel` was renamed to `helpers.canvas._alignPixel`
|
||||
* `helpers._decimalPlaces` was renamed to `helpers.math._decimalPlaces`
|
||||
* `chart.initialize` was renamed to `chart._initialize` (labeled as private but not named as such)
|
||||
|
||||
### Changed
|
||||
|
||||
@ -207,3 +209,8 @@ Animation system was completely rewritten in Chart.js v3. Each property can now
|
||||
|
||||
* The second parameter to `drawPoint` is now the full options object, so `style`, `rotation`, and `radius` are no longer passed explicitly
|
||||
|
||||
#### Platform
|
||||
|
||||
* `Chart.platform` is no longer the platform object used by charts. It contains only a single configuration option, `disableCSSInjection`. Every chart instance now has a separate platform instance.
|
||||
* `Chart.platforms` is an object that contains two usable platform classes, `BasicPlatform` and `DomPlatform`. It also contains `BasePlatform`, a class that all platforms must extend from.
|
||||
* If the canvas passed in is an instance of `OffscreenCanvas`, the `BasicPlatform` is automatically used.
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
var utils = Samples.utils;
|
||||
|
||||
utils.srand(110);
|
||||
// CSP: disable automatic style injection
|
||||
Chart.platform.disableCSSInjection = true;
|
||||
|
||||
utils.srand(110);
|
||||
|
||||
function generateData() {
|
||||
var DATA_COUNT = 16;
|
||||
var MIN_XY = -150;
|
||||
|
||||
@ -6,7 +6,7 @@ import defaults from './core.defaults';
|
||||
import helpers from '../helpers/index';
|
||||
import Interaction from './core.interaction';
|
||||
import layouts from './core.layouts';
|
||||
import platform from '../platforms/platform';
|
||||
import {BasicPlatform, DomPlatform} from '../platform/platforms';
|
||||
import plugins from './core.plugins';
|
||||
import scaleService from '../core/core.scaleService';
|
||||
|
||||
@ -145,13 +145,38 @@ function onAnimationProgress(ctx) {
|
||||
helpers.callback(animationOptions && animationOptions.onProgress, arguments, chart);
|
||||
}
|
||||
|
||||
function isDomSupported() {
|
||||
return typeof window !== undefined && typeof document !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Chart.js can take a string id of a canvas element, a 2d context, or a canvas element itself.
|
||||
* Attempt to unwrap the item passed into the chart constructor so that it is a canvas element (if possible).
|
||||
*/
|
||||
function getCanvas(item) {
|
||||
if (isDomSupported() && typeof item === 'string') {
|
||||
item = document.getElementById(item);
|
||||
} else if (item.length) {
|
||||
// Support for array based queries (such as jQuery)
|
||||
item = item[0];
|
||||
}
|
||||
|
||||
if (item && item.canvas) {
|
||||
// Support for any object associated to a canvas (including a context2d)
|
||||
item = item.canvas;
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
class Chart {
|
||||
constructor(item, config) {
|
||||
const me = this;
|
||||
|
||||
config = initConfig(config);
|
||||
const initialCanvas = getCanvas(item);
|
||||
me._initializePlatform(initialCanvas, config);
|
||||
|
||||
const context = platform.acquireContext(item, config);
|
||||
const context = me.platform.acquireContext(initialCanvas, config);
|
||||
const canvas = context && context.canvas;
|
||||
const height = canvas && canvas.height;
|
||||
const width = canvas && canvas.width;
|
||||
@ -193,14 +218,14 @@ class Chart {
|
||||
Animator.listen(me, 'complete', onAnimationsComplete);
|
||||
Animator.listen(me, 'progress', onAnimationProgress);
|
||||
|
||||
me.initialize();
|
||||
me._initialize();
|
||||
me.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
initialize() {
|
||||
_initialize() {
|
||||
const me = this;
|
||||
|
||||
// Before init plugin notification
|
||||
@ -221,6 +246,23 @@ class Chart {
|
||||
return me;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_initializePlatform(canvas, config) {
|
||||
const me = this;
|
||||
|
||||
if (config.platform) {
|
||||
me.platform = new config.platform();
|
||||
} else if (!isDomSupported()) {
|
||||
me.platform = new BasicPlatform();
|
||||
} else if (window.OffscreenCanvas && canvas instanceof window.OffscreenCanvas) {
|
||||
me.platform = new BasicPlatform();
|
||||
} else {
|
||||
me.platform = new DomPlatform();
|
||||
}
|
||||
}
|
||||
|
||||
clear() {
|
||||
helpers.canvas.clear(this);
|
||||
return this;
|
||||
@ -244,7 +286,7 @@ class Chart {
|
||||
// Set to 0 instead of canvas.size because the size defaults to 300x150 if the element is collapsed
|
||||
const newWidth = Math.max(0, Math.floor(helpers.dom.getMaximumWidth(canvas)));
|
||||
const newHeight = Math.max(0, Math.floor(aspectRatio ? newWidth / aspectRatio : helpers.dom.getMaximumHeight(canvas)));
|
||||
const newRatio = options.devicePixelRatio || platform.getDevicePixelRatio();
|
||||
const newRatio = options.devicePixelRatio || me.platform.getDevicePixelRatio();
|
||||
|
||||
if (me.width === newWidth && me.height === newHeight && oldRatio === newRatio) {
|
||||
return;
|
||||
@ -824,7 +866,7 @@ class Chart {
|
||||
if (canvas) {
|
||||
me.unbindEvents();
|
||||
helpers.canvas.clear(me);
|
||||
platform.releaseContext(me.ctx);
|
||||
me.platform.releaseContext(me.ctx);
|
||||
me.canvas = null;
|
||||
me.ctx = null;
|
||||
}
|
||||
@ -849,7 +891,7 @@ class Chart {
|
||||
};
|
||||
|
||||
helpers.each(me.options.events, function(type) {
|
||||
platform.addEventListener(me, type, listener);
|
||||
me.platform.addEventListener(me, type, listener);
|
||||
listeners[type] = listener;
|
||||
});
|
||||
|
||||
@ -860,7 +902,7 @@ class Chart {
|
||||
me.resize();
|
||||
};
|
||||
|
||||
platform.addEventListener(me, 'resize', listener);
|
||||
me.platform.addEventListener(me, 'resize', listener);
|
||||
listeners.resize = listener;
|
||||
}
|
||||
}
|
||||
@ -877,7 +919,7 @@ class Chart {
|
||||
|
||||
delete me._listeners;
|
||||
helpers.each(listeners, function(listener, type) {
|
||||
platform.removeEventListener(me, type, listener);
|
||||
me.platform.removeEventListener(me, type, listener);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -15,7 +15,8 @@ import Element from './core/core.element';
|
||||
import elements from './elements';
|
||||
import Interaction from './core/core.interaction';
|
||||
import layouts from './core/core.layouts';
|
||||
import platform from './platforms/platform';
|
||||
import platforms from './platform/platforms';
|
||||
import platform from './platform/platform';
|
||||
import pluginsCore from './core/core.plugins';
|
||||
import Scale from './core/core.scale';
|
||||
import scaleService from './core/core.scaleService';
|
||||
@ -33,6 +34,7 @@ Chart.Element = Element;
|
||||
Chart.elements = elements;
|
||||
Chart.Interaction = Interaction;
|
||||
Chart.layouts = layouts;
|
||||
Chart.platforms = platforms;
|
||||
Chart.platform = platform;
|
||||
Chart.plugins = pluginsCore;
|
||||
Chart.Scale = Scale;
|
||||
@ -57,8 +59,6 @@ for (var k in plugins) {
|
||||
}
|
||||
}
|
||||
|
||||
Chart.platform.initialize();
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window.Chart = Chart;
|
||||
}
|
||||
|
||||
@ -1,31 +1,22 @@
|
||||
'use strict';
|
||||
|
||||
import helpers from '../helpers/index';
|
||||
import basic from './platform.basic';
|
||||
import dom from './platform.dom';
|
||||
|
||||
// @TODO Make possible to select another platform at build time.
|
||||
const implementation = dom._enabled ? dom : basic;
|
||||
|
||||
/**
|
||||
* @namespace Chart.platform
|
||||
* @see https://chartjs.gitbooks.io/proposals/content/Platform.html
|
||||
* @since 2.4.0
|
||||
* Abstract class that allows abstracting platform dependencies away from the chart.
|
||||
*/
|
||||
export default helpers.extend({
|
||||
export default class BasePlatform {
|
||||
/**
|
||||
* @since 2.7.0
|
||||
* @constructor
|
||||
*/
|
||||
initialize: function() {},
|
||||
constructor() {}
|
||||
|
||||
/**
|
||||
* Called at chart construction time, returns a context2d instance implementing
|
||||
* the [W3C Canvas 2D Context API standard]{@link https://www.w3.org/TR/2dcontext/}.
|
||||
* @param {*} item - The native item from which to acquire context (platform specific)
|
||||
* @param {canvas} canvas - The canvas from which to acquire context (platform specific)
|
||||
* @param {object} options - The chart options
|
||||
* @returns {CanvasRenderingContext2D} context2d instance
|
||||
*/
|
||||
acquireContext: function() {},
|
||||
acquireContext() {}
|
||||
|
||||
/**
|
||||
* Called at chart destruction time, releases any resources associated to the context
|
||||
@ -33,7 +24,7 @@ export default helpers.extend({
|
||||
* @param {CanvasRenderingContext2D} context - The context2d instance
|
||||
* @returns {boolean} true if the method succeeded, else false
|
||||
*/
|
||||
releaseContext: function() {},
|
||||
releaseContext() {}
|
||||
|
||||
/**
|
||||
* Registers the specified listener on the given chart.
|
||||
@ -42,7 +33,7 @@ export default helpers.extend({
|
||||
* @param {function} listener - Receives a notification (an object that implements
|
||||
* the {@link IEvent} interface) when an event of the specified type occurs.
|
||||
*/
|
||||
addEventListener: function() {},
|
||||
addEventListener() {}
|
||||
|
||||
/**
|
||||
* Removes the specified listener previously registered with addEventListener.
|
||||
@ -50,25 +41,15 @@ export default helpers.extend({
|
||||
* @param {string} type - The ({@link IEvent}) type to remove
|
||||
* @param {function} listener - The listener function to remove from the event target.
|
||||
*/
|
||||
removeEventListener: function() {},
|
||||
removeEventListener() {}
|
||||
|
||||
/**
|
||||
* Returs current devicePixelRatio of the device this platform is connected to.
|
||||
* @returns {number} the current devicePixelRatio of the device this platform is connected to.
|
||||
*/
|
||||
getDevicePixelRatio: function() {
|
||||
getDevicePixelRatio() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
}, implementation);
|
||||
|
||||
/**
|
||||
* @interface IPlatform
|
||||
* Allows abstracting platform dependencies away from the chart
|
||||
* @borrows Chart.platform.acquireContext as acquireContext
|
||||
* @borrows Chart.platform.releaseContext as releaseContext
|
||||
* @borrows Chart.platform.addEventListener as addEventListener
|
||||
* @borrows Chart.platform.removeEventListener as removeEventListener
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* @interface IEvent
|
||||
22
src/platform/platform.basic.js
Normal file
22
src/platform/platform.basic.js
Normal file
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Platform fallback implementation (minimal).
|
||||
* @see https://github.com/chartjs/Chart.js/pull/4591#issuecomment-319575939
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import BasePlatform from './platform.base';
|
||||
|
||||
/**
|
||||
* Platform class for charts without access to the DOM or to many element properties
|
||||
* This platform is used by default for any chart passed an OffscreenCanvas.
|
||||
* @extends BasePlatform
|
||||
*/
|
||||
export default class BasicPlatform extends BasePlatform {
|
||||
acquireContext(item) {
|
||||
// To prevent canvas fingerprinting, some add-ons undefine the getContext
|
||||
// method, for example: https://github.com/kkapsner/CanvasBlocker
|
||||
// https://github.com/chartjs/Chart.js/issues/2807
|
||||
return item && item.getContext && item.getContext('2d') || null;
|
||||
}
|
||||
}
|
||||
@ -6,6 +6,8 @@
|
||||
|
||||
import helpers from '../helpers';
|
||||
import stylesheet from './platform.dom.css';
|
||||
import BasePlatform from './platform.base';
|
||||
import platform from './platform';
|
||||
|
||||
var EXPANDO_KEY = '$chartjs';
|
||||
var CSS_PREFIX = 'chartjs-';
|
||||
@ -312,29 +314,33 @@ function injectCSS(rootNode, css) {
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
/**
|
||||
* Platform class for charts that can access the DOM and global window/document properties
|
||||
* @extends BasePlatform
|
||||
*/
|
||||
export default class DomPlatform extends BasePlatform {
|
||||
/**
|
||||
* When `true`, prevents the automatic injection of the stylesheet required to
|
||||
* correctly detect when the chart is added to the DOM and then resized. This
|
||||
* switch has been added to allow external stylesheet (`dist/Chart(.min)?.js`)
|
||||
* to be manually imported to make this library compatible with any CSP.
|
||||
* See https://github.com/chartjs/Chart.js/issues/5208
|
||||
* @constructor
|
||||
*/
|
||||
disableCSSInjection: false,
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
/**
|
||||
* This property holds whether this platform is enabled for the current environment.
|
||||
* Currently used by platform.js to select the proper implementation.
|
||||
* @private
|
||||
*/
|
||||
_enabled: typeof window !== 'undefined' && typeof document !== 'undefined',
|
||||
/**
|
||||
* When `true`, prevents the automatic injection of the stylesheet required to
|
||||
* correctly detect when the chart is added to the DOM and then resized. This
|
||||
* switch has been added to allow external stylesheet (`dist/Chart(.min)?.js`)
|
||||
* to be manually imported to make this library compatible with any CSP.
|
||||
* See https://github.com/chartjs/Chart.js/issues/5208
|
||||
*/
|
||||
this.disableCSSInjection = platform.disableCSSInjection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes resources that depend on platform options.
|
||||
* @param {HTMLCanvasElement} canvas - The Canvas element.
|
||||
* @private
|
||||
*/
|
||||
_ensureLoaded: function(canvas) {
|
||||
_ensureLoaded(canvas) {
|
||||
if (!this.disableCSSInjection) {
|
||||
// If the canvas is in a shadow DOM, then the styles must also be inserted
|
||||
// into the same shadow DOM.
|
||||
@ -343,45 +349,33 @@ export default {
|
||||
var targetNode = root.host ? root : document.head;
|
||||
injectCSS(targetNode, stylesheet);
|
||||
}
|
||||
},
|
||||
|
||||
acquireContext: function(item, config) {
|
||||
if (typeof item === 'string') {
|
||||
item = document.getElementById(item);
|
||||
} else if (item.length) {
|
||||
// Support for array based queries (such as jQuery)
|
||||
item = item[0];
|
||||
}
|
||||
|
||||
if (item && item.canvas) {
|
||||
// Support for any object associated to a canvas (including a context2d)
|
||||
item = item.canvas;
|
||||
}
|
||||
}
|
||||
|
||||
acquireContext(canvas, config) {
|
||||
// To prevent canvas fingerprinting, some add-ons undefine the getContext
|
||||
// method, for example: https://github.com/kkapsner/CanvasBlocker
|
||||
// https://github.com/chartjs/Chart.js/issues/2807
|
||||
var context = item && item.getContext && item.getContext('2d');
|
||||
var context = canvas && canvas.getContext && canvas.getContext('2d');
|
||||
|
||||
// `instanceof HTMLCanvasElement/CanvasRenderingContext2D` fails when the item is
|
||||
// `instanceof HTMLCanvasElement/CanvasRenderingContext2D` fails when the canvas is
|
||||
// inside an iframe or when running in a protected environment. We could guess the
|
||||
// types from their toString() value but let's keep things flexible and assume it's
|
||||
// a sufficient condition if the item has a context2D which has item as `canvas`.
|
||||
// a sufficient condition if the canvas has a context2D which has canvas as `canvas`.
|
||||
// https://github.com/chartjs/Chart.js/issues/3887
|
||||
// https://github.com/chartjs/Chart.js/issues/4102
|
||||
// https://github.com/chartjs/Chart.js/issues/4152
|
||||
if (context && context.canvas === item) {
|
||||
if (context && context.canvas === canvas) {
|
||||
// Load platform resources on first chart creation, to make it possible to
|
||||
// import the library before setting platform options.
|
||||
this._ensureLoaded(item);
|
||||
initCanvas(item, config);
|
||||
this._ensureLoaded(canvas);
|
||||
initCanvas(canvas, config);
|
||||
return context;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
}
|
||||
|
||||
releaseContext: function(context) {
|
||||
releaseContext(context) {
|
||||
const canvas = context.canvas;
|
||||
if (!canvas[EXPANDO_KEY]) {
|
||||
return;
|
||||
@ -410,9 +404,9 @@ export default {
|
||||
canvas.width = canvas.width;
|
||||
|
||||
delete canvas[EXPANDO_KEY];
|
||||
},
|
||||
}
|
||||
|
||||
addEventListener: function(chart, type, listener) {
|
||||
addEventListener(chart, type, listener) {
|
||||
var canvas = chart.canvas;
|
||||
if (type === 'resize') {
|
||||
// Note: the resize event is not supported on all browsers.
|
||||
@ -427,9 +421,9 @@ export default {
|
||||
}, chart);
|
||||
|
||||
addListener(canvas, type, proxy);
|
||||
},
|
||||
}
|
||||
|
||||
removeEventListener: function(chart, type, listener) {
|
||||
removeEventListener(chart, type, listener) {
|
||||
var canvas = chart.canvas;
|
||||
if (type === 'resize') {
|
||||
// Note: the resize event is not supported on all browsers.
|
||||
@ -445,9 +439,9 @@ export default {
|
||||
}
|
||||
|
||||
removeListener(canvas, type, proxy);
|
||||
},
|
||||
}
|
||||
|
||||
getDevicePixelRatio: function() {
|
||||
getDevicePixelRatio() {
|
||||
return window.devicePixelRatio;
|
||||
}
|
||||
};
|
||||
}
|
||||
3
src/platform/platform.js
Normal file
3
src/platform/platform.js
Normal file
@ -0,0 +1,3 @@
|
||||
'use strict';
|
||||
|
||||
export default {disableCSSInjection: false};
|
||||
13
src/platform/platforms.js
Normal file
13
src/platform/platforms.js
Normal file
@ -0,0 +1,13 @@
|
||||
'use strict';
|
||||
|
||||
import BasePlatform from './platform.base';
|
||||
import BasicPlatform from './platform.basic';
|
||||
import DomPlatform from './platform.dom';
|
||||
|
||||
export {BasicPlatform, DomPlatform, BasePlatform};
|
||||
|
||||
/**
|
||||
* @namespace Chart.platforms
|
||||
* @see https://chartjs.gitbooks.io/proposals/content/Platform.html
|
||||
*/
|
||||
export default {BasicPlatform, DomPlatform, BasePlatform};
|
||||
@ -1,15 +0,0 @@
|
||||
/**
|
||||
* Platform fallback implementation (minimal).
|
||||
* @see https://github.com/chartjs/Chart.js/pull/4591#issuecomment-319575939
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
acquireContext: function(item) {
|
||||
if (item && item.canvas) {
|
||||
// Support for any object associated to a canvas (including a context2d)
|
||||
item = item.canvas;
|
||||
}
|
||||
|
||||
return item && item.getContext('2d') || null;
|
||||
}
|
||||
};
|
||||
@ -12,7 +12,7 @@ describe('Chart namespace', function() {
|
||||
expect(Chart.Interaction instanceof Object).toBeTruthy();
|
||||
expect(Chart.layouts instanceof Object).toBeTruthy();
|
||||
expect(Chart.plugins instanceof Object).toBeTruthy();
|
||||
expect(Chart.platform instanceof Object).toBeTruthy();
|
||||
expect(Chart.platforms instanceof Object).toBeTruthy();
|
||||
expect(Chart.Scale instanceof Object).toBeTruthy();
|
||||
expect(Chart.scaleService instanceof Object).toBeTruthy();
|
||||
expect(Chart.Ticks instanceof Object).toBeTruthy();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user