mirror of
https://github.com/chartjs/Chart.js.git
synced 2025-12-08 20:36:08 +00:00
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:
parent
f544707e2c
commit
6bd5ad5518
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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, {
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -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 '';
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -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',
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
@ -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
|
||||
};
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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).
|
||||
*/
|
||||
|
||||
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
154
src/core/core.registry.js
Normal 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();
|
||||
@ -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);
|
||||
});
|
||||
}
|
||||
};
|
||||
104
src/core/core.typedRegistry.js
Normal file
104
src/core/core.typedRegistry.js
Normal 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;
|
||||
}
|
||||
@ -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';
|
||||
|
||||
@ -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'
|
||||
};
|
||||
|
||||
@ -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'
|
||||
};
|
||||
|
||||
@ -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'
|
||||
};
|
||||
|
||||
@ -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'
|
||||
};
|
||||
|
||||
21
src/index.js
21
src/index.js
@ -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]);
|
||||
|
||||
@ -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 = {};
|
||||
|
||||
@ -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
|
||||
}
|
||||
};
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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
|
||||
}));
|
||||
});
|
||||
});
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@ -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: {
|
||||
|
||||
@ -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));
|
||||
});
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user