### 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
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.
```
The following methods may optionally be overridden by derived dataset controllers.
+
```javascript
{
// Initializes the controller
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
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, {
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
}
-BarController.prototype.dataElementType = Rectangle;
-
-BarController.prototype.dataElementOptions = [
- 'backgroundColor',
- 'borderColor',
- 'borderSkipped',
- 'borderWidth',
- 'barPercentage',
- 'barThickness',
- 'categoryPercentage',
- 'maxBarThickness',
- 'minBarLength'
-];
+BarController.id = 'bar';
+
+/**
+ * @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,
+ }
+ }
+};
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 {
/**
}
}
-BubbleController.prototype.dataElementType = Point;
-
-BubbleController.prototype.dataElementOptions = [
- 'backgroundColor',
- 'borderColor',
- 'borderWidth',
- 'hitRadius',
- 'radius',
- 'pointStyle',
- 'rotation'
-];
+BubbleController.id = 'bubble';
+
+/**
+ * @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 '';
+ }
+ }
+ }
+};
import DatasetController from '../core/core.datasetController';
-import defaults from '../core/core.defaults';
-import {Arc} from '../elements/index';
import {isArray, valueOrDefault} from '../helpers/helpers.core';
/**
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;
}
}
-DoughnutController.prototype.dataElementType = Arc;
-
-DoughnutController.prototype.dataElementOptions = [
- 'backgroundColor',
- 'borderColor',
- 'borderWidth',
- 'borderAlign',
- 'hoverBackgroundColor',
- 'hoverBorderColor',
- 'hoverBorderWidth',
-];
+DoughnutController.id = 'doughnut';
+
+/**
+ * @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;
+ }
+ }
+ }
+};
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) {
}
}
-LineController.prototype.datasetElementType = Line;
-
-LineController.prototype.dataElementType = Point;
-
-LineController.prototype.datasetElementOptions = [
- 'backgroundColor',
- 'borderCapStyle',
- 'borderColor',
- 'borderDash',
- 'borderDashOffset',
- 'borderJoinStyle',
- 'borderWidth',
- 'capBezierPoints',
- 'cubicInterpolationMode',
- 'fill'
-];
-
-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'
+LineController.id = 'line';
+
+/**
+ * @type {any}
+ */
+LineController.defaults = {
+ datasetElementType: 'line',
+ 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'
+ },
+
+ showLines: true,
+ spanGaps: false,
+
+ hover: {
+ mode: 'index'
+ },
+
+ scales: {
+ _index_: {
+ type: 'category',
+ },
+ _value_: {
+ type: 'linear',
+ },
+ }
};
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
+};
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.
}
}
-PolarAreaController.prototype.dataElementType = Arc;
-
-PolarAreaController.prototype.dataElementOptions = [
- 'backgroundColor',
- 'borderColor',
- 'borderWidth',
- 'borderAlign',
- 'hoverBackgroundColor',
- 'hoverBorderColor',
- 'hoverBorderWidth'
-];
+PolarAreaController.id = 'polarArea';
+
+/**
+ * @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;
+ }
+ }
+ }
+};
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 {
/**
}
}
-RadarController.prototype.datasetElementType = Line;
-
-RadarController.prototype.dataElementType = Point;
-
-RadarController.prototype.datasetElementOptions = [
- 'backgroundColor',
- 'borderColor',
- 'borderCapStyle',
- 'borderDash',
- 'borderDashOffset',
- 'borderJoinStyle',
- 'borderWidth',
- 'fill'
-];
-
-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'
+RadarController.id = 'radar';
+
+/**
+ * @type {any}
+ */
+RadarController.defaults = {
+ datasetElementType: 'line',
+ 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'
+ },
+
+ aspectRatio: 1,
+ spanGaps: false,
+ scales: {
+ r: {
+ type: 'radialLinear',
+ }
+ },
+ datasets: {
+ indexAxis: 'r'
+ },
+ elements: {
+ line: {
+ fill: 'start',
+ tension: 0 // no bezier in radar
+ }
+ }
};
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'
}
}
}
-});
-
-// Scatter charts use line controllers
-export default LineController;
+};
-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';
/* 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';
// 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;
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,
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);
+ });
}
/**
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);
}
*/
Chart.instances = {};
+Chart.registry = registry;
+
export default Chart;
datasetIndex: this.index,
active
};
-
}
/**
resolveDatasetElementOptions(active) {
return this._resolveOptions(this.datasetElementOptions, {
active,
- type: this.datasetElementType._type
+ type: this.datasetElementType.id
});
}
index,
active,
info,
- type: me.dataElementType._type
+ type: me.dataElementType.id
});
if (info.cacheable) {
}
}
+/**
+ * @type {any}
+ */
+DatasetController.defaults = {};
+
/**
* Element type used to generate a meta dataset (e.g. Chart.element.Line).
*/
-import {merge, isArray, valueOrDefault} from '../helpers/helpers.core';
+import {merge, valueOrDefault} from '../helpers/helpers.core';
/**
* @param {object} node
this.tooltips = undefined;
this.doughnut = undefined;
this._routes = {};
+ this.scales = undefined;
+ this.controllers = undefined;
}
/**
* @param {string} scope
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;
}
- });
+ }
});
}
}
return ret;
}
}
+
+/**
+ * @type any
+ */
+Element.defaults = {};
+
+/**
+ * @type any
+ */
+Element.defaultRoutes = undefined;
--- /dev/null
+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();
+++ /dev/null
-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);
- });
- }
-};
--- /dev/null
+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;
+}
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';
-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']);
+const TAU = Math.PI * 2;
function clipArc(ctx, model) {
const {startAngle, endAngle, pixelMargin, x, y} = model;
ctx.stroke();
}
-class Arc extends Element {
+export default class Arc extends Element {
constructor(cfg) {
super();
}
}
-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'
+};
-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';
* @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);
return _pointInLine;
}
-class Line extends Element {
+export default class Line extends Element {
constructor(cfg) {
super();
}
}
-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'
+};
-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();
}
}
-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'
+};
-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
&& (skipY || y >= bounds.top && y <= bounds.bottom);
}
-class Rectangle extends Element {
+export default class Rectangle extends Element {
constructor(cfg) {
super();
}
}
-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'
+};
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;
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]);
import Scale from '../core/core.scale';
-const defaultConfig = {
-};
-
-class CategoryScale extends Scale {
+export default class CategoryScale extends Scale {
constructor(cfg) {
super(cfg);
CategoryScale.id = 'category';
-// INTERNAL: default options, registered in src/index.js
-CategoryScale.defaults = defaultConfig;
-
-export default CategoryScale;
+/**
+ * @type {any}
+ */
+CategoryScale.defaults = {};
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;
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
+ }
+};
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);
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
+ }
+ }
+};
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;
return isNumber(param) ? param : 0;
}
-class RadialLinearScale extends LinearScaleBase {
+export default class RadialLinearScale extends LinearScaleBase {
constructor(cfg) {
super(cfg);
RadialLinearScale.id = 'radialLinear';
-// INTERNAL: default options, registered in src/index.js
-RadialLinearScale.defaults = defaultConfig;
+/**
+ * @type {any}
+ */
+RadialLinearScale.defaults = {
+ 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
+ },
-export default RadialLinearScale;
+ 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;
+ }
+ }
+};
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
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
+ }
+ }
+};
TimeSeriesScale.id = 'timeseries';
-// INTERNAL: default options, registered in src/index.js
+/**
+ * @type {any}
+ */
TimeSeriesScale.defaults = TimeScale.defaults;
export default TimeSeriesScale;
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() {
}
});
}
- expect(createChart).toThrow(new Error('"area" is not a chart type.'));
+ expect(createChart).toThrow(new Error('"area" is not a registered controller.'));
});
});
_jasmineCheckC: 'c0'
});
- Chart.helpers.merge(Chart.scaleService.defaults.logarithmic, {
+ Chart.helpers.merge(Chart.defaults.scales.logarithmic, {
_jasmineCheckB: 'b1',
_jasmineCheckC: 'c1',
});
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() {
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();
});
});
}
CustomScale.id = 'customScale';
CustomScale.defaults = {};
- Chart.scaleService.registerScale(CustomScale);
+ Chart.register(CustomScale);
var chart = window.acquireChart({
type: 'line',
+++ /dev/null
-// 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
- }));
- });
-});
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();
});
});
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({});
});
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: {
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: {
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));
});
}
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
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)',
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
- },
- pointLabels: {
- display: true,
- font: {
- size: 10,
- },
- callback: defaultConfig.pointLabels.callback, // make this nicer, then check explicitly below
+ circular: false
},
- 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
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: {},
+ 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: {
- 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,
- minUnit: 'millisecond',
- displayFormats: {}
+ }
}
});
-
- // Is this actually a function
- expect(defaultConfig.ticks.callback).toEqual(jasmine.any(Function));
});
it('should correctly determine the unit', 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';
});
unit: 'week',
isoWeekday: 3 // Wednesday
}
- }, Chart.scaleService.getScaleDefaults('time'));
+ }, Chart.defaults.scales.time);
var scale = createScale(mockData, config);
var ticks = getLabels(scale);