Generic registry for controllers, scales, elements and plugins (#7435)

* Generic registry for controllers, scales, elements and plugins
* Remove references to scale service
This commit is contained in:
Jukka Kurkela 2020-07-07 00:38:04 +03:00 committed by GitHub
parent f544707e2c
commit 6bd5ad5518
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 994 additions and 922 deletions

View File

@ -45,14 +45,12 @@ There are a number of config callbacks that can be used to change parameters in
### Updating Axis Defaults
The default configuration for a scale can be easily changed using the scale service. All you need to do is to pass in a partial configuration that will be merged with the current scale default configuration to form the new default.
The default configuration for a scale can be easily changed. All you need to do is set the new options to `Chart.defaults.scales[type]`.
For example, to set the minimum value of 0 for all linear scales, you would do the following. Any linear scales created after this time would now have a minimum of 0.
```javascript
Chart.scaleService.updateScaleDefaults('linear', {
min: 0
});
Chart.defaults.scales.linear.min = 0;
```
## Creating New Axes

View File

@ -17,7 +17,7 @@ MyScale.defaults = defaultConfigObject;
Once you have created your scale class, you need to register it with the global chart object so that it can be used. A default config for the scale may be provided when registering the constructor. The first parameter to the register function is a string key that is used later to identify which scale type to use for a chart.
```javascript
Chart.scaleService.registerScale(MyScale);
Chart.register(MyScale);
```
To use the new scale, simply pass in the string key to the config when creating a chart.

View File

@ -49,6 +49,7 @@ Dataset controllers must implement the following interface.
```
The following methods may optionally be overridden by derived dataset controllers.
```javascript
{
// Initializes the controller
@ -79,12 +80,6 @@ The built in controller types are:
For example, to derive a new chart type that extends from a bubble chart, you would do the following.
```javascript
// Sets the default config for 'derivedBubble' to be the same as the bubble defaults.
// We look for the defaults by doing Chart.defaults[chartType]
// It looks like a bug exists when the defaults don't exist
Chart.defaults.derivedBubble = Chart.defaults.bubble;
// I think the recommend using Chart.controllers.bubble.extend({ extensions here });
class Custom extends Chart.controllers.bubble {
draw() {
// Call super method first
@ -103,10 +98,11 @@ class Custom extends Chart.controllers.bubble {
ctx.restore();
}
});
Custom.id = 'derivedBubble';
Custom.defaults = Chart.defaults.bubble;
// Stores the controller so that the chart initialization routine can look it up with
// Chart.controllers[type]
Chart.controllers.derivedBubble = Custom;
// Stores the controller so that the chart initialization routine can look it up
Chart.register(Custom);
// Now we can create and use our new chart type
new Chart(ctx, {

View File

@ -1,41 +1,8 @@
import DatasetController from '../core/core.datasetController';
import defaults from '../core/core.defaults';
import {Rectangle} from '../elements/index';
import {clipArea, unclipArea} from '../helpers/helpers.canvas';
import {isArray, isNullOrUndef, valueOrDefault, resolveObjectKey} from '../helpers/helpers.core';
import {_limitValue, sign} from '../helpers/helpers.math';
defaults.set('bar', {
hover: {
mode: 'index'
},
datasets: {
categoryPercentage: 0.8,
barPercentage: 0.9,
animation: {
numbers: {
type: 'number',
properties: ['x', 'y', 'base', 'width', 'height']
}
}
},
scales: {
_index_: {
type: 'category',
offset: true,
gridLines: {
offsetGridLines: true
}
},
_value_: {
type: 'linear',
beginAtZero: true,
}
}
});
/**
* Computes the "optimal" sample size to maintain bars equally sized while preventing overlap.
* @private
@ -507,16 +474,51 @@ export default class BarController extends DatasetController {
}
BarController.prototype.dataElementType = Rectangle;
BarController.id = 'bar';
BarController.prototype.dataElementOptions = [
'backgroundColor',
'borderColor',
'borderSkipped',
'borderWidth',
'barPercentage',
'barThickness',
'categoryPercentage',
'maxBarThickness',
'minBarLength'
];
/**
* @type {any}
*/
BarController.defaults = {
datasetElementType: false,
dataElementType: 'rectangle',
dataElementOptions: [
'backgroundColor',
'borderColor',
'borderSkipped',
'borderWidth',
'barPercentage',
'barThickness',
'categoryPercentage',
'maxBarThickness',
'minBarLength'
],
hover: {
mode: 'index'
},
datasets: {
categoryPercentage: 0.8,
barPercentage: 0.9,
animation: {
numbers: {
type: 'number',
properties: ['x', 'y', 'base', 'width', 'height']
}
}
},
scales: {
_index_: {
type: 'category',
offset: true,
gridLines: {
offsetGridLines: true
}
},
_value_: {
type: 'linear',
beginAtZero: true,
}
}
};

View File

@ -1,34 +1,7 @@
import DatasetController from '../core/core.datasetController';
import defaults from '../core/core.defaults';
import {Point} from '../elements/index';
import {resolve} from '../helpers/helpers.options';
import {resolveObjectKey} from '../helpers/helpers.core';
defaults.set('bubble', {
animation: {
numbers: {
properties: ['x', 'y', 'borderWidth', 'radius']
}
},
scales: {
x: {
type: 'linear'
},
y: {
type: 'linear'
}
},
tooltips: {
callbacks: {
title() {
// Title doesn't make sense for scatter since we format the data as a point
return '';
}
}
}
});
export default class BubbleController extends DatasetController {
/**
@ -165,14 +138,42 @@ export default class BubbleController extends DatasetController {
}
}
BubbleController.prototype.dataElementType = Point;
BubbleController.id = 'bubble';
BubbleController.prototype.dataElementOptions = [
'backgroundColor',
'borderColor',
'borderWidth',
'hitRadius',
'radius',
'pointStyle',
'rotation'
];
/**
* @type {any}
*/
BubbleController.defaults = {
datasetElementType: false,
dataElementType: 'point',
dataElementOptions: [
'backgroundColor',
'borderColor',
'borderWidth',
'hitRadius',
'radius',
'pointStyle',
'rotation'
],
animation: {
numbers: {
properties: ['x', 'y', 'borderWidth', 'radius']
}
},
scales: {
x: {
type: 'linear'
},
y: {
type: 'linear'
}
},
tooltips: {
callbacks: {
title() {
// Title doesn't make sense for scatter since we format the data as a point
return '';
}
}
}
};

View File

@ -1,6 +1,4 @@
import DatasetController from '../core/core.datasetController';
import defaults from '../core/core.defaults';
import {Arc} from '../elements/index';
import {isArray, valueOrDefault} from '../helpers/helpers.core';
/**
@ -11,83 +9,6 @@ const PI = Math.PI;
const DOUBLE_PI = PI * 2;
const HALF_PI = PI / 2;
defaults.set('doughnut', {
animation: {
numbers: {
type: 'number',
properties: ['circumference', 'endAngle', 'innerRadius', 'outerRadius', 'startAngle', 'x', 'y']
},
// Boolean - Whether we animate the rotation of the Doughnut
animateRotate: true,
// Boolean - Whether we animate scaling the Doughnut from the centre
animateScale: false
},
aspectRatio: 1,
legend: {
labels: {
generateLabels(chart) {
const data = chart.data;
if (data.labels.length && data.datasets.length) {
return data.labels.map((label, i) => {
const meta = chart.getDatasetMeta(0);
const style = meta.controller.getStyle(i);
return {
text: label,
fillStyle: style.backgroundColor,
strokeStyle: style.borderColor,
lineWidth: style.borderWidth,
hidden: !chart.getDataVisibility(i),
// Extra data used for toggling the correct item
index: i
};
});
}
return [];
}
},
onClick(e, legendItem, legend) {
legend.chart.toggleDataVisibility(legendItem.index);
legend.chart.update();
}
},
// The percentage of the chart that we cut out of the middle.
cutoutPercentage: 50,
// The rotation of the chart, where the first data arc begins.
rotation: -HALF_PI,
// The total circumference of the chart.
circumference: DOUBLE_PI,
// Need to override these to give a nice default
tooltips: {
callbacks: {
title() {
return '';
},
label(tooltipItem, data) {
let dataLabel = data.labels[tooltipItem.index];
const value = ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
if (isArray(dataLabel)) {
// show value on first line of multiline label
// need to clone because we are changing the value
dataLabel = dataLabel.slice();
dataLabel[0] += value;
} else {
dataLabel += value;
}
return dataLabel;
}
}
}
});
function getRatioAndOffset(rotation, circumference, cutout) {
let ratioX = 1;
let ratioY = 1;
@ -330,14 +251,95 @@ export default class DoughnutController extends DatasetController {
}
}
DoughnutController.prototype.dataElementType = Arc;
DoughnutController.id = 'doughnut';
DoughnutController.prototype.dataElementOptions = [
'backgroundColor',
'borderColor',
'borderWidth',
'borderAlign',
'hoverBackgroundColor',
'hoverBorderColor',
'hoverBorderWidth',
];
/**
* @type {any}
*/
DoughnutController.defaults = {
datasetElementType: false,
dataElementType: 'arc',
dataElementOptions: [
'backgroundColor',
'borderColor',
'borderWidth',
'borderAlign',
'hoverBackgroundColor',
'hoverBorderColor',
'hoverBorderWidth',
],
animation: {
numbers: {
type: 'number',
properties: ['circumference', 'endAngle', 'innerRadius', 'outerRadius', 'startAngle', 'x', 'y']
},
// Boolean - Whether we animate the rotation of the Doughnut
animateRotate: true,
// Boolean - Whether we animate scaling the Doughnut from the centre
animateScale: false
},
aspectRatio: 1,
legend: {
labels: {
generateLabels(chart) {
const data = chart.data;
if (data.labels.length && data.datasets.length) {
return data.labels.map((label, i) => {
const meta = chart.getDatasetMeta(0);
const style = meta.controller.getStyle(i);
return {
text: label,
fillStyle: style.backgroundColor,
strokeStyle: style.borderColor,
lineWidth: style.borderWidth,
hidden: !chart.getDataVisibility(i),
// Extra data used for toggling the correct item
index: i
};
});
}
return [];
}
},
onClick(e, legendItem, legend) {
legend.chart.toggleDataVisibility(legendItem.index);
legend.chart.update();
}
},
// The percentage of the chart that we cut out of the middle.
cutoutPercentage: 50,
// The rotation of the chart, where the first data arc begins.
rotation: -HALF_PI,
// The total circumference of the chart.
circumference: DOUBLE_PI,
// Need to override these to give a nice default
tooltips: {
callbacks: {
title() {
return '';
},
label(tooltipItem, data) {
let dataLabel = data.labels[tooltipItem.index];
const value = ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
if (isArray(dataLabel)) {
// show value on first line of multiline label
// need to clone because we are changing the value
dataLabel = dataLabel.slice();
dataLabel[0] += value;
} else {
dataLabel += value;
}
return dataLabel;
}
}
}
};

View File

@ -1,28 +1,8 @@
import DatasetController from '../core/core.datasetController';
import defaults from '../core/core.defaults';
import {Line, Point} from '../elements/index';
import {valueOrDefault} from '../helpers/helpers.core';
import {isNumber} from '../helpers/helpers.math';
import {resolve} from '../helpers/helpers.options';
defaults.set('line', {
showLines: true,
spanGaps: false,
hover: {
mode: 'index'
},
scales: {
_index_: {
type: 'category',
},
_value_: {
type: 'linear',
},
}
});
export default class LineController extends DatasetController {
constructor(chart, datasetIndex) {
@ -158,34 +138,55 @@ export default class LineController extends DatasetController {
}
}
LineController.prototype.datasetElementType = Line;
LineController.id = 'line';
LineController.prototype.dataElementType = Point;
/**
* @type {any}
*/
LineController.defaults = {
datasetElementType: 'line',
datasetElementOptions: [
'backgroundColor',
'borderCapStyle',
'borderColor',
'borderDash',
'borderDashOffset',
'borderJoinStyle',
'borderWidth',
'capBezierPoints',
'cubicInterpolationMode',
'fill'
],
LineController.prototype.datasetElementOptions = [
'backgroundColor',
'borderCapStyle',
'borderColor',
'borderDash',
'borderDashOffset',
'borderJoinStyle',
'borderWidth',
'capBezierPoints',
'cubicInterpolationMode',
'fill'
];
dataElementType: 'point',
dataElementOptions: {
backgroundColor: 'pointBackgroundColor',
borderColor: 'pointBorderColor',
borderWidth: 'pointBorderWidth',
hitRadius: 'pointHitRadius',
hoverHitRadius: 'pointHitRadius',
hoverBackgroundColor: 'pointHoverBackgroundColor',
hoverBorderColor: 'pointHoverBorderColor',
hoverBorderWidth: 'pointHoverBorderWidth',
hoverRadius: 'pointHoverRadius',
pointStyle: 'pointStyle',
radius: 'pointRadius',
rotation: 'pointRotation'
},
LineController.prototype.dataElementOptions = {
backgroundColor: 'pointBackgroundColor',
borderColor: 'pointBorderColor',
borderWidth: 'pointBorderWidth',
hitRadius: 'pointHitRadius',
hoverHitRadius: 'pointHitRadius',
hoverBackgroundColor: 'pointHoverBackgroundColor',
hoverBorderColor: 'pointHoverBorderColor',
hoverBorderWidth: 'pointHoverBorderWidth',
hoverRadius: 'pointHoverRadius',
pointStyle: 'pointStyle',
radius: 'pointRadius',
rotation: 'pointRotation'
showLines: true,
spanGaps: false,
hover: {
mode: 'index'
},
scales: {
_index_: {
type: 'category',
},
_value_: {
type: 'linear',
},
}
};

View File

@ -1,11 +1,15 @@
import DoughnutController from './controller.doughnut';
import defaults from '../core/core.defaults';
import {clone} from '../helpers/helpers.core';
defaults.set('pie', clone(defaults.doughnut));
defaults.set('pie', {
cutoutPercentage: 0
});
// Pie charts are Doughnut chart with different defaults
export default DoughnutController;
export default class PieController extends DoughnutController {
}
PieController.id = 'pie';
/**
* @type {any}
*/
PieController.defaults = {
cutoutPercentage: 0
};

View File

@ -1,83 +1,7 @@
import DatasetController from '../core/core.datasetController';
import defaults from '../core/core.defaults';
import {Arc} from '../elements/index';
import {toRadians} from '../helpers/helpers.math';
import {resolve} from '../helpers/helpers.options';
defaults.set('polarArea', {
animation: {
numbers: {
type: 'number',
properties: ['x', 'y', 'startAngle', 'endAngle', 'innerRadius', 'outerRadius']
},
animateRotate: true,
animateScale: true
},
aspectRatio: 1,
datasets: {
indexAxis: 'r'
},
scales: {
r: {
type: 'radialLinear',
angleLines: {
display: false
},
beginAtZero: true,
gridLines: {
circular: true
},
pointLabels: {
display: false
}
}
},
startAngle: 0,
legend: {
labels: {
generateLabels(chart) {
const data = chart.data;
if (data.labels.length && data.datasets.length) {
return data.labels.map((label, i) => {
const meta = chart.getDatasetMeta(0);
const style = meta.controller.getStyle(i);
return {
text: label,
fillStyle: style.backgroundColor,
strokeStyle: style.borderColor,
lineWidth: style.borderWidth,
hidden: !chart.getDataVisibility(i),
// Extra data used for toggling the correct item
index: i
};
});
}
return [];
}
},
onClick(e, legendItem, legend) {
legend.chart.toggleDataVisibility(legendItem.index);
legend.chart.update();
}
},
// Need to override these to give a nice default
tooltips: {
callbacks: {
title() {
return '';
},
label(item, data) {
return data.labels[item.index] + ': ' + item.value;
}
}
}
});
function getStartAngleRadians(deg) {
// radialLinear scale draws angleLines using startAngle. 0 is expected to be at top.
// Here we adjust to standard unit circle used in drawing, where 0 is at right.
@ -211,14 +135,92 @@ export default class PolarAreaController extends DatasetController {
}
}
PolarAreaController.prototype.dataElementType = Arc;
PolarAreaController.id = 'polarArea';
PolarAreaController.prototype.dataElementOptions = [
'backgroundColor',
'borderColor',
'borderWidth',
'borderAlign',
'hoverBackgroundColor',
'hoverBorderColor',
'hoverBorderWidth'
];
/**
* @type {any}
*/
PolarAreaController.defaults = {
dataElementType: 'arc',
dataElementOptions: [
'backgroundColor',
'borderColor',
'borderWidth',
'borderAlign',
'hoverBackgroundColor',
'hoverBorderColor',
'hoverBorderWidth'
],
animation: {
numbers: {
type: 'number',
properties: ['x', 'y', 'startAngle', 'endAngle', 'innerRadius', 'outerRadius']
},
animateRotate: true,
animateScale: true
},
aspectRatio: 1,
datasets: {
indexAxis: 'r'
},
scales: {
r: {
type: 'radialLinear',
angleLines: {
display: false
},
beginAtZero: true,
gridLines: {
circular: true
},
pointLabels: {
display: false
}
}
},
startAngle: 0,
legend: {
labels: {
generateLabels(chart) {
const data = chart.data;
if (data.labels.length && data.datasets.length) {
return data.labels.map((label, i) => {
const meta = chart.getDatasetMeta(0);
const style = meta.controller.getStyle(i);
return {
text: label,
fillStyle: style.backgroundColor,
strokeStyle: style.borderColor,
lineWidth: style.borderWidth,
hidden: !chart.getDataVisibility(i),
// Extra data used for toggling the correct item
index: i
};
});
}
return [];
}
},
onClick(e, legendItem, legend) {
legend.chart.toggleDataVisibility(legendItem.index);
legend.chart.update();
}
},
// Need to override these to give a nice default
tooltips: {
callbacks: {
title() {
return '';
},
label(item, data) {
return data.labels[item.index] + ': ' + item.value;
}
}
}
};

View File

@ -1,27 +1,6 @@
import DatasetController from '../core/core.datasetController';
import defaults from '../core/core.defaults';
import {Line, Point} from '../elements/index';
import {valueOrDefault} from '../helpers/helpers.core';
defaults.set('radar', {
aspectRatio: 1,
spanGaps: false,
scales: {
r: {
type: 'radialLinear',
}
},
datasets: {
indexAxis: 'r'
},
elements: {
line: {
fill: 'start',
tension: 0 // no bezier in radar
}
}
});
export default class RadarController extends DatasetController {
/**
@ -104,31 +83,53 @@ export default class RadarController extends DatasetController {
}
}
RadarController.prototype.datasetElementType = Line;
RadarController.id = 'radar';
RadarController.prototype.dataElementType = Point;
/**
* @type {any}
*/
RadarController.defaults = {
datasetElementType: 'line',
datasetElementOptions: [
'backgroundColor',
'borderColor',
'borderCapStyle',
'borderDash',
'borderDashOffset',
'borderJoinStyle',
'borderWidth',
'fill'
],
RadarController.prototype.datasetElementOptions = [
'backgroundColor',
'borderColor',
'borderCapStyle',
'borderDash',
'borderDashOffset',
'borderJoinStyle',
'borderWidth',
'fill'
];
dataElementType: 'point',
dataElementOptions: {
backgroundColor: 'pointBackgroundColor',
borderColor: 'pointBorderColor',
borderWidth: 'pointBorderWidth',
hitRadius: 'pointHitRadius',
hoverBackgroundColor: 'pointHoverBackgroundColor',
hoverBorderColor: 'pointHoverBorderColor',
hoverBorderWidth: 'pointHoverBorderWidth',
hoverRadius: 'pointHoverRadius',
pointStyle: 'pointStyle',
radius: 'pointRadius',
rotation: 'pointRotation'
},
RadarController.prototype.dataElementOptions = {
backgroundColor: 'pointBackgroundColor',
borderColor: 'pointBorderColor',
borderWidth: 'pointBorderWidth',
hitRadius: 'pointHitRadius',
hoverBackgroundColor: 'pointHoverBackgroundColor',
hoverBorderColor: 'pointHoverBorderColor',
hoverBorderWidth: 'pointHoverBorderWidth',
hoverRadius: 'pointHoverRadius',
pointStyle: 'pointStyle',
radius: 'pointRadius',
rotation: 'pointRotation'
aspectRatio: 1,
spanGaps: false,
scales: {
r: {
type: 'radialLinear',
}
},
datasets: {
indexAxis: 'r'
},
elements: {
line: {
fill: 'start',
tension: 0 // no bezier in radar
}
}
};

View File

@ -1,7 +1,15 @@
import LineController from './controller.line';
import defaults from '../core/core.defaults';
defaults.set('scatter', {
export default class ScatterController extends LineController {
}
ScatterController.id = 'scatter';
/**
* @type {any}
*/
ScatterController.defaults = {
scales: {
x: {
type: 'linear'
@ -25,7 +33,4 @@ defaults.set('scatter', {
}
}
}
});
// Scatter charts use line controllers
export default LineController;
};

View File

@ -1,8 +1,8 @@
export {default as bar} from './controller.bar';
export {default as bubble} from './controller.bubble';
export {default as doughnut} from './controller.doughnut';
export {default as line} from './controller.line';
export {default as polarArea} from './controller.polarArea';
export {default as pie} from './controller.pie';
export {default as radar} from './controller.radar';
export {default as scatter} from './controller.scatter';
export {default as BarController} from './controller.bar';
export {default as BubbleController} from './controller.bubble';
export {default as DoughnutController} from './controller.doughnut';
export {default as LineController} from './controller.line';
export {default as PolarAreaController} from './controller.polarArea';
export {default as PieController} from './controller.pie';
export {default as RadarController} from './controller.radar';
export {default as ScatterController} from './controller.scatter';

View File

@ -1,12 +1,11 @@
/* eslint-disable import/no-namespace, import/namespace */
import animator from './core.animator';
import * as controllers from '../controllers';
import defaults from './core.defaults';
import Interaction from './core.interaction';
import layouts from './core.layouts';
import {BasicPlatform, DomPlatform} from '../platform';
import plugins from './core.plugins';
import scaleService from './core.scaleService';
import registry from './core.registry';
import {getMaximumWidth, getMaximumHeight, retinaScale} from '../helpers/helpers.dom';
import {mergeIf, merge, _merger, each, callback as callCallback, uid, valueOrDefault, _elementsEqual} from '../helpers/helpers.core';
import {clear as canvasClear, clipArea, unclipArea, _isPointInArea} from '../helpers/helpers.canvas';
@ -80,7 +79,7 @@ function mergeScaleConfig(config, options) {
// apply scale defaults, if not overridden by dataset defaults
Object.keys(scales).forEach(key => {
const scale = scales[key];
mergeIf(scale, scaleService.getScaleDefaults(scale.type));
mergeIf(scale, [defaults.scales[scale.type], defaults.scale]);
});
return scales;
@ -442,10 +441,7 @@ class Chart {
if (id in scales && scales[id].type === scaleType) {
scale = scales[id];
} else {
const scaleClass = scaleService.getScaleConstructor(scaleType);
if (!scaleClass) {
return;
}
const scaleClass = registry.getScale(scaleType);
scale = new scaleClass({
id,
type: scaleType,
@ -473,7 +469,13 @@ class Chart {
me.scales = scales;
scaleService.addScalesToLayout(this);
each(scales, (scale) => {
// Set ILayoutItem parameters for backwards compatibility
scale.fullWidth = scale.options.fullWidth;
scale.position = scale.options.position;
scale.weight = scale.options.weight;
layouts.addBox(me, scale);
});
}
/**
@ -537,11 +539,14 @@ class Chart {
meta.controller.updateIndex(i);
meta.controller.linkScales();
} else {
const ControllerClass = controllers[meta.type];
if (ControllerClass === undefined) {
throw new Error('"' + meta.type + '" is not a chart type.');
}
const controllerDefaults = defaults[type];
const ControllerClass = registry.getController(type);
Object.assign(ControllerClass.prototype, {
dataElementType: registry.getElement(controllerDefaults.dataElementType),
datasetElementType: controllerDefaults.datasetElementType && registry.getElement(controllerDefaults.datasetElementType),
dataElementOptions: controllerDefaults.dataElementOptions,
datasetElementOptions: controllerDefaults.datasetElementOptions
});
meta.controller = new ControllerClass(me, i);
newControllers.push(meta.controller);
}
@ -1171,4 +1176,6 @@ Chart.version = version;
*/
Chart.instances = {};
Chart.registry = registry;
export default Chart;

View File

@ -686,7 +686,6 @@ export default class DatasetController {
datasetIndex: this.index,
active
};
}
/**
@ -696,7 +695,7 @@ export default class DatasetController {
resolveDatasetElementOptions(active) {
return this._resolveOptions(this.datasetElementOptions, {
active,
type: this.datasetElementType._type
type: this.datasetElementType.id
});
}
@ -718,7 +717,7 @@ export default class DatasetController {
index,
active,
info,
type: me.dataElementType._type
type: me.dataElementType.id
});
if (info.cacheable) {
@ -975,6 +974,11 @@ export default class DatasetController {
}
}
/**
* @type {any}
*/
DatasetController.defaults = {};
/**
* Element type used to generate a meta dataset (e.g. Chart.element.Line).
*/

View File

@ -1,4 +1,4 @@
import {merge, isArray, valueOrDefault} from '../helpers/helpers.core';
import {merge, valueOrDefault} from '../helpers/helpers.core';
/**
* @param {object} node
@ -58,6 +58,8 @@ export class Defaults {
this.tooltips = undefined;
this.doughnut = undefined;
this._routes = {};
this.scales = undefined;
this.controllers = undefined;
}
/**
* @param {string} scope
@ -67,52 +69,48 @@ export class Defaults {
return merge(getScope(this, scope), values);
}
get(scope) {
return getScope(this, scope);
}
/**
* Routes the named defaults to fallback to another scope/name.
* This routing is useful when those target values, like defaults.color, are changed runtime.
* If the values would be copied, the runtime change would not take effect. By routing, the
* fallback is evaluated at each access, so its always up to date.
*
* Examples:
* Example:
*
* defaults.route('elements.arc', 'backgroundColor', '', 'color')
* - reads the backgroundColor from defaults.color when undefined locally
*
* defaults.route('elements.line', ['backgroundColor', 'borderColor'], '', 'color')
* - reads the backgroundColor and borderColor from defaults.color when undefined locally
*
* defaults.route('elements.customLine', ['borderWidth', 'tension'], 'elements.line', ['borderWidth', 'tension'])
* - reads the borderWidth and tension from elements.line when those are not defined in elements.customLine
*
* @param {string} scope Scope this route applies to.
* @param {string[]} names Names of the properties that should be routed to different namespace when not defined here.
* @param {string} targetScope The namespace where those properties should be routed to. Empty string ('') is the root of defaults.
* @param {string|string[]} targetNames The target name/names in the target scope the properties should be routed to.
* @param {string} name Property name that should be routed to different namespace when not defined here.
* @param {string} targetScope The namespace where those properties should be routed to.
* Empty string ('') is the root of defaults.
* @param {string} targetName The target name in the target scope the property should be routed to.
*/
route(scope, names, targetScope, targetNames) {
route(scope, name, targetScope, targetName) {
const scopeObject = getScope(this, scope);
const targetScopeObject = getScope(this, targetScope);
const targetNamesIsArray = isArray(targetNames);
names.forEach((name, index) => {
const privateName = '_' + name;
const targetName = targetNamesIsArray ? targetNames[index] : targetNames;
Object.defineProperties(scopeObject, {
// A private property is defined to hold the actual value, when this property is set in its scope (set in the setter)
[privateName]: {
writable: true
const privateName = '_' + name;
Object.defineProperties(scopeObject, {
// A private property is defined to hold the actual value, when this property is set in its scope (set in the setter)
[privateName]: {
writable: true
},
// The actual property is defined as getter/setter so we can do the routing when value is not locally set.
[name]: {
enumerable: true,
get() {
// @ts-ignore
return valueOrDefault(this[privateName], targetScopeObject[targetName]);
},
// The actual property is defined as getter/setter so we can do the routing when value is not locally set.
[name]: {
enumerable: true,
get() {
// @ts-ignore
return valueOrDefault(this[privateName], targetScopeObject[targetName]);
},
set(value) {
this[privateName] = value;
}
set(value) {
this[privateName] = value;
}
});
}
});
}
}

View File

@ -42,3 +42,13 @@ export default class Element {
return ret;
}
}
/**
* @type any
*/
Element.defaults = {};
/**
* @type any
*/
Element.defaultRoutes = undefined;

154
src/core/core.registry.js Normal file
View File

@ -0,0 +1,154 @@
import DatasetController from './core.datasetController';
import Element from './core.element';
import Scale from './core.scale';
import TypedRegistry from './core.typedRegistry';
import {each, callback as call} from '../helpers/helpers.core';
/**
* Please use the module's default export which provides a singleton instance
* Note: class is exported for typedoc
*/
export class Registry {
constructor() {
this.controllers = new TypedRegistry(DatasetController, '');
this.elements = new TypedRegistry(Element, 'elements');
this.plugins = new TypedRegistry(Object, 'plugins');
this.scales = new TypedRegistry(Scale, 'scales');
// Order is important, Scale has Element in prototype chain,
// so Scales must be before Elements. Plugins are a fallback, so not listed here.
this._typedRegistries = [this.controllers, this.scales, this.elements];
}
/**
* @param {...any} args
*/
add(...args) {
this._registerEach(args);
}
/**
* @param {...typeof DatasetController} args
*/
addControllers(...args) {
this._registerEach(args, this.controllers);
}
/**
* @param {...typeof Element} args
*/
addElements(...args) {
this._registerEach(args, this.elements);
}
/**
* @param {...any} args
*/
addPlugins(...args) {
this._registerEach(args, this.plugins);
}
/**
* @param {...typeof Scale} args
*/
addScales(...args) {
this._registerEach(args, this.scales);
}
/**
* @param {string} id
* @returns {typeof DatasetController}
*/
getController(id) {
return this._get(id, this.controllers, 'controller');
}
/**
* @param {string} id
* @returns {typeof Element}
*/
getElement(id) {
return this._get(id, this.elements, 'element');
}
/**
* @param {string} id
* @returns {object}
*/
getPlugin(id) {
return this._get(id, this.plugins, 'plugin');
}
/**
* @param {string} id
* @returns {typeof Scale}
*/
getScale(id) {
return this._get(id, this.scales, 'scale');
}
/**
* @private
*/
_registerEach(args, typedRegistry) {
const me = this;
[...args].forEach(arg => {
const reg = typedRegistry || me._getRegistryForType(arg);
if (reg.isForType(arg)) {
me._registerComponent(reg, arg);
} else {
// Handle loopable args
// Use case:
// import * as plugins from './plugins';
// Chart.register(plugins);
each(arg, item => {
// If there are mixed types in the loopable, make sure those are
// registered in correct registry
// Use case: (treemap exporting controller, elements etc)
// import * as treemap from 'chartjs-chart-treemap';
// Chart.register(treemap);
const itemReg = typedRegistry || me._getRegistryForType(item);
me._registerComponent(itemReg, item);
});
}
});
}
/**
* @private
*/
_registerComponent(registry, component) {
call(component.beforeRegister, [], component);
registry.register(component);
call(component.afterRegister, [], component);
}
/**
* @private
*/
_getRegistryForType(type) {
for (let i = 0; i < this._typedRegistries.length; i++) {
const reg = this._typedRegistries[i];
if (reg.isForType(type)) {
return reg;
}
}
// plugins is the fallback registry
return this.plugins;
}
/**
* @private
*/
_get(id, typedRegistry, type) {
const item = typedRegistry.get(id);
if (item === undefined) {
throw new Error('"' + id + '" is not a registered ' + type + '.');
}
return item;
}
}
// singleton instance
export default new Registry();

View File

@ -1,43 +0,0 @@
import defaults from './core.defaults';
import {clone, each, merge} from '../helpers/helpers.core';
import layouts from './core.layouts';
export default {
// Scale registration object. Extensions can register new scale types (such as log or DB scales) and then
// use the new chart options to grab the correct scale
constructors: {},
// Use a registration function so that we can move to an ES6 map when we no longer need to support
// old browsers
// Scale config defaults
defaults: {},
registerScale(scaleConstructor) {
const me = this;
const type = scaleConstructor.id;
me.constructors[type] = scaleConstructor;
me.defaults[type] = clone(scaleConstructor.defaults);
},
getScaleConstructor(type) {
return Object.prototype.hasOwnProperty.call(this.constructors, type) ? this.constructors[type] : undefined;
},
getScaleDefaults(type) {
// Return the scale defaults merged with the global settings so that we always use the latest ones
return Object.prototype.hasOwnProperty.call(this.defaults, type) ? merge({}, [defaults.scale, this.defaults[type]]) : {};
},
updateScaleDefaults(type, additions) {
const me = this;
if (Object.prototype.hasOwnProperty.call(me.defaults, type)) {
me.defaults[type] = Object.assign(me.defaults[type], additions);
}
},
addScalesToLayout(chart) {
// Adds each scale to the chart.boxes array to be sized accordingly
each(chart.scales, (scale) => {
// Set ILayoutItem parameters for backwards compatibility
scale.fullWidth = scale.options.fullWidth;
scale.position = scale.options.position;
scale.weight = scale.options.weight;
layouts.addBox(chart, scale);
});
}
};

View File

@ -0,0 +1,104 @@
import defaults from './core.defaults';
import {valueOrDefault} from '../helpers/helpers.core';
/**
* @typedef {{id: string, defaults: any, defaultRoutes: any}} IChartComponent
*/
export default class TypedRegistry {
constructor(type, scope) {
this.type = type;
this.scope = scope;
this.items = Object.create(null);
}
isForType(type) {
return Object.prototype.isPrototypeOf.call(this.type, type);
}
/**
* @param {IChartComponent} item
* @param {string} [scopeOverride]
* @returns {string} The scope where items defaults were registered to.
*/
register(item, scopeOverride) {
const proto = Object.getPrototypeOf(item);
let parentScope;
if (isIChartComponent(proto)) {
// Make sure the parent is registered and note the scope where its defaults are.
parentScope = this.register(proto);
}
const items = this.items;
const id = item.id;
const baseScope = valueOrDefault(scopeOverride, this.scope);
const scope = baseScope ? baseScope + '.' + id : id;
if (!id) {
throw new Error('class does not have id: ' + Object.getPrototypeOf(item));
}
if (id in items) {
// already registered
return scope;
}
items[id] = item;
registerDefaults(item, scope, parentScope);
return scope;
}
/**
* @param {string} id
* @returns {object?}
*/
get(id) {
return this.items[id];
}
/**
* @param {IChartComponent} item
*/
unregister(item) {
const items = this.items;
const id = item.id;
if (id in items) {
delete items[id];
}
if (id in defaults[this.scope]) {
delete defaults[this.scope][id];
} else if (id in defaults) {
delete defaults[id];
}
}
}
function registerDefaults(item, scope, parentScope) {
// Inherit the parent's defaults
const itemDefaults = parentScope
? Object.assign({}, defaults.get(parentScope), item.defaults)
: item.defaults;
defaults.set(scope, itemDefaults);
if (item.defaultRoutes) {
routeDefaults(scope, item.defaultRoutes);
}
}
function routeDefaults(scope, routes) {
Object.keys(routes).forEach(property => {
const parts = routes[property].split('.');
const targetName = parts.pop();
const targetScope = parts.join('.');
defaults.route(scope, property, targetScope, targetName);
});
}
function isIChartComponent(proto) {
return 'id' in proto && 'defaults' in proto;
}

View File

@ -9,6 +9,6 @@ export {default as Element} from './core.element';
export {default as Interaction} from './core.interaction';
export {default as layouts} from './core.layouts';
export {default as plugins} from './core.plugins';
export {default as registry} from './core.registry';
export {default as Scale} from './core.scale';
export {default as ScaleService} from './core.scaleService';
export {default as Ticks} from './core.ticks';

View File

@ -1,17 +1,8 @@
import defaults from '../core/core.defaults';
import Element from '../core/core.element';
import {_angleBetween, getAngleFromPoint} from '../helpers/helpers.math';
const TAU = Math.PI * 2;
const scope = 'elements.arc';
defaults.set(scope, {
borderAlign: 'center',
borderColor: '#fff',
borderWidth: 2
});
defaults.route(scope, ['backgroundColor'], '', ['color']);
function clipArc(ctx, model) {
const {startAngle, endAngle, pixelMargin, x, y} = model;
let angleMargin = pixelMargin / model.outerRadius;
@ -108,7 +99,7 @@ function drawBorder(ctx, element, model) {
ctx.stroke();
}
class Arc extends Element {
export default class Arc extends Element {
constructor(cfg) {
super();
@ -207,6 +198,20 @@ class Arc extends Element {
}
}
Arc._type = 'arc';
Arc.id = 'arc';
export default Arc;
/**
* @type {any}
*/
Arc.defaults = {
borderAlign: 'center',
borderColor: '#fff',
borderWidth: 2
};
/**
* @type {any}
*/
Arc.defaultRoutes = {
backgroundColor: 'color'
};

View File

@ -1,4 +1,3 @@
import defaults from '../core/core.defaults';
import Element from '../core/core.element';
import {_bezierInterpolation, _pointInLine, _steppedInterpolation} from '../helpers/helpers.interpolation';
import {_computeSegments, _boundSegments} from '../helpers/helpers.segment';
@ -9,20 +8,6 @@ import {_updateBezierControlPoints} from '../helpers/helpers.curve';
* @typedef { import("./element.point").default } Point
*/
const scope = 'elements.line';
defaults.set(scope, {
borderCapStyle: 'butt',
borderDash: [],
borderDashOffset: 0,
borderJoinStyle: 'miter',
borderWidth: 3,
capBezierPoints: true,
fill: true,
tension: 0
});
defaults.route(scope, ['backgroundColor', 'borderColor'], '', 'color');
function setStyle(ctx, vm) {
ctx.lineCap = vm.borderCapStyle;
ctx.setLineDash(vm.borderDash);
@ -199,7 +184,7 @@ function _getInterpolationMethod(options) {
return _pointInLine;
}
class Line extends Element {
export default class Line extends Element {
constructor(cfg) {
super();
@ -359,6 +344,26 @@ class Line extends Element {
}
}
Line._type = 'line';
Line.id = 'line';
export default Line;
/**
* @type {any}
*/
Line.defaults = {
borderCapStyle: 'butt',
borderDash: [],
borderDashOffset: 0,
borderJoinStyle: 'miter',
borderWidth: 3,
capBezierPoints: true,
fill: true,
tension: 0
};
/**
* @type {any}
*/
Line.defaultRoutes = {
backgroundColor: 'color',
borderColor: 'color'
};

View File

@ -1,20 +1,7 @@
import defaults from '../core/core.defaults';
import Element from '../core/core.element';
import {_isPointInArea, drawPoint} from '../helpers/helpers.canvas';
const scope = 'elements.point';
defaults.set(scope, {
borderWidth: 1,
hitRadius: 1,
hoverBorderWidth: 1,
hoverRadius: 4,
pointStyle: 'circle',
radius: 3
});
defaults.route(scope, ['backgroundColor', 'borderColor'], '', 'color');
class Point extends Element {
export default class Point extends Element {
constructor(cfg) {
super();
@ -82,6 +69,24 @@ class Point extends Element {
}
}
Point._type = 'point';
Point.id = 'point';
export default Point;
/**
* @type {any}
*/
Point.defaults = {
borderWidth: 1,
hitRadius: 1,
hoverBorderWidth: 1,
hoverRadius: 4,
pointStyle: 'circle',
radius: 3
};
/**
* @type {any}
*/
Point.defaultRoutes = {
backgroundColor: 'color',
borderColor: 'color'
};

View File

@ -1,15 +1,6 @@
import defaults from '../core/core.defaults';
import Element from '../core/core.element';
import {isObject} from '../helpers/helpers.core';
const scope = 'elements.rectangle';
defaults.set(scope, {
borderSkipped: 'start',
borderWidth: 0
});
defaults.route(scope, ['backgroundColor', 'borderColor'], '', 'color');
/**
* Helper function to get the bounds of the bar regardless of the orientation
* @param {Rectangle} bar the bar
@ -131,7 +122,7 @@ function inRange(bar, x, y, useFinalPosition) {
&& (skipY || y >= bounds.top && y <= bounds.bottom);
}
class Rectangle extends Element {
export default class Rectangle extends Element {
constructor(cfg) {
super();
@ -193,6 +184,20 @@ class Rectangle extends Element {
}
}
Rectangle._type = 'rectangle';
Rectangle.id = 'rectangle';
export default Rectangle;
/**
* @type {any}
*/
Rectangle.defaults = {
borderSkipped: 'start',
borderWidth: 0
};
/**
* @type {any}
*/
Rectangle.defaultRoutes = {
backgroundColor: 'color',
borderColor: 'color'
};

View File

@ -17,18 +17,25 @@ import Element from './core/core.element';
import * as elements from './elements/index';
import Interaction from './core/core.interaction';
import layouts from './core/core.layouts';
import * as platforms from './platform';
import * as platforms from './platform/index';
import * as plugins from './plugins';
import pluginsCore from './core/core.plugins';
import registry from './core/core.registry';
import Scale from './core/core.scale';
import scaleService from './core/core.scaleService';
import * as scales from './scales';
import Ticks from './core/core.ticks';
Chart.register = (...items) => registry.add(...items);
// Register built-ins
Chart.register(controllers, scales, elements, plugins);
Chart.helpers = helpers;
Chart._adapters = _adapters;
Chart.Animation = Animation;
Chart.animator = animator;
Chart.animationService = animationService;
Chart.controllers = controllers;
Chart.controllers = registry.controllers.items;
Chart.DatasetController = DatasetController;
Chart.defaults = defaults;
Chart.Element = Element;
@ -37,16 +44,10 @@ Chart.Interaction = Interaction;
Chart.layouts = layouts;
Chart.platforms = platforms;
Chart.plugins = pluginsCore;
Chart.registry = registry;
Chart.Scale = Scale;
Chart.scaleService = scaleService;
Chart.Ticks = Ticks;
// Register built-in scales
import * as scales from './scales';
Object.keys(scales).forEach(key => Chart.scaleService.registerScale(scales[key]));
// Loading built-in plugins
import * as plugins from './plugins';
for (const k in plugins) {
if (Object.prototype.hasOwnProperty.call(plugins, k)) {
Chart.plugins.register(plugins[k]);

View File

@ -1,9 +1,6 @@
import Scale from '../core/core.scale';
const defaultConfig = {
};
class CategoryScale extends Scale {
export default class CategoryScale extends Scale {
constructor(cfg) {
super(cfg);
@ -108,7 +105,7 @@ class CategoryScale extends Scale {
CategoryScale.id = 'category';
// INTERNAL: default options, registered in src/index.js
CategoryScale.defaults = defaultConfig;
export default CategoryScale;
/**
* @type {any}
*/
CategoryScale.defaults = {};

View File

@ -2,13 +2,7 @@ import {isFinite, valueOrDefault} from '../helpers/helpers.core';
import LinearScaleBase from './scale.linearbase';
import Ticks from '../core/core.ticks';
const defaultConfig = {
ticks: {
callback: Ticks.formatters.numeric
}
};
class LinearScale extends LinearScaleBase {
export default class LinearScale extends LinearScaleBase {
determineDataLimits() {
const me = this;
@ -54,7 +48,11 @@ class LinearScale extends LinearScaleBase {
LinearScale.id = 'linear';
// INTERNAL: default options, registered in src/index.js
LinearScale.defaults = defaultConfig;
export default LinearScale;
/**
* @type {any}
*/
LinearScale.defaults = {
ticks: {
callback: Ticks.formatters.numeric
}
};

View File

@ -47,17 +47,7 @@ function generateTicks(generationOptions, dataRange) {
return ticks;
}
const defaultConfig = {
// label settings
ticks: {
callback: Ticks.formatters.logarithmic,
major: {
enabled: true
}
}
};
class LogarithmicScale extends Scale {
export default class LogarithmicScale extends Scale {
constructor(cfg) {
super(cfg);
@ -184,7 +174,14 @@ class LogarithmicScale extends Scale {
LogarithmicScale.id = 'logarithmic';
// INTERNAL: default options, registered in src/index.js
LogarithmicScale.defaults = defaultConfig;
export default LogarithmicScale;
/**
* @type {any}
*/
LogarithmicScale.defaults = {
ticks: {
callback: Ticks.formatters.logarithmic,
major: {
enabled: true
}
}
};

View File

@ -6,59 +6,6 @@ import Ticks from '../core/core.ticks';
import {valueOrDefault, isArray, isFinite, callback as callCallback, isNullOrUndef} from '../helpers/helpers.core';
import {toFont, resolve} from '../helpers/helpers.options';
const defaultConfig = {
display: true,
// Boolean - Whether to animate scaling the chart from the centre
animate: true,
position: 'chartArea',
angleLines: {
display: true,
color: 'rgba(0,0,0,0.1)',
lineWidth: 1,
borderDash: [],
borderDashOffset: 0.0
},
gridLines: {
circular: false
},
// label settings
ticks: {
// Boolean - Show a backdrop to the scale label
showLabelBackdrop: true,
// String - The colour of the label backdrop
backdropColor: 'rgba(255,255,255,0.75)',
// Number - The backdrop padding above & below the label in pixels
backdropPaddingY: 2,
// Number - The backdrop padding to the side of the label in pixels
backdropPaddingX: 2,
callback: Ticks.formatters.numeric
},
pointLabels: {
// Boolean - if true, show point labels
display: true,
// Number - Point label font size in pixels
font: {
size: 10
},
// Function - Used to convert point labels
callback(label) {
return label;
}
}
};
function getTickBackdropHeight(opts) {
const tickOpts = opts.ticks;
@ -306,7 +253,7 @@ function numberOrZero(param) {
return isNumber(param) ? param : 0;
}
class RadialLinearScale extends LinearScaleBase {
export default class RadialLinearScale extends LinearScaleBase {
constructor(cfg) {
super(cfg);
@ -593,7 +540,57 @@ class RadialLinearScale extends LinearScaleBase {
RadialLinearScale.id = 'radialLinear';
// INTERNAL: default options, registered in src/index.js
RadialLinearScale.defaults = defaultConfig;
/**
* @type {any}
*/
RadialLinearScale.defaults = {
display: true,
export default RadialLinearScale;
// Boolean - Whether to animate scaling the chart from the centre
animate: true,
position: 'chartArea',
angleLines: {
display: true,
color: 'rgba(0,0,0,0.1)',
lineWidth: 1,
borderDash: [],
borderDashOffset: 0.0
},
gridLines: {
circular: false
},
// label settings
ticks: {
// Boolean - Show a backdrop to the scale label
showLabelBackdrop: true,
// String - The colour of the label backdrop
backdropColor: 'rgba(255,255,255,0.75)',
// Number - The backdrop padding above & below the label in pixels
backdropPaddingY: 2,
// Number - The backdrop padding to the side of the label in pixels
backdropPaddingX: 2,
callback: Ticks.formatters.numeric
},
pointLabels: {
// Boolean - if true, show point labels
display: true,
// Number - Point label font size in pixels
font: {
size: 10
},
// Function - Used to convert point labels
callback(label) {
return label;
}
}
};

View File

@ -198,44 +198,7 @@ function ticksFromTimestamps(scale, values, majorUnit) {
return (ilen === 0 || !majorUnit) ? ticks : setMajorTicks(scale, ticks, map, majorUnit);
}
const defaultConfig = {
/**
* Scale boundary strategy (bypassed by min/max time options)
* - `data`: make sure data are fully visible, ticks outside are removed
* - `ticks`: make sure ticks are fully visible, data outside are truncated
* @see https://github.com/chartjs/Chart.js/pull/4556
* @since 2.7.0
*/
bounds: 'data',
adapters: {},
time: {
parser: false, // false == a pattern string from or a custom callback that converts its argument to a timestamp
unit: false, // false == automatic or override with week, month, year, etc.
round: false, // none, or override with week, month, year, etc.
isoWeekday: false, // override week start day
minUnit: 'millisecond',
displayFormats: {}
},
ticks: {
/**
* Ticks generation input values:
* - 'auto': generates "optimal" ticks based on scale size and time options.
* - 'data': generates ticks from data (including labels from data {t|x|y} objects).
* - 'labels': generates ticks from user given `data.labels` values ONLY.
* @see https://github.com/chartjs/Chart.js/pull/4507
* @since 2.7.0
*/
source: 'auto',
major: {
enabled: false
}
}
};
class TimeScale extends Scale {
export default class TimeScale extends Scale {
/**
* @param {object} props
@ -644,7 +607,41 @@ class TimeScale extends Scale {
TimeScale.id = 'time';
// INTERNAL: default options, registered in src/index.js
TimeScale.defaults = defaultConfig;
/**
* @type {any}
*/
TimeScale.defaults = {
/**
* Scale boundary strategy (bypassed by min/max time options)
* - `data`: make sure data are fully visible, ticks outside are removed
* - `ticks`: make sure ticks are fully visible, data outside are truncated
* @see https://github.com/chartjs/Chart.js/pull/4556
* @since 2.7.0
*/
bounds: 'data',
export default TimeScale;
adapters: {},
time: {
parser: false, // false == a pattern string from or a custom callback that converts its argument to a timestamp
unit: false, // false == automatic or override with week, month, year, etc.
round: false, // none, or override with week, month, year, etc.
isoWeekday: false, // override week start day
minUnit: 'millisecond',
displayFormats: {}
},
ticks: {
/**
* Ticks generation input values:
* - 'auto': generates "optimal" ticks based on scale size and time options.
* - 'data': generates ticks from data (including labels from data {t|x|y} objects).
* - 'labels': generates ticks from user given `data.labels` values ONLY.
* @see https://github.com/chartjs/Chart.js/pull/4507
* @since 2.7.0
*/
source: 'auto',
major: {
enabled: false
}
}
};

View File

@ -191,7 +191,9 @@ class TimeSeriesScale extends TimeScale {
TimeSeriesScale.id = 'timeseries';
// INTERNAL: default options, registered in src/index.js
/**
* @type {any}
*/
TimeSeriesScale.defaults = TimeScale.defaults;
export default TimeSeriesScale;

View File

@ -3,7 +3,7 @@ describe('Chart.controllers.doughnut', function() {
it('should be registered as dataset controller', function() {
expect(typeof Chart.controllers.doughnut).toBe('function');
expect(Chart.controllers.doughnut).toBe(Chart.controllers.pie);
expect(typeof Chart.controllers.pie).toBe('function');
});
it('should be constructed', function() {

View File

@ -163,7 +163,7 @@ describe('Chart', function() {
}
});
}
expect(createChart).toThrow(new Error('"area" is not a chart type.'));
expect(createChart).toThrow(new Error('"area" is not a registered controller.'));
});
});
@ -175,7 +175,7 @@ describe('Chart', function() {
_jasmineCheckC: 'c0'
});
Chart.helpers.merge(Chart.scaleService.defaults.logarithmic, {
Chart.helpers.merge(Chart.defaults.scales.logarithmic, {
_jasmineCheckB: 'b1',
_jasmineCheckC: 'c1',
});
@ -185,8 +185,8 @@ describe('Chart', function() {
delete Chart.defaults.scale._jasmineCheckA;
delete Chart.defaults.scale._jasmineCheckB;
delete Chart.defaults.scale._jasmineCheckC;
delete Chart.scaleService.defaults.logarithmic._jasmineCheckB;
delete Chart.scaleService.defaults.logarithmic._jasmineCheckC;
delete Chart.defaults.scales.logarithmic._jasmineCheckB;
delete Chart.defaults.scales.logarithmic._jasmineCheckC;
});
it('should default to "category" for x scales and "linear" for y scales', function() {
@ -298,8 +298,8 @@ describe('Chart', function() {
expect(Chart.defaults.line._jasmineCheck).not.toBeDefined();
expect(Chart.defaults._jasmineCheck).not.toBeDefined();
expect(Chart.scaleService.defaults.linear._jasmineCheck).not.toBeDefined();
expect(Chart.scaleService.defaults.category._jasmineCheck).not.toBeDefined();
expect(Chart.defaults.scales.linear._jasmineCheck).not.toBeDefined();
expect(Chart.defaults.scales.category._jasmineCheck).not.toBeDefined();
});
});

View File

@ -462,7 +462,7 @@ describe('Core.scale', function() {
}
CustomScale.id = 'customScale';
CustomScale.defaults = {};
Chart.scaleService.registerScale(CustomScale);
Chart.register(CustomScale);
var chart = window.acquireChart({
type: 'line',

View File

@ -1,30 +0,0 @@
// Tests of the scale service
describe('Test the scale service', function() {
it('should update scale defaults', function() {
var type = 'my_test_type';
var Constructor = function() {
this.initialized = true;
};
Constructor.id = type;
Constructor.defaults = {
testProp: true
};
Chart.scaleService.registerScale(Constructor);
// Should equal defaults but not be an identical object
expect(Chart.scaleService.getScaleDefaults(type)).toEqual(jasmine.objectContaining({
testProp: true
}));
Chart.scaleService.updateScaleDefaults(type, {
testProp: 'red',
newProp: 42
});
expect(Chart.scaleService.getScaleDefaults(type)).toEqual(jasmine.objectContaining({
testProp: 'red',
newProp: 42
}));
});
});

View File

@ -17,8 +17,8 @@ describe('Chart namespace', function() {
expect(Chart.platforms.BasicPlatform instanceof Function).toBeTruthy();
expect(Chart.platforms.DomPlatform instanceof Function).toBeTruthy();
expect(Chart.registry instanceof Object).toBeTruthy();
expect(Chart.Scale instanceof Object).toBeTruthy();
expect(Chart.scaleService instanceof Object).toBeTruthy();
expect(Chart.Ticks instanceof Object).toBeTruthy();
});
});

View File

@ -11,52 +11,15 @@ function getValues(scale) {
describe('Category scale tests', function() {
describe('auto', jasmine.fixture.specs('scale.category'));
it('Should register the constructor with the scale service', function() {
var Constructor = Chart.scaleService.getScaleConstructor('category');
it('Should register the constructor with the registry', function() {
var Constructor = Chart.registry.getScale('category');
expect(Constructor).not.toBe(undefined);
expect(typeof Constructor).toBe('function');
});
it('Should have the correct default config', function() {
var defaultConfig = Chart.scaleService.getScaleDefaults('category');
expect(defaultConfig).toEqual({
display: true,
reverse: false,
beginAtZero: false,
gridLines: {
color: 'rgba(0,0,0,0.1)',
drawBorder: true,
drawOnChartArea: true,
drawTicks: true, // draw ticks extending towards the label
tickMarkLength: 10,
lineWidth: 1,
offsetGridLines: false,
display: true,
borderDash: [],
borderDashOffset: 0.0
},
offset: false,
scaleLabel: Chart.defaults.scale.scaleLabel,
ticks: {
minRotation: 0,
maxRotation: 50,
mirror: false,
padding: 0,
display: true,
callback: defaultConfig.ticks.callback, // make this nicer, then check explicitly below
autoSkip: true,
autoSkipPadding: 0,
labelOffset: 0,
minor: {},
major: {},
lineWidth: 0,
strokeStyle: '',
}
});
// Is this actually a function
expect(defaultConfig.ticks.callback).toEqual(jasmine.any(Function));
var defaultConfig = Chart.defaults.scales.category;
expect(defaultConfig).toEqual({});
});
@ -71,9 +34,9 @@ describe('Category scale tests', function() {
xLabels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5']
};
var config = Chart.helpers.clone(Chart.scaleService.getScaleDefaults('category'));
var config = Chart.helpers.clone(Chart.defaults.scales.category);
config.position = 'bottom';
var Constructor = Chart.scaleService.getScaleConstructor('category');
var Constructor = Chart.registry.getScale('category');
var scale = new Constructor({
ctx: {},
chart: {
@ -99,9 +62,9 @@ describe('Category scale tests', function() {
yLabels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5']
};
var config = Chart.helpers.clone(Chart.scaleService.getScaleDefaults('category'));
var config = Chart.helpers.clone(Chart.defaults.scales.category);
config.position = 'left'; // y axis
var Constructor = Chart.scaleService.getScaleConstructor('category');
var Constructor = Chart.registry.getScale('category');
var scale = new Constructor({
ctx: {},
chart: {

View File

@ -5,48 +5,14 @@ function getLabels(scale) {
describe('Linear Scale', function() {
describe('auto', jasmine.fixture.specs('scale.linear'));
it('Should register the constructor with the scale service', function() {
var Constructor = Chart.scaleService.getScaleConstructor('linear');
it('Should register the constructor with the registry', function() {
var Constructor = Chart.registry.getScale('linear');
expect(Constructor).not.toBe(undefined);
expect(typeof Constructor).toBe('function');
});
it('Should have the correct default config', function() {
var defaultConfig = Chart.scaleService.getScaleDefaults('linear');
expect(defaultConfig).toEqual({
display: true,
gridLines: {
color: 'rgba(0,0,0,0.1)',
drawBorder: true,
drawOnChartArea: true,
drawTicks: true, // draw ticks extending towards the label
tickMarkLength: 10,
lineWidth: 1,
offsetGridLines: false,
display: true,
borderDash: [],
borderDashOffset: 0.0
},
offset: false,
reverse: false,
beginAtZero: false,
scaleLabel: Chart.defaults.scale.scaleLabel,
ticks: {
minRotation: 0,
maxRotation: 50,
mirror: false,
padding: 0,
display: true,
callback: defaultConfig.ticks.callback, // make this work nicer, then check below
autoSkip: true,
autoSkipPadding: 0,
labelOffset: 0,
minor: {},
major: {},
lineWidth: 0,
strokeStyle: '',
}
});
var defaultConfig = Chart.defaults.scales.linear;
expect(defaultConfig.ticks.callback).toEqual(jasmine.any(Function));
});

View File

@ -3,49 +3,21 @@ function getLabels(scale) {
}
describe('Logarithmic Scale tests', function() {
it('should register the constructor with the scale service', function() {
var Constructor = Chart.scaleService.getScaleConstructor('logarithmic');
it('should register', function() {
var Constructor = Chart.registry.getScale('logarithmic');
expect(Constructor).not.toBe(undefined);
expect(typeof Constructor).toBe('function');
});
it('should have the correct default config', function() {
var defaultConfig = Chart.scaleService.getScaleDefaults('logarithmic');
var defaultConfig = Chart.defaults.scales.logarithmic;
expect(defaultConfig).toEqual({
display: true,
gridLines: {
color: 'rgba(0,0,0,0.1)',
drawBorder: true,
drawOnChartArea: true,
drawTicks: true,
tickMarkLength: 10,
lineWidth: 1,
offsetGridLines: false,
display: true,
borderDash: [],
borderDashOffset: 0.0
},
offset: false,
reverse: false,
beginAtZero: false,
scaleLabel: Chart.defaults.scale.scaleLabel,
ticks: {
minRotation: 0,
maxRotation: 50,
mirror: false,
padding: 0,
display: true,
callback: defaultConfig.ticks.callback, // make this nicer, then check explicitly below
autoSkip: true,
autoSkipPadding: 0,
labelOffset: 0,
minor: {},
lineWidth: 0,
strokeStyle: '',
callback: Chart.Ticks.formatters.logarithmic,
major: {
enabled: true
},
},
}
}
});
// Is this actually a function

View File

@ -6,15 +6,19 @@ function getLabels(scale) {
describe('Test the radial linear scale', function() {
describe('auto', jasmine.fixture.specs('scale.radialLinear'));
it('Should register the constructor with the scale service', function() {
var Constructor = Chart.scaleService.getScaleConstructor('radialLinear');
it('Should register the constructor with the registry', function() {
var Constructor = Chart.registry.getScale('radialLinear');
expect(Constructor).not.toBe(undefined);
expect(typeof Constructor).toBe('function');
});
it('Should have the correct default config', function() {
var defaultConfig = Chart.scaleService.getScaleDefaults('radialLinear');
var defaultConfig = Chart.defaults.scales.radialLinear;
expect(defaultConfig).toEqual({
display: true,
animate: true,
position: 'chartArea',
angleLines: {
display: true,
color: 'rgba(0,0,0,0.1)',
@ -22,52 +26,26 @@ describe('Test the radial linear scale', function() {
borderDash: [],
borderDashOffset: 0.0
},
animate: true,
display: true,
gridLines: {
circular: false,
color: 'rgba(0,0,0,0.1)',
drawBorder: true,
drawOnChartArea: true,
drawTicks: true,
tickMarkLength: 10,
lineWidth: 1,
offsetGridLines: false,
display: true,
borderDash: [],
borderDashOffset: 0.0
circular: false
},
pointLabels: {
display: true,
font: {
size: 10,
},
callback: defaultConfig.pointLabels.callback, // make this nicer, then check explicitly below
},
position: 'chartArea',
offset: false,
reverse: false,
beginAtZero: false,
scaleLabel: Chart.defaults.scale.scaleLabel,
ticks: {
showLabelBackdrop: true,
backdropColor: 'rgba(255,255,255,0.75)',
backdropPaddingY: 2,
backdropPaddingX: 2,
minRotation: 0,
maxRotation: 50,
mirror: false,
padding: 0,
showLabelBackdrop: true,
display: true,
callback: defaultConfig.ticks.callback, // make this nicer, then check explicitly below
autoSkip: true,
autoSkipPadding: 0,
labelOffset: 0,
minor: {},
major: {},
lineWidth: 0,
strokeStyle: '',
callback: defaultConfig.ticks.callback
},
pointLabels: {
display: true,
font: {
size: 10
},
callback: defaultConfig.pointLabels.callback
}
});
// Is this actually a function

View File

@ -52,64 +52,32 @@ describe('Time scale tests', function() {
expect(window.moment).not.toBe(undefined);
});
it('should register the constructor with the scale service', function() {
var Constructor = Chart.scaleService.getScaleConstructor('time');
it('should register the constructor with the registry', function() {
var Constructor = Chart.registry.getScale('time');
expect(Constructor).not.toBe(undefined);
expect(typeof Constructor).toBe('function');
});
it('should have the correct default config', function() {
var defaultConfig = Chart.scaleService.getScaleDefaults('time');
var defaultConfig = Chart.defaults.scales.time;
expect(defaultConfig).toEqual({
display: true,
gridLines: {
color: 'rgba(0,0,0,0.1)',
drawBorder: true,
drawOnChartArea: true,
drawTicks: true,
tickMarkLength: 10,
lineWidth: 1,
offsetGridLines: false,
display: true,
borderDash: [],
borderDashOffset: 0.0
},
offset: false,
reverse: false,
beginAtZero: false,
scaleLabel: Chart.defaults.scale.scaleLabel,
bounds: 'data',
adapters: {},
ticks: {
minRotation: 0,
maxRotation: 50,
mirror: false,
source: 'auto',
padding: 0,
display: true,
callback: defaultConfig.ticks.callback, // make this nicer, then check explicitly below,
autoSkip: true,
autoSkipPadding: 0,
labelOffset: 0,
minor: {},
major: {
enabled: false
},
lineWidth: 0,
strokeStyle: '',
},
time: {
parser: false,
unit: false,
round: false,
isoWeekday: false,
parser: false, // false == a pattern string from or a custom callback that converts its argument to a timestamp
unit: false, // false == automatic or override with week, month, year, etc.
round: false, // none, or override with week, month, year, etc.
isoWeekday: false, // override week start day
minUnit: 'millisecond',
displayFormats: {}
},
ticks: {
source: 'auto',
major: {
enabled: false
}
}
});
// Is this actually a function
expect(defaultConfig.ticks.callback).toEqual(jasmine.any(Function));
});
it('should correctly determine the unit', function() {
@ -153,7 +121,7 @@ describe('Time scale tests', function() {
var config;
beforeEach(function() {
config = Chart.helpers.clone(Chart.scaleService.getScaleDefaults('time'));
config = Chart.helpers.clone(Chart.defaults.scales.time);
config.ticks.source = 'labels';
config.time.unit = 'day';
});
@ -202,7 +170,7 @@ describe('Time scale tests', function() {
unit: 'week',
isoWeekday: 3 // Wednesday
}
}, Chart.scaleService.getScaleDefaults('time'));
}, Chart.defaults.scales.time);
var scale = createScale(mockData, config);
var ticks = getLabels(scale);