Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow switching platforms #6964

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/getting-started/v3-migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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.
3 changes: 1 addition & 2 deletions samples/advanced/content-security-policy.js
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
60 changes: 51 additions & 9 deletions src/core/core.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
});

Expand All @@ -860,7 +902,7 @@ class Chart {
me.resize();
};

platform.addEventListener(me, 'resize', listener);
me.platform.addEventListener(me, 'resize', listener);
listeners.resize = listener;
}
}
Expand All @@ -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);
});
}

Expand Down
6 changes: 3 additions & 3 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
Expand All @@ -57,8 +59,6 @@ for (var k in plugins) {
}
}

Chart.platform.initialize();

if (typeof window !== 'undefined') {
window.Chart = Chart;
}
Expand Down
43 changes: 12 additions & 31 deletions src/platforms/platform.js → src/platform/platform.base.js
Original file line number Diff line number Diff line change
@@ -1,39 +1,30 @@
'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
* previously returned by the acquireContext() method.
* @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.
Expand All @@ -42,33 +33,23 @@ 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.
* @param {Chart} chart - Chart from which to remove the listener
* @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
Expand Down
22 changes: 22 additions & 0 deletions src/platform/platform.basic.js
Original file line number Diff line number Diff line change
@@ -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;
}
}
File renamed without changes.
Loading