Rewrite animation logic (#6845)

* Rewrite animation logic

* Review update 1

* Review update 2

* Review update 3

* Add 'none' to api.md
This commit is contained in:
Jukka Kurkela 2019-12-28 00:13:24 +02:00 committed by Evert Timberg
parent 3abe9bfebf
commit b83f64b16e
59 changed files with 2840 additions and 2517 deletions

View File

@ -1,6 +1,7 @@
extends: chartjs
env:
es6: true
browser: true
node: true

View File

@ -10,12 +10,33 @@ The following animation options are available. The global options for are define
| ---- | ---- | ------- | -----------
| `duration` | `number` | `1000` | The number of milliseconds an animation takes.
| `easing` | `string` | `'easeOutQuart'` | Easing function to use. [more...](#easing)
| `debug` | `boolean` | `undefined` | Running animation count + FPS display in upper left corner of the chart.
| `onProgress` | `function` | `null` | Callback called on each step of an animation. [more...](#animation-callbacks)
| `onComplete` | `function` | `null` | Callback called at the end of an animation. [more...](#animation-callbacks)
| `onComplete` | `function` | `null` | Callback called when all animations are completed. [more...](#animation-callbacks)
| `delay` | `number` | `undefined` | Delay before starting the animations.
| `loop` | `boolean` | `undefined` | If set to `true`, loop the animations loop endlessly.
| `type` | `string` | `typeof property` | Type of property, determines the interpolator used. Possible values: `'number'`, '`color`'.
| `from` | <code>number&#124;Color</code> | `undefined` | Start value for the animation. Current value is used when `undefined`
| `active` | `object` | `{ duration: 400 }` | Option overrides for `active` animations (hover)
| `resize` | `object` | `{ duration: 0 }` | Option overrides for `resize` animations.
| [property] | `object` | `undefined` | Option overrides for [property].
| [collection] | `object` | `undefined` | Option overrides for multiple properties, identified by `properties` array.
Default collections:
| Name | option | value
| `numbers` | `type` | `'number'`
| | `properties` | `['x', 'y', 'borderWidth', 'radius', 'tension']`
| `colors` | `type` | `'color'`
| | `properties` | `['borderColor', 'backgroundColor']`
Direct property configuration overrides configuration of same property in a collection.
These defaults can be overridden in `options.animation` and `dataset.animation`.
## Easing
Available options are:
* `'linear'`
* `'easeInQuad'`
* `'easeOutQuad'`
@ -52,34 +73,23 @@ See [Robert Penner's easing equations](http://robertpenner.com/easing/).
## Animation Callbacks
The `onProgress` and `onComplete` callbacks are useful for synchronizing an external draw to the chart animation. The callback is passed a `Chart.Animation` instance:
The `onProgress` and `onComplete` callbacks are useful for synchronizing an external draw to the chart animation. The callback is passed following object:
```javascript
{
// Chart object
chart: Chart,
// Current Animation frame number
// Number of animations still in progress
currentStep: number,
// Number of animation frames
// Total number of animations at the start of current animation
numSteps: number,
// Animation easing to use
easing: string,
// Function that renders the chart
render: function,
// User callback
onAnimationProgress: function,
// User callback
onAnimationComplete: function
}
```
The following example fills a progress bar during the chart animation.
```javascript
var chart = new Chart(ctx, {
type: 'line',

View File

@ -356,7 +356,7 @@ The tooltip model contains parameters that can be used to render the tooltip.
// 0 opacity is a hidden tooltip
opacity: number,
legendColorBackground: Color,
multiKeyBackground: Color,
displayColors: boolean,
borderColor: Color,
borderWidth: number

View File

@ -17,32 +17,23 @@ This must be called before the canvas is reused for a new chart.
myLineChart.destroy();
```
## .update(config)
## .update(mode)
Triggers an update of the chart. This can be safely called after updating the data object. This will update all scales, legends, and then re-render the chart.
```javascript
// duration is the time for the animation of the redraw in milliseconds
// lazy is a boolean. if true, the animation can be interrupted by other animations
myLineChart.data.datasets[0].data[2] = 50; // Would update the first dataset's value of 'March' to be 50
myLineChart.update(); // Calling update now animates the position of March from 90 to 50.
```
> **Note:** replacing the data reference (e.g. `myLineChart.data = {datasets: [...]}` only works starting **version 2.6**. Prior that, replacing the entire data object could be achieved with the following workaround: `myLineChart.config.data = {datasets: [...]}`.
A `config` object can be provided with additional configuration for the update process. This is useful when `update` is manually called inside an event handler and some different animation is desired.
The following properties are supported:
* **duration** (number): Time for the animation of the redraw in milliseconds
* **lazy** (boolean): If true, the animation can be interrupted by other animations
* **easing** (string): The animation easing function. See [Animation Easing](../configuration/animations.md) for possible values.
A `mode` string can be provided to indicate what should be updated and what animation configuration should be used. Core calls this method using any of `undefined`, `'reset'`, `'resize'` or `'active'`. `'none'` is also a supported mode for skipping animations for single update.
Example:
```javascript
myChart.update({
duration: 800,
easing: 'easeOutBounce'
});
myChart.update();
```
See [Updating Charts](updates.md) for more details.
@ -55,25 +46,13 @@ Reset the chart to it's state before the initial animation. A new animation can
myLineChart.reset();
```
## .render(config)
## .render()
Triggers a redraw of all chart elements. Note, this does not update elements for new data. Use `.update()` in that case.
See `.update(config)` for more details on the config object.
```javascript
// duration is the time for the animation of the redraw in milliseconds
// lazy is a boolean. if true, the animation can be interrupted by other animations
myLineChart.render({
duration: 800,
lazy: false,
easing: 'easeOutBounce'
});
```
## .stop()
Use this to stop any current animation loop. This will pause the chart during any current animation frame. Call `.render()` to re-animate.
Use this to stop any current animation. This will pause the chart during any current animation frame. Call `.render()` to re-animate.
```javascript
// Stops the charts animation loop at its current frame
@ -175,5 +154,5 @@ Extensive examples of usage are available in the [Chart.js tests](https://github
```javascript
var meta = myChart.getDatasetMeta(0);
var x = meta.data[0]._model.x;
var x = meta.data[0].x;
```

View File

@ -94,13 +94,13 @@ var custom = Chart.controllers.bubble.extend({
// Now we can do some custom drawing for this dataset. Here we'll draw a red box around the first point in each dataset
var meta = this.getMeta();
var pt0 = meta.data[0];
var radius = pt0._view.radius;
var radius = pt0.radius;
var ctx = this.chart.chart.ctx;
ctx.save();
ctx.strokeStyle = 'red';
ctx.lineWidth = 1;
ctx.strokeRect(pt0._view.x - radius, pt0._view.y - radius, 2 * radius, 2 * radius);
ctx.strokeRect(pt0.x - radius, pt0.y - radius, 2 * radius, 2 * radius);
ctx.restore();
}
});

View File

@ -97,4 +97,4 @@ Code sample for updating options can be found in [toggle-scale-type.html](../../
## Preventing Animations
Sometimes when a chart updates, you may not want an animation. To achieve this you can call `update` with a duration of `0`. This will render the chart synchronously and without an animation.
Sometimes when a chart updates, you may not want an animation. To achieve this you can call `update` with `'none'` as mode.

View File

@ -7,4 +7,3 @@ The hover configuration is passed into the `options.hover` namespace. The global
| `mode` | `string` | `'nearest'` | Sets which elements appear in the tooltip. See [Interaction Modes](./modes.md#interaction-modes) for details.
| `intersect` | `boolean` | `true` | if true, the hover mode only applies when the mouse position intersects an item on the chart.
| `axis` | `string` | `'x'` | Can be set to `'x'`, `'y'`, or `'xy'` to define which directions are used in calculating distances. Defaults to `'x'` for `'index'` mode and `'xy'` in `dataset` and `'nearest'` modes.
| `animationDuration` | `number` | `400` | Duration in milliseconds it takes to animate hover style changes.

View File

@ -23,13 +23,7 @@ new Chart(ctx, {
type: 'line',
data: data,
options: {
animation: {
duration: 0 // general animation time
},
hover: {
animationDuration: 0 // duration of animations when hovering an item
},
responsiveAnimationDuration: 0 // animation duration after a resize
animation: false
}
});
```

View File

@ -14,7 +14,6 @@ Chart.js provides a [few options](#configuration-options) to enable responsivene
| Name | Type | Default | Description
| ---- | ---- | ------- | -----------
| `responsive` | `boolean` | `true` | Resizes the chart canvas when its container does ([important note...](#important-note)).
| `responsiveAnimationDuration` | `number` | `0` | Duration in milliseconds it takes to animate to new size after a resize event.
| `maintainAspectRatio` | `boolean` | `true` | Maintain the original canvas aspect ratio `(width / height)` when resizing.
| `aspectRatio` | `number` | `2` | Canvas aspect ratio (i.e. `width / height`, a value of 1 representing a square canvas). Note that this option is ignored if the height is explicitly defined either as attribute or via the style.
| `onResize` | `function` | `null` | Called when a resize occurs. Gets passed two arguments: the chart instance and the new size.

View File

@ -49,6 +49,13 @@ Chart.js 3.0 introduces a number of breaking changes. Chart.js 2.0 was released
* `scales.[x/y]Axes.time.max` was renamed to `scales[id].max`
* `scales.[x/y]Axes.time.min` was renamed to `scales[id].min`
### Animations
Animation system was completely rewritten in Chart.js v3. Each property can now be animated separately. Please see [animations](../configuration/animations.md) docs for details.
* `hover.animationDuration` is now configured in `animation.active.duration`
* `responsiveAnimationDuration` is now configured in `animation.resize.duration`
## Developer migration
### Removed
@ -90,10 +97,8 @@ Chart.js 3.0 introduces a number of breaking changes. Chart.js 2.0 was released
* `Chart.data.datasets[datasetIndex]._meta`
* `Element._ctx`
* `Element._model.datasetLabel`
* `Element._model.label`
* `Point._model.tension`
* `Point._model.steppedLine`
* `Element._model`
* `Element._view`
* `TimeScale._getPixelForOffset`
* `TimeScale.getLabelWidth`
@ -108,7 +113,6 @@ Chart.js 3.0 introduces a number of breaking changes. Chart.js 2.0 was released
* `helpers.log10` was renamed to `helpers.math.log10`
* `helpers.almostEquals` was renamed to `helpers.math.almostEquals`
* `helpers.almostWhole` was renamed to `helpers.math.almostWhole`
* `helpers._decimalPlaces` was renamed to `helpers.math._decimalPlaces`
* `helpers.distanceBetweenPoints` was renamed to `helpers.math.distanceBetweenPoints`
* `helpers.isNumber` was renamed to `helpers.math.isNumber`
* `helpers.sign` was renamed to `helpers.math.sign`
@ -129,10 +133,12 @@ Chart.js 3.0 introduces a number of breaking changes. Chart.js 2.0 was released
* `TimeScale.getLabelCapacity` was renamed to `TimeScale._getLabelCapacity`
* `TimeScale.tickFormatFunction` was renamed to `TimeScale._tickFormatFunction`
* `TimeScale.getPixelForOffset` was renamed to `TimeScale._getPixelForOffset`
* `Tooltip.options.legendColorBackgroupd` was renamed to `Tooltip.options.multiKeyBackground`
#### Renamed private APIs
* `helpers._alignPixel` was renamed to `helpers.canvas._alignPixel`
* `helpers._decimalPlaces` was renamed to `helpers.math._decimalPlaces`
### Changed

View File

@ -0,0 +1,122 @@
<!doctype html>
<html>
<head>
<title>Stacked Bar Chart</title>
<script src="../../dist/Chart.min.js"></script>
<script src="../utils.js"></script>
<style>
canvas {
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
}
</style>
</head>
<body>
<div style="width: 75%">
<canvas id="canvas"></canvas>
</div>
<button id="randomizeData">Randomize Data</button>
<script>
var barChartData = {
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
datasets: [{
label: 'Dataset 1',
backgroundColor: window.chartColors.red,
data: [
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor()
]
}, {
label: 'Dataset 2',
backgroundColor: window.chartColors.blue,
data: [
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor()
]
}, {
label: 'Dataset 3',
backgroundColor: window.chartColors.green,
data: [
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor()
]
}]
};
window.onload = function() {
var ctx = document.getElementById('canvas').getContext('2d');
var started = {};
window.myBar = new Chart(ctx, {
type: 'bar',
data: barChartData,
options: {
animation: (context) => {
if (context.active) {
return {
duration: 400
};
}
var delay = 0;
var dsIndex = context.datasetIndex;
var index = context.dataIndex;
if (!started[index + dsIndex * 1000]) {
delay = index * 300 + dsIndex * 100;
started[index + dsIndex * 1000] = true;
}
return {
easing: 'linear',
duration: 600,
delay
};
},
title: {
display: true,
text: 'Chart.js Bar Chart - Stacked'
},
tooltips: {
mode: 'index',
intersect: false
},
responsive: true,
scales: {
x: {
stacked: true,
},
y: {
stacked: true
}
}
}
});
};
document.getElementById('randomizeData').addEventListener('click', function() {
barChartData.datasets.forEach(function(dataset) {
dataset.data = dataset.data.map(function() {
return randomScalingFactor();
});
});
window.myBar.update();
});
</script>
</body>
</html>

View File

@ -0,0 +1,175 @@
<!doctype html>
<html>
<head>
<title>Line Chart</title>
<script src="../../dist/Chart.min.js"></script>
<script src="../utils.js"></script>
<style>
canvas{
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
}
</style>
</head>
<body>
<div style="width:75%;">
<canvas id="canvas"></canvas>
</div>
<br>
<br>
<button id="randomizeData">Randomize Data</button>
<button id="addDataset">Add Dataset</button>
<button id="removeDataset">Remove Dataset</button>
<button id="addData">Add Data</button>
<button id="removeData">Remove Data</button>
<script>
var MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
var config = {
type: 'line',
data: {
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
datasets: [{
label: 'My First dataset',
animation: {
y: {
duration: 2000,
delay: 100
}
},
backgroundColor: window.chartColors.red,
borderColor: window.chartColors.red,
data: [
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor()
],
fill: false,
}, {
label: 'My Second dataset',
fill: false,
backgroundColor: window.chartColors.blue,
borderColor: window.chartColors.blue,
data: [
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor()
],
}]
},
options: {
animation: {
y: {
easing: 'easeInOutElastic',
from: 0
}
},
responsive: true,
title: {
display: true,
text: 'Chart.js Line Chart'
},
tooltips: {
mode: 'index',
intersect: false,
},
hover: {
mode: 'nearest',
intersect: true
},
scales: {
x: {
display: true,
scaleLabel: {
display: true,
labelString: 'Month'
}
},
y: {
display: true,
scaleLabel: {
display: true,
labelString: 'Value'
}
}
}
}
};
window.onload = function() {
var ctx = document.getElementById('canvas').getContext('2d');
window.myLine = new Chart(ctx, config);
};
document.getElementById('randomizeData').addEventListener('click', function() {
config.data.datasets.forEach(function(dataset) {
dataset.data = dataset.data.map(function() {
return randomScalingFactor();
});
});
window.myLine.update();
});
var colorNames = Object.keys(window.chartColors);
document.getElementById('addDataset').addEventListener('click', function() {
var colorName = colorNames[config.data.datasets.length % colorNames.length];
var newColor = window.chartColors[colorName];
var newDataset = {
label: 'Dataset ' + config.data.datasets.length,
backgroundColor: newColor,
borderColor: newColor,
data: [],
fill: false
};
for (var index = 0; index < config.data.labels.length; ++index) {
newDataset.data.push(randomScalingFactor());
}
config.data.datasets.push(newDataset);
window.myLine.update();
});
document.getElementById('addData').addEventListener('click', function() {
if (config.data.datasets.length > 0) {
var month = MONTHS[config.data.labels.length % MONTHS.length];
config.data.labels.push(month);
config.data.datasets.forEach(function(dataset) {
dataset.data.push(randomScalingFactor());
});
window.myLine.update();
}
});
document.getElementById('removeDataset').addEventListener('click', function() {
config.data.datasets.splice(0, 1);
window.myLine.update();
});
document.getElementById('removeData').addEventListener('click', function() {
config.data.labels.splice(-1, 1); // remove the label first
config.data.datasets.forEach(function(dataset) {
dataset.data.pop();
});
window.myLine.update();
});
</script>
</body>
</html>

View File

@ -0,0 +1,181 @@
<!doctype html>
<html>
<head>
<title>Line Chart</title>
<script src="../../dist/Chart.min.js"></script>
<script src="../utils.js"></script>
<style>
canvas{
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
}
</style>
</head>
<body>
<div style="width:75%;">
<canvas id="canvas"></canvas>
</div>
<br>
<br>
<button id="randomizeData">Randomize Data</button>
<button id="addDataset">Add Dataset</button>
<button id="removeDataset">Remove Dataset</button>
<button id="addData">Add Data</button>
<button id="removeData">Remove Data</button>
<script>
var MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
var config = {
type: 'line',
data: {
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
datasets: [{
label: 'My First dataset',
fill: false,
backgroundColor: window.chartColors.red,
borderColor: window.chartColors.red,
data: [
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor()
],
}, {
label: 'My Second dataset',
fill: false,
backgroundColor: window.chartColors.blue,
borderColor: window.chartColors.blue,
data: [
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor()
],
}]
},
options: {
animation: (context) => {
if (context.active) {
return {
radius: {
duration: 400,
loop: true
}
};
}
return Chart.defaults.global.animation;
},
elements: {
point: {
hoverRadius: 6
}
},
responsive: true,
title: {
display: true,
text: 'Chart.js Line Chart'
},
tooltips: {
mode: 'nearest',
axis: 'x',
intersect: false,
},
hover: {
mode: 'nearest',
axis: 'x',
intersect: false
},
scales: {
x: {
display: true,
scaleLabel: {
display: true,
labelString: 'Month'
}
},
y: {
display: true,
scaleLabel: {
display: true,
labelString: 'Value'
}
}
}
}
};
window.onload = function() {
var ctx = document.getElementById('canvas').getContext('2d');
window.myLine = new Chart(ctx, config);
};
document.getElementById('randomizeData').addEventListener('click', function() {
config.data.datasets.forEach(function(dataset) {
dataset.data = dataset.data.map(function() {
return randomScalingFactor();
});
});
window.myLine.update();
});
var colorNames = Object.keys(window.chartColors);
document.getElementById('addDataset').addEventListener('click', function() {
var colorName = colorNames[config.data.datasets.length % colorNames.length];
var newColor = window.chartColors[colorName];
var newDataset = {
label: 'Dataset ' + config.data.datasets.length,
backgroundColor: newColor,
borderColor: newColor,
data: [],
fill: false
};
for (var index = 0; index < config.data.labels.length; ++index) {
newDataset.data.push(randomScalingFactor());
}
config.data.datasets.push(newDataset);
window.myLine.update();
});
document.getElementById('addData').addEventListener('click', function() {
if (config.data.datasets.length > 0) {
var month = MONTHS[config.data.labels.length % MONTHS.length];
config.data.labels.push(month);
config.data.datasets.forEach(function(dataset) {
dataset.data.push(randomScalingFactor());
});
window.myLine.update();
}
});
document.getElementById('removeDataset').addEventListener('click', function() {
config.data.datasets.splice(0, 1);
window.myLine.update();
});
document.getElementById('removeData').addEventListener('click', function() {
config.data.labels.splice(-1, 1); // remove the label first
config.data.datasets.forEach(function(dataset) {
dataset.data.pop();
});
window.myLine.update();
});
</script>
</body>
</html>

View File

@ -29,9 +29,6 @@
var addedCount = 0;
var color = Chart.helpers.color;
var bubbleChartData = {
animation: {
duration: 10000
},
datasets: [{
label: 'My First dataset',
backgroundColor: color(window.chartColors.red).alpha(0.5).rgbString(),

View File

@ -65,6 +65,9 @@
}, {
title: 'Other charts',
items: [{
title: 'Bubble',
path: 'charts/bubble.html'
}, {
title: 'Scatter',
path: 'charts/scatter/basic.html'
}, {
@ -209,6 +212,18 @@
title: 'Radar Chart',
path: 'scriptable/radar.html'
}]
}, {
title: 'Animations',
items: [{
title: 'Delay',
path: 'animations/delay.html'
}, {
title: 'Drop',
path: 'animations/drop.html'
}, {
title: 'Loop',
path: 'animations/loop.html'
}]
}, {
title: 'Advanced',
items: [{

View File

@ -30,7 +30,13 @@ defaults._set('global', {
datasets: {
bar: {
categoryPercentage: 0.8,
barPercentage: 0.9
barPercentage: 0.9,
animation: {
numbers: {
type: 'number',
properties: ['x', 'y', 'base', 'width', 'height']
}
}
}
}
});
@ -267,50 +273,53 @@ module.exports = DatasetController.extend({
meta.bar = true;
},
update: function(reset) {
update: function(mode) {
const me = this;
const rects = me._cachedMeta.data;
me.updateElements(rects, 0, rects.length, reset);
me.updateElements(rects, 0, rects.length, mode);
},
updateElements: function(rectangles, start, count, reset) {
updateElements: function(rectangles, start, count, mode) {
const me = this;
const reset = mode === 'reset';
const vscale = me._cachedMeta.vScale;
const base = vscale.getBasePixel();
const horizontal = vscale.isHorizontal();
const ruler = me.getRuler();
const firstOpts = me._resolveDataElementOptions(start, mode);
const sharedOptions = me._getSharedOptions(mode, rectangles[start], firstOpts);
const includeOptions = me._includeOptions(mode, sharedOptions);
let i;
for (i = 0; i < start + count; i++) {
const rectangle = rectangles[i];
const options = me._resolveDataElementOptions(i);
const options = me._resolveDataElementOptions(i, mode);
const vpixels = me.calculateBarValuePixels(i, options);
const ipixels = me.calculateBarIndexPixels(i, ruler, options);
rectangle._model = {
backgroundColor: options.backgroundColor,
borderColor: options.borderColor,
borderSkipped: options.borderSkipped,
borderWidth: options.borderWidth
const properties = {
horizontal,
base: reset ? base : vpixels.base,
x: horizontal ? reset ? base : vpixels.head : ipixels.center,
y: horizontal ? ipixels.center : reset ? base : vpixels.head,
height: horizontal ? ipixels.size : undefined,
width: horizontal ? undefined : ipixels.size
};
const model = rectangle._model;
// all borders are drawn for floating bar
/* TODO: float bars border skipping magic
if (me._getParsed(i)._custom) {
model.borderSkipped = null;
}
model.horizontal = horizontal;
model.base = reset ? base : vpixels.base;
model.x = horizontal ? reset ? base : vpixels.head : ipixels.center;
model.y = horizontal ? ipixels.center : reset ? base : vpixels.head;
model.height = horizontal ? ipixels.size : undefined;
model.width = horizontal ? undefined : ipixels.size;
rectangle.pivot(me.chart._animationsDisabled);
*/
if (includeOptions) {
properties.options = options;
}
me._updateElement(rectangles[i], i, properties, mode);
}
me._updateSharedOptions(sharedOptions, mode);
},
/**

View File

@ -1,14 +1,18 @@
'use strict';
var DatasetController = require('../core/core.datasetController');
var defaults = require('../core/core.defaults');
var elements = require('../elements/index');
var helpers = require('../helpers/index');
const DatasetController = require('../core/core.datasetController');
const defaults = require('../core/core.defaults');
const elements = require('../elements/index');
const helpers = require('../helpers/index');
var valueOrDefault = helpers.valueOrDefault;
var resolve = helpers.options.resolve;
const resolve = helpers.options.resolve;
defaults._set('bubble', {
animation: {
numbers: {
properties: ['x', 'y', 'borderWidth', 'radius']
}
},
scales: {
x: {
type: 'linear',
@ -43,11 +47,8 @@ module.exports = DatasetController.extend({
'backgroundColor',
'borderColor',
'borderWidth',
'hoverBackgroundColor',
'hoverBorderColor',
'hoverBorderWidth',
'hoverRadius',
'hitRadius',
'radius',
'pointStyle',
'rotation'
],
@ -77,15 +78,14 @@ module.exports = DatasetController.extend({
* @private
*/
_getMaxOverflow: function() {
var me = this;
var meta = me._cachedMeta;
var data = meta.data || [];
if (!data.length) {
return false;
const me = this;
const meta = me._cachedMeta;
let i = (meta.data || []).length - 1;
let max = 0;
for (; i >= 0; --i) {
max = Math.max(max, me.getStyle(i, true).radius);
}
var firstPoint = data[0].size();
var lastPoint = data[data.length - 1].size();
return Math.max(firstPoint, lastPoint) / 2;
return max > 0 && max;
},
/**
@ -109,72 +109,56 @@ module.exports = DatasetController.extend({
/**
* @protected
*/
update: function(reset) {
update: function(mode) {
const me = this;
const points = me._cachedMeta.data;
// Update Points
me.updateElements(points, 0, points.length, reset);
me.updateElements(points, 0, points.length, mode);
},
/**
* @protected
*/
updateElements: function(points, start, count, reset) {
updateElements: function(points, start, count, mode) {
const me = this;
const reset = mode === 'reset';
const {xScale, yScale} = me._cachedMeta;
const firstOpts = me._resolveDataElementOptions(start, mode);
const sharedOptions = me._getSharedOptions(mode, points[start], firstOpts);
const includeOptions = me._includeOptions(mode, sharedOptions);
let i;
for (i = start; i < start + count; i++) {
const point = points[i];
const options = me._resolveDataElementOptions(i);
const parsed = !reset && me._getParsed(i);
const x = reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(parsed[xScale.id]);
const y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(parsed[yScale.id]);
point._options = options;
point._model = {
backgroundColor: options.backgroundColor,
borderColor: options.borderColor,
borderWidth: options.borderWidth,
hitRadius: options.hitRadius,
pointStyle: options.pointStyle,
rotation: options.rotation,
radius: reset ? 0 : options.radius,
skip: isNaN(x) || isNaN(y),
x: x,
y: y,
const properties = {
x,
y,
skip: isNaN(x) || isNaN(y)
};
point.pivot(me.chart._animationsDisabled);
if (includeOptions) {
properties.options = i === start ? firstOpts
: me._resolveDataElementOptions(i, mode);
if (reset) {
properties.options.radius = 0;
}
}
me._updateElement(point, i, properties, mode);
}
},
/**
* @protected
*/
setHoverStyle: function(point) {
var model = point._model;
var options = point._options;
var getHoverColor = helpers.getHoverColor;
point.$previousStyle = {
backgroundColor: model.backgroundColor,
borderColor: model.borderColor,
borderWidth: model.borderWidth,
radius: model.radius
};
model.backgroundColor = valueOrDefault(options.hoverBackgroundColor, getHoverColor(options.backgroundColor));
model.borderColor = valueOrDefault(options.hoverBorderColor, getHoverColor(options.borderColor));
model.borderWidth = valueOrDefault(options.hoverBorderWidth, options.borderWidth);
model.radius = options.radius + options.hoverRadius;
me._updateSharedOptions(sharedOptions, mode);
},
/**
* @private
*/
_resolveDataElementOptions: function(index) {
_resolveDataElementOptions: function(index, mode) {
var me = this;
var chart = me.chart;
var dataset = me.getDataset();
@ -190,12 +174,16 @@ module.exports = DatasetController.extend({
};
// In case values were cached (and thus frozen), we need to clone the values
if (me._cachedDataOpts === values) {
values = helpers.extend({}, values);
if (values.$shared) {
values = helpers.extend({}, values, {$shared: false});
}
// Custom radius resolution
values.radius = resolve([
if (mode !== 'active') {
values.radius = 0;
}
values.radius += resolve([
parsed && parsed._custom,
me._config.radius,
chart.options.elements.point.radius

View File

@ -13,6 +13,10 @@ var HALF_PI = PI / 2;
defaults._set('doughnut', {
animation: {
numbers: {
type: 'number',
properties: ['x', 'y', 'startAngle', 'endAngle', 'innerRadius', 'outerRadius']
},
// Boolean - Whether we animate the rotation of the Doughnut
animateRotate: true,
// Boolean - Whether we animate scaling the Doughnut from the centre
@ -160,7 +164,7 @@ module.exports = DatasetController.extend({
return ringIndex;
},
update: function(reset) {
update: function(mode) {
var me = this;
var chart = me.chart;
var chartArea = chart.chartArea;
@ -200,7 +204,7 @@ module.exports = DatasetController.extend({
}
for (i = 0, ilen = arcs.length; i < ilen; ++i) {
arcs[i]._options = me._resolveDataElementOptions(i);
arcs[i]._options = me._resolveDataElementOptions(i, mode);
}
chart.borderWidth = me.getMaxBorderWidth();
@ -217,57 +221,45 @@ module.exports = DatasetController.extend({
me.outerRadius = chart.outerRadius - chart.radiusLength * me._getRingWeightOffset(me.index);
me.innerRadius = Math.max(me.outerRadius - chart.radiusLength * chartWeight, 0);
me.updateElements(arcs, 0, arcs.length, reset);
me.updateElements(arcs, 0, arcs.length, mode);
},
updateElements: function(arcs, start, count, reset) {
updateElements: function(arcs, start, count, mode) {
const me = this;
const reset = mode === 'reset';
const chart = me.chart;
const chartArea = chart.chartArea;
const opts = chart.options;
const animationOpts = opts.animation;
const centerX = (chartArea.left + chartArea.right) / 2;
const centerY = (chartArea.top + chartArea.bottom) / 2;
const startAngle = opts.rotation; // non reset case handled later
const endAngle = opts.rotation; // non reset case handled later
const meta = me.getMeta();
const innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius;
const outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius;
let startAngle = opts.rotation;
let i;
for (i = 0; i < start + count; ++i) {
const arc = arcs[i];
const circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(meta._parsed[i] * opts.circumference / DOUBLE_PI);
const options = arc._options || {};
const model = {
// Desired view properties
backgroundColor: options.backgroundColor,
borderColor: options.borderColor,
borderWidth: options.borderWidth,
borderAlign: options.borderAlign,
if (i < start) {
startAngle += circumference;
continue;
}
const properties = {
x: centerX + chart.offsetX,
y: centerY + chart.offsetY,
startAngle: startAngle,
endAngle: endAngle,
circumference: circumference,
outerRadius: outerRadius,
innerRadius: innerRadius
startAngle,
endAngle: startAngle + circumference,
circumference,
outerRadius,
innerRadius,
options
};
startAngle += circumference;
arc._model = model;
// Set correct angles if not resetting
if (!reset || !animationOpts.animateRotate) {
if (i === 0) {
model.startAngle = opts.rotation;
} else {
model.startAngle = me._cachedMeta.data[i - 1]._model.endAngle;
}
model.endAngle = model.startAngle + model.circumference;
}
arc.pivot(chart._animationsDisabled);
me._updateElement(arc, i, properties, mode);
}
},
@ -304,7 +296,7 @@ module.exports = DatasetController.extend({
var me = this;
var max = 0;
var chart = me.chart;
var i, ilen, meta, arc, controller, options, borderWidth, hoverWidth;
var i, ilen, meta, controller, options;
if (!arcs) {
// Find the outmost visible dataset
@ -312,8 +304,9 @@ module.exports = DatasetController.extend({
if (chart.isDatasetVisible(i)) {
meta = chart.getDatasetMeta(i);
arcs = meta.data;
if (i !== me.index) {
controller = meta.controller;
controller = meta.controller;
if (controller !== me) {
controller._configure();
}
break;
}
@ -325,43 +318,14 @@ module.exports = DatasetController.extend({
}
for (i = 0, ilen = arcs.length; i < ilen; ++i) {
arc = arcs[i];
if (controller) {
controller._configure();
options = controller._resolveDataElementOptions(i);
} else {
options = arc._options;
}
options = controller._resolveDataElementOptions(i);
if (options.borderAlign !== 'inner') {
borderWidth = options.borderWidth;
hoverWidth = options.hoverBorderWidth;
max = borderWidth > max ? borderWidth : max;
max = hoverWidth > max ? hoverWidth : max;
max = Math.max(max, options.borderWidth || 0, options.hoverBorderWidth || 0);
}
}
return max;
},
/**
* @protected
*/
setHoverStyle: function(arc) {
var model = arc._model;
var options = arc._options;
var getHoverColor = helpers.getHoverColor;
arc.$previousStyle = {
backgroundColor: model.backgroundColor,
borderColor: model.borderColor,
borderWidth: model.borderWidth,
};
model.backgroundColor = valueOrDefault(options.hoverBackgroundColor, getHoverColor(options.backgroundColor));
model.borderColor = valueOrDefault(options.hoverBorderColor, getHoverColor(options.borderColor));
model.borderWidth = valueOrDefault(options.hoverBorderWidth, options.borderWidth);
},
/**
* Get radius length offset of the dataset in relation to the visible datasets weights. This allows determining the inner and outer radius correctly
* @private

View File

@ -7,7 +7,6 @@ const helpers = require('../helpers/index');
const valueOrDefault = helpers.valueOrDefault;
const resolve = helpers.options.resolve;
const isPointInArea = helpers.canvas._isPointInArea;
defaults._set('line', {
showLines: true,
@ -44,6 +43,7 @@ module.exports = DatasetController.extend({
'borderDashOffset',
'borderJoinStyle',
'borderWidth',
'capBezierPoints',
'cubicInterpolationMode',
'fill'
],
@ -56,6 +56,7 @@ module.exports = DatasetController.extend({
borderColor: 'pointBorderColor',
borderWidth: 'pointBorderWidth',
hitRadius: 'pointHitRadius',
hoverHitRadius: 'pointHitRadius',
hoverBackgroundColor: 'pointHoverBackgroundColor',
hoverBorderColor: 'pointHoverBorderColor',
hoverBorderWidth: 'pointHoverBorderWidth',
@ -65,7 +66,7 @@ module.exports = DatasetController.extend({
rotation: 'pointRotation'
},
update: function(reset) {
update: function(mode) {
const me = this;
const meta = me._cachedMeta;
const line = meta.dataset;
@ -73,62 +74,53 @@ module.exports = DatasetController.extend({
const options = me.chart.options;
const config = me._config;
const showLine = me._showLine = valueOrDefault(config.showLine, options.showLines);
let i, ilen;
// Update Line
if (showLine) {
// Data
line._children = points;
// Model
line._model = me._resolveDatasetElementOptions();
if (showLine && mode !== 'resize') {
line.pivot();
const properties = {
_children: points,
options: me._resolveDatasetElementOptions()
};
me._updateElement(line, undefined, properties, mode);
}
// Update Points
me.updateElements(points, 0, points.length, reset);
if (showLine && line._model.tension !== 0) {
me.updateBezierControlPoints();
}
// Now pivot the point for animation
for (i = 0, ilen = points.length; i < ilen; ++i) {
points[i].pivot(me.chart._animationsDisabled);
if (meta.visible) {
me.updateElements(points, 0, points.length, mode);
}
},
updateElements: function(points, start, count, reset) {
updateElements: function(points, start, count, mode) {
const me = this;
const reset = mode === 'reset';
const {xScale, yScale, _stacked} = me._cachedMeta;
const firstOpts = me._resolveDataElementOptions(start, mode);
const sharedOptions = me._getSharedOptions(mode, points[start], firstOpts);
const includeOptions = me._includeOptions(mode, sharedOptions);
let i;
for (i = start; i < start + count; ++i) {
const point = points[i];
const parsed = me._getParsed(i);
const options = me._resolveDataElementOptions(i);
const x = xScale.getPixelForValue(parsed[xScale.id]);
const y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(_stacked ? me._applyStack(yScale, parsed) : parsed[yScale.id]);
// Utility
point._options = options;
// Desired view properties
point._model = {
x: x,
y: y,
skip: isNaN(x) || isNaN(y),
// Appearance
radius: options.radius,
pointStyle: options.pointStyle,
rotation: options.rotation,
backgroundColor: options.backgroundColor,
borderColor: options.borderColor,
borderWidth: options.borderWidth,
// Tooltip
hitRadius: options.hitRadius
const properties = {
x,
y,
skip: isNaN(x) || isNaN(y)
};
if (includeOptions) {
properties.options = i === start ? firstOpts
: me._resolveDataElementOptions(i, mode);
}
me._updateElement(point, i, properties, mode);
}
me._updateSharedOptions(sharedOptions, mode);
},
/**
@ -161,67 +153,12 @@ module.exports = DatasetController.extend({
if (!data.length) {
return false;
}
const border = me._showLine ? meta.dataset._model.borderWidth : 0;
const border = me._showLine && meta.dataset.options.borderWidth || 0;
const firstPoint = data[0].size();
const lastPoint = data[data.length - 1].size();
return Math.max(border, firstPoint, lastPoint) / 2;
},
updateBezierControlPoints: function() {
const me = this;
const chart = me.chart;
const meta = me._cachedMeta;
const lineModel = meta.dataset._model;
const area = chart.chartArea;
let points = meta.data || [];
let i, ilen;
// Only consider points that are drawn in case the spanGaps option is used
if (lineModel.spanGaps) {
points = points.filter(function(pt) {
return !pt._model.skip;
});
}
function capControlPoint(pt, min, max) {
return Math.max(Math.min(pt, max), min);
}
if (lineModel.cubicInterpolationMode === 'monotone') {
helpers.curve.splineCurveMonotone(points);
} else {
for (i = 0, ilen = points.length; i < ilen; ++i) {
const model = points[i]._model;
const controlPoints = helpers.curve.splineCurve(
points[Math.max(0, i - 1)]._model,
model,
points[Math.min(i + 1, ilen - 1)]._model,
lineModel.tension
);
model.controlPointPreviousX = controlPoints.previous.x;
model.controlPointPreviousY = controlPoints.previous.y;
model.controlPointNextX = controlPoints.next.x;
model.controlPointNextY = controlPoints.next.y;
}
}
if (chart.options.elements.line.capBezierPoints) {
for (i = 0, ilen = points.length; i < ilen; ++i) {
const model = points[i]._model;
if (isPointInArea(model, area)) {
if (i > 0 && isPointInArea(points[i - 1]._model, area)) {
model.controlPointPreviousX = capControlPoint(model.controlPointPreviousX, area.left, area.right);
model.controlPointPreviousY = capControlPoint(model.controlPointPreviousY, area.top, area.bottom);
}
if (i < points.length - 1 && isPointInArea(points[i + 1]._model, area)) {
model.controlPointNextX = capControlPoint(model.controlPointNextX, area.left, area.right);
model.controlPointNextY = capControlPoint(model.controlPointNextY, area.top, area.bottom);
}
}
}
}
},
draw: function() {
const me = this;
const ctx = me._ctx;
@ -233,7 +170,7 @@ module.exports = DatasetController.extend({
let i = 0;
if (me._showLine) {
meta.dataset.draw(ctx);
meta.dataset.draw(ctx, area);
}
// Draw the points
@ -241,25 +178,4 @@ module.exports = DatasetController.extend({
points[i].draw(ctx, area);
}
},
/**
* @protected
*/
setHoverStyle: function(point) {
const model = point._model;
const options = point._options;
const getHoverColor = helpers.getHoverColor;
point.$previousStyle = {
backgroundColor: model.backgroundColor,
borderColor: model.borderColor,
borderWidth: model.borderWidth,
radius: model.radius
};
model.backgroundColor = valueOrDefault(options.hoverBackgroundColor, getHoverColor(options.backgroundColor));
model.borderColor = valueOrDefault(options.hoverBorderColor, getHoverColor(options.borderColor));
model.borderWidth = valueOrDefault(options.hoverBorderWidth, options.borderWidth);
model.radius = valueOrDefault(options.hoverRadius, options.radius);
},
});

View File

@ -8,6 +8,14 @@ var helpers = require('../helpers/index');
var resolve = helpers.options.resolve;
defaults._set('polarArea', {
animation: {
numbers: {
type: 'number',
properties: ['x', 'y', 'startAngle', 'endAngle', 'innerRadius', 'outerRadius']
},
animateRotate: true,
animateScale: true
},
scales: {
r: {
type: 'radialLinear',
@ -24,12 +32,6 @@ defaults._set('polarArea', {
}
},
// Boolean - Whether to animate the rotation of the chart
animation: {
animateRotate: true,
animateScale: true
},
startAngle: -0.5 * Math.PI,
legendCallback: function(chart) {
var list = document.createElement('ul');
@ -135,28 +137,14 @@ module.exports = DatasetController.extend({
return this._cachedMeta.rAxisID;
},
update: function(reset) {
update: function(mode) {
var me = this;
var dataset = me.getDataset();
var meta = me._cachedMeta;
var start = me.chart.options.startAngle || 0;
var starts = me._starts = [];
var angles = me._angles = [];
var arcs = meta.data;
var i, ilen, angle;
me._updateRadius();
meta.count = me.countVisibleElements();
for (i = 0, ilen = dataset.data.length; i < ilen; i++) {
starts[i] = start;
angle = me._computeAngle(i);
angles[i] = angle;
start += angle;
}
me.updateElements(arcs, 0, arcs.length, reset);
me.updateElements(arcs, 0, arcs.length, mode);
},
/**
@ -177,8 +165,9 @@ module.exports = DatasetController.extend({
me.innerRadius = me.outerRadius - chart.radiusLength;
},
updateElements: function(arcs, start, count, reset) {
updateElements: function(arcs, start, count, mode) {
const me = this;
const reset = mode === 'reset';
const chart = me.chart;
const dataset = me.getDataset();
const opts = chart.options;
@ -186,33 +175,43 @@ module.exports = DatasetController.extend({
const scale = chart.scales.r;
const centerX = scale.xCenter;
const centerY = scale.yCenter;
var i;
const datasetStartAngle = opts.startAngle || 0;
let angle = datasetStartAngle;
let i;
for (i = 0; i < start + count; i++) {
me._cachedMeta.count = me.countVisibleElements();
for (i = 0; i < start; ++i) {
angle += me._computeAngle(i);
}
for (; i < start + count; i++) {
const arc = arcs[i];
// var negHalfPI = -0.5 * Math.PI;
const datasetStartAngle = opts.startAngle;
const distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[i]);
const startAngle = me._starts[i];
const endAngle = startAngle + (arc.hidden ? 0 : me._angles[i]);
let startAngle = angle;
let endAngle = angle + me._computeAngle(i);
let outerRadius = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[i]);
angle = endAngle;
const resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[i]);
const options = arc._options = me._resolveDataElementOptions(i);
if (reset) {
if (animationOpts.animateScale) {
outerRadius = 0;
}
if (animationOpts.animateRotate) {
startAngle = datasetStartAngle;
endAngle = datasetStartAngle;
}
}
arc._model = {
backgroundColor: options.backgroundColor,
borderColor: options.borderColor,
borderWidth: options.borderWidth,
borderAlign: options.borderAlign,
const properties = {
x: centerX,
y: centerY,
innerRadius: 0,
outerRadius: reset ? resetRadius : distance,
startAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle,
endAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle
outerRadius,
startAngle,
endAngle,
options: me._resolveDataElementOptions(i)
};
arc.pivot(chart._animationsDisabled);
me._updateElement(arc, i, properties, mode);
}
},
@ -230,26 +229,6 @@ module.exports = DatasetController.extend({
return count;
},
/**
* @protected
*/
setHoverStyle: function(arc) {
var model = arc._model;
var options = arc._options;
var getHoverColor = helpers.getHoverColor;
var valueOrDefault = helpers.valueOrDefault;
arc.$previousStyle = {
backgroundColor: model.backgroundColor,
borderColor: model.borderColor,
borderWidth: model.borderWidth,
};
model.backgroundColor = valueOrDefault(options.hoverBackgroundColor, getHoverColor(options.backgroundColor));
model.borderColor = valueOrDefault(options.hoverBorderColor, getHoverColor(options.borderColor));
model.borderWidth = valueOrDefault(options.hoverBorderWidth, options.borderWidth);
},
/**
* @private
*/

View File

@ -21,14 +21,6 @@ defaults._set('radar', {
}
});
function nextItem(collection, index) {
return index >= collection.length - 1 ? collection[0] : collection[index + 1];
}
function previousItem(collection, index) {
return index <= 0 ? collection[collection.length - 1] : collection[index - 1];
}
module.exports = DatasetController.extend({
datasetElementType: elements.Line,
@ -93,66 +85,49 @@ module.exports = DatasetController.extend({
};
},
update: function(reset) {
update: function(mode) {
var me = this;
var meta = me._cachedMeta;
var line = meta.dataset;
var points = meta.data || [];
var animationsDisabled = me.chart._animationsDisabled;
var i, ilen;
// Data
line._children = points;
line._loop = true;
// Model
line._model = me._resolveDatasetElementOptions();
const properties = {
_children: points,
_loop: true,
options: me._resolveDatasetElementOptions()
};
line.pivot(animationsDisabled);
me._updateElement(line, undefined, properties, mode);
// Update Points
me.updateElements(points, 0, points.length, reset);
me.updateElements(points, 0, points.length, mode);
// Update bezier control points
me.updateBezierControlPoints();
// Now pivot the point for animation
for (i = 0, ilen = points.length; i < ilen; ++i) {
points[i].pivot(animationsDisabled);
}
line.updateControlPoints(me.chart.chartArea);
},
updateElements: function(points, start, count, reset) {
updateElements: function(points, start, count, mode) {
const me = this;
const dataset = me.getDataset();
const scale = me.chart.scales.r;
const reset = mode === 'reset';
var i;
for (i = start; i < start + count; i++) {
const point = points[i];
const pointPosition = scale.getPointPositionForValue(i, dataset.data[i]);
const options = me._resolveDataElementOptions(i);
const pointPosition = scale.getPointPositionForValue(i, dataset.data[i]);
const x = reset ? scale.xCenter : pointPosition.x;
const y = reset ? scale.yCenter : pointPosition.y;
// Utility
point._options = options;
// Desired view properties
point._model = {
x: x, // value not used in dataset scale, but we want a consistent API between scales
const properties = {
x: x,
y: y,
skip: isNaN(x) || isNaN(y),
// Appearance
radius: options.radius,
pointStyle: options.pointStyle,
rotation: options.rotation,
backgroundColor: options.backgroundColor,
borderColor: options.borderColor,
borderWidth: options.borderWidth,
// Tooltip
hitRadius: options.hitRadius
options,
};
me._updateElement(point, i, properties, mode);
}
},
@ -169,59 +144,5 @@ module.exports = DatasetController.extend({
values.tension = valueOrDefault(config.lineTension, options.elements.line.tension);
return values;
},
updateBezierControlPoints: function() {
var me = this;
var meta = me._cachedMeta;
var lineModel = meta.dataset._model;
var area = me.chart.chartArea;
var points = meta.data || [];
var i, ilen, model, controlPoints;
// Only consider points that are drawn in case the spanGaps option is used
if (meta.dataset._model.spanGaps) {
points = points.filter(function(pt) {
return !pt._model.skip;
});
}
function capControlPoint(pt, min, max) {
return Math.max(Math.min(pt, max), min);
}
for (i = 0, ilen = points.length; i < ilen; ++i) {
model = points[i]._model;
controlPoints = helpers.curve.splineCurve(
previousItem(points, i)._model,
model,
nextItem(points, i)._model,
lineModel.tension
);
// Prevent the bezier going outside of the bounds of the graph
model.controlPointPreviousX = capControlPoint(controlPoints.previous.x, area.left, area.right);
model.controlPointPreviousY = capControlPoint(controlPoints.previous.y, area.top, area.bottom);
model.controlPointNextX = capControlPoint(controlPoints.next.x, area.left, area.right);
model.controlPointNextY = capControlPoint(controlPoints.next.y, area.top, area.bottom);
}
},
setHoverStyle: function(point) {
var model = point._model;
var options = point._options;
var getHoverColor = helpers.getHoverColor;
point.$previousStyle = {
backgroundColor: model.backgroundColor,
borderColor: model.borderColor,
borderWidth: model.borderWidth,
radius: model.radius
};
model.backgroundColor = valueOrDefault(options.hoverBackgroundColor, getHoverColor(options.backgroundColor));
model.borderColor = valueOrDefault(options.hoverBorderColor, getHoverColor(options.borderColor));
model.borderWidth = valueOrDefault(options.hoverBorderWidth, options.borderWidth);
model.radius = valueOrDefault(options.hoverRadius, options.radius);
}
});

View File

@ -1,24 +1,92 @@
'use strict';
const Element = require('./core.element');
const helpers = require('../helpers/index');
class Animation extends Element {
const transparent = 'transparent';
const interpolators = {
number: function(from, to, factor) {
return from + (to - from) * factor;
},
color: function(from, to, factor) {
var c0 = helpers.color(from || transparent);
var c1 = c0.valid && helpers.color(to || transparent);
return c1 && c1.valid
? c1.mix(c0, factor).rgbaString()
: to;
}
};
constructor(props) {
super({
chart: null, // the animation associated chart instance
currentStep: 0, // the current animation step
numSteps: 60, // default number of steps
easing: '', // the easing to use for this animation
render: null, // render function used by the animation service
class Animation {
constructor(cfg, target, prop, to) {
const me = this;
let from = cfg.from;
onAnimationProgress: null, // user specified callback to fire on each step of the animation
onAnimationComplete: null, // user specified callback to fire when the animation finishes
});
helpers.extend(this, props);
if (from === undefined) {
from = target[prop];
}
if (to === undefined) {
to = target[prop];
}
if (from === undefined) {
from = to;
} else if (to === undefined) {
to = from;
}
me._active = true;
me._fn = cfg.fn || interpolators[cfg.type || typeof from];
me._easing = helpers.easing.effects[cfg.easing || 'linear'];
me._start = Math.floor(Date.now() + (cfg.delay || 0));
me._duration = Math.floor(cfg.duration);
me._loop = !!cfg.loop;
me._target = target;
me._prop = prop;
me._from = from;
me._to = to;
}
active() {
return this._active;
}
cancel() {
const me = this;
if (me._active) {
// update current evaluated value, for smoother animations
me.tick(Date.now());
me._active = false;
}
}
tick(date) {
const me = this;
const elapsed = date - me._start;
const duration = me._duration;
const prop = me._prop;
const from = me._from;
const loop = me._loop;
const to = me._to;
let factor;
me._active = from !== to && (loop || (elapsed < duration));
if (!me._active) {
me._target[prop] = to;
return;
}
if (elapsed < 0) {
me._target[prop] = from;
return;
}
factor = (elapsed / duration) % 2;
factor = loop && factor > 1 ? 2 - factor : factor;
factor = me._easing(Math.min(1, Math.max(0, factor)));
me._target[prop] = me._fn(from, to, factor);
}
}
module.exports = Animation;

View File

@ -1,121 +1,166 @@
'use strict';
var defaults = require('./core.defaults');
var helpers = require('../helpers/index');
const Animator = require('./core.animator');
const Animation = require('./core.animation');
const helpers = require('../helpers/index');
const defaults = require('./core.defaults');
defaults._set('global', {
animation: {
duration: 1000,
easing: 'easeOutQuart',
active: {
duration: 400
},
resize: {
duration: 0
},
numbers: {
type: 'number',
properties: ['x', 'y', 'borderWidth', 'radius', 'tension']
},
colors: {
type: 'color',
properties: ['borderColor', 'backgroundColor']
},
onProgress: helpers.noop,
onComplete: helpers.noop
}
});
module.exports = {
animations: [],
request: null,
function copyOptions(target, values) {
let oldOpts = target.options;
let newOpts = values.options;
if (!oldOpts || !newOpts || newOpts.$shared) {
return;
}
if (oldOpts.$shared) {
target.options = helpers.extend({}, oldOpts, newOpts, {$shared: false});
} else {
helpers.extend(oldOpts, newOpts);
}
delete values.options;
}
/**
* @param {Chart} chart - The chart to animate.
* @param {Chart.Animation} animation - The animation that we will animate.
* @param {number} duration - The animation duration in ms.
* @param {boolean} lazy - if true, the chart is not marked as animating to enable more responsive interactions
*/
addAnimation: function(chart, animation, duration, lazy) {
var animations = this.animations;
var i, ilen;
class Animations {
constructor(chart, animations) {
this._chart = chart;
this._properties = new Map();
this.configure(animations);
}
animation.chart = chart;
animation.startTime = Date.now();
animation.duration = duration;
configure(animations) {
const animatedProps = this._properties;
const animDefaults = Object.fromEntries(Object.entries(animations).filter(({1: value}) => !helpers.isObject(value)));
if (!lazy) {
chart.animating = true;
}
for (i = 0, ilen = animations.length; i < ilen; ++i) {
if (animations[i].chart === chart) {
animations[i] = animation;
return;
for (let [key, cfg] of Object.entries(animations)) {
if (!helpers.isObject(cfg)) {
continue;
}
}
animations.push(animation);
// If there are no animations queued, manually kickstart a digest, for lack of a better word
if (animations.length === 1) {
this.requestAnimationFrame();
}
},
cancelAnimation: function(chart) {
var index = helpers.findIndex(this.animations, function(animation) {
return animation.chart === chart;
});
if (index !== -1) {
this.animations.splice(index, 1);
chart.animating = false;
}
},
requestAnimationFrame: function() {
var me = this;
if (me.request === null) {
// Skip animation frame requests until the active one is executed.
// This can happen when processing mouse events, e.g. 'mousemove'
// and 'mouseout' events will trigger multiple renders.
me.request = helpers.requestAnimFrame.call(window, function() {
me.request = null;
me.startDigest();
});
}
},
/**
* @private
*/
startDigest: function() {
var me = this;
me.advance();
// Do we have more stuff to animate?
if (me.animations.length > 0) {
me.requestAnimationFrame();
}
},
/**
* @private
*/
advance: function() {
var animations = this.animations;
var animation, chart, numSteps, nextStep;
var i = 0;
// 1 animation per chart, so we are looping charts here
while (i < animations.length) {
animation = animations[i];
chart = animation.chart;
numSteps = animation.numSteps;
// Make sure that currentStep starts at 1
// https://github.com/chartjs/Chart.js/issues/6104
nextStep = Math.floor((Date.now() - animation.startTime) / animation.duration * numSteps) + 1;
animation.currentStep = Math.min(nextStep, numSteps);
helpers.callback(animation.render, [chart, animation], chart);
helpers.callback(animation.onAnimationProgress, [animation], chart);
if (animation.currentStep >= numSteps) {
helpers.callback(animation.onAnimationComplete, [animation], chart);
chart.animating = false;
animations.splice(i, 1);
} else {
++i;
for (let prop of cfg.properties || [key]) {
// Can have only one config per animation.
if (!animatedProps.has(prop)) {
animatedProps.set(prop, helpers.extend({}, animDefaults, cfg));
} else if (prop === key) {
// Single property targetting config wins over multi-targetting.
animatedProps.set(prop, helpers.extend({}, animatedProps.get(prop), cfg));
}
}
}
}
};
/**
* Utility to handle animation of `options`.
* This should not be called, when animating $shared options to $shared new options.
* @private
* @todo if new options are $shared, target.options should be replaced with those new shared
* options after all animations have completed
*/
_animateOptions(target, values) {
const newOptions = values.options;
let animations = [];
if (!newOptions) {
return animations;
}
let options = target.options;
if (options) {
if (options.$shared) {
// If the current / old options are $shared, meaning other elements are
// using the same options, we need to clone to become unique.
target.options = options = helpers.extend({}, options, {$shared: false, $animations: {}});
}
animations = this._createAnimations(options, newOptions);
} else {
target.options = newOptions;
}
return animations;
}
/**
* @private
*/
_createAnimations(target, values) {
const animatedProps = this._properties;
const animations = [];
const running = target.$animations || (target.$animations = {});
const props = Object.keys(values);
let i;
for (i = props.length - 1; i >= 0; --i) {
let prop = props[i];
if (prop.charAt(0) === '$') {
continue;
}
if (prop === 'options') {
animations.push.apply(animations, this._animateOptions(target, values));
continue;
}
let value = values[prop];
const cfg = animatedProps.get(prop);
if (!cfg || !cfg.duration) {
// not animated, set directly to new value
target[prop] = value;
continue;
}
let animation = running[prop];
if (animation) {
animation.cancel();
}
running[prop] = animation = new Animation(cfg, target, prop, value);
animations.push(animation);
}
return animations;
}
/**
* Update `target` properties to new values, using configured animations
* @param {object} target - object to update
* @param {object} values - new target properties
* @returns {boolean|undefined} - `true` if animations were started
**/
update(target, values) {
if (this._properties.size === 0) {
// Nothing is animated, just apply the new values.
// Options can be shared, need to account for that.
copyOptions(target, values);
// copyOptions removes the `options` from `values`,
// unless it can be directly assigned.
helpers.extend(target, values);
return;
}
const animations = this._createAnimations(target, values);
if (animations.length) {
Animator.add(this._chart, animations);
return true;
}
}
}
module.exports = Animations;

211
src/core/core.animator.js Normal file
View File

@ -0,0 +1,211 @@
'use strict';
const helpers = require('../helpers/index');
function drawFPS(chart, count, date, lastDate) {
const fps = (1000 / (date - lastDate)) | 0;
const ctx = chart.ctx;
ctx.save();
ctx.clearRect(0, 0, 50, 24);
ctx.fillStyle = 'black';
ctx.textAlign = 'right';
if (count) {
ctx.fillText(count, 50, 8);
ctx.fillText(fps + ' fps', 50, 18);
}
ctx.restore();
}
class Animator {
constructor() {
this._request = null;
this._charts = new Map();
this._running = false;
}
/**
* @private
*/
_notify(chart, anims, date, type) {
const callbacks = anims.listeners[type] || [];
const numSteps = anims.duration;
callbacks.forEach(fn => fn({
chart: chart,
numSteps,
currentStep: date - anims.start
}));
}
/**
* @private
*/
_refresh() {
const me = this;
if (me._request) {
return;
}
me._running = true;
me._request = helpers.requestAnimFrame.call(window, function() {
me._update();
me._request = null;
if (me._running) {
me._refresh();
}
});
}
/**
* @private
*/
_update() {
const me = this;
const date = Date.now();
const charts = me._charts;
let remaining = 0;
for (let [chart, anims] of charts) {
if (!anims.running || !anims.items.length) {
continue;
}
const items = anims.items;
let i = items.length - 1;
let draw = false;
let item;
for (; i >= 0; --i) {
item = items[i];
if (item._active) {
item.tick(date);
draw = true;
} else {
// Remove the item by replacing it with last item and removing the last
// A lot faster than splice.
items[i] = items[items.length - 1];
items.pop();
}
}
if (draw) {
chart.draw();
if (chart.options.animation.debug) {
drawFPS(chart, items.length, date, me._lastDate);
}
}
me._notify(chart, anims, date, 'progress');
if (!items.length) {
anims.running = false;
me._notify(chart, anims, date, 'complete');
}
remaining += items.length;
}
this._lastDate = date;
if (remaining === 0) {
this._running = false;
}
}
_getAnims(chart) {
const charts = this._charts;
let anims = charts.get(chart);
if (!anims) {
anims = {
running: false,
items: [],
listeners: {
complete: [],
progress: []
}
};
charts.set(chart, anims);
}
return anims;
}
/**
* @param {Chart} chart
* @param {string} event - event name
* @param {Function} cb - callback
*/
listen(chart, event, cb) {
this._getAnims(chart).listeners[event].push(cb);
}
/**
* Add animations
* @param {Chart} chart
* @param {Animation[]} items - animations
*/
add(chart, items) {
if (!items || !items.length) {
return;
}
this._getAnims(chart).items.push(...items);
}
/**
* Counts number of active animations for the chart
* @param {Chart} chart
*/
has(chart) {
return this._getAnims(chart).items.length > 0;
}
/**
* Start animating (all charts)
* @param {Chart} chart
*/
start(chart) {
const anims = this._charts.get(chart);
if (!anims) {
return;
}
anims.running = true;
anims.start = Date.now();
anims.duration = anims.items.reduce((acc, cur) => Math.max(acc, cur._duration), 0);
this._refresh();
}
running(chart) {
if (!this._running) {
return false;
}
const anims = this._charts.get(chart);
if (!anims || !anims.running || !anims.items.length) {
return false;
}
return true;
}
/**
* Stop all animations for the chart
* @param {Chart} chart
*/
stop(chart) {
const anims = this._charts.get(chart);
if (!anims || !anims.items.length) {
return;
}
const items = anims.items;
let i = items.length - 1;
for (; i >= 0; --i) {
items[i].cancel();
}
anims.items = [];
this._notify(chart, anims, Date.now(), 'complete');
}
}
const instance = new Animator();
module.exports = instance;

View File

@ -1,7 +1,6 @@
'use strict';
var Animation = require('./core.animation');
var animations = require('./core.animations');
var Animator = require('./core.animator');
var controllers = require('../controllers/index');
var defaults = require('./core.defaults');
var helpers = require('../helpers/index');
@ -26,13 +25,11 @@ defaults._set('global', {
hover: {
onHover: null,
mode: 'nearest',
intersect: true,
animationDuration: 400
intersect: true
},
onClick: null,
maintainAspectRatio: true,
responsive: true,
responsiveAnimationDuration: 0
responsive: true
});
function mergeScaleConfig(config, options) {
@ -115,11 +112,7 @@ function initConfig(config) {
}
function isAnimationDisabled(config) {
return !config.animation || !(
config.animation.duration ||
(config.hover && config.hover.animationDuration) ||
config.responsiveAnimationDuration
);
return !config.animation;
}
function updateConfig(chart) {
@ -143,8 +136,6 @@ function updateConfig(chart) {
chart.ensureScalesHaveIDs();
chart.buildOrUpdateScales();
// Tooltip
chart.tooltip._options = newOptions.tooltips;
chart.tooltip.initialize();
}
@ -161,6 +152,20 @@ function compare2Level(l1, l2) {
};
}
function onAnimationsComplete(ctx) {
const chart = ctx.chart;
const animationOptions = chart.options.animation;
plugins.notify(chart, 'afterRender');
helpers.callback(animationOptions && animationOptions.onComplete, arguments, chart);
}
function onAnimationProgress(ctx) {
const chart = ctx.chart;
const animationOptions = chart.options.animation;
helpers.callback(animationOptions && animationOptions.onProgress, arguments, chart);
}
var Chart = function(item, config) {
this.construct(item, config);
return this;
@ -213,6 +218,9 @@ helpers.extend(Chart.prototype, /** @lends Chart */ {
return;
}
Animator.listen(me, 'complete', onAnimationsComplete);
Animator.listen(me, 'progress', onAnimationProgress);
me.initialize();
me.update();
},
@ -249,8 +257,7 @@ helpers.extend(Chart.prototype, /** @lends Chart */ {
},
stop: function() {
// Stops any current animation loop occurring
animations.cancelAnimation(this);
Animator.stop(this);
return this;
},
@ -289,9 +296,7 @@ helpers.extend(Chart.prototype, /** @lends Chart */ {
}
me.stop();
me.update({
duration: options.responsiveAnimationDuration
});
me.update('resize');
}
},
@ -455,11 +460,11 @@ helpers.extend(Chart.prototype, /** @lends Chart */ {
this.tooltip.initialize();
},
update: function(config) {
update: function(mode) {
var me = this;
var i, ilen;
config = config || {};
me._updating = true;
updateConfig(me);
@ -471,9 +476,6 @@ helpers.extend(Chart.prototype, /** @lends Chart */ {
return;
}
// In case the entire data object changed
me.tooltip._data = me.data;
// Make sure dataset controllers are updated and new controllers are reset
var newControllers = me.buildOrUpdateControllers();
@ -485,40 +487,27 @@ helpers.extend(Chart.prototype, /** @lends Chart */ {
me.updateLayout();
// Can only reset the new controllers after the scales have been updated
if (me.options.animation && me.options.animation.duration) {
if (me.options.animation) {
helpers.each(newControllers, function(controller) {
controller.reset();
});
}
me.updateDatasets();
// Need to reset tooltip in case it is displayed with elements that are removed
// after update.
me.tooltip.initialize();
// Last active contains items that were previously hovered.
me.lastActive = [];
me.updateDatasets(mode);
// Do this before render so that any plugins that need final scale updates can use it
plugins.notify(me, 'afterUpdate');
me._layers.sort(compare2Level('z', '_idx'));
if (me._bufferedRender) {
me._bufferedRequest = {
duration: config.duration,
easing: config.easing,
lazy: config.lazy
};
} else {
me.render(config);
}
// Replay last event from before update
if (me._lastEvent) {
me.eventHandler(me._lastEvent);
}
me.render();
me._updating = false;
},
/**
@ -557,7 +546,7 @@ helpers.extend(Chart.prototype, /** @lends Chart */ {
* hook, in which case, plugins will not be called on `afterDatasetsUpdate`.
* @private
*/
updateDatasets: function() {
updateDatasets: function(mode) {
var me = this;
if (plugins.notify(me, 'beforeDatasetsUpdate') === false) {
@ -565,7 +554,7 @@ helpers.extend(Chart.prototype, /** @lends Chart */ {
}
for (var i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
me.updateDataset(i);
me.updateDataset(i, mode);
}
plugins.notify(me, 'afterDatasetsUpdate');
@ -576,91 +565,52 @@ helpers.extend(Chart.prototype, /** @lends Chart */ {
* hook, in which case, plugins will not be called on `afterDatasetUpdate`.
* @private
*/
updateDataset: function(index) {
var me = this;
var meta = me.getDatasetMeta(index);
var args = {
meta: meta,
index: index
};
updateDataset: function(index, mode) {
const me = this;
const meta = me.getDatasetMeta(index);
const args = {meta, index, mode};
if (plugins.notify(me, 'beforeDatasetUpdate', [args]) === false) {
return;
}
meta.controller._update();
meta.controller._update(mode);
plugins.notify(me, 'afterDatasetUpdate', [args]);
},
render: function(config) {
var me = this;
if (!config || typeof config !== 'object') {
// backwards compatibility
config = {
duration: config,
lazy: arguments[1]
};
}
var animationOptions = me.options.animation;
var duration = valueOrDefault(config.duration, animationOptions && animationOptions.duration);
var lazy = config.lazy;
render: function() {
const me = this;
const animationOptions = me.options.animation;
if (plugins.notify(me, 'beforeRender') === false) {
return;
}
var onComplete = function(animation) {
var onComplete = function() {
plugins.notify(me, 'afterRender');
helpers.callback(animationOptions && animationOptions.onComplete, [animation], me);
helpers.callback(animationOptions && animationOptions.onComplete, [], me);
};
if (animationOptions && duration) {
var animation = new Animation({
numSteps: duration / 16.66, // 60 fps
easing: config.easing || animationOptions.easing,
render: function(chart, animationObject) {
const easingFunction = helpers.easing.effects[animationObject.easing];
const stepDecimal = animationObject.currentStep / animationObject.numSteps;
chart.draw(easingFunction(stepDecimal));
},
onAnimationProgress: animationOptions.onProgress,
onAnimationComplete: onComplete
});
animations.addAnimation(me, animation, duration, lazy);
if (Animator.has(me)) {
if (!Animator.running(me)) {
Animator.start(me);
}
} else {
me.draw();
// See https://github.com/chartjs/Chart.js/issues/3781
onComplete(new Animation({numSteps: 0, chart: me}));
onComplete();
}
return me;
},
draw: function(easingValue) {
draw: function() {
var me = this;
var i, layers;
me.clear();
if (helpers.isNullOrUndef(easingValue)) {
easingValue = 1;
}
me.transition(easingValue);
if (me.width <= 0 || me.height <= 0) {
return;
}
if (plugins.notify(me, 'beforeDraw', [easingValue]) === false) {
if (plugins.notify(me, 'beforeDraw') === false) {
return;
}
@ -672,41 +622,16 @@ helpers.extend(Chart.prototype, /** @lends Chart */ {
layers[i].draw(me.chartArea);
}
me.drawDatasets(easingValue);
me.drawDatasets();
// Rest of layers
for (; i < layers.length; ++i) {
layers[i].draw(me.chartArea);
}
me._drawTooltip(easingValue);
me._drawTooltip();
plugins.notify(me, 'afterDraw', [easingValue]);
},
/**
* @private
*/
transition: function(easingValue) {
const me = this;
var i, ilen;
if (!me._animationsDisabled) {
const metas = me._getSortedDatasetMetas();
for (i = 0, ilen = metas.length; i < ilen; ++i) {
let meta = metas[i];
if (meta.visible) {
meta.controller.transition(easingValue);
}
}
}
me.tooltip.transition(easingValue);
if (me._lastEvent && me.animating) {
// If, during animation, element under mouse changes, let's react to that.
me.handleEvent(me._lastEvent);
}
plugins.notify(me, 'afterDraw');
},
/**
@ -740,20 +665,20 @@ helpers.extend(Chart.prototype, /** @lends Chart */ {
* hook, in which case, plugins will not be called on `afterDatasetsDraw`.
* @private
*/
drawDatasets: function(easingValue) {
drawDatasets: function() {
var me = this;
var metasets, i;
if (plugins.notify(me, 'beforeDatasetsDraw', [easingValue]) === false) {
if (plugins.notify(me, 'beforeDatasetsDraw') === false) {
return;
}
metasets = me._getSortedVisibleDatasetMetas();
for (i = metasets.length - 1; i >= 0; --i) {
me.drawDataset(metasets[i], easingValue);
me.drawDataset(metasets[i]);
}
plugins.notify(me, 'afterDatasetsDraw', [easingValue]);
plugins.notify(me, 'afterDatasetsDraw');
},
/**
@ -761,7 +686,7 @@ helpers.extend(Chart.prototype, /** @lends Chart */ {
* hook, in which case, plugins will not be called on `afterDatasetDraw`.
* @private
*/
drawDataset: function(meta, easingValue) {
drawDataset: function(meta) {
var me = this;
var ctx = me.ctx;
var clip = meta._clip;
@ -770,7 +695,6 @@ helpers.extend(Chart.prototype, /** @lends Chart */ {
var args = {
meta: meta,
index: meta.index,
easingValue: easingValue
};
if (plugins.notify(me, 'beforeDatasetDraw', [args]) === false) {
@ -784,7 +708,7 @@ helpers.extend(Chart.prototype, /** @lends Chart */ {
bottom: clip.bottom === false ? canvas.height : area.bottom + clip.bottom
});
meta.controller.draw(easingValue);
meta.controller.draw();
helpers.canvas.unclipArea(ctx);
@ -796,19 +720,18 @@ helpers.extend(Chart.prototype, /** @lends Chart */ {
* hook, in which case, plugins will not be called on `afterTooltipDraw`.
* @private
*/
_drawTooltip: function(easingValue) {
_drawTooltip: function() {
var me = this;
var tooltip = me.tooltip;
var args = {
tooltip: tooltip,
easingValue: easingValue
tooltip: tooltip
};
if (plugins.notify(me, 'beforeTooltipDraw', [args]) === false) {
return;
}
tooltip.draw();
tooltip.draw(me.ctx);
plugins.notify(me, 'afterTooltipDraw', [args]);
},
@ -925,12 +848,7 @@ helpers.extend(Chart.prototype, /** @lends Chart */ {
},
initToolTip: function() {
var me = this;
me.tooltip = new Tooltip({
_chart: me,
_data: me.data,
_options: me.options.tooltips
});
this.tooltip = new Tooltip({_chart: this});
},
/**
@ -1020,48 +938,22 @@ helpers.extend(Chart.prototype, /** @lends Chart */ {
* @private
*/
eventHandler: function(e) {
var me = this;
var tooltip = me.tooltip;
const me = this;
const tooltip = me.tooltip;
if (plugins.notify(me, 'beforeEvent', [e]) === false) {
return;
}
// Buffer any update calls so that renders do not occur
me._bufferedRender = true;
me._bufferedRequest = null;
me.handleEvent(e);
var changed = me.handleEvent(e);
// for smooth tooltip animations issue #4989
// the tooltip should be the source of change
// Animation check workaround:
// tooltip._start will be null when tooltip isn't animating
if (tooltip) {
changed = tooltip._start
? tooltip.handleEvent(e)
: changed | tooltip.handleEvent(e);
tooltip.handleEvent(e);
}
plugins.notify(me, 'afterEvent', [e]);
var bufferedRequest = me._bufferedRequest;
if (bufferedRequest) {
// If we have an update that was triggered, we need to do a normal render
me.render(bufferedRequest);
} else if (changed && !me.animating) {
// If entering, leaving, or changing elements, animate the change via pivot
me.stop();
// We only need to render at this point. Updating will cause scales to be
// recomputed generating flicker & using more memory than necessary.
me.render({
duration: me.options.hover.animationDuration,
lazy: true
});
}
me._bufferedRender = false;
me._bufferedRequest = null;
me.render();
return me;
},
@ -1086,7 +978,7 @@ helpers.extend(Chart.prototype, /** @lends Chart */ {
me._lastEvent = null;
} else {
me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions);
me._lastEvent = e.type === 'click' ? null : e;
me._lastEvent = e.type === 'click' ? me._lastEvent : e;
}
// Invoke onHover hook
@ -1100,8 +992,10 @@ helpers.extend(Chart.prototype, /** @lends Chart */ {
}
}
me._updateHoverStyles();
changed = !helpers._elementsEqual(me.active, me.lastActive);
if (changed) {
me._updateHoverStyles();
}
// Remember Last Actives
me.lastActive = me.active;

View File

@ -1,6 +1,7 @@
'use strict';
var helpers = require('../helpers/index');
var Animations = require('./core.animations');
var resolve = helpers.options.resolve;
@ -268,6 +269,8 @@ helpers.extend(DatasetController.prototype, {
me.chart = chart;
me._ctx = chart.ctx;
me.index = datasetIndex;
me._cachedAnimations = {};
me._cachedDataOpts = {};
me._cachedMeta = meta = me.getMeta();
me._type = meta.type;
me._configure();
@ -347,7 +350,7 @@ helpers.extend(DatasetController.prototype, {
},
reset: function() {
this._update(true);
this._update('reset');
},
/**
@ -450,7 +453,7 @@ helpers.extend(DatasetController.prototype, {
},
/**
* Returns the merged user-supplied and default dataset-level options
* Merges user-supplied and default dataset-level options
* @private
*/
_configure: function() {
@ -740,33 +743,19 @@ helpers.extend(DatasetController.prototype, {
/**
* @private
*/
_update: function(reset) {
_update: function(mode) {
const me = this;
const meta = me._cachedMeta;
me._configure();
me._cachedDataOpts = null;
me.update(reset);
me._cachedAnimations = {};
me._cachedDataOpts = {};
me.update(mode);
meta._clip = toClip(helpers.valueOrDefault(me._config.clip, defaultClip(meta.xScale, meta.yScale, me._getMaxOverflow())));
me._cacheScaleStackStatus();
},
update: helpers.noop,
transition: function(easingValue) {
const meta = this._cachedMeta;
const elements = meta.data || [];
const ilen = elements.length;
let i = 0;
for (; i < ilen; ++i) {
elements[i].transition(easingValue);
}
if (meta.dataset) {
meta.dataset.transition(easingValue);
}
},
draw: function() {
const ctx = this._ctx;
const meta = this._cachedMeta;
@ -783,30 +772,54 @@ helpers.extend(DatasetController.prototype, {
}
},
_addAutomaticHoverColors: function(index, options) {
const me = this;
const getHoverColor = helpers.getHoverColor;
const normalOptions = me.getStyle(index);
const missingColors = Object.keys(normalOptions).filter(key => {
return key.indexOf('Color') !== -1 && !(key in options);
});
let i = missingColors.length - 1;
let color;
for (; i >= 0; i--) {
color = missingColors[i];
options[color] = getHoverColor(normalOptions[color]);
}
},
/**
* Returns a set of predefined style properties that should be used to represent the dataset
* or the data if the index is specified
* @param {number} index - data index
* @return {IStyleInterface} style object
*/
getStyle: function(index) {
getStyle: function(index, active) {
const me = this;
const meta = me._cachedMeta;
const dataset = meta.dataset;
let style;
if (dataset && index === undefined) {
style = me._resolveDatasetElementOptions();
} else {
index = index || 0;
style = me._resolveDataElementOptions(index);
if (!me._config) {
me._configure();
}
if (style.fill === false || style.fill === null) {
style.backgroundColor = style.borderColor;
const options = dataset && index === undefined
? me._resolveDatasetElementOptions(active)
: me._resolveDataElementOptions(index || 0, active && 'active');
if (active) {
me._addAutomaticHoverColors(index, options);
}
return options;
},
_getContext(index, active) {
return {
chart: this.chart,
dataIndex: index,
dataset: this.getDataset(),
datasetIndex: this.index,
active
};
return style;
},
/**
@ -819,21 +832,19 @@ helpers.extend(DatasetController.prototype, {
const options = chart.options.elements[me.datasetElementType.prototype._type] || {};
const elementOptions = me._datasetElementOptions;
const values = {};
const context = {
chart,
dataset: me.getDataset(),
datasetIndex: me.index,
active
};
let i, ilen, key, readKey;
const context = me._getContext(undefined, active);
let i, ilen, key, readKey, value;
for (i = 0, ilen = elementOptions.length; i < ilen; ++i) {
key = elementOptions[i];
readKey = active ? 'hover' + key.charAt(0).toUpperCase() + key.slice(1) : key;
values[key] = resolve([
value = resolve([
datasetOpts[readKey],
options[readKey]
], context);
if (value !== undefined) {
values[key] = value;
}
}
return values;
@ -842,72 +853,152 @@ helpers.extend(DatasetController.prototype, {
/**
* @private
*/
_resolveDataElementOptions: function(index) {
_resolveDataElementOptions: function(index, mode) {
const me = this;
const active = mode === 'active';
const cached = me._cachedDataOpts;
if (cached) {
return cached;
if (cached[mode]) {
return cached[mode];
}
const chart = me.chart;
const datasetOpts = me._config;
const options = chart.options.elements[me.dataElementType.prototype._type] || {};
const elementOptions = me._dataElementOptions;
const values = {};
const context = {
chart: chart,
dataIndex: index,
dataset: me.getDataset(),
datasetIndex: me.index
};
const info = {cacheable: true};
let keys, i, ilen, key;
const context = me._getContext(index, active);
const info = {cacheable: !active};
let keys, i, ilen, key, value, readKey;
if (helpers.isArray(elementOptions)) {
for (i = 0, ilen = elementOptions.length; i < ilen; ++i) {
key = elementOptions[i];
values[key] = resolve([
datasetOpts[key],
options[key]
readKey = active ? 'hover' + key.charAt(0).toUpperCase() + key.slice(1) : key;
value = resolve([
datasetOpts[readKey],
options[readKey]
], context, index, info);
if (value !== undefined) {
values[key] = value;
}
}
} else {
keys = Object.keys(elementOptions);
for (i = 0, ilen = keys.length; i < ilen; ++i) {
key = keys[i];
values[key] = resolve([
datasetOpts[elementOptions[key]],
datasetOpts[key],
options[key]
readKey = active ? 'hover' + key.charAt(0).toUpperCase() + key.slice(1) : key;
value = resolve([
datasetOpts[elementOptions[readKey]],
datasetOpts[readKey],
options[readKey]
], context, index, info);
if (value !== undefined) {
values[key] = value;
}
}
}
if (info.cacheable) {
me._cachedDataOpts = Object.freeze(values);
// `$shared` indicades this set of options can be shared between multiple elements.
// Sharing is used to reduce number of properties to change during animation.
values.$shared = true;
// We cache options by `mode`, which can be 'active' for example. This enables us
// to have the 'active' element options and 'default' options to switch between
// when interacting.
cached[mode] = values;
}
return values;
},
removeHoverStyle: function(element) {
helpers.merge(element._model, element.$previousStyle || {});
delete element.$previousStyle;
/**
* @private
*/
_resolveAnimations: function(index, mode, active) {
const me = this;
const chart = me.chart;
const cached = me._cachedAnimations;
mode = mode || 'default';
if (cached[mode]) {
return cached[mode];
}
const info = {cacheable: true};
const context = me._getContext(index, active);
const datasetAnim = resolve([me._config.animation], context, index, info);
const chartAnim = resolve([chart.options.animation], context, index, info);
let config = helpers.mergeIf({}, [datasetAnim, chartAnim]);
if (active && config.active) {
config = helpers.extend({}, config, config.active);
}
if (mode === 'resize' && config.resize) {
config = helpers.extend({}, config, config.resize);
}
const animations = new Animations(chart, config);
if (info.cacheable) {
cached[mode] = animations && Object.freeze(animations);
}
return animations;
},
/**
* Utility for checking if the options are shared and should be animated separately.
* @private
*/
_getSharedOptions: function(mode, el, options) {
if (mode !== 'reset' && options && options.$shared && el && el.options && el.options.$shared) {
return {target: el.options, options};
}
},
/**
* Utility for determining if `options` should be included in the updated properties
* @private
*/
_includeOptions: function(mode, sharedOptions) {
return mode !== 'resize' && !sharedOptions;
},
/**
* Utility for updating a element with new properties, using animations when appropriate.
* @private
*/
_updateElement: function(element, index, properties, mode) {
if (mode === 'reset' || mode === 'none') {
helpers.extend(element, properties);
} else {
this._resolveAnimations(index, mode).update(element, properties);
}
},
/**
* Utility to animate the shared options, that are potentially affecting multiple elements.
* @private
*/
_updateSharedOptions: function(sharedOptions, mode) {
if (sharedOptions) {
this._resolveAnimations(undefined, mode).update(sharedOptions.target, sharedOptions.options);
}
},
/**
* @private
*/
_setStyle(element, index, mode, active) {
this._resolveAnimations(index, mode, active).update(element, {options: this.getStyle(index, active)});
},
removeHoverStyle: function(element, datasetIndex, index) {
this._setStyle(element, index, 'active', false);
},
setHoverStyle: function(element, datasetIndex, index) {
const dataset = this.chart.data.datasets[datasetIndex];
const model = element._model;
const getHoverColor = helpers.getHoverColor;
element.$previousStyle = {
backgroundColor: model.backgroundColor,
borderColor: model.borderColor,
borderWidth: model.borderWidth
};
model.backgroundColor = resolve([dataset.hoverBackgroundColor, getHoverColor(model.backgroundColor)], undefined, index);
model.borderColor = resolve([dataset.hoverBorderColor, getHoverColor(model.borderColor)], undefined, index);
model.borderWidth = resolve([dataset.hoverBorderWidth, model.borderWidth], undefined, index);
this._setStyle(element, index, 'active', true);
},
/**
@ -917,7 +1008,7 @@ helpers.extend(DatasetController.prototype, {
const element = this._cachedMeta.dataset;
if (element) {
this.removeHoverStyle(element);
this._setStyle(element, undefined, 'active', false);
}
},
@ -926,24 +1017,10 @@ helpers.extend(DatasetController.prototype, {
*/
_setDatasetHoverStyle: function() {
const element = this._cachedMeta.dataset;
const prev = {};
let i, ilen, key, keys, hoverOptions, model;
if (!element) {
return;
if (element) {
this._setStyle(element, undefined, 'active', true);
}
model = element._model;
hoverOptions = this._resolveDatasetElementOptions(true);
keys = Object.keys(hoverOptions);
for (i = 0, ilen = keys.length; i < ilen; ++i) {
key = keys[i];
prev[key] = model[key];
model[key] = hoverOptions[key];
}
element.$previousStyle = prev;
},
/**
@ -986,7 +1063,7 @@ helpers.extend(DatasetController.prototype, {
}
me._parse(start, count);
me.updateElements(data, start, count);
me.updateElements(data, start, count, 'reset');
},
/**

View File

@ -1,119 +1,27 @@
'use strict';
import color from 'chartjs-color';
import helpers from '../helpers/index';
import {extend, inherits} from '../helpers/helpers.core';
import {isNumber} from '../helpers/helpers.math';
function interpolate(start, view, model, ease) {
var keys = Object.keys(model);
var i, ilen, key, actual, origin, target, type, c0, c1;
for (i = 0, ilen = keys.length; i < ilen; ++i) {
key = keys[i];
target = model[key];
// if a value is added to the model after pivot() has been called, the view
// doesn't contain it, so let's initialize the view to the target value.
if (!Object.prototype.hasOwnProperty.call(view, key)) {
view[key] = target;
}
actual = view[key];
if (actual === target || key[0] === '_') {
continue;
}
if (!Object.prototype.hasOwnProperty.call(start, key)) {
start[key] = actual;
}
origin = start[key];
type = typeof target;
if (type === typeof origin) {
if (type === 'string') {
c0 = color(origin);
if (c0.valid) {
c1 = color(target);
if (c1.valid) {
view[key] = c1.mix(c0, ease).rgbString();
continue;
}
}
} else if (helpers.isFinite(origin) && helpers.isFinite(target)) {
view[key] = origin + (target - origin) * ease;
continue;
}
}
view[key] = target;
}
}
class Element {
constructor(configuration) {
helpers.extend(this, configuration);
extend(this, configuration);
// this.hidden = false; we assume Element has an attribute called hidden, but do not initialize to save memory
}
pivot(animationsDisabled) {
var me = this;
if (animationsDisabled) {
me._view = me._model;
return me;
}
if (!me._view) {
me._view = helpers.extend({}, me._model);
}
me._start = {};
return me;
}
transition(ease) {
var me = this;
var model = me._model;
var start = me._start;
var view = me._view;
// No animation -> No Transition
if (!model || ease === 1) {
// _model has to be cloned to _view
// Otherwise, when _model properties are set on hover, _view.* is also set to the same value, and hover animation doesn't occur
me._view = helpers.extend({}, model);
me._start = null;
return me;
}
if (!view) {
view = me._view = {};
}
if (!start) {
start = me._start = {};
}
interpolate(start, view, model, ease);
return me;
}
tooltipPosition() {
return {
x: this._model.x,
y: this._model.y
x: this.x,
y: this.y
};
}
hasValue() {
return isNumber(this._model.x) && isNumber(this._model.y);
return isNumber(this.x) && isNumber(this.y);
}
}
Element.extend = helpers.inherits;
Element.extend = inherits;
export default Element;

View File

@ -33,7 +33,7 @@ function evaluateAllVisibleItems(chart, handler) {
({index, data} = metasets[i]);
for (let j = 0, jlen = data.length; j < jlen; ++j) {
element = data[j];
if (!element._view.skip) {
if (!element.skip) {
handler(element, index, j);
}
}
@ -66,7 +66,7 @@ function evaluateItemsAtIndex(chart, axis, position, handler) {
const metaset = metasets[i];
const index = indices[i];
const element = metaset.data[index];
if (!element._view.skip) {
if (!element.skip) {
handler(element, metaset.index, index);
}
}
@ -193,7 +193,7 @@ export default {
const element = meta.data[index];
// don't count items that are skipped (null data)
if (element && !element._view.skip) {
if (element && !element.skip) {
elements.push({element, datasetIndex: meta.index, index});
}
});

View File

@ -272,20 +272,17 @@ module.exports = {
*/
/**
* @method IPlugin#beforeDraw
* @desc Called before drawing `chart` at every animation frame specified by the given
* easing value. If any plugin returns `false`, the frame drawing is cancelled until
* another `render` is triggered.
* @desc Called before drawing `chart` at every animation frame. If any plugin returns `false`,
* the frame drawing is cancelled untilanother `render` is triggered.
* @param {Chart.Controller} chart - The chart instance.
* @param {number} easingValue - The current animation value, between 0.0 and 1.0.
* @param {object} options - The plugin options.
* @returns {boolean} `false` to cancel the chart drawing.
*/
/**
* @method IPlugin#afterDraw
* @desc Called after the `chart` has been drawn for the specific easing value. Note
* that this hook will not be called if the drawing has been previously cancelled.
* @desc Called after the `chart` has been drawn. Note that this hook will not be called
* if the drawing has been previously cancelled.
* @param {Chart.Controller} chart - The chart instance.
* @param {number} easingValue - The current animation value, between 0.0 and 1.0.
* @param {object} options - The plugin options.
*/
/**
@ -293,7 +290,6 @@ module.exports = {
* @desc Called before drawing the `chart` datasets. If any plugin returns `false`,
* the datasets drawing is cancelled until another `render` is triggered.
* @param {Chart.Controller} chart - The chart instance.
* @param {number} easingValue - The current animation value, between 0.0 and 1.0.
* @param {object} options - The plugin options.
* @returns {boolean} `false` to cancel the chart datasets drawing.
*/
@ -302,7 +298,6 @@ module.exports = {
* @desc Called after the `chart` datasets have been drawn. Note that this hook
* will not be called if the datasets drawing has been previously cancelled.
* @param {Chart.Controller} chart - The chart instance.
* @param {number} easingValue - The current animation value, between 0.0 and 1.0.
* @param {object} options - The plugin options.
*/
/**
@ -314,7 +309,6 @@ module.exports = {
* @param {object} args - The call arguments.
* @param {number} args.index - The dataset index.
* @param {object} args.meta - The dataset metadata.
* @param {number} args.easingValue - The current animation value, between 0.0 and 1.0.
* @param {object} options - The plugin options.
* @returns {boolean} `false` to cancel the chart datasets drawing.
*/
@ -327,7 +321,6 @@ module.exports = {
* @param {object} args - The call arguments.
* @param {number} args.index - The dataset index.
* @param {object} args.meta - The dataset metadata.
* @param {number} args.easingValue - The current animation value, between 0.0 and 1.0.
* @param {object} options - The plugin options.
*/
/**
@ -337,7 +330,6 @@ module.exports = {
* @param {Chart} chart - The chart instance.
* @param {object} args - The call arguments.
* @param {Tooltip} args.tooltip - The tooltip.
* @param {number} args.easingValue - The current animation value, between 0.0 and 1.0.
* @param {object} options - The plugin options.
* @returns {boolean} `false` to cancel the chart tooltip drawing.
*/
@ -348,7 +340,6 @@ module.exports = {
* @param {Chart} chart - The chart instance.
* @param {object} args - The call arguments.
* @param {Tooltip} args.tooltip - The tooltip.
* @param {number} args.easingValue - The current animation value, between 0.0 and 1.0.
* @param {object} options - The plugin options.
*/
/**

File diff suppressed because it is too large Load Diff

View File

@ -66,13 +66,14 @@ function drawFullCircleBorders(ctx, vm, arc, inner) {
}
function drawBorder(ctx, vm, arc) {
var inner = vm.borderAlign === 'inner';
const options = vm.options;
var inner = options.borderAlign === 'inner';
if (inner) {
ctx.lineWidth = vm.borderWidth * 2;
ctx.lineWidth = options.borderWidth * 2;
ctx.lineJoin = 'round';
} else {
ctx.lineWidth = vm.borderWidth;
ctx.lineWidth = options.borderWidth;
ctx.lineJoin = 'bevel';
}
@ -98,75 +99,73 @@ class Arc extends Element {
}
inRange(chartX, chartY) {
var vm = this._view;
var me = this;
if (vm) {
var pointRelativePosition = getAngleFromPoint(vm, {x: chartX, y: chartY});
var angle = pointRelativePosition.angle;
var distance = pointRelativePosition.distance;
var pointRelativePosition = getAngleFromPoint(me, {x: chartX, y: chartY});
var angle = pointRelativePosition.angle;
var distance = pointRelativePosition.distance;
// Sanitise angle range
var startAngle = vm.startAngle;
var endAngle = vm.endAngle;
while (endAngle < startAngle) {
endAngle += TAU;
}
while (angle > endAngle) {
angle -= TAU;
}
while (angle < startAngle) {
angle += TAU;
}
// Check if within the range of the open/close angle
var betweenAngles = (angle >= startAngle && angle <= endAngle);
var withinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius);
return (betweenAngles && withinRadius);
// Sanitise angle range
var startAngle = me.startAngle;
var endAngle = me.endAngle;
while (endAngle < startAngle) {
endAngle += TAU;
}
return false;
while (angle > endAngle) {
angle -= TAU;
}
while (angle < startAngle) {
angle += TAU;
}
// Check if within the range of the open/close angle
var betweenAngles = (angle >= startAngle && angle <= endAngle);
var withinRadius = (distance >= me.innerRadius && distance <= me.outerRadius);
return (betweenAngles && withinRadius);
}
getCenterPoint() {
var vm = this._view;
var halfAngle = (vm.startAngle + vm.endAngle) / 2;
var halfRadius = (vm.innerRadius + vm.outerRadius) / 2;
var me = this;
var halfAngle = (me.startAngle + me.endAngle) / 2;
var halfRadius = (me.innerRadius + me.outerRadius) / 2;
return {
x: vm.x + Math.cos(halfAngle) * halfRadius,
y: vm.y + Math.sin(halfAngle) * halfRadius
x: me.x + Math.cos(halfAngle) * halfRadius,
y: me.y + Math.sin(halfAngle) * halfRadius
};
}
tooltipPosition() {
var vm = this._view;
var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2);
var rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius;
var me = this;
var centreAngle = me.startAngle + ((me.endAngle - me.startAngle) / 2);
var rangeFromCentre = (me.outerRadius - me.innerRadius) / 2 + me.innerRadius;
return {
x: vm.x + (Math.cos(centreAngle) * rangeFromCentre),
y: vm.y + (Math.sin(centreAngle) * rangeFromCentre)
x: me.x + (Math.cos(centreAngle) * rangeFromCentre),
y: me.y + (Math.sin(centreAngle) * rangeFromCentre)
};
}
draw(ctx) {
var vm = this._view;
var pixelMargin = (vm.borderAlign === 'inner') ? 0.33 : 0;
var me = this;
var options = me.options;
var pixelMargin = (options.borderAlign === 'inner') ? 0.33 : 0;
var arc = {
x: vm.x,
y: vm.y,
innerRadius: vm.innerRadius,
outerRadius: Math.max(vm.outerRadius - pixelMargin, 0),
x: me.x,
y: me.y,
innerRadius: me.innerRadius,
outerRadius: Math.max(me.outerRadius - pixelMargin, 0),
pixelMargin: pixelMargin,
startAngle: vm.startAngle,
endAngle: vm.endAngle,
fullCircles: Math.floor(vm.circumference / TAU)
startAngle: me.startAngle,
endAngle: me.endAngle,
fullCircles: Math.floor(me.circumference / TAU)
};
var i;
ctx.save();
ctx.fillStyle = vm.backgroundColor;
ctx.strokeStyle = vm.borderColor;
ctx.fillStyle = options.backgroundColor;
ctx.strokeStyle = options.borderColor;
if (arc.fullCircles) {
arc.endAngle = arc.startAngle + TAU;
@ -177,7 +176,7 @@ class Arc extends Element {
for (i = 0; i < arc.fullCircles; ++i) {
ctx.fill();
}
arc.endAngle = arc.startAngle + vm.circumference % TAU;
arc.endAngle = arc.startAngle + me.circumference % TAU;
}
ctx.beginPath();
@ -186,8 +185,8 @@ class Arc extends Element {
ctx.closePath();
ctx.fill();
if (vm.borderWidth) {
drawBorder(ctx, vm, arc);
if (options.borderWidth) {
drawBorder(ctx, me, arc);
}
ctx.restore();

View File

@ -5,6 +5,7 @@ import Element from '../core/core.element';
import helpers from '../helpers';
const defaultColor = defaults.global.defaultColor;
const isPointInArea = helpers.canvas._isPointInArea;
defaults._set('global', {
elements: {
@ -18,48 +19,71 @@ defaults._set('global', {
borderDashOffset: 0.0,
borderJoinStyle: 'miter',
capBezierPoints: true,
fill: true, // do we fill in the area between the line and its base axis
fill: true
}
}
});
function startAtGap(points, spanGaps) {
let closePath = true;
let previous = points.length && points[0]._view;
let index, view;
let previous = points.length && points[0];
let index, point;
for (index = 1; index < points.length; ++index) {
// If there is a gap in the (looping) line, start drawing from that gap
view = points[index]._view;
if (!view.skip && previous.skip) {
point = points[index];
if (!point.skip && previous.skip) {
points = points.slice(index).concat(points.slice(0, index));
closePath = spanGaps;
break;
}
previous = view;
previous = point;
}
points.closePath = closePath;
return points;
}
function setStyle(ctx, vm) {
ctx.lineCap = vm.borderCapStyle;
ctx.setLineDash(vm.borderDash);
ctx.lineDashOffset = vm.borderDashOffset;
ctx.lineJoin = vm.borderJoinStyle;
ctx.lineWidth = vm.borderWidth;
ctx.strokeStyle = vm.borderColor;
function setStyle(ctx, options) {
ctx.lineCap = options.borderCapStyle;
ctx.setLineDash(options.borderDash);
ctx.lineDashOffset = options.borderDashOffset;
ctx.lineJoin = options.borderJoinStyle;
ctx.lineWidth = options.borderWidth;
ctx.strokeStyle = options.borderColor;
}
function normalPath(ctx, points, spanGaps, vm) {
const steppedLine = vm.steppedLine;
const lineMethod = steppedLine ? helpers.canvas._steppedLineTo : helpers.canvas._bezierCurveTo;
function bezierCurveTo(ctx, previous, target, flip) {
ctx.bezierCurveTo(
flip ? previous.controlPointPreviousX : previous.controlPointNextX,
flip ? previous.controlPointPreviousY : previous.controlPointNextY,
flip ? target.controlPointNextX : target.controlPointPreviousX,
flip ? target.controlPointNextY : target.controlPointPreviousY,
target.x,
target.y);
}
function steppedLineTo(ctx, previous, target, flip, mode) {
if (mode === 'middle') {
const midpoint = (previous.x + target.x) / 2.0;
ctx.lineTo(midpoint, flip ? target.y : previous.y);
ctx.lineTo(midpoint, flip ? previous.y : target.y);
} else if ((mode === 'after' && !flip) || (mode !== 'after' && flip)) {
ctx.lineTo(previous.x, target.y);
} else {
ctx.lineTo(target.x, previous.y);
}
ctx.lineTo(target.x, target.y);
}
function normalPath(ctx, points, spanGaps, options) {
const steppedLine = options.steppedLine;
const lineMethod = steppedLine ? steppedLineTo : bezierCurveTo;
let move = true;
let index, currentVM, previousVM;
for (index = 0; index < points.length; ++index) {
currentVM = points[index]._view;
currentVM = points[index];
if (currentVM.skip) {
move = move || !spanGaps;
@ -68,7 +92,7 @@ function normalPath(ctx, points, spanGaps, vm) {
if (move) {
ctx.moveTo(currentVM.x, currentVM.y);
move = false;
} else if (vm.tension || steppedLine) {
} else if (options.tension || steppedLine) {
lineMethod(ctx, previousVM, currentVM, false, steppedLine);
} else {
ctx.lineTo(currentVM.x, currentVM.y);
@ -91,7 +115,7 @@ function fastPath(ctx, points, spanGaps) {
let index, vm, truncX, x, y, prevX, minY, maxY, lastY;
for (index = 0; index < points.length; ++index) {
vm = points[index]._view;
vm = points[index];
// If point is skipped, we either move to next (not skipped) point
// or line to it if spanGaps is true. `move` can already be true.
@ -135,8 +159,64 @@ function fastPath(ctx, points, spanGaps) {
}
}
function useFastPath(vm) {
return vm.tension === 0 && !vm.steppedLine && !vm.fill && !vm.borderDash.length;
function useFastPath(options) {
return options.tension === 0 && !options.steppedLine && !options.fill && !options.borderDash.length;
}
function capControlPoint(pt, min, max) {
return Math.max(Math.min(pt, max), min);
}
function capBezierPoints(points, area) {
var i, ilen, model;
for (i = 0, ilen = points.length; i < ilen; ++i) {
model = points[i];
if (isPointInArea(model, area)) {
if (i > 0 && isPointInArea(points[i - 1], area)) {
model.controlPointPreviousX = capControlPoint(model.controlPointPreviousX, area.left, area.right);
model.controlPointPreviousY = capControlPoint(model.controlPointPreviousY, area.top, area.bottom);
}
if (i < points.length - 1 && isPointInArea(points[i + 1], area)) {
model.controlPointNextX = capControlPoint(model.controlPointNextX, area.left, area.right);
model.controlPointNextY = capControlPoint(model.controlPointNextY, area.top, area.bottom);
}
}
}
}
function updateBezierControlPoints(points, options, area, loop) {
var i, ilen, point, controlPoints;
// Only consider points that are drawn in case the spanGaps option is used
if (options.spanGaps) {
points = points.filter(function(pt) {
return !pt.skip;
});
}
if (options.cubicInterpolationMode === 'monotone') {
helpers.curve.splineCurveMonotone(points);
} else {
let prev = loop ? points[points.length - 1] : points[0];
for (i = 0, ilen = points.length; i < ilen; ++i) {
point = points[i];
controlPoints = helpers.curve.splineCurve(
prev,
point,
points[Math.min(i + 1, ilen - (loop ? 0 : 1)) % ilen],
options.tension
);
point.controlPointPreviousX = controlPoints.previous.x;
point.controlPointPreviousY = controlPoints.previous.y;
point.controlPointNextX = controlPoints.next.x;
point.controlPointNextY = controlPoints.next.y;
prev = point;
}
}
if (options.capBezierPoints) {
capBezierPoints(points, area);
}
}
class Line extends Element {
@ -145,10 +225,21 @@ class Line extends Element {
super(props);
}
draw(ctx) {
updateControlPoints(chartArea) {
const me = this;
const vm = me._view;
const spanGaps = vm.spanGaps;
if (me._controlPointsUpdated) {
return;
}
const options = me.options;
if (options.tension && !options.steppedLine) {
updateBezierControlPoints(me._children, options, chartArea, me._loop);
}
}
drawPath(ctx, area) {
const me = this;
const options = me.options;
const spanGaps = options.spanGaps;
let closePath = me._loop;
let points = me._children;
@ -161,19 +252,30 @@ class Line extends Element {
closePath = points.closePath;
}
if (useFastPath(options)) {
fastPath(ctx, points, spanGaps);
} else {
me.updateControlPoints(area);
normalPath(ctx, points, spanGaps, options);
}
return closePath;
}
draw(ctx, area) {
const me = this;
if (!me._children.length) {
return;
}
ctx.save();
setStyle(ctx, vm);
setStyle(ctx, me.options);
ctx.beginPath();
if (useFastPath(vm)) {
fastPath(ctx, points, spanGaps);
} else {
normalPath(ctx, points, spanGaps, vm);
}
if (closePath) {
if (me.drawPath(ctx, area)) {
ctx.closePath();
}

View File

@ -29,62 +29,55 @@ class Point extends Element {
}
inRange(mouseX, mouseY) {
const vm = this._view;
return vm ? ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(vm.hitRadius + vm.radius, 2)) : false;
const options = this.options;
return ((Math.pow(mouseX - this.x, 2) + Math.pow(mouseY - this.y, 2)) < Math.pow(options.hitRadius + options.radius, 2));
}
inXRange(mouseX) {
const vm = this._view;
return vm ? (Math.abs(mouseX - vm.x) < vm.radius + vm.hitRadius) : false;
const options = this.options;
return (Math.abs(mouseX - this.x) < options.radius + options.hitRadius);
}
inYRange(mouseY) {
const vm = this._view;
return vm ? (Math.abs(mouseY - vm.y) < vm.radius + vm.hitRadius) : false;
const options = this.options;
return (Math.abs(mouseY - this.y) < options.radius + options.hitRadius);
}
getCenterPoint() {
const vm = this._view;
return {
x: vm.x,
y: vm.y
};
return {x: this.x, y: this.y};
}
size() {
const vm = this._view;
const radius = vm.radius || 0;
const borderWidth = vm.borderWidth || 0;
const options = this.options || {};
const radius = options.radius || 0;
const borderWidth = radius && options.borderWidth || 0;
return (radius + borderWidth) * 2;
}
tooltipPosition() {
const vm = this._view;
const options = this.options;
return {
x: vm.x,
y: vm.y,
padding: vm.radius + vm.borderWidth
x: this.x,
y: this.y,
padding: options.radius + options.borderWidth
};
}
draw(ctx, chartArea) {
const vm = this._view;
const pointStyle = vm.pointStyle;
const rotation = vm.rotation;
const radius = vm.radius;
const x = vm.x;
const y = vm.y;
const me = this;
const options = me.options;
const radius = options.radius;
if (vm.skip || radius <= 0) {
if (me.skip || radius <= 0) {
return;
}
// Clipping for Points.
if (chartArea === undefined || helpers.canvas._isPointInArea(vm, chartArea)) {
ctx.strokeStyle = vm.borderColor;
ctx.lineWidth = vm.borderWidth;
ctx.fillStyle = vm.backgroundColor;
helpers.canvas.drawPoint(ctx, pointStyle, radius, x, y, rotation);
if (chartArea === undefined || helpers.canvas._isPointInArea(me, chartArea)) {
ctx.strokeStyle = options.borderColor;
ctx.lineWidth = options.borderWidth;
ctx.fillStyle = options.backgroundColor;
helpers.canvas.drawPoint(ctx, options.pointStyle, radius, me.x, me.y, options.rotation);
}
}
}

View File

@ -17,31 +17,27 @@ defaults._set('global', {
}
});
function isVertical(vm) {
return vm && vm.width !== undefined;
}
/**
* Helper function to get the bounds of the bar regardless of the orientation
* @param bar {Chart.Element.Rectangle} the bar
* @return {Bounds} bounds of the bar
* @private
*/
function getBarBounds(vm) {
function getBarBounds(bar) {
var x1, x2, y1, y2, half;
if (isVertical(vm)) {
half = vm.width / 2;
x1 = vm.x - half;
x2 = vm.x + half;
y1 = Math.min(vm.y, vm.base);
y2 = Math.max(vm.y, vm.base);
if (bar.horizontal) {
half = bar.height / 2;
x1 = Math.min(bar.x, bar.base);
x2 = Math.max(bar.x, bar.base);
y1 = bar.y - half;
y2 = bar.y + half;
} else {
half = vm.height / 2;
x1 = Math.min(vm.x, vm.base);
x2 = Math.max(vm.x, vm.base);
y1 = vm.y - half;
y2 = vm.y + half;
half = bar.width / 2;
x1 = bar.x - half;
x2 = bar.x + half;
y1 = Math.min(bar.y, bar.base);
y2 = Math.max(bar.y, bar.base);
}
return {
@ -56,19 +52,19 @@ function swap(orig, v1, v2) {
return orig === v1 ? v2 : orig === v2 ? v1 : orig;
}
function parseBorderSkipped(vm) {
var edge = vm.borderSkipped;
function parseBorderSkipped(bar) {
var edge = bar.options.borderSkipped;
var res = {};
if (!edge) {
return res;
}
if (vm.horizontal) {
if (vm.base > vm.x) {
if (bar.horizontal) {
if (bar.base > bar.x) {
edge = swap(edge, 'left', 'right');
}
} else if (vm.base < vm.y) {
} else if (bar.base < bar.y) {
edge = swap(edge, 'bottom', 'top');
}
@ -76,9 +72,9 @@ function parseBorderSkipped(vm) {
return res;
}
function parseBorderWidth(vm, maxW, maxH) {
var value = vm.borderWidth;
var skip = parseBorderSkipped(vm);
function parseBorderWidth(bar, maxW, maxH) {
var value = bar.options.borderWidth;
var skip = parseBorderSkipped(bar);
var t, r, b, l;
if (helpers.isObject(value)) {
@ -98,11 +94,11 @@ function parseBorderWidth(vm, maxW, maxH) {
};
}
function boundingRects(vm) {
var bounds = getBarBounds(vm);
function boundingRects(bar) {
var bounds = getBarBounds(bar);
var width = bounds.right - bounds.left;
var height = bounds.bottom - bounds.top;
var border = parseBorderWidth(vm, width / 2, height / 2);
var border = parseBorderWidth(bar, width / 2, height / 2);
return {
outer: {
@ -120,10 +116,10 @@ function boundingRects(vm) {
};
}
function inRange(vm, x, y) {
function inRange(bar, x, y) {
var skipX = x === null;
var skipY = y === null;
var bounds = !vm || (skipX && skipY) ? false : getBarBounds(vm);
var bounds = !bar || (skipX && skipY) ? false : getBarBounds(bar);
return bounds
&& (skipX || x >= bounds.left && x <= bounds.right)
@ -137,12 +133,12 @@ class Rectangle extends Element {
}
draw(ctx) {
var vm = this._view;
var rects = boundingRects(vm);
var options = this.options;
var rects = boundingRects(this);
var outer = rects.outer;
var inner = rects.inner;
ctx.fillStyle = vm.backgroundColor;
ctx.fillStyle = options.backgroundColor;
ctx.fillRect(outer.x, outer.y, outer.w, outer.h);
if (outer.w === inner.w && outer.h === inner.h) {
@ -153,43 +149,36 @@ class Rectangle extends Element {
ctx.beginPath();
ctx.rect(outer.x, outer.y, outer.w, outer.h);
ctx.clip();
ctx.fillStyle = vm.borderColor;
ctx.fillStyle = options.borderColor;
ctx.rect(inner.x, inner.y, inner.w, inner.h);
ctx.fill('evenodd');
ctx.restore();
}
inRange(mouseX, mouseY) {
return inRange(this._view, mouseX, mouseY);
return inRange(this, mouseX, mouseY);
}
inXRange(mouseX) {
return inRange(this._view, mouseX, null);
return inRange(this, mouseX, null);
}
inYRange(mouseY) {
return inRange(this._view, null, mouseY);
return inRange(this, null, mouseY);
}
getCenterPoint() {
var vm = this._view;
var x, y;
if (isVertical(vm)) {
x = vm.x;
y = (vm.y + vm.base) / 2;
} else {
x = (vm.x + vm.base) / 2;
y = vm.y;
}
return {x: x, y: y};
const {x, y, base, horizontal} = this;
return {
x: horizontal ? (x + base) / 2 : x,
y: horizontal ? y : (y + base) / 2
};
}
tooltipPosition() {
var vm = this._view;
return {
x: vm.x,
y: vm.y
x: this.x,
y: this.y
};
}
}

View File

@ -45,7 +45,7 @@ export function splineCurveMonotone(points) {
var pointsWithTangents = (points || []).map(function(point) {
return {
model: point._model,
model: point,
deltaK: 0,
mK: 0
};

View File

@ -6,6 +6,7 @@ var Chart = require('./core/core.controller');
Chart.helpers = require('./helpers/index');
Chart._adapters = require('./core/core.adapters');
Chart.Animation = require('./core/core.animation');
Chart.Animator = require('./core/core.animator');
Chart.animationService = require('./core/core.animations');
Chart.controllers = require('./controllers/index');
Chart.DatasetController = require('./core/core.datasetController');

View File

@ -28,7 +28,7 @@ var mappers = {
var length = points.length || 0;
return !length ? null : function(point, i) {
return (i < length && points[i]._view) || null;
return (i < length && points[i]) || null;
};
},
@ -55,7 +55,7 @@ var mappers = {
// @todo if (fill[0] === '#')
function decodeFill(el, index, count) {
var model = el._model || {};
var model = el.options || {};
var fillOption = model.fill;
var fill = fillOption && typeof fillOption.target !== 'undefined' ? fillOption.target : fillOption;
var target;
@ -105,7 +105,7 @@ function decodeFill(el, index, count) {
}
function computeLinearBoundary(source) {
var model = source.el._model || {};
var model = source.el || {};
var scale = source.scale || {};
var fill = source.fill;
var target = null;
@ -352,11 +352,11 @@ function clipAndFill(ctx, clippingPointsSets, fillingPointsSets, color, stepped,
function doFill(ctx, points, mapper, colors, el, area) {
const count = points.length;
const view = el._view;
const options = el.options;
const loop = el._loop;
const span = view.spanGaps;
const stepped = view.steppedLine;
const tension = view.tension;
const span = options.spanGaps;
const stepped = options.steppedLine;
const tension = options.tension;
let curve0 = [];
let curve1 = [];
let len0 = 0;
@ -369,8 +369,8 @@ function doFill(ctx, points, mapper, colors, el, area) {
for (i = 0, ilen = count; i < ilen; ++i) {
index = i % count;
p0 = points[index]._view;
p1 = mapper(p0, index, view);
p0 = points[index];
p1 = mapper(p0, index);
d0 = isDrawable(p0);
d1 = isDrawable(p1);
@ -423,7 +423,7 @@ module.exports = {
el = meta.dataset;
source = null;
if (el && el._model && el instanceof elements.Line) {
if (el && el.options && el instanceof elements.Line) {
source = {
visible: chart.isDatasetVisible(i),
fill: decodeFill(el, i, count),
@ -450,9 +450,19 @@ module.exports = {
},
beforeDatasetsDraw: function(chart) {
var metasets = chart._getSortedVisibleDatasetMetas();
var ctx = chart.ctx;
var meta, i, el, view, points, mapper, color, colors, fillOption;
const metasets = chart._getSortedVisibleDatasetMetas();
const area = chart.chartArea;
const ctx = chart.ctx;
var meta, i, el, options, points, mapper, color, colors, fillOption;
for (i = metasets.length - 1; i >= 0; --i) {
meta = metasets[i].$filler;
if (!meta || !meta.visible) {
continue;
}
meta.el.updateControlPoints(area);
}
for (i = metasets.length - 1; i >= 0; --i) {
meta = metasets[i].$filler;
@ -462,11 +472,11 @@ module.exports = {
}
el = meta.el;
view = el._view;
options = el.options;
points = el._children || [];
mapper = meta.mapper;
fillOption = meta.el._model.fill;
color = view.backgroundColor || defaults.global.defaultColor;
fillOption = options.fill;
color = options.backgroundColor || defaults.global.defaultColor;
colors = {above: color, below: color};
if (fillOption && typeof fillOption === 'object') {
@ -474,8 +484,8 @@ module.exports = {
colors.below = fillOption.below || color;
}
if (mapper && points.length) {
helpers.canvas.clipArea(ctx, chart.chartArea);
doFill(ctx, points, mapper, colors, el, chart.chartArea);
helpers.canvas.clipArea(ctx, area);
doFill(ctx, points, mapper, colors, el, area);
helpers.canvas.unclipArea(ctx);
}
}

View File

@ -58,7 +58,7 @@ defaults._set('global', {
return {
text: datasets[meta.index].label,
fillStyle: style.backgroundColor,
hidden: !chart.isDatasetVisible(meta.index),
hidden: !meta.visible,
lineCap: style.borderCapStyle,
lineDash: style.borderDash,
lineDashOffset: style.borderDashOffset,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -82,14 +82,13 @@ module.exports = {
event = {
type: 'mousemove',
target: canvas,
clientX: rect.left + point._model.x,
clientY: rect.top + point._model.y
clientX: rect.left + point.x,
clientY: rect.top + point.y
};
chart.handleEvent(event);
chart.tooltip.handleEvent(event);
chart.tooltip.transition(1);
chart.tooltip._view.opacity = j / 10;
chart.tooltip.draw();
chart.tooltip.opacity = j / 10;
chart.tooltip.draw(chart.ctx);
}
}
}

View File

@ -728,11 +728,11 @@ describe('Chart.controllers.bar', function() {
{x: 89, y: 512},
{x: 217, y: 0}
].forEach(function(expected, i) {
expect(meta.data[i]._model.x).toBeCloseToPixel(expected.x);
expect(meta.data[i]._model.y).toBeCloseToPixel(expected.y);
expect(meta.data[i]._model.base).toBeCloseToPixel(1024);
expect(meta.data[i]._model.width).toBeCloseToPixel(46);
expect(meta.data[i]._model).toEqual(jasmine.objectContaining({
expect(meta.data[i].x).toBeCloseToPixel(expected.x);
expect(meta.data[i].y).toBeCloseToPixel(expected.y);
expect(meta.data[i].base).toBeCloseToPixel(1024);
expect(meta.data[i].width).toBeCloseToPixel(46);
expect(meta.data[i].options).toEqual(jasmine.objectContaining({
backgroundColor: 'red',
borderSkipped: 'top',
borderColor: 'blue',
@ -785,10 +785,10 @@ describe('Chart.controllers.bar', function() {
var bar1 = meta.data[0];
var bar2 = meta.data[1];
expect(bar1._model.x).toBeCloseToPixel(179);
expect(bar1._model.y).toBeCloseToPixel(114);
expect(bar2._model.x).toBeCloseToPixel(435);
expect(bar2._model.y).toBeCloseToPixel(0);
expect(bar1.x).toBeCloseToPixel(179);
expect(bar1.y).toBeCloseToPixel(114);
expect(bar2.x).toBeCloseToPixel(435);
expect(bar2.y).toBeCloseToPixel(0);
});
it('should update elements when the scales are stacked', function() {
@ -829,10 +829,10 @@ describe('Chart.controllers.bar', function() {
{b: 293, w: 92 / 2, x: 295, y: 146},
{b: 293, w: 92 / 2, x: 422, y: 439}
].forEach(function(values, i) {
expect(meta0.data[i]._model.base).toBeCloseToPixel(values.b);
expect(meta0.data[i]._model.width).toBeCloseToPixel(values.w);
expect(meta0.data[i]._model.x).toBeCloseToPixel(values.x);
expect(meta0.data[i]._model.y).toBeCloseToPixel(values.y);
expect(meta0.data[i].base).toBeCloseToPixel(values.b);
expect(meta0.data[i].width).toBeCloseToPixel(values.w);
expect(meta0.data[i].x).toBeCloseToPixel(values.x);
expect(meta0.data[i].y).toBeCloseToPixel(values.y);
});
var meta1 = chart.getDatasetMeta(1);
@ -843,10 +843,10 @@ describe('Chart.controllers.bar', function() {
{b: 146, w: 92 / 2, x: 345, y: 146},
{b: 439, w: 92 / 2, x: 473, y: 497}
].forEach(function(values, i) {
expect(meta1.data[i]._model.base).toBeCloseToPixel(values.b);
expect(meta1.data[i]._model.width).toBeCloseToPixel(values.w);
expect(meta1.data[i]._model.x).toBeCloseToPixel(values.x);
expect(meta1.data[i]._model.y).toBeCloseToPixel(values.y);
expect(meta1.data[i].base).toBeCloseToPixel(values.b);
expect(meta1.data[i].width).toBeCloseToPixel(values.w);
expect(meta1.data[i].x).toBeCloseToPixel(values.x);
expect(meta1.data[i].y).toBeCloseToPixel(values.y);
});
});
@ -890,10 +890,10 @@ describe('Chart.controllers.bar', function() {
{b: 1024, w: 92 / 2, x: 294, y: 922},
{b: 1024, w: 92 / 2, x: 422.5, y: 0}
].forEach(function(values, i) {
expect(meta0.data[i]._model.base).toBeCloseToPixel(values.b);
expect(meta0.data[i]._model.width).toBeCloseToPixel(values.w);
expect(meta0.data[i]._model.x).toBeCloseToPixel(values.x);
expect(meta0.data[i]._model.y).toBeCloseToPixel(values.y);
expect(meta0.data[i].base).toBeCloseToPixel(values.b);
expect(meta0.data[i].width).toBeCloseToPixel(values.w);
expect(meta0.data[i].x).toBeCloseToPixel(values.x);
expect(meta0.data[i].y).toBeCloseToPixel(values.y);
});
var meta1 = chart.getDatasetMeta(1);
@ -904,10 +904,10 @@ describe('Chart.controllers.bar', function() {
{b: 922, w: 92 / 2, x: 345, y: 0},
{b: 0, w: 92 / 2, x: 473.5, y: 0}
].forEach(function(values, i) {
expect(meta1.data[i]._model.base).toBeCloseToPixel(values.b);
expect(meta1.data[i]._model.width).toBeCloseToPixel(values.w);
expect(meta1.data[i]._model.x).toBeCloseToPixel(values.x);
expect(meta1.data[i]._model.y).toBeCloseToPixel(values.y);
expect(meta1.data[i].base).toBeCloseToPixel(values.b);
expect(meta1.data[i].width).toBeCloseToPixel(values.w);
expect(meta1.data[i].x).toBeCloseToPixel(values.x);
expect(meta1.data[i].y).toBeCloseToPixel(values.y);
});
});
@ -949,10 +949,10 @@ describe('Chart.controllers.bar', function() {
{b: 293, w: 92, x: 320, y: 146},
{b: 293, w: 92, x: 448, y: 439}
].forEach(function(values, i) {
expect(meta0.data[i]._model.base).toBeCloseToPixel(values.b);
expect(meta0.data[i]._model.width).toBeCloseToPixel(values.w);
expect(meta0.data[i]._model.x).toBeCloseToPixel(values.x);
expect(meta0.data[i]._model.y).toBeCloseToPixel(values.y);
expect(meta0.data[i].base).toBeCloseToPixel(values.b);
expect(meta0.data[i].width).toBeCloseToPixel(values.w);
expect(meta0.data[i].x).toBeCloseToPixel(values.x);
expect(meta0.data[i].y).toBeCloseToPixel(values.y);
});
var meta1 = chart.getDatasetMeta(1);
@ -963,10 +963,10 @@ describe('Chart.controllers.bar', function() {
{b: 293, w: 92, x: 320, y: 293},
{b: 293, w: 92, x: 448, y: 497}
].forEach(function(values, i) {
expect(meta1.data[i]._model.base).toBeCloseToPixel(values.b);
expect(meta1.data[i]._model.width).toBeCloseToPixel(values.w);
expect(meta1.data[i]._model.x).toBeCloseToPixel(values.x);
expect(meta1.data[i]._model.y).toBeCloseToPixel(values.y);
expect(meta1.data[i].base).toBeCloseToPixel(values.b);
expect(meta1.data[i].width).toBeCloseToPixel(values.w);
expect(meta1.data[i].x).toBeCloseToPixel(values.x);
expect(meta1.data[i].y).toBeCloseToPixel(values.y);
});
});
@ -1008,10 +1008,10 @@ describe('Chart.controllers.bar', function() {
{b: 293, w: 92 / 2, x: 295, y: 146},
{b: 293, w: 92 / 2, x: 422, y: 439}
].forEach(function(values, i) {
expect(meta0.data[i]._model.base).toBeCloseToPixel(values.b);
expect(meta0.data[i]._model.width).toBeCloseToPixel(values.w);
expect(meta0.data[i]._model.x).toBeCloseToPixel(values.x);
expect(meta0.data[i]._model.y).toBeCloseToPixel(values.y);
expect(meta0.data[i].base).toBeCloseToPixel(values.b);
expect(meta0.data[i].width).toBeCloseToPixel(values.w);
expect(meta0.data[i].x).toBeCloseToPixel(values.x);
expect(meta0.data[i].y).toBeCloseToPixel(values.y);
});
var meta1 = chart.getDatasetMeta(1);
@ -1022,10 +1022,10 @@ describe('Chart.controllers.bar', function() {
{b: 146, w: 92 / 2, x: 345, y: 146},
{b: 439, w: 92 / 2, x: 473, y: 497}
].forEach(function(values, i) {
expect(meta1.data[i]._model.base).toBeCloseToPixel(values.b);
expect(meta1.data[i]._model.width).toBeCloseToPixel(values.w);
expect(meta1.data[i]._model.x).toBeCloseToPixel(values.x);
expect(meta1.data[i]._model.y).toBeCloseToPixel(values.y);
expect(meta1.data[i].base).toBeCloseToPixel(values.b);
expect(meta1.data[i].width).toBeCloseToPixel(values.w);
expect(meta1.data[i].x).toBeCloseToPixel(values.x);
expect(meta1.data[i].y).toBeCloseToPixel(values.y);
});
});
@ -1069,10 +1069,10 @@ describe('Chart.controllers.bar', function() {
{b: 293, w: 92, x: 320, y: 146},
{b: 293, w: 92, x: 448, y: 439}
].forEach(function(values, i) {
expect(meta0.data[i]._model.base).toBeCloseToPixel(values.b);
expect(meta0.data[i]._model.width).toBeCloseToPixel(values.w);
expect(meta0.data[i]._model.x).toBeCloseToPixel(values.x);
expect(meta0.data[i]._model.y).toBeCloseToPixel(values.y);
expect(meta0.data[i].base).toBeCloseToPixel(values.b);
expect(meta0.data[i].width).toBeCloseToPixel(values.w);
expect(meta0.data[i].x).toBeCloseToPixel(values.x);
expect(meta0.data[i].y).toBeCloseToPixel(values.y);
});
var meta = chart.getDatasetMeta(1);
@ -1083,10 +1083,10 @@ describe('Chart.controllers.bar', function() {
{b: 146, w: 92, x: 320, y: 146},
{b: 439, w: 92, x: 448, y: 497}
].forEach(function(values, i) {
expect(meta.data[i]._model.base).toBeCloseToPixel(values.b);
expect(meta.data[i]._model.width).toBeCloseToPixel(values.w);
expect(meta.data[i]._model.x).toBeCloseToPixel(values.x);
expect(meta.data[i]._model.y).toBeCloseToPixel(values.y);
expect(meta.data[i].base).toBeCloseToPixel(values.b);
expect(meta.data[i].width).toBeCloseToPixel(values.w);
expect(meta.data[i].x).toBeCloseToPixel(values.x);
expect(meta.data[i].y).toBeCloseToPixel(values.y);
});
});
@ -1126,10 +1126,10 @@ describe('Chart.controllers.bar', function() {
{x: 89, y: 256},
{x: 217, y: 0}
].forEach(function(values, i) {
expect(meta.data[i]._model.base).toBeCloseToPixel(512);
expect(meta.data[i]._model.width).toBeCloseToPixel(46);
expect(meta.data[i]._model.x).toBeCloseToPixel(values.x);
expect(meta.data[i]._model.y).toBeCloseToPixel(values.y);
expect(meta.data[i].base).toBeCloseToPixel(512);
expect(meta.data[i].width).toBeCloseToPixel(46);
expect(meta.data[i].x).toBeCloseToPixel(values.x);
expect(meta.data[i].y).toBeCloseToPixel(values.y);
});
});
@ -1172,10 +1172,10 @@ describe('Chart.controllers.bar', function() {
{b: 384, x: 89, y: 256},
{b: 256, x: 217, y: 0}
].forEach(function(values, i) {
expect(meta.data[i]._model.base).toBeCloseToPixel(values.b);
expect(meta.data[i]._model.width).toBeCloseToPixel(46);
expect(meta.data[i]._model.x).toBeCloseToPixel(values.x);
expect(meta.data[i]._model.y).toBeCloseToPixel(values.y);
expect(meta.data[i].base).toBeCloseToPixel(values.b);
expect(meta.data[i].width).toBeCloseToPixel(46);
expect(meta.data[i].x).toBeCloseToPixel(values.x);
expect(meta.data[i].y).toBeCloseToPixel(values.y);
});
});
@ -1235,29 +1235,31 @@ describe('Chart.controllers.bar', function() {
var bar = meta.data[0];
meta.controller.setHoverStyle(bar, 1, 0);
expect(bar._model.backgroundColor).toBe('rgb(230, 0, 0)');
expect(bar._model.borderColor).toBe('rgb(0, 0, 230)');
expect(bar._model.borderWidth).toBe(2);
expect(bar.options.backgroundColor).toBe('rgb(230, 0, 0)');
expect(bar.options.borderColor).toBe('rgb(0, 0, 230)');
expect(bar.options.borderWidth).toBe(2);
// Set a dataset style
chart.data.datasets[1].hoverBackgroundColor = 'rgb(128, 128, 128)';
chart.data.datasets[1].hoverBorderColor = 'rgb(0, 0, 0)';
chart.data.datasets[1].hoverBorderWidth = 5;
chart.update();
meta.controller.setHoverStyle(bar, 1, 0);
expect(bar._model.backgroundColor).toBe('rgb(128, 128, 128)');
expect(bar._model.borderColor).toBe('rgb(0, 0, 0)');
expect(bar._model.borderWidth).toBe(5);
expect(bar.options.backgroundColor).toBe('rgb(128, 128, 128)');
expect(bar.options.borderColor).toBe('rgb(0, 0, 0)');
expect(bar.options.borderWidth).toBe(5);
// Should work with array styles so that we can set per bar
chart.data.datasets[1].hoverBackgroundColor = ['rgb(255, 255, 255)', 'rgb(128, 128, 128)'];
chart.data.datasets[1].hoverBorderColor = ['rgb(9, 9, 9)', 'rgb(0, 0, 0)'];
chart.data.datasets[1].hoverBorderWidth = [2.5, 5];
chart.update();
meta.controller.setHoverStyle(bar, 1, 0);
expect(bar._model.backgroundColor).toBe('rgb(255, 255, 255)');
expect(bar._model.borderColor).toBe('rgb(9, 9, 9)');
expect(bar._model.borderWidth).toBe(2.5);
expect(bar.options.backgroundColor).toBe('rgb(255, 255, 255)');
expect(bar.options.borderColor).toBe('rgb(9, 9, 9)');
expect(bar.options.borderWidth).toBe(2.5);
});
it('should remove a hover style from a bar', function() {
@ -1293,17 +1295,17 @@ describe('Chart.controllers.bar', function() {
chart.options.elements.rectangle.borderWidth = 3.14;
chart.update();
expect(bar._model.backgroundColor).toBe('rgb(128, 128, 128)');
expect(bar._model.borderColor).toBe('rgb(15, 15, 15)');
expect(bar._model.borderWidth).toBe(3.14);
expect(bar.options.backgroundColor).toBe('rgb(128, 128, 128)');
expect(bar.options.borderColor).toBe('rgb(15, 15, 15)');
expect(bar.options.borderWidth).toBe(3.14);
meta.controller.setHoverStyle(bar, 1, 0);
expect(bar._model.backgroundColor).toBe(helpers.getHoverColor('rgb(128, 128, 128)'));
expect(bar._model.borderColor).toBe(helpers.getHoverColor('rgb(15, 15, 15)'));
expect(bar._model.borderWidth).toBe(3.14);
expect(bar.options.backgroundColor).toBe(helpers.getHoverColor('rgb(128, 128, 128)'));
expect(bar.options.borderColor).toBe(helpers.getHoverColor('rgb(15, 15, 15)'));
expect(bar.options.borderWidth).toBe(3.14);
meta.controller.removeHoverStyle(bar);
expect(bar._model.backgroundColor).toBe('rgb(128, 128, 128)');
expect(bar._model.borderColor).toBe('rgb(15, 15, 15)');
expect(bar._model.borderWidth).toBe(3.14);
expect(bar.options.backgroundColor).toBe('rgb(128, 128, 128)');
expect(bar.options.borderColor).toBe('rgb(15, 15, 15)');
expect(bar.options.borderWidth).toBe(3.14);
// Should work with array styles so that we can set per bar
chart.data.datasets[1].backgroundColor = ['rgb(255, 255, 255)', 'rgb(128, 128, 128)'];
@ -1311,17 +1313,17 @@ describe('Chart.controllers.bar', function() {
chart.data.datasets[1].borderWidth = [2.5, 5];
chart.update();
expect(bar._model.backgroundColor).toBe('rgb(255, 255, 255)');
expect(bar._model.borderColor).toBe('rgb(9, 9, 9)');
expect(bar._model.borderWidth).toBe(2.5);
expect(bar.options.backgroundColor).toBe('rgb(255, 255, 255)');
expect(bar.options.borderColor).toBe('rgb(9, 9, 9)');
expect(bar.options.borderWidth).toBe(2.5);
meta.controller.setHoverStyle(bar, 1, 0);
expect(bar._model.backgroundColor).toBe(helpers.getHoverColor('rgb(255, 255, 255)'));
expect(bar._model.borderColor).toBe(helpers.getHoverColor('rgb(9, 9, 9)'));
expect(bar._model.borderWidth).toBe(2.5);
expect(bar.options.backgroundColor).toBe(helpers.getHoverColor('rgb(255, 255, 255)'));
expect(bar.options.borderColor).toBe(helpers.getHoverColor('rgb(9, 9, 9)'));
expect(bar.options.borderWidth).toBe(2.5);
meta.controller.removeHoverStyle(bar);
expect(bar._model.backgroundColor).toBe('rgb(255, 255, 255)');
expect(bar._model.borderColor).toBe('rgb(9, 9, 9)');
expect(bar._model.borderWidth).toBe(2.5);
expect(bar.options.backgroundColor).toBe('rgb(255, 255, 255)');
expect(bar.options.borderColor).toBe('rgb(9, 9, 9)');
expect(bar.options.borderWidth).toBe(2.5);
});
describe('Bar width', function() {
@ -1351,7 +1353,7 @@ describe('Chart.controllers.bar', function() {
for (var i = 0; i < chart.data.datasets.length; i++) {
var bars = chart.getDatasetMeta(i).data;
for (var j = xScale.min; j <= xScale.max; j++) {
totalBarWidth += bars[j]._model.width;
totalBarWidth += bars[j].width;
}
if (stacked) {
break;
@ -1425,7 +1427,7 @@ describe('Chart.controllers.bar', function() {
for (var i = 0; i < chart.data.datasets.length; i++) {
var bars = chart.getDatasetMeta(i).data;
for (var j = yScale.min; j <= yScale.max; j++) {
totalBarHeight += bars[j]._model.height;
totalBarHeight += bars[j].height;
}
if (stacked) {
break;
@ -1525,8 +1527,8 @@ describe('Chart.controllers.bar', function() {
for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) {
meta = chart.getDatasetMeta(i);
expect(meta.data[0]._model.width).toBeCloseToPixel(expected);
expect(meta.data[1]._model.width).toBeCloseToPixel(expected);
expect(meta.data[0].width).toBeCloseToPixel(expected);
expect(meta.data[1].width).toBeCloseToPixel(expected);
}
});
@ -1540,8 +1542,8 @@ describe('Chart.controllers.bar', function() {
for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) {
meta = chart.getDatasetMeta(i);
expect(meta.data[0]._model.width).toBeCloseToPixel(10);
expect(meta.data[1]._model.width).toBeCloseToPixel(10);
expect(meta.data[0].width).toBeCloseToPixel(10);
expect(meta.data[1].width).toBeCloseToPixel(10);
}
});
});
@ -1562,8 +1564,8 @@ describe('Chart.controllers.bar', function() {
var data = chart.getDatasetMeta(0).data;
expect(data[0]._model.base - minBarLength).toEqual(data[0]._model.y);
expect(data[1]._model.base + minBarLength).toEqual(data[1]._model.y);
expect(data[0].base - minBarLength).toEqual(data[0].y);
expect(data[1].base + minBarLength).toEqual(data[1].y);
});
it('minBarLength settings should be used on X axis on horizontalBar chart', function() {
@ -1580,7 +1582,7 @@ describe('Chart.controllers.bar', function() {
var data = chart.getDatasetMeta(0).data;
expect(data[0]._model.base + minBarLength).toEqual(data[0]._model.x);
expect(data[1]._model.base - minBarLength).toEqual(data[1]._model.x);
expect(data[0].base + minBarLength).toEqual(data[0].x);
expect(data[1].base - minBarLength).toEqual(data[1].x);
});
});

View File

@ -138,15 +138,14 @@ describe('Chart.controllers.bubble', function() {
{r: 2, x: 341, y: 486},
{r: 1, x: 512, y: 0}
].forEach(function(expected, i) {
expect(meta.data[i]._model.radius).toBe(expected.r);
expect(meta.data[i]._model.x).toBeCloseToPixel(expected.x);
expect(meta.data[i]._model.y).toBeCloseToPixel(expected.y);
expect(meta.data[i]._model).toEqual(jasmine.objectContaining({
expect(meta.data[i].x).toBeCloseToPixel(expected.x);
expect(meta.data[i].y).toBeCloseToPixel(expected.y);
expect(meta.data[i].options).toEqual(jasmine.objectContaining({
backgroundColor: Chart.defaults.global.defaultColor,
borderColor: Chart.defaults.global.defaultColor,
borderWidth: 1,
hitRadius: 1,
skip: false
radius: expected.r
}));
});
@ -162,12 +161,11 @@ describe('Chart.controllers.bubble', function() {
chart.update();
for (var i = 0; i < 4; ++i) {
expect(meta.data[i]._model).toEqual(jasmine.objectContaining({
expect(meta.data[i].options).toEqual(jasmine.objectContaining({
backgroundColor: 'rgb(98, 98, 98)',
borderColor: 'rgb(8, 8, 8)',
borderWidth: 0.55,
hitRadius: 3.3,
skip: false
hitRadius: 3.3
}));
}
});
@ -295,16 +293,16 @@ describe('Chart.controllers.bubble', function() {
var point = chart.getDatasetMeta(0).data[0];
jasmine.triggerMouseEvent(chart, 'mousemove', point);
expect(point._model.backgroundColor).toBe('rgb(49, 135, 221)');
expect(point._model.borderColor).toBe('rgb(22, 89, 156)');
expect(point._model.borderWidth).toBe(1);
expect(point._model.radius).toBe(20 + 4);
expect(point.options.backgroundColor).toBe('rgb(49, 135, 221)');
expect(point.options.borderColor).toBe('rgb(22, 89, 156)');
expect(point.options.borderWidth).toBe(1);
expect(point.options.radius).toBe(20 + 4);
jasmine.triggerMouseEvent(chart, 'mouseout', point);
expect(point._model.backgroundColor).toBe('rgb(100, 150, 200)');
expect(point._model.borderColor).toBe('rgb(50, 100, 150)');
expect(point._model.borderWidth).toBe(2);
expect(point._model.radius).toBe(20);
expect(point.options.backgroundColor).toBe('rgb(100, 150, 200)');
expect(point.options.borderColor).toBe('rgb(50, 100, 150)');
expect(point.options.borderWidth).toBe(2);
expect(point.options.radius).toBe(20);
});
it ('should handle hover styles defined via dataset properties', function() {
@ -321,16 +319,16 @@ describe('Chart.controllers.bubble', function() {
chart.update();
jasmine.triggerMouseEvent(chart, 'mousemove', point);
expect(point._model.backgroundColor).toBe('rgb(200, 100, 150)');
expect(point._model.borderColor).toBe('rgb(150, 50, 100)');
expect(point._model.borderWidth).toBe(8.4);
expect(point._model.radius).toBe(20 + 4.2);
expect(point.options.backgroundColor).toBe('rgb(200, 100, 150)');
expect(point.options.borderColor).toBe('rgb(150, 50, 100)');
expect(point.options.borderWidth).toBe(8.4);
expect(point.options.radius).toBe(20 + 4.2);
jasmine.triggerMouseEvent(chart, 'mouseout', point);
expect(point._model.backgroundColor).toBe('rgb(100, 150, 200)');
expect(point._model.borderColor).toBe('rgb(50, 100, 150)');
expect(point._model.borderWidth).toBe(2);
expect(point._model.radius).toBe(20);
expect(point.options.backgroundColor).toBe('rgb(100, 150, 200)');
expect(point.options.borderColor).toBe('rgb(50, 100, 150)');
expect(point.options.borderWidth).toBe(2);
expect(point.options.radius).toBe(20);
});
it ('should handle hover styles defined via element options', function() {
@ -347,16 +345,16 @@ describe('Chart.controllers.bubble', function() {
chart.update();
jasmine.triggerMouseEvent(chart, 'mousemove', point);
expect(point._model.backgroundColor).toBe('rgb(200, 100, 150)');
expect(point._model.borderColor).toBe('rgb(150, 50, 100)');
expect(point._model.borderWidth).toBe(8.4);
expect(point._model.radius).toBe(20 + 4.2);
expect(point.options.backgroundColor).toBe('rgb(200, 100, 150)');
expect(point.options.borderColor).toBe('rgb(150, 50, 100)');
expect(point.options.borderWidth).toBe(8.4);
expect(point.options.radius).toBe(20 + 4.2);
jasmine.triggerMouseEvent(chart, 'mouseout', point);
expect(point._model.backgroundColor).toBe('rgb(100, 150, 200)');
expect(point._model.borderColor).toBe('rgb(50, 100, 150)');
expect(point._model.borderWidth).toBe(2);
expect(point._model.radius).toBe(20);
expect(point.options.backgroundColor).toBe('rgb(100, 150, 200)');
expect(point.options.borderColor).toBe('rgb(50, 100, 150)');
expect(point.options.borderWidth).toBe(2);
expect(point.options.radius).toBe(20);
});
});
});

View File

@ -78,6 +78,7 @@ describe('Chart.controllers.doughnut', function() {
legend: false,
title: false,
animation: {
duration: 0,
animateRotate: true,
animateScale: false
},
@ -106,14 +107,14 @@ describe('Chart.controllers.doughnut', function() {
{c: 0},
{c: 0}
].forEach(function(expected, i) {
expect(meta.data[i]._model.x).toBeCloseToPixel(256);
expect(meta.data[i]._model.y).toBeCloseToPixel(256);
expect(meta.data[i]._model.outerRadius).toBeCloseToPixel(256);
expect(meta.data[i]._model.innerRadius).toBeCloseToPixel(192);
expect(meta.data[i]._model.circumference).toBeCloseTo(expected.c, 8);
expect(meta.data[i]._model).toEqual(jasmine.objectContaining({
startAngle: Math.PI * -0.5,
endAngle: Math.PI * -0.5,
expect(meta.data[i].x).toBeCloseToPixel(256);
expect(meta.data[i].y).toBeCloseToPixel(256);
expect(meta.data[i].outerRadius).toBeCloseToPixel(256);
expect(meta.data[i].innerRadius).toBeCloseToPixel(192);
expect(meta.data[i].circumference).toBeCloseTo(expected.c, 8);
expect(meta.data[i].startAngle).toBeCloseToPixel(Math.PI * -0.5);
expect(meta.data[i].endAngle).toBeCloseToPixel(Math.PI * -0.5);
expect(meta.data[i].options).toEqual(jasmine.objectContaining({
backgroundColor: 'rgb(255, 0, 0)',
borderColor: 'rgb(0, 0, 255)',
borderWidth: 2
@ -128,14 +129,14 @@ describe('Chart.controllers.doughnut', function() {
{c: 0, s: 2.2689280275, e: 2.2689280275},
{c: 2.4434609527, s: 2.2689280275, e: 4.7123889803}
].forEach(function(expected, i) {
expect(meta.data[i]._model.x).toBeCloseToPixel(256);
expect(meta.data[i]._model.y).toBeCloseToPixel(256);
expect(meta.data[i]._model.outerRadius).toBeCloseToPixel(256);
expect(meta.data[i]._model.innerRadius).toBeCloseToPixel(192);
expect(meta.data[i]._model.circumference).toBeCloseTo(expected.c, 8);
expect(meta.data[i]._model.startAngle).toBeCloseTo(expected.s, 8);
expect(meta.data[i]._model.endAngle).toBeCloseTo(expected.e, 8);
expect(meta.data[i]._model).toEqual(jasmine.objectContaining({
expect(meta.data[i].x).toBeCloseToPixel(256);
expect(meta.data[i].y).toBeCloseToPixel(256);
expect(meta.data[i].outerRadius).toBeCloseToPixel(256);
expect(meta.data[i].innerRadius).toBeCloseToPixel(192);
expect(meta.data[i].circumference).toBeCloseTo(expected.c, 8);
expect(meta.data[i].startAngle).toBeCloseTo(expected.s, 8);
expect(meta.data[i].endAngle).toBeCloseTo(expected.e, 8);
expect(meta.data[i].options).toEqual(jasmine.objectContaining({
backgroundColor: 'rgb(255, 0, 0)',
borderColor: 'rgb(0, 0, 255)',
borderWidth: 2
@ -200,13 +201,13 @@ describe('Chart.controllers.doughnut', function() {
{c: Math.PI / 8, s: Math.PI, e: Math.PI + Math.PI / 8},
{c: 3 * Math.PI / 8, s: Math.PI + Math.PI / 8, e: Math.PI + Math.PI / 2}
].forEach(function(expected, i) {
expect(meta.data[i]._model.x).toBeCloseToPixel(512);
expect(meta.data[i]._model.y).toBeCloseToPixel(512);
expect(meta.data[i]._model.outerRadius).toBeCloseToPixel(512);
expect(meta.data[i]._model.innerRadius).toBeCloseToPixel(384);
expect(meta.data[i]._model.circumference).toBeCloseTo(expected.c, 8);
expect(meta.data[i]._model.startAngle).toBeCloseTo(expected.s, 8);
expect(meta.data[i]._model.endAngle).toBeCloseTo(expected.e, 8);
expect(meta.data[i].x).toBeCloseToPixel(512);
expect(meta.data[i].y).toBeCloseToPixel(512);
expect(meta.data[i].outerRadius).toBeCloseToPixel(512);
expect(meta.data[i].innerRadius).toBeCloseToPixel(384);
expect(meta.data[i].circumference).toBeCloseTo(expected.c, 8);
expect(meta.data[i].startAngle).toBeCloseTo(expected.s, 8);
expect(meta.data[i].endAngle).toBeCloseTo(expected.e, 8);
});
});
@ -244,9 +245,9 @@ describe('Chart.controllers.doughnut', function() {
{c: Math.PI / 8, s: Math.PI, e: Math.PI + Math.PI / 8},
{c: 3 * Math.PI / 8, s: Math.PI + Math.PI / 8, e: Math.PI + Math.PI / 2}
].forEach(function(expected, i) {
expect(meta.data[i]._model.circumference).toBeCloseTo(expected.c, 8);
expect(meta.data[i]._model.startAngle).toBeCloseTo(expected.s, 8);
expect(meta.data[i]._model.endAngle).toBeCloseTo(expected.e, 8);
expect(meta.data[i].circumference).toBeCloseTo(expected.c, 8);
expect(meta.data[i].startAngle).toBeCloseTo(expected.s, 8);
expect(meta.data[i].endAngle).toBeCloseTo(expected.e, 8);
});
});
@ -351,14 +352,14 @@ describe('Chart.controllers.doughnut', function() {
var arc = chart.getDatasetMeta(0).data[0];
jasmine.triggerMouseEvent(chart, 'mousemove', arc);
expect(arc._model.backgroundColor).toBe('rgb(49, 135, 221)');
expect(arc._model.borderColor).toBe('rgb(22, 89, 156)');
expect(arc._model.borderWidth).toBe(2);
expect(arc.options.backgroundColor).toBe('rgb(49, 135, 221)');
expect(arc.options.borderColor).toBe('rgb(22, 89, 156)');
expect(arc.options.borderWidth).toBe(2);
jasmine.triggerMouseEvent(chart, 'mouseout', arc);
expect(arc._model.backgroundColor).toBe('rgb(100, 150, 200)');
expect(arc._model.borderColor).toBe('rgb(50, 100, 150)');
expect(arc._model.borderWidth).toBe(2);
expect(arc.options.backgroundColor).toBe('rgb(100, 150, 200)');
expect(arc.options.borderColor).toBe('rgb(50, 100, 150)');
expect(arc.options.borderWidth).toBe(2);
});
it ('should handle hover styles defined via dataset properties', function() {
@ -374,14 +375,14 @@ describe('Chart.controllers.doughnut', function() {
chart.update();
jasmine.triggerMouseEvent(chart, 'mousemove', arc);
expect(arc._model.backgroundColor).toBe('rgb(200, 100, 150)');
expect(arc._model.borderColor).toBe('rgb(150, 50, 100)');
expect(arc._model.borderWidth).toBe(8.4);
expect(arc.options.backgroundColor).toBe('rgb(200, 100, 150)');
expect(arc.options.borderColor).toBe('rgb(150, 50, 100)');
expect(arc.options.borderWidth).toBe(8.4);
jasmine.triggerMouseEvent(chart, 'mouseout', arc);
expect(arc._model.backgroundColor).toBe('rgb(100, 150, 200)');
expect(arc._model.borderColor).toBe('rgb(50, 100, 150)');
expect(arc._model.borderWidth).toBe(2);
expect(arc.options.backgroundColor).toBe('rgb(100, 150, 200)');
expect(arc.options.borderColor).toBe('rgb(50, 100, 150)');
expect(arc.options.borderWidth).toBe(2);
});
it ('should handle hover styles defined via element options', function() {
@ -397,14 +398,14 @@ describe('Chart.controllers.doughnut', function() {
chart.update();
jasmine.triggerMouseEvent(chart, 'mousemove', arc);
expect(arc._model.backgroundColor).toBe('rgb(200, 100, 150)');
expect(arc._model.borderColor).toBe('rgb(150, 50, 100)');
expect(arc._model.borderWidth).toBe(8.4);
expect(arc.options.backgroundColor).toBe('rgb(200, 100, 150)');
expect(arc.options.borderColor).toBe('rgb(150, 50, 100)');
expect(arc.options.borderWidth).toBe(8.4);
jasmine.triggerMouseEvent(chart, 'mouseout', arc);
expect(arc._model.backgroundColor).toBe('rgb(100, 150, 200)');
expect(arc._model.borderColor).toBe('rgb(50, 100, 150)');
expect(arc._model.borderWidth).toBe(2);
expect(arc.options.backgroundColor).toBe('rgb(100, 150, 200)');
expect(arc.options.borderColor).toBe('rgb(50, 100, 150)');
expect(arc.options.borderWidth).toBe(2);
});
});
});

View File

@ -203,9 +203,9 @@ describe('Chart.controllers.line', function() {
{x: 0, y: 512},
{x: 171, y: 0}
].forEach(function(expected, i) {
expect(meta.data[i]._model.x).toBeCloseToPixel(expected.x);
expect(meta.data[i]._model.y).toBeCloseToPixel(expected.y);
expect(meta.data[i]._model).toEqual(jasmine.objectContaining({
expect(meta.data[i].x).toBeCloseToPixel(expected.x);
expect(meta.data[i].y).toBeCloseToPixel(expected.y);
expect(meta.data[i].options).toEqual(jasmine.objectContaining({
backgroundColor: 'red',
borderColor: 'blue',
}));
@ -248,7 +248,7 @@ describe('Chart.controllers.line', function() {
var meta = chart.getDatasetMeta(0);
// 1 point
var point = meta.data[0];
expect(point._model.x).toBeCloseToPixel(0);
expect(point.x).toBeCloseToPixel(0);
// 2 points
chart.data.labels = ['One', 'Two'];
@ -257,8 +257,8 @@ describe('Chart.controllers.line', function() {
var points = meta.data;
expect(points[0]._model.x).toBeCloseToPixel(0);
expect(points[1]._model.x).toBeCloseToPixel(512);
expect(points[0].x).toBeCloseToPixel(0);
expect(points[1].x).toBeCloseToPixel(512);
// 3 points
chart.data.labels = ['One', 'Two', 'Three'];
@ -267,9 +267,9 @@ describe('Chart.controllers.line', function() {
points = meta.data;
expect(points[0]._model.x).toBeCloseToPixel(0);
expect(points[1]._model.x).toBeCloseToPixel(256);
expect(points[2]._model.x).toBeCloseToPixel(512);
expect(points[0].x).toBeCloseToPixel(0);
expect(points[1].x).toBeCloseToPixel(256);
expect(points[2].x).toBeCloseToPixel(512);
// 4 points
chart.data.labels = ['One', 'Two', 'Three', 'Four'];
@ -278,10 +278,10 @@ describe('Chart.controllers.line', function() {
points = meta.data;
expect(points[0]._model.x).toBeCloseToPixel(0);
expect(points[1]._model.x).toBeCloseToPixel(171);
expect(points[2]._model.x).toBeCloseToPixel(340);
expect(points[3]._model.x).toBeCloseToPixel(512);
expect(points[0].x).toBeCloseToPixel(0);
expect(points[1].x).toBeCloseToPixel(171);
expect(points[2].x).toBeCloseToPixel(340);
expect(points[3].x).toBeCloseToPixel(512);
});
it('should update elements when the y scale is stacked', function() {
@ -320,8 +320,8 @@ describe('Chart.controllers.line', function() {
{x: 341, y: 146},
{x: 512, y: 439}
].forEach(function(values, i) {
expect(meta0.data[i]._model.x).toBeCloseToPixel(values.x);
expect(meta0.data[i]._model.y).toBeCloseToPixel(values.y);
expect(meta0.data[i].x).toBeCloseToPixel(values.x);
expect(meta0.data[i].y).toBeCloseToPixel(values.y);
});
var meta1 = chart.getDatasetMeta(1);
@ -332,8 +332,8 @@ describe('Chart.controllers.line', function() {
{x: 341, y: 146},
{x: 512, y: 497}
].forEach(function(values, i) {
expect(meta1.data[i]._model.x).toBeCloseToPixel(values.x);
expect(meta1.data[i]._model.y).toBeCloseToPixel(values.y);
expect(meta1.data[i].x).toBeCloseToPixel(values.x);
expect(meta1.data[i].y).toBeCloseToPixel(values.y);
});
});
@ -383,8 +383,8 @@ describe('Chart.controllers.line', function() {
{x: 341, y: 146},
{x: 512, y: 439}
].forEach(function(values, i) {
expect(meta0.data[i]._model.x).toBeCloseToPixel(values.x);
expect(meta0.data[i]._model.y).toBeCloseToPixel(values.y);
expect(meta0.data[i].x).toBeCloseToPixel(values.x);
expect(meta0.data[i].y).toBeCloseToPixel(values.y);
});
var meta1 = chart.getDatasetMeta(1);
@ -395,8 +395,8 @@ describe('Chart.controllers.line', function() {
{x: 341, y: 146},
{x: 512, y: 497}
].forEach(function(values, i) {
expect(meta1.data[i]._model.x).toBeCloseToPixel(values.x);
expect(meta1.data[i]._model.y).toBeCloseToPixel(values.y);
expect(meta1.data[i].x).toBeCloseToPixel(values.x);
expect(meta1.data[i].y).toBeCloseToPixel(values.y);
});
});
@ -461,8 +461,8 @@ describe('Chart.controllers.line', function() {
{x: 341, y: 146},
{x: 512, y: 439}
].forEach(function(values, i) {
expect(meta0.data[i]._model.x).toBeCloseToPixel(values.x);
expect(meta0.data[i]._model.y).toBeCloseToPixel(values.y);
expect(meta0.data[i].x).toBeCloseToPixel(values.x);
expect(meta0.data[i].y).toBeCloseToPixel(values.y);
});
var meta1 = chart.getDatasetMeta(1);
@ -473,8 +473,8 @@ describe('Chart.controllers.line', function() {
{x: 341, y: 146},
{x: 512, y: 497}
].forEach(function(values, i) {
expect(meta1.data[i]._model.x).toBeCloseToPixel(values.x);
expect(meta1.data[i]._model.y).toBeCloseToPixel(values.y);
expect(meta1.data[i].x).toBeCloseToPixel(values.x);
expect(meta1.data[i].y).toBeCloseToPixel(values.y);
});
});
@ -515,8 +515,8 @@ describe('Chart.controllers.line', function() {
{x: 341, y: 146},
{x: 512, y: 439}
].forEach(function(values, i) {
expect(meta0.data[i]._model.x).toBeCloseToPixel(values.x);
expect(meta0.data[i]._model.y).toBeCloseToPixel(values.y);
expect(meta0.data[i].x).toBeCloseToPixel(values.x);
expect(meta0.data[i].y).toBeCloseToPixel(values.y);
});
var meta1 = chart.getDatasetMeta(1);
@ -527,8 +527,8 @@ describe('Chart.controllers.line', function() {
{x: 341, y: 146},
{x: 512, y: 497}
].forEach(function(values, i) {
expect(meta1.data[i]._model.x).toBeCloseToPixel(values.x);
expect(meta1.data[i]._model.y).toBeCloseToPixel(values.y);
expect(meta1.data[i].x).toBeCloseToPixel(values.x);
expect(meta1.data[i].y).toBeCloseToPixel(values.y);
});
});
@ -552,9 +552,9 @@ describe('Chart.controllers.line', function() {
var meta = chart.getDatasetMeta(0);
expect(meta.dataset._model.backgroundColor).toBe('rgb(98, 98, 98)');
expect(meta.dataset._model.borderColor).toBe('rgb(8, 8, 8)');
expect(meta.dataset._model.borderWidth).toBe(0.55);
expect(meta.dataset.options.backgroundColor).toBe('rgb(98, 98, 98)');
expect(meta.dataset.options.borderColor).toBe('rgb(8, 8, 8)');
expect(meta.dataset.options.borderWidth).toBe(0.55);
});
describe('dataset global defaults', function() {
@ -595,19 +595,19 @@ describe('Chart.controllers.line', function() {
}
});
var model = chart.getDatasetMeta(0).dataset._model;
var options = chart.getDatasetMeta(0).dataset.options;
expect(model.spanGaps).toBe(true);
expect(model.tension).toBe(0.231);
expect(model.backgroundColor).toBe('#add');
expect(model.borderWidth).toBe('#daa');
expect(model.borderColor).toBe('#dad');
expect(model.borderCapStyle).toBe('round');
expect(model.borderDash).toEqual([0]);
expect(model.borderDashOffset).toBe(0.871);
expect(model.borderJoinStyle).toBe('miter');
expect(model.fill).toBe('start');
expect(model.cubicInterpolationMode).toBe('monotone');
expect(options.spanGaps).toBe(true);
expect(options.tension).toBe(0.231);
expect(options.backgroundColor).toBe('#add');
expect(options.borderWidth).toBe('#daa');
expect(options.borderColor).toBe('#dad');
expect(options.borderCapStyle).toBe('round');
expect(options.borderDash).toEqual([0]);
expect(options.borderDashOffset).toBe(0.871);
expect(options.borderJoinStyle).toBe('miter');
expect(options.fill).toBe('start');
expect(options.cubicInterpolationMode).toBe('monotone');
});
it('should be overriden by user-supplied values', function() {
@ -639,14 +639,14 @@ describe('Chart.controllers.line', function() {
}
});
var model = chart.getDatasetMeta(0).dataset._model;
var options = chart.getDatasetMeta(0).dataset.options;
// dataset-level option overrides global default
expect(model.spanGaps).toBe(true);
expect(options.spanGaps).toBe(true);
// chart-level default overrides global default
expect(model.tension).toBe(0.345);
expect(options.tension).toBe(0.345);
// dataset-level option overrides chart-level default
expect(model.backgroundColor).toBe('#dad');
expect(options.backgroundColor).toBe('#dad');
});
});
@ -679,19 +679,19 @@ describe('Chart.controllers.line', function() {
}
});
var model = chart.getDatasetMeta(0).dataset._model;
var options = chart.getDatasetMeta(0).dataset.options;
expect(model.spanGaps).toBe(true);
expect(model.tension).toBe(0.231);
expect(model.backgroundColor).toBe('#add');
expect(model.borderWidth).toBe('#daa');
expect(model.borderColor).toBe('#dad');
expect(model.borderCapStyle).toBe('round');
expect(model.borderDash).toEqual([0]);
expect(model.borderDashOffset).toBe(0.871);
expect(model.borderJoinStyle).toBe('miter');
expect(model.fill).toBe('start');
expect(model.cubicInterpolationMode).toBe('monotone');
expect(options.spanGaps).toBe(true);
expect(options.tension).toBe(0.231);
expect(options.backgroundColor).toBe('#add');
expect(options.borderWidth).toBe('#daa');
expect(options.borderColor).toBe('#dad');
expect(options.borderCapStyle).toBe('round');
expect(options.borderDash).toEqual([0]);
expect(options.borderDashOffset).toBe(0.871);
expect(options.borderJoinStyle).toBe('miter');
expect(options.fill).toBe('start');
expect(options.cubicInterpolationMode).toBe('monotone');
});
it('should obey the dataset options', function() {
@ -717,19 +717,19 @@ describe('Chart.controllers.line', function() {
}
});
var model = chart.getDatasetMeta(0).dataset._model;
var options = chart.getDatasetMeta(0).dataset.options;
expect(model.spanGaps).toBe(true);
expect(model.tension).toBe(0.231);
expect(model.backgroundColor).toBe('#add');
expect(model.borderWidth).toBe('#daa');
expect(model.borderColor).toBe('#dad');
expect(model.borderCapStyle).toBe('round');
expect(model.borderDash).toEqual([0]);
expect(model.borderDashOffset).toBe(0.871);
expect(model.borderJoinStyle).toBe('miter');
expect(model.fill).toBe('start');
expect(model.cubicInterpolationMode).toBe('monotone');
expect(options.spanGaps).toBe(true);
expect(options.tension).toBe(0.231);
expect(options.backgroundColor).toBe('#add');
expect(options.borderWidth).toBe('#daa');
expect(options.borderColor).toBe('#dad');
expect(options.borderCapStyle).toBe('round');
expect(options.borderDash).toEqual([0]);
expect(options.borderDashOffset).toBe(0.871);
expect(options.borderJoinStyle).toBe('miter');
expect(options.fill).toBe('start');
expect(options.cubicInterpolationMode).toBe('monotone');
});
it('should handle number of data point changes in update', function() {
@ -790,16 +790,16 @@ describe('Chart.controllers.line', function() {
var point = chart.getDatasetMeta(0).data[0];
jasmine.triggerMouseEvent(chart, 'mousemove', point);
expect(point._model.backgroundColor).toBe('rgb(49, 135, 221)');
expect(point._model.borderColor).toBe('rgb(22, 89, 156)');
expect(point._model.borderWidth).toBe(1);
expect(point._model.radius).toBe(4);
expect(point.options.backgroundColor).toBe('rgb(49, 135, 221)');
expect(point.options.borderColor).toBe('rgb(22, 89, 156)');
expect(point.options.borderWidth).toBe(1);
expect(point.options.radius).toBe(4);
jasmine.triggerMouseEvent(chart, 'mouseout', point);
expect(point._model.backgroundColor).toBe('rgb(100, 150, 200)');
expect(point._model.borderColor).toBe('rgb(50, 100, 150)');
expect(point._model.borderWidth).toBe(2);
expect(point._model.radius).toBe(3);
expect(point.options.backgroundColor).toBe('rgb(100, 150, 200)');
expect(point.options.borderColor).toBe('rgb(50, 100, 150)');
expect(point.options.borderWidth).toBe(2);
expect(point.options.radius).toBe(3);
});
it ('should handle hover styles defined via dataset properties', function() {
@ -816,16 +816,16 @@ describe('Chart.controllers.line', function() {
chart.update();
jasmine.triggerMouseEvent(chart, 'mousemove', point);
expect(point._model.backgroundColor).toBe('rgb(200, 100, 150)');
expect(point._model.borderColor).toBe('rgb(150, 50, 100)');
expect(point._model.borderWidth).toBe(8.4);
expect(point._model.radius).toBe(4.2);
expect(point.options.backgroundColor).toBe('rgb(200, 100, 150)');
expect(point.options.borderColor).toBe('rgb(150, 50, 100)');
expect(point.options.borderWidth).toBe(8.4);
expect(point.options.radius).toBe(4.2);
jasmine.triggerMouseEvent(chart, 'mouseout', point);
expect(point._model.backgroundColor).toBe('rgb(100, 150, 200)');
expect(point._model.borderColor).toBe('rgb(50, 100, 150)');
expect(point._model.borderWidth).toBe(2);
expect(point._model.radius).toBe(3);
expect(point.options.backgroundColor).toBe('rgb(100, 150, 200)');
expect(point.options.borderColor).toBe('rgb(50, 100, 150)');
expect(point.options.borderWidth).toBe(2);
expect(point.options.radius).toBe(3);
});
it ('should handle hover styles defined via element options', function() {
@ -842,16 +842,16 @@ describe('Chart.controllers.line', function() {
chart.update();
jasmine.triggerMouseEvent(chart, 'mousemove', point);
expect(point._model.backgroundColor).toBe('rgb(200, 100, 150)');
expect(point._model.borderColor).toBe('rgb(150, 50, 100)');
expect(point._model.borderWidth).toBe(8.4);
expect(point._model.radius).toBe(4.2);
expect(point.options.backgroundColor).toBe('rgb(200, 100, 150)');
expect(point.options.borderColor).toBe('rgb(150, 50, 100)');
expect(point.options.borderWidth).toBe(8.4);
expect(point.options.radius).toBe(4.2);
jasmine.triggerMouseEvent(chart, 'mouseout', point);
expect(point._model.backgroundColor).toBe('rgb(100, 150, 200)');
expect(point._model.borderColor).toBe('rgb(50, 100, 150)');
expect(point._model.borderWidth).toBe(2);
expect(point._model.radius).toBe(3);
expect(point.options.backgroundColor).toBe('rgb(100, 150, 200)');
expect(point.options.borderColor).toBe('rgb(50, 100, 150)');
expect(point.options.borderWidth).toBe(2);
expect(point.options.radius).toBe(3);
});
it ('should handle dataset hover styles defined via dataset properties', function() {
@ -872,14 +872,14 @@ describe('Chart.controllers.line', function() {
chart.update();
jasmine.triggerMouseEvent(chart, 'mousemove', point);
expect(dataset._model.backgroundColor).toBe('#000');
expect(dataset._model.borderColor).toBe('#111');
expect(dataset._model.borderWidth).toBe(12);
expect(dataset.options.backgroundColor).toBe('#000');
expect(dataset.options.borderColor).toBe('#111');
expect(dataset.options.borderWidth).toBe(12);
jasmine.triggerMouseEvent(chart, 'mouseout', point);
expect(dataset._model.backgroundColor).toBe('#AAA');
expect(dataset._model.borderColor).toBe('#BBB');
expect(dataset._model.borderWidth).toBe(6);
expect(dataset.options.backgroundColor).toBe('#AAA');
expect(dataset.options.borderColor).toBe('#BBB');
expect(dataset.options.borderWidth).toBe(6);
});
});
@ -899,7 +899,7 @@ describe('Chart.controllers.line', function() {
var meta = chart.getDatasetMeta(0);
var point = meta.data[0];
expect(point._model.borderWidth).toBe(0);
expect(point.options.borderWidth).toBe(0);
});
it('should allow an array as the point border width setting', function() {
@ -916,9 +916,9 @@ describe('Chart.controllers.line', function() {
});
var meta = chart.getDatasetMeta(0);
expect(meta.data[0]._model.borderWidth).toBe(1);
expect(meta.data[1]._model.borderWidth).toBe(2);
expect(meta.data[2]._model.borderWidth).toBe(3);
expect(meta.data[3]._model.borderWidth).toBe(4);
expect(meta.data[0].options.borderWidth).toBe(1);
expect(meta.data[1].options.borderWidth).toBe(2);
expect(meta.data[2].options.borderWidth).toBe(3);
expect(meta.data[3].options.borderWidth).toBe(4);
});
});

View File

@ -108,13 +108,13 @@ describe('Chart.controllers.polarArea', function() {
{o: 51, s: 0.5 * Math.PI, e: Math.PI},
{o: 0, s: Math.PI, e: 1.5 * Math.PI}
].forEach(function(expected, i) {
expect(meta.data[i]._model.x).toBeCloseToPixel(256);
expect(meta.data[i]._model.y).toBeCloseToPixel(259);
expect(meta.data[i]._model.innerRadius).toBeCloseToPixel(0);
expect(meta.data[i]._model.outerRadius).toBeCloseToPixel(expected.o);
expect(meta.data[i]._model.startAngle).toBe(expected.s);
expect(meta.data[i]._model.endAngle).toBe(expected.e);
expect(meta.data[i]._model).toEqual(jasmine.objectContaining({
expect(meta.data[i].x).toBeCloseToPixel(256);
expect(meta.data[i].y).toBeCloseToPixel(259);
expect(meta.data[i].innerRadius).toBeCloseToPixel(0);
expect(meta.data[i].outerRadius).toBeCloseToPixel(expected.o);
expect(meta.data[i].startAngle).toBe(expected.s);
expect(meta.data[i].endAngle).toBe(expected.e);
expect(meta.data[i].options).toEqual(jasmine.objectContaining({
backgroundColor: 'rgb(255, 0, 0)',
borderColor: 'rgb(0, 255, 0)',
borderWidth: 1.2
@ -129,17 +129,17 @@ describe('Chart.controllers.polarArea', function() {
chart.update();
for (var i = 0; i < 4; ++i) {
expect(meta.data[i]._model.backgroundColor).toBe('rgb(128, 129, 130)');
expect(meta.data[i]._model.borderColor).toBe('rgb(56, 57, 58)');
expect(meta.data[i]._model.borderWidth).toBe(1.123);
expect(meta.data[i].options.backgroundColor).toBe('rgb(128, 129, 130)');
expect(meta.data[i].options.borderColor).toBe('rgb(56, 57, 58)');
expect(meta.data[i].options.borderWidth).toBe(1.123);
}
chart.update();
expect(meta.data[0]._model.x).toBeCloseToPixel(256);
expect(meta.data[0]._model.y).toBeCloseToPixel(259);
expect(meta.data[0]._model.innerRadius).toBeCloseToPixel(0);
expect(meta.data[0]._model.outerRadius).toBeCloseToPixel(177);
expect(meta.data[0].x).toBeCloseToPixel(256);
expect(meta.data[0].y).toBeCloseToPixel(259);
expect(meta.data[0].innerRadius).toBeCloseToPixel(0);
expect(meta.data[0].outerRadius).toBeCloseToPixel(177);
});
it('should update elements with start angle from options', function() {
@ -176,13 +176,13 @@ describe('Chart.controllers.polarArea', function() {
{o: 51, s: Math.PI, e: 1.5 * Math.PI},
{o: 0, s: 1.5 * Math.PI, e: 2.0 * Math.PI}
].forEach(function(expected, i) {
expect(meta.data[i]._model.x).toBeCloseToPixel(256);
expect(meta.data[i]._model.y).toBeCloseToPixel(259);
expect(meta.data[i]._model.innerRadius).toBeCloseToPixel(0);
expect(meta.data[i]._model.outerRadius).toBeCloseToPixel(expected.o);
expect(meta.data[i]._model.startAngle).toBe(expected.s);
expect(meta.data[i]._model.endAngle).toBe(expected.e);
expect(meta.data[i]._model).toEqual(jasmine.objectContaining({
expect(meta.data[i].x).toBeCloseToPixel(256);
expect(meta.data[i].y).toBeCloseToPixel(259);
expect(meta.data[i].innerRadius).toBeCloseToPixel(0);
expect(meta.data[i].outerRadius).toBeCloseToPixel(expected.o);
expect(meta.data[i].startAngle).toBe(expected.s);
expect(meta.data[i].endAngle).toBe(expected.e);
expect(meta.data[i].options).toEqual(jasmine.objectContaining({
backgroundColor: 'rgb(255, 0, 0)',
borderColor: 'rgb(0, 255, 0)',
borderWidth: 1.2
@ -265,14 +265,14 @@ describe('Chart.controllers.polarArea', function() {
var arc = chart.getDatasetMeta(0).data[0];
jasmine.triggerMouseEvent(chart, 'mousemove', arc);
expect(arc._model.backgroundColor).toBe('rgb(49, 135, 221)');
expect(arc._model.borderColor).toBe('rgb(22, 89, 156)');
expect(arc._model.borderWidth).toBe(2);
expect(arc.options.backgroundColor).toBe('rgb(49, 135, 221)');
expect(arc.options.borderColor).toBe('rgb(22, 89, 156)');
expect(arc.options.borderWidth).toBe(2);
jasmine.triggerMouseEvent(chart, 'mouseout', arc);
expect(arc._model.backgroundColor).toBe('rgb(100, 150, 200)');
expect(arc._model.borderColor).toBe('rgb(50, 100, 150)');
expect(arc._model.borderWidth).toBe(2);
expect(arc.options.backgroundColor).toBe('rgb(100, 150, 200)');
expect(arc.options.borderColor).toBe('rgb(50, 100, 150)');
expect(arc.options.borderWidth).toBe(2);
});
it ('should handle hover styles defined via dataset properties', function() {
@ -288,14 +288,14 @@ describe('Chart.controllers.polarArea', function() {
chart.update();
jasmine.triggerMouseEvent(chart, 'mousemove', arc);
expect(arc._model.backgroundColor).toBe('rgb(200, 100, 150)');
expect(arc._model.borderColor).toBe('rgb(150, 50, 100)');
expect(arc._model.borderWidth).toBe(8.4);
expect(arc.options.backgroundColor).toBe('rgb(200, 100, 150)');
expect(arc.options.borderColor).toBe('rgb(150, 50, 100)');
expect(arc.options.borderWidth).toBe(8.4);
jasmine.triggerMouseEvent(chart, 'mouseout', arc);
expect(arc._model.backgroundColor).toBe('rgb(100, 150, 200)');
expect(arc._model.borderColor).toBe('rgb(50, 100, 150)');
expect(arc._model.borderWidth).toBe(2);
expect(arc.options.backgroundColor).toBe('rgb(100, 150, 200)');
expect(arc.options.borderColor).toBe('rgb(50, 100, 150)');
expect(arc.options.borderWidth).toBe(2);
});
it ('should handle hover styles defined via element options', function() {
@ -311,14 +311,14 @@ describe('Chart.controllers.polarArea', function() {
chart.update();
jasmine.triggerMouseEvent(chart, 'mousemove', arc);
expect(arc._model.backgroundColor).toBe('rgb(200, 100, 150)');
expect(arc._model.borderColor).toBe('rgb(150, 50, 100)');
expect(arc._model.borderWidth).toBe(8.4);
expect(arc.options.backgroundColor).toBe('rgb(200, 100, 150)');
expect(arc.options.borderColor).toBe('rgb(150, 50, 100)');
expect(arc.options.borderWidth).toBe(8.4);
jasmine.triggerMouseEvent(chart, 'mouseout', arc);
expect(arc._model.backgroundColor).toBe('rgb(100, 150, 200)');
expect(arc._model.borderColor).toBe('rgb(50, 100, 150)');
expect(arc._model.borderWidth).toBe(2);
expect(arc.options.backgroundColor).toBe('rgb(100, 150, 200)');
expect(arc.options.borderColor).toBe('rgb(50, 100, 150)');
expect(arc.options.borderWidth).toBe(2);
});
});
});

View File

@ -118,7 +118,7 @@ describe('Chart.controllers.radar', function() {
meta.controller.reset(); // reset first
// Line element
expect(meta.dataset._model).toEqual(jasmine.objectContaining({
expect(meta.dataset.options).toEqual(jasmine.objectContaining({
backgroundColor: 'rgb(255, 0, 0)',
borderCapStyle: 'round',
borderColor: 'rgb(0, 255, 0)',
@ -136,20 +136,19 @@ describe('Chart.controllers.radar', function() {
{x: 256, y: 260, cppx: 256, cppy: 260, cpnx: 256, cpny: 260},
{x: 256, y: 260, cppx: 256, cppy: 260, cpnx: 256, cpny: 260},
].forEach(function(expected, i) {
expect(meta.data[i]._model.x).toBeCloseToPixel(expected.x);
expect(meta.data[i]._model.y).toBeCloseToPixel(expected.y);
expect(meta.data[i]._model.controlPointPreviousX).toBeCloseToPixel(expected.cppx);
expect(meta.data[i]._model.controlPointPreviousY).toBeCloseToPixel(expected.cppy);
expect(meta.data[i]._model.controlPointNextX).toBeCloseToPixel(expected.cpnx);
expect(meta.data[i]._model.controlPointNextY).toBeCloseToPixel(expected.cpny);
expect(meta.data[i]._model).toEqual(jasmine.objectContaining({
expect(meta.data[i].x).toBeCloseToPixel(expected.x);
expect(meta.data[i].y).toBeCloseToPixel(expected.y);
expect(meta.data[i].controlPointPreviousX).toBeCloseToPixel(expected.cppx);
expect(meta.data[i].controlPointPreviousY).toBeCloseToPixel(expected.cppy);
expect(meta.data[i].controlPointNextX).toBeCloseToPixel(expected.cpnx);
expect(meta.data[i].controlPointNextY).toBeCloseToPixel(expected.cpny);
expect(meta.data[i].options).toEqual(jasmine.objectContaining({
backgroundColor: Chart.defaults.global.defaultColor,
borderWidth: 1,
borderColor: Chart.defaults.global.defaultColor,
hitRadius: 1,
radius: 3,
pointStyle: 'circle',
skip: false,
}));
});
@ -162,20 +161,19 @@ describe('Chart.controllers.radar', function() {
{x: 256, y: 260, cppx: 277, cppy: 260, cpnx: 250, cpny: 260},
{x: 200, y: 260, cppx: 200, cppy: 264, cpnx: 200, cpny: 250},
].forEach(function(expected, i) {
expect(meta.data[i]._model.x).toBeCloseToPixel(expected.x);
expect(meta.data[i]._model.y).toBeCloseToPixel(expected.y);
expect(meta.data[i]._model.controlPointPreviousX).toBeCloseToPixel(expected.cppx);
expect(meta.data[i]._model.controlPointPreviousY).toBeCloseToPixel(expected.cppy);
expect(meta.data[i]._model.controlPointNextX).toBeCloseToPixel(expected.cpnx);
expect(meta.data[i]._model.controlPointNextY).toBeCloseToPixel(expected.cpny);
expect(meta.data[i]._model).toEqual(jasmine.objectContaining({
expect(meta.data[i].x).toBeCloseToPixel(expected.x);
expect(meta.data[i].y).toBeCloseToPixel(expected.y);
expect(meta.data[i].controlPointPreviousX).toBeCloseToPixel(expected.cppx);
expect(meta.data[i].controlPointPreviousY).toBeCloseToPixel(expected.cppy);
expect(meta.data[i].controlPointNextX).toBeCloseToPixel(expected.cpnx);
expect(meta.data[i].controlPointNextY).toBeCloseToPixel(expected.cpny);
expect(meta.data[i].options).toEqual(jasmine.objectContaining({
backgroundColor: Chart.defaults.global.defaultColor,
borderWidth: 1,
borderColor: Chart.defaults.global.defaultColor,
hitRadius: 1,
radius: 3,
pointStyle: 'circle',
skip: false,
}));
});
@ -199,7 +197,7 @@ describe('Chart.controllers.radar', function() {
meta.controller._update();
expect(meta.dataset._model).toEqual(jasmine.objectContaining({
expect(meta.dataset.options).toEqual(jasmine.objectContaining({
backgroundColor: 'rgb(98, 98, 98)',
borderCapStyle: 'butt',
borderColor: 'rgb(8, 8, 8)',
@ -218,16 +216,15 @@ describe('Chart.controllers.radar', function() {
{x: 256, y: 260},
{x: 200, y: 260},
].forEach(function(expected, i) {
expect(meta.data[i]._model.x).toBeCloseToPixel(expected.x);
expect(meta.data[i]._model.y).toBeCloseToPixel(expected.y);
expect(meta.data[i]._model).toEqual(jasmine.objectContaining({
expect(meta.data[i].x).toBeCloseToPixel(expected.x);
expect(meta.data[i].y).toBeCloseToPixel(expected.y);
expect(meta.data[i].options).toEqual(jasmine.objectContaining({
backgroundColor: 'rgb(128, 129, 130)',
borderWidth: 1.123,
borderColor: 'rgb(56, 57, 58)',
hitRadius: 3.3,
radius: 22,
pointStyle: 'circle',
skip: false,
pointStyle: 'circle'
}));
});
});
@ -260,16 +257,16 @@ describe('Chart.controllers.radar', function() {
var point = chart.getDatasetMeta(0).data[0];
jasmine.triggerMouseEvent(chart, 'mousemove', point);
expect(point._model.backgroundColor).toBe('rgb(49, 135, 221)');
expect(point._model.borderColor).toBe('rgb(22, 89, 156)');
expect(point._model.borderWidth).toBe(1);
expect(point._model.radius).toBe(4);
expect(point.options.backgroundColor).toBe('rgb(49, 135, 221)');
expect(point.options.borderColor).toBe('rgb(22, 89, 156)');
expect(point.options.borderWidth).toBe(1);
expect(point.options.radius).toBe(4);
jasmine.triggerMouseEvent(chart, 'mouseout', point);
expect(point._model.backgroundColor).toBe('rgb(100, 150, 200)');
expect(point._model.borderColor).toBe('rgb(50, 100, 150)');
expect(point._model.borderWidth).toBe(2);
expect(point._model.radius).toBe(3);
expect(point.options.backgroundColor).toBe('rgb(100, 150, 200)');
expect(point.options.borderColor).toBe('rgb(50, 100, 150)');
expect(point.options.borderWidth).toBe(2);
expect(point.options.radius).toBe(3);
});
it ('should handle hover styles defined via dataset properties', function() {
@ -286,16 +283,16 @@ describe('Chart.controllers.radar', function() {
chart.update();
jasmine.triggerMouseEvent(chart, 'mousemove', point);
expect(point._model.backgroundColor).toBe('rgb(200, 100, 150)');
expect(point._model.borderColor).toBe('rgb(150, 50, 100)');
expect(point._model.borderWidth).toBe(8.4);
expect(point._model.radius).toBe(4.2);
expect(point.options.backgroundColor).toBe('rgb(200, 100, 150)');
expect(point.options.borderColor).toBe('rgb(150, 50, 100)');
expect(point.options.borderWidth).toBe(8.4);
expect(point.options.radius).toBe(4.2);
jasmine.triggerMouseEvent(chart, 'mouseout', point);
expect(point._model.backgroundColor).toBe('rgb(100, 150, 200)');
expect(point._model.borderColor).toBe('rgb(50, 100, 150)');
expect(point._model.borderWidth).toBe(2);
expect(point._model.radius).toBe(3);
expect(point.options.backgroundColor).toBe('rgb(100, 150, 200)');
expect(point.options.borderColor).toBe('rgb(50, 100, 150)');
expect(point.options.borderWidth).toBe(2);
expect(point.options.radius).toBe(3);
});
it ('should handle hover styles defined via element options', function() {
@ -312,16 +309,16 @@ describe('Chart.controllers.radar', function() {
chart.update();
jasmine.triggerMouseEvent(chart, 'mousemove', point);
expect(point._model.backgroundColor).toBe('rgb(200, 100, 150)');
expect(point._model.borderColor).toBe('rgb(150, 50, 100)');
expect(point._model.borderWidth).toBe(8.4);
expect(point._model.radius).toBe(4.2);
expect(point.options.backgroundColor).toBe('rgb(200, 100, 150)');
expect(point.options.borderColor).toBe('rgb(150, 50, 100)');
expect(point.options.borderWidth).toBe(8.4);
expect(point.options.radius).toBe(4.2);
jasmine.triggerMouseEvent(chart, 'mouseout', point);
expect(point._model.backgroundColor).toBe('rgb(100, 150, 200)');
expect(point._model.borderColor).toBe('rgb(50, 100, 150)');
expect(point._model.borderWidth).toBe(2);
expect(point._model.radius).toBe(3);
expect(point.options.backgroundColor).toBe('rgb(100, 150, 200)');
expect(point.options.borderColor).toBe('rgb(50, 100, 150)');
expect(point.options.borderWidth).toBe(2);
expect(point.options.radius).toBe(3);
});
});
@ -339,7 +336,7 @@ describe('Chart.controllers.radar', function() {
var meta = chart.getDatasetMeta(0);
var point = meta.data[0];
expect(point._model.borderWidth).toBe(0);
expect(point.options.borderWidth).toBe(0);
});
it('should use the pointRadius setting over the radius setting', function() {
@ -360,8 +357,8 @@ describe('Chart.controllers.radar', function() {
var meta0 = chart.getDatasetMeta(0);
var meta1 = chart.getDatasetMeta(1);
expect(meta0.data[0]._model.radius).toBe(10);
expect(meta1.data[0]._model.radius).toBe(20);
expect(meta0.data[0].options.radius).toBe(10);
expect(meta1.data[0].options.radius).toBe(20);
});
it('should return id for value scale', function() {

View File

@ -21,8 +21,8 @@ describe('Chart.controllers.scatter', function() {
jasmine.triggerMouseEvent(chart, 'mousemove', point);
// Title should be empty
expect(chart.tooltip._view.title.length).toBe(0);
expect(chart.tooltip._view.body[0].lines).toEqual(['(10, 15)']);
expect(chart.tooltip.title.length).toBe(0);
expect(chart.tooltip.body[0].lines).toEqual(['(10, 15)']);
});
describe('showLines option', function() {

View File

@ -66,7 +66,6 @@ describe('Chart', function() {
var callback = function() {};
var defaults = Chart.defaults;
defaults.global.responsiveAnimationDuration = 42;
defaults.global.hover.onHover = callback;
defaults.line.spanGaps = true;
defaults.line.hover.mode = 'x-axis';
@ -79,11 +78,9 @@ describe('Chart', function() {
expect(options.defaultFontSize).toBe(defaults.global.defaultFontSize);
expect(options.showLines).toBe(defaults.line.showLines);
expect(options.spanGaps).toBe(true);
expect(options.responsiveAnimationDuration).toBe(42);
expect(options.hover.onHover).toBe(callback);
expect(options.hover.mode).toBe('x-axis');
defaults.global.responsiveAnimationDuration = 0;
defaults.global.hover.onHover = null;
defaults.line.spanGaps = false;
defaults.line.hover.mode = 'index';
@ -93,7 +90,6 @@ describe('Chart', function() {
var callback = function() {};
var defaults = Chart.defaults;
defaults.global.responsiveAnimationDuration = 42;
defaults.global.hover.onHover = callback;
defaults.line.hover.mode = 'x-axis';
defaults.line.spanGaps = true;
@ -101,7 +97,6 @@ describe('Chart', function() {
var chart = acquireChart({
type: 'line',
options: {
responsiveAnimationDuration: 4242,
spanGaps: false,
hover: {
mode: 'dataset',
@ -113,13 +108,11 @@ describe('Chart', function() {
});
var options = chart.options;
expect(options.responsiveAnimationDuration).toBe(4242);
expect(options.showLines).toBe(defaults.global.showLines);
expect(options.spanGaps).toBe(false);
expect(options.hover.mode).toBe('dataset');
expect(options.title.position).toBe('bottom');
defaults.global.responsiveAnimationDuration = 0;
defaults.global.hover.onHover = null;
defaults.line.hover.mode = 'index';
defaults.line.spanGaps = false;
@ -950,18 +943,18 @@ describe('Chart', function() {
// Verify that points are at their initial correct location,
// then we will reset and see that they moved
expect(meta.data[0]._model.y).toBeCloseToPixel(333);
expect(meta.data[1]._model.y).toBeCloseToPixel(183);
expect(meta.data[2]._model.y).toBeCloseToPixel(32);
expect(meta.data[3]._model.y).toBeCloseToPixel(482);
expect(meta.data[0].y).toBeCloseToPixel(333);
expect(meta.data[1].y).toBeCloseToPixel(183);
expect(meta.data[2].y).toBeCloseToPixel(32);
expect(meta.data[3].y).toBeCloseToPixel(482);
chart.reset();
// For a line chart, the animation state is the bottom
expect(meta.data[0]._model.y).toBeCloseToPixel(482);
expect(meta.data[1]._model.y).toBeCloseToPixel(482);
expect(meta.data[2]._model.y).toBeCloseToPixel(482);
expect(meta.data[3]._model.y).toBeCloseToPixel(482);
expect(meta.data[0].y).toBeCloseToPixel(482);
expect(meta.data[1].y).toBeCloseToPixel(482);
expect(meta.data[2].y).toBeCloseToPixel(482);
expect(meta.data[3].y).toBeCloseToPixel(482);
});
});
@ -1106,7 +1099,7 @@ describe('Chart', function() {
chart.options.tooltips = newTooltipConfig;
chart.update();
expect(chart.tooltip._options).toEqual(jasmine.objectContaining(newTooltipConfig));
expect(chart.tooltip.options).toEqual(jasmine.objectContaining(newTooltipConfig));
});
it ('should update the tooltip on update', function() {
@ -1283,46 +1276,4 @@ describe('Chart', function() {
]);
});
});
describe('controller.update', function() {
beforeEach(function() {
this.chart = acquireChart({
type: 'doughnut',
options: {
animation: {
easing: 'linear',
duration: 500
}
}
});
this.addAnimationSpy = spyOn(Chart.animationService, 'addAnimation');
});
it('should add an animation with the default options', function() {
this.chart.update();
expect(this.addAnimationSpy).toHaveBeenCalledWith(
this.chart,
jasmine.objectContaining({easing: 'linear'}),
500,
undefined
);
});
it('should add an animation with the provided options', function() {
this.chart.update({
duration: 800,
easing: 'easeOutBounce',
lazy: false,
});
expect(this.addAnimationSpy).toHaveBeenCalledWith(
this.chart,
jasmine.objectContaining({easing: 'easeOutBounce'}),
800,
false
);
});
});
});

View File

@ -1,51 +0,0 @@
// Test the core element functionality
describe('Core element tests', function() {
it ('should transition model properties', function() {
var element = new Chart.Element({
_model: {
numberProp: 0,
numberProp2: 100,
_underscoreProp: 0,
stringProp: 'abc',
objectProp: {
myObject: true
},
colorProp: 'rgb(0, 0, 0)'
}
});
// First transition clones model into view
element.transition(0.25);
expect(element._view).toEqual(element._model);
expect(element._view).not.toBe(element._model);
expect(element._view.objectProp).toBe(element._model.objectProp); // not cloned
element._model.numberProp = 100;
element._model.numberProp2 = 250;
element._model._underscoreProp = 200;
element._model.stringProp = 'def';
element._model.newStringProp = 'newString';
element._model.colorProp = 'rgb(255, 255, 0)';
element.transition(0.25);
expect(element._view).toEqual({
numberProp: 25,
numberProp2: 137.5,
_underscoreProp: 0, // underscore props are not transition to a new value
stringProp: 'def',
newStringProp: 'newString',
objectProp: {
myObject: true
},
colorProp: 'rgb(64, 64, 0)',
});
// Final transition clones model into view
element.transition(1);
expect(element._view).toEqual(element._model);
expect(element._view).not.toBe(element._model);
});
});

View File

@ -33,8 +33,8 @@ describe('Core.Interaction', function() {
type: 'click',
chart: chart,
native: true, // needed otherwise things its a DOM event
x: point._model.x,
y: point._model.y,
x: point.x,
y: point.y,
};
var elements = Chart.Interaction.modes.point(chart, evt, {}).map(item => item.element);
@ -88,8 +88,8 @@ describe('Core.Interaction', function() {
type: 'click',
chart: chart,
native: true, // needed otherwise things its a DOM event
x: point._model.x,
y: point._model.y,
x: point.x,
y: point.y,
};
var elements = Chart.Interaction.modes.index(chart, evt, {intersect: true}).map(item => item.element);
@ -223,8 +223,8 @@ describe('Core.Interaction', function() {
type: 'click',
chart: chart,
native: true, // needed otherwise things its a DOM event
x: point._model.x,
y: point._model.y
x: point.x,
y: point.y
};
var elements = Chart.Interaction.modes.dataset(chart, evt, {intersect: true});
@ -365,8 +365,8 @@ describe('Core.Interaction', function() {
// Halfway between 2 mid points
var pt = {
x: meta0.data[1]._view.x,
y: (meta0.data[1]._view.y + meta1.data[1]._view.y) / 2
x: meta0.data[1].x,
y: (meta0.data[1].y + meta1.data[1].y) / 2
};
var evt = {
@ -391,8 +391,8 @@ describe('Core.Interaction', function() {
// At 'Point 2', 10
var pt = {
x: meta0.data[1]._view.x,
y: meta0.data[0]._view.y
x: meta0.data[1].x,
y: meta0.data[0].y
};
var evt = {
@ -415,8 +415,8 @@ describe('Core.Interaction', function() {
// Haflway between 'Point 1' and 'Point 2', y=10
var pt = {
x: (meta0.data[0]._view.x + meta0.data[1]._view.x) / 2,
y: meta0.data[0]._view.y
x: (meta0.data[0].x + meta0.data[1].x) / 2,
y: meta0.data[0].y
};
var evt = {
@ -440,8 +440,8 @@ describe('Core.Interaction', function() {
// 'Point 1', y = 30
var pt = {
x: meta0.data[0]._view.x,
y: meta0.data[2]._view.y
x: meta0.data[0].x,
y: meta0.data[2].y
};
var evt = {
@ -464,8 +464,8 @@ describe('Core.Interaction', function() {
// 'Point 1', y = 40
var pt = {
x: meta0.data[0]._view.x,
y: meta0.data[1]._view.y
x: meta0.data[0].x,
y: meta0.data[1].y
};
var evt = {
@ -514,8 +514,8 @@ describe('Core.Interaction', function() {
type: 'click',
chart: chart,
native: true, // needed otherwise things its a DOM event
x: point._view.x + 15,
y: point._view.y
x: point.x + 15,
y: point.y
};
// Nothing intersects so find nothing
@ -526,8 +526,8 @@ describe('Core.Interaction', function() {
type: 'click',
chart: chart,
native: true,
x: point._view.x,
y: point._view.y
x: point.x,
y: point.y
};
elements = Chart.Interaction.modes.nearest(chart, evt, {intersect: true}).map(item => item.element);
expect(elements).toEqual([point]);
@ -547,8 +547,8 @@ describe('Core.Interaction', function() {
// Halfway between 2 mid points
var pt = {
x: meta0.data[1]._view.x,
y: meta0.data[1]._view.y
x: meta0.data[1].x,
y: meta0.data[1].y
};
var evt = {
@ -577,8 +577,8 @@ describe('Core.Interaction', function() {
// Halfway between 2 mid points
var pt = {
x: meta0.data[1]._view.x,
y: meta0.data[1]._view.y
x: meta0.data[1].x,
y: meta0.data[1].y
};
var evt = {
@ -626,8 +626,8 @@ describe('Core.Interaction', function() {
// Halfway between 2 mid points
var pt = {
x: meta0.data[1]._view.x,
y: meta0.data[1]._view.y
x: meta0.data[1].x,
y: meta0.data[1].y
};
var evt = {
@ -660,8 +660,8 @@ describe('Core.Interaction', function() {
// Halfway between 2 mid points
var pt = {
x: meta0.data[1]._view.x,
y: meta0.data[1]._view.y
x: meta0.data[1].x,
y: meta0.data[1].y
};
var evt = {
@ -718,8 +718,8 @@ describe('Core.Interaction', function() {
// Halfway between 2 mid points
var pt = {
x: meta0.data[1]._view.x,
y: meta0.data[1]._view.y
x: meta0.data[1].x,
y: meta0.data[1].y
};
var evt = {
@ -752,8 +752,8 @@ describe('Core.Interaction', function() {
// Halfway between 2 mid points
var pt = {
x: meta0.data[1]._view.x,
y: meta0.data[1]._view.y
x: meta0.data[1].x,
y: meta0.data[1].y
};
var evt = {

View File

@ -69,7 +69,7 @@ describe('Core.Tooltip', function() {
view: window,
bubbles: true,
cancelable: true,
clientX: rect.left + point._model.x,
clientX: rect.left + point.x,
clientY: 0
});
@ -80,46 +80,55 @@ describe('Core.Tooltip', function() {
var tooltip = chart.tooltip;
var globalDefaults = Chart.defaults.global;
expect(tooltip._view).toEqual(jasmine.objectContaining({
// Positioning
xPadding: 6,
yPadding: 6,
xAlign: 'left',
yAlign: 'center',
expect(tooltip.options.xPadding).toEqual(6);
expect(tooltip.options.yPadding).toEqual(6);
expect(tooltip.xAlign).toEqual('left');
expect(tooltip.yAlign).toEqual('center');
expect(tooltip.options).toEqual(jasmine.objectContaining({
// Body
bodyFontColor: '#fff',
_bodyFontFamily: globalDefaults.defaultFontFamily,
_bodyFontStyle: globalDefaults.defaultFontStyle,
_bodyAlign: 'left',
bodyFontFamily: globalDefaults.defaultFontFamily,
bodyFontStyle: globalDefaults.defaultFontStyle,
bodyAlign: 'left',
bodyFontSize: globalDefaults.defaultFontSize,
bodySpacing: 2,
}));
expect(tooltip.options).toEqual(jasmine.objectContaining({
// Title
titleFontColor: '#fff',
_titleFontFamily: globalDefaults.defaultFontFamily,
_titleFontStyle: 'bold',
titleFontFamily: globalDefaults.defaultFontFamily,
titleFontStyle: 'bold',
titleFontSize: globalDefaults.defaultFontSize,
_titleAlign: 'left',
titleAlign: 'left',
titleSpacing: 2,
titleMarginBottom: 6,
}));
expect(tooltip.options).toEqual(jasmine.objectContaining({
// Footer
footerFontColor: '#fff',
_footerFontFamily: globalDefaults.defaultFontFamily,
_footerFontStyle: 'bold',
footerFontFamily: globalDefaults.defaultFontFamily,
footerFontStyle: 'bold',
footerFontSize: globalDefaults.defaultFontSize,
_footerAlign: 'left',
footerAlign: 'left',
footerSpacing: 2,
footerMarginTop: 6,
}));
expect(tooltip.options).toEqual(jasmine.objectContaining({
// Appearance
caretSize: 5,
caretPadding: 2,
cornerRadius: 6,
backgroundColor: 'rgba(0,0,0,0.8)',
multiKeyBackground: '#fff',
displayColors: true
}));
expect(tooltip).toEqual(jasmine.objectContaining({
opacity: 1,
legendColorBackground: '#fff',
displayColors: true,
// Text
title: ['Point 2'],
@ -135,7 +144,6 @@ describe('Core.Tooltip', function() {
}],
afterBody: [],
footer: [],
caretPadding: 2,
labelColors: [{
borderColor: globalDefaults.defaultColor,
backgroundColor: globalDefaults.defaultColor
@ -145,8 +153,8 @@ describe('Core.Tooltip', function() {
}]
}));
expect(tooltip._view.x).toBeCloseToPixel(267);
expect(tooltip._view.y).toBeCloseToPixel(155);
expect(tooltip.x).toBeCloseToPixel(267);
expect(tooltip.y).toBeCloseToPixel(155);
});
it('Should only display if intersecting if intersect is set', function() {
@ -185,7 +193,7 @@ describe('Core.Tooltip', function() {
view: window,
bubbles: true,
cancelable: true,
clientX: rect.left + point._model.x,
clientX: rect.left + point.x,
clientY: 0
});
@ -194,46 +202,9 @@ describe('Core.Tooltip', function() {
// Check and see if tooltip was displayed
var tooltip = chart.tooltip;
var globalDefaults = Chart.defaults.global;
expect(tooltip._view).toEqual(jasmine.objectContaining({
// Positioning
xPadding: 6,
yPadding: 6,
// Body
bodyFontColor: '#fff',
_bodyFontFamily: globalDefaults.defaultFontFamily,
_bodyFontStyle: globalDefaults.defaultFontStyle,
_bodyAlign: 'left',
bodyFontSize: globalDefaults.defaultFontSize,
bodySpacing: 2,
// Title
titleFontColor: '#fff',
_titleFontFamily: globalDefaults.defaultFontFamily,
_titleFontStyle: 'bold',
titleFontSize: globalDefaults.defaultFontSize,
_titleAlign: 'left',
titleSpacing: 2,
titleMarginBottom: 6,
// Footer
footerFontColor: '#fff',
_footerFontFamily: globalDefaults.defaultFontFamily,
_footerFontStyle: 'bold',
footerFontSize: globalDefaults.defaultFontSize,
_footerAlign: 'left',
footerSpacing: 2,
footerMarginTop: 6,
// Appearance
caretSize: 5,
cornerRadius: 6,
backgroundColor: 'rgba(0,0,0,0.8)',
expect(tooltip).toEqual(jasmine.objectContaining({
opacity: 0,
legendColorBackground: '#fff',
displayColors: true,
}));
});
});
@ -274,8 +245,8 @@ describe('Core.Tooltip', function() {
view: window,
bubbles: true,
cancelable: true,
clientX: rect.left + point._model.x,
clientY: rect.top + point._model.y
clientX: rect.left + point.x,
clientY: rect.top + point.y
});
// Manually trigger rather than having an async test
@ -285,46 +256,55 @@ describe('Core.Tooltip', function() {
var tooltip = chart.tooltip;
var globalDefaults = Chart.defaults.global;
expect(tooltip._view).toEqual(jasmine.objectContaining({
// Positioning
xPadding: 6,
yPadding: 6,
xAlign: 'left',
yAlign: 'center',
expect(tooltip.options.xPadding).toEqual(6);
expect(tooltip.options.yPadding).toEqual(6);
expect(tooltip.xAlign).toEqual('left');
expect(tooltip.yAlign).toEqual('center');
expect(tooltip.options).toEqual(jasmine.objectContaining({
// Body
bodyFontColor: '#fff',
_bodyFontFamily: globalDefaults.defaultFontFamily,
_bodyFontStyle: globalDefaults.defaultFontStyle,
_bodyAlign: 'left',
bodyFontFamily: globalDefaults.defaultFontFamily,
bodyFontStyle: globalDefaults.defaultFontStyle,
bodyAlign: 'left',
bodyFontSize: globalDefaults.defaultFontSize,
bodySpacing: 2,
}));
expect(tooltip.options).toEqual(jasmine.objectContaining({
// Title
titleFontColor: '#fff',
_titleFontFamily: globalDefaults.defaultFontFamily,
_titleFontStyle: 'bold',
titleFontFamily: globalDefaults.defaultFontFamily,
titleFontStyle: 'bold',
titleFontSize: globalDefaults.defaultFontSize,
_titleAlign: 'left',
titleAlign: 'left',
titleSpacing: 2,
titleMarginBottom: 6,
}));
expect(tooltip.options).toEqual(jasmine.objectContaining({
// Footer
footerFontColor: '#fff',
_footerFontFamily: globalDefaults.defaultFontFamily,
_footerFontStyle: 'bold',
footerFontFamily: globalDefaults.defaultFontFamily,
footerFontStyle: 'bold',
footerFontSize: globalDefaults.defaultFontSize,
_footerAlign: 'left',
footerAlign: 'left',
footerSpacing: 2,
footerMarginTop: 6,
}));
expect(tooltip.options).toEqual(jasmine.objectContaining({
// Appearance
caretSize: 5,
caretPadding: 2,
cornerRadius: 6,
backgroundColor: 'rgba(0,0,0,0.8)',
multiKeyBackground: '#fff',
displayColors: true
}));
expect(tooltip).toEqual(jasmine.objectContaining({
opacity: 1,
legendColorBackground: '#fff',
displayColors: true,
// Text
title: ['Point 2'],
@ -336,7 +316,6 @@ describe('Core.Tooltip', function() {
}],
afterBody: [],
footer: [],
caretPadding: 2,
labelTextColors: ['#fff'],
labelColors: [{
borderColor: globalDefaults.defaultColor,
@ -344,8 +323,8 @@ describe('Core.Tooltip', function() {
}]
}));
expect(tooltip._view.x).toBeCloseToPixel(267);
expect(tooltip._view.y).toBeCloseToPixel(312);
expect(tooltip.x).toBeCloseToPixel(267);
expect(tooltip.y).toBeCloseToPixel(312);
});
it('Should display information from user callbacks', function() {
@ -421,8 +400,8 @@ describe('Core.Tooltip', function() {
view: window,
bubbles: true,
cancelable: true,
clientX: rect.left + point._model.x,
clientY: rect.top + point._model.y
clientX: rect.left + point.x,
clientY: rect.top + point.y
});
// Manually trigger rather than having an async test
@ -432,45 +411,54 @@ describe('Core.Tooltip', function() {
var tooltip = chart.tooltip;
var globalDefaults = Chart.defaults.global;
expect(tooltip._view).toEqual(jasmine.objectContaining({
// Positioning
xPadding: 6,
yPadding: 6,
xAlign: 'center',
yAlign: 'top',
expect(tooltip.options.xPadding).toEqual(6);
expect(tooltip.options.yPadding).toEqual(6);
expect(tooltip.xAlign).toEqual('center');
expect(tooltip.yAlign).toEqual('top');
expect(tooltip.options).toEqual(jasmine.objectContaining({
// Body
bodyFontColor: '#fff',
_bodyFontFamily: globalDefaults.defaultFontFamily,
_bodyFontStyle: globalDefaults.defaultFontStyle,
_bodyAlign: 'left',
bodyFontFamily: globalDefaults.defaultFontFamily,
bodyFontStyle: globalDefaults.defaultFontStyle,
bodyAlign: 'left',
bodyFontSize: globalDefaults.defaultFontSize,
bodySpacing: 2,
}));
expect(tooltip.options).toEqual(jasmine.objectContaining({
// Title
titleFontColor: '#fff',
_titleFontFamily: globalDefaults.defaultFontFamily,
_titleFontStyle: 'bold',
titleFontFamily: globalDefaults.defaultFontFamily,
titleFontStyle: 'bold',
titleFontSize: globalDefaults.defaultFontSize,
_titleAlign: 'left',
titleAlign: 'left',
titleSpacing: 2,
titleMarginBottom: 6,
}));
expect(tooltip.options).toEqual(jasmine.objectContaining({
// Footer
footerFontColor: '#fff',
_footerFontFamily: globalDefaults.defaultFontFamily,
_footerFontStyle: 'bold',
footerFontFamily: globalDefaults.defaultFontFamily,
footerFontStyle: 'bold',
footerFontSize: globalDefaults.defaultFontSize,
_footerAlign: 'left',
footerAlign: 'left',
footerSpacing: 2,
footerMarginTop: 6,
}));
expect(tooltip.options).toEqual(jasmine.objectContaining({
// Appearance
caretSize: 5,
caretPadding: 2,
cornerRadius: 6,
backgroundColor: 'rgba(0,0,0,0.8)',
multiKeyBackground: '#fff',
}));
expect(tooltip).toEqual(jasmine.objectContaining({
opacity: 1,
legendColorBackground: '#fff',
// Text
title: ['beforeTitle', 'title', 'afterTitle'],
@ -486,7 +474,6 @@ describe('Core.Tooltip', function() {
}],
afterBody: ['afterBody'],
footer: ['beforeFooter', 'footer', 'afterFooter'],
caretPadding: 2,
labelTextColors: ['labelTextColor', 'labelTextColor'],
labelColors: [{
borderColor: globalDefaults.defaultColor,
@ -497,8 +484,8 @@ describe('Core.Tooltip', function() {
}]
}));
expect(tooltip._view.x).toBeCloseToPixel(214);
expect(tooltip._view.y).toBeCloseToPixel(190);
expect(tooltip.x).toBeCloseToPixel(214);
expect(tooltip.y).toBeCloseToPixel(190);
});
it('Should allow sorting items', function() {
@ -539,8 +526,8 @@ describe('Core.Tooltip', function() {
view: window,
bubbles: true,
cancelable: true,
clientX: rect.left + point0._model.x,
clientY: rect.top + point0._model.y
clientX: rect.left + point0.x,
clientY: rect.top + point0.y
});
// Manually trigger rather than having an async test
@ -550,7 +537,7 @@ describe('Core.Tooltip', function() {
var tooltip = chart.tooltip;
var globalDefaults = Chart.defaults.global;
expect(tooltip._view).toEqual(jasmine.objectContaining({
expect(tooltip).toEqual(jasmine.objectContaining({
// Positioning
xAlign: 'left',
yAlign: 'center',
@ -578,8 +565,8 @@ describe('Core.Tooltip', function() {
}]
}));
expect(tooltip._view.x).toBeCloseToPixel(267);
expect(tooltip._view.y).toBeCloseToPixel(155);
expect(tooltip.x).toBeCloseToPixel(267);
expect(tooltip.y).toBeCloseToPixel(155);
});
it('Should allow reversing items', function() {
@ -618,8 +605,8 @@ describe('Core.Tooltip', function() {
view: window,
bubbles: true,
cancelable: true,
clientX: rect.left + point0._model.x,
clientY: rect.top + point0._model.y
clientX: rect.left + point0.x,
clientY: rect.top + point0.y
});
// Manually trigger rather than having an async test
@ -629,7 +616,7 @@ describe('Core.Tooltip', function() {
var tooltip = chart.tooltip;
var globalDefaults = Chart.defaults.global;
expect(tooltip._view).toEqual(jasmine.objectContaining({
expect(tooltip).toEqual(jasmine.objectContaining({
// Positioning
xAlign: 'left',
yAlign: 'center',
@ -657,8 +644,8 @@ describe('Core.Tooltip', function() {
}]
}));
expect(tooltip._view.x).toBeCloseToPixel(267);
expect(tooltip._view.y).toBeCloseToPixel(155);
expect(tooltip.x).toBeCloseToPixel(267);
expect(tooltip.y).toBeCloseToPixel(155);
});
it('Should follow dataset order', function() {
@ -698,8 +685,8 @@ describe('Core.Tooltip', function() {
view: window,
bubbles: true,
cancelable: true,
clientX: rect.left + point0._model.x,
clientY: rect.top + point0._model.y
clientX: rect.left + point0.x,
clientY: rect.top + point0.y
});
// Manually trigger rather than having an async test
@ -709,7 +696,7 @@ describe('Core.Tooltip', function() {
var tooltip = chart.tooltip;
var globalDefaults = Chart.defaults.global;
expect(tooltip._view).toEqual(jasmine.objectContaining({
expect(tooltip).toEqual(jasmine.objectContaining({
// Positioning
xAlign: 'left',
yAlign: 'center',
@ -737,8 +724,8 @@ describe('Core.Tooltip', function() {
}]
}));
expect(tooltip._view.x).toBeCloseToPixel(267);
expect(tooltip._view.y).toBeCloseToPixel(155);
expect(tooltip.x).toBeCloseToPixel(267);
expect(tooltip.y).toBeCloseToPixel(155);
});
it('should filter items from the tooltip using the callback', function() {
@ -781,8 +768,8 @@ describe('Core.Tooltip', function() {
view: window,
bubbles: true,
cancelable: true,
clientX: rect.left + point0._model.x,
clientY: rect.top + point0._model.y
clientX: rect.left + point0.x,
clientY: rect.top + point0.y
});
// Manually trigger rather than having an async test
@ -792,7 +779,7 @@ describe('Core.Tooltip', function() {
var tooltip = chart.tooltip;
var globalDefaults = Chart.defaults.global;
expect(tooltip._view).toEqual(jasmine.objectContaining({
expect(tooltip).toEqual(jasmine.objectContaining({
// Positioning
xAlign: 'left',
yAlign: 'center',
@ -850,8 +837,8 @@ describe('Core.Tooltip', function() {
view: window,
bubbles: true,
cancelable: true,
clientX: rect.left + point0._model.x,
clientY: rect.top + point0._model.y
clientX: rect.left + point0.x,
clientY: rect.top + point0.y
});
// Manually trigger rather than having an async test
@ -860,7 +847,7 @@ describe('Core.Tooltip', function() {
// Check and see if tooltip was displayed
var tooltip = chart.tooltip;
expect(tooltip._model).toEqual(jasmine.objectContaining({
expect(tooltip.options).toEqual(jasmine.objectContaining({
// Positioning
caretPadding: 10,
}));
@ -901,11 +888,11 @@ describe('Core.Tooltip', function() {
// Check and see if tooltip was displayed
var tooltip = chart.tooltip;
expect(tooltip._view instanceof Object).toBe(true);
expect(tooltip._view.dataPoints instanceof Array).toBe(true);
expect(tooltip._view.dataPoints.length).toBe(1);
expect(tooltip instanceof Object).toBe(true);
expect(tooltip.dataPoints instanceof Array).toBe(true);
expect(tooltip.dataPoints.length).toBe(1);
var tooltipItem = tooltip._view.dataPoints[0];
var tooltipItem = tooltip.dataPoints[0];
expect(tooltipItem.index).toBe(pointIndex);
expect(tooltipItem.datasetIndex).toBe(datasetIndex);
@ -957,8 +944,8 @@ describe('Core.Tooltip', function() {
view: window,
bubbles: false,
cancelable: true,
clientX: rect.left + firstPoint._model.x,
clientY: rect.top + firstPoint._model.y
clientX: rect.left + firstPoint.x,
clientY: rect.top + firstPoint.y
});
var tooltip = chart.tooltip;
@ -1022,8 +1009,8 @@ describe('Core.Tooltip', function() {
view: window,
bubbles: true,
cancelable: true,
clientX: rect.left + point._model.x,
clientY: rect.top + point._model.y
clientX: rect.left + point.x,
clientY: rect.top + point.y
});
// Manually trigger rather than having an async test
@ -1063,6 +1050,9 @@ describe('Core.Tooltip', function() {
animation: {
// without this slice center point is calculated wrong
animateRotate: false
},
tooltips: {
animation: false
}
}
});
@ -1091,14 +1081,15 @@ describe('Core.Tooltip', function() {
chart.update();
node.dispatchEvent(mouseOutEvent);
node.dispatchEvent(mouseMoveEvent);
var model = chart.tooltip._model;
expect(model.x).toBeGreaterThanOrEqual(0);
if (model.width <= chart.width) {
expect(model.x + model.width).toBeLessThanOrEqual(chart.width);
var tooltip = chart.tooltip;
expect(tooltip.dataPoints.length).toBe(1);
expect(tooltip.x).toBeGreaterThanOrEqual(0);
if (tooltip.width <= chart.width) {
expect(tooltip.x + tooltip.width).toBeLessThanOrEqual(chart.width);
}
expect(model.caretX).toBeCloseToPixel(tooltipPosition.x);
expect(tooltip.caretX).toBeCloseToPixel(tooltipPosition.x);
// if tooltip is longer than chart area then all tests done
if (model.width > chart.width) {
if (tooltip.width > chart.width) {
break;
}
}
@ -1176,8 +1167,8 @@ describe('Core.Tooltip', function() {
view: window,
bubbles: true,
cancelable: true,
clientX: rect.left + point._model.x,
clientY: rect.top + point._model.y
clientX: rect.left + point.x,
clientY: rect.top + point.y
});
// Manually trigger rather than having an async test
@ -1187,45 +1178,54 @@ describe('Core.Tooltip', function() {
var tooltip = chart.tooltip;
var globalDefaults = Chart.defaults.global;
expect(tooltip._view).toEqual(jasmine.objectContaining({
// Positioning
xPadding: 6,
yPadding: 6,
xAlign: 'center',
yAlign: 'top',
expect(tooltip.options.xPadding).toEqual(6);
expect(tooltip.options.yPadding).toEqual(6);
expect(tooltip.xAlign).toEqual('center');
expect(tooltip.yAlign).toEqual('top');
expect(tooltip.options).toEqual(jasmine.objectContaining({
// Body
bodyFontColor: '#fff',
_bodyFontFamily: globalDefaults.defaultFontFamily,
_bodyFontStyle: globalDefaults.defaultFontStyle,
_bodyAlign: 'left',
bodyFontFamily: globalDefaults.defaultFontFamily,
bodyFontStyle: globalDefaults.defaultFontStyle,
bodyAlign: 'left',
bodyFontSize: globalDefaults.defaultFontSize,
bodySpacing: 2,
}));
expect(tooltip.options).toEqual(jasmine.objectContaining({
// Title
titleFontColor: '#fff',
_titleFontFamily: globalDefaults.defaultFontFamily,
_titleFontStyle: 'bold',
titleFontFamily: globalDefaults.defaultFontFamily,
titleFontStyle: 'bold',
titleFontSize: globalDefaults.defaultFontSize,
_titleAlign: 'left',
titleAlign: 'left',
titleSpacing: 2,
titleMarginBottom: 6,
}));
expect(tooltip.options).toEqual(jasmine.objectContaining({
// Footer
footerFontColor: '#fff',
_footerFontFamily: globalDefaults.defaultFontFamily,
_footerFontStyle: 'bold',
footerFontFamily: globalDefaults.defaultFontFamily,
footerFontStyle: 'bold',
footerFontSize: globalDefaults.defaultFontSize,
_footerAlign: 'left',
footerAlign: 'left',
footerSpacing: 2,
footerMarginTop: 6,
}));
expect(tooltip.options).toEqual(jasmine.objectContaining({
// Appearance
caretSize: 5,
caretPadding: 2,
cornerRadius: 6,
backgroundColor: 'rgba(0,0,0,0.8)',
multiKeyBackground: '#fff',
}));
expect(tooltip).toEqual(jasmine.objectContaining({
opacity: 1,
legendColorBackground: '#fff',
// Text
title: ['beforeTitle', 'newline', 'title', 'newline', 'afterTitle', 'newline'],
@ -1241,7 +1241,6 @@ describe('Core.Tooltip', function() {
}],
afterBody: ['afterBody', 'newline'],
footer: ['beforeFooter', 'newline', 'footer', 'newline', 'afterFooter', 'newline'],
caretPadding: 2,
labelTextColors: ['labelTextColor', 'labelTextColor'],
labelColors: [{
borderColor: globalDefaults.defaultColor,
@ -1262,45 +1261,51 @@ describe('Core.Tooltip', function() {
y: 100,
width: 100,
height: 100,
xPadding: 5,
yPadding: 5,
xAlign: 'left',
yAlign: 'top',
// Body
bodyFontColor: '#fff',
_bodyFontFamily: globalDefaults.defaultFontFamily,
_bodyFontStyle: globalDefaults.defaultFontStyle,
_bodyAlign: body,
bodyFontSize: globalDefaults.defaultFontSize,
bodySpacing: 2,
options: {
xPadding: 5,
yPadding: 5,
// Title
titleFontColor: '#fff',
_titleFontFamily: globalDefaults.defaultFontFamily,
_titleFontStyle: 'bold',
titleFontSize: globalDefaults.defaultFontSize,
_titleAlign: title,
titleSpacing: 2,
titleMarginBottom: 6,
// Body
bodyFontColor: '#fff',
bodyFontFamily: globalDefaults.defaultFontFamily,
bodyFontStyle: globalDefaults.defaultFontStyle,
bodyAlign: body,
bodyFontSize: globalDefaults.defaultFontSize,
bodySpacing: 2,
// Footer
footerFontColor: '#fff',
_footerFontFamily: globalDefaults.defaultFontFamily,
_footerFontStyle: 'bold',
footerFontSize: globalDefaults.defaultFontSize,
_footerAlign: footer,
footerSpacing: 2,
footerMarginTop: 6,
// Title
titleFontColor: '#fff',
titleFontFamily: globalDefaults.defaultFontFamily,
titleFontStyle: 'bold',
titleFontSize: globalDefaults.defaultFontSize,
titleAlign: title,
titleSpacing: 2,
titleMarginBottom: 6,
// Appearance
caretSize: 5,
cornerRadius: 6,
borderColor: '#aaa',
borderWidth: 1,
backgroundColor: 'rgba(0,0,0,0.8)',
// Footer
footerFontColor: '#fff',
footerFontFamily: globalDefaults.defaultFontFamily,
footerFontStyle: 'bold',
footerFontSize: globalDefaults.defaultFontSize,
footerAlign: footer,
footerSpacing: 2,
footerMarginTop: 6,
// Appearance
caretSize: 5,
cornerRadius: 6,
caretPadding: 2,
borderColor: '#aaa',
borderWidth: 1,
backgroundColor: 'rgba(0,0,0,0.8)',
multiKeyBackground: '#fff',
displayColors: false
},
opacity: 1,
legendColorBackground: '#fff',
// Text
title: ['title'],
@ -1312,7 +1317,6 @@ describe('Core.Tooltip', function() {
}],
afterBody: [],
footer: ['footer'],
caretPadding: 2,
labelTextColors: ['#fff'],
labelColors: [{
borderColor: 'rgb(255, 0, 0)',
@ -1348,16 +1352,19 @@ describe('Core.Tooltip', function() {
var mockContext = window.createMockContext();
var tooltip = new Chart.Tooltip({
_options: globalDefaults.tooltips,
_chart: {
ctx: mockContext,
options: {
tooltips: {
animation: false,
}
}
}
});
it('Should go left', function() {
mockContext.resetCalls();
tooltip._view = makeView('left', 'left', 'left');
tooltip.draw();
Chart.helpers.merge(tooltip, makeView('left', 'left', 'left'));
tooltip.draw(mockContext);
expect(mockContext.getCalls()).toEqual(Array.prototype.concat(drawBody, [
{name: 'setTextAlign', args: ['left']},
@ -1376,8 +1383,8 @@ describe('Core.Tooltip', function() {
it('Should go right', function() {
mockContext.resetCalls();
tooltip._view = makeView('right', 'right', 'right');
tooltip.draw();
Chart.helpers.merge(tooltip, makeView('right', 'right', 'right'));
tooltip.draw(mockContext);
expect(mockContext.getCalls()).toEqual(Array.prototype.concat(drawBody, [
{name: 'setTextAlign', args: ['right']},
@ -1396,8 +1403,8 @@ describe('Core.Tooltip', function() {
it('Should center', function() {
mockContext.resetCalls();
tooltip._view = makeView('center', 'center', 'center');
tooltip.draw();
Chart.helpers.merge(tooltip, makeView('center', 'center', 'center'));
tooltip.draw(mockContext);
expect(mockContext.getCalls()).toEqual(Array.prototype.concat(drawBody, [
{name: 'setTextAlign', args: ['center']},
@ -1416,8 +1423,8 @@ describe('Core.Tooltip', function() {
it('Should allow mixed', function() {
mockContext.resetCalls();
tooltip._view = makeView('right', 'center', 'left');
tooltip.draw();
Chart.helpers.merge(tooltip, makeView('right', 'center', 'left'));
tooltip.draw(mockContext);
expect(mockContext.getCalls()).toEqual(Array.prototype.concat(drawBody, [
{name: 'setTextAlign', args: ['right']},

View File

@ -13,20 +13,15 @@ describe('Arc element tests', function() {
});
it ('should determine if in range', function() {
// Mock out the arc as if the controller put it there
var arc = new Chart.elements.Arc({
_datasetIndex: 2,
_index: 1
});
// Mock out the view as if the controller put it there
arc._view = {
startAngle: 0,
endAngle: Math.PI / 2,
x: 0,
y: 0,
innerRadius: 5,
outerRadius: 10,
};
});
expect(arc.inRange(2, 2)).toBe(false);
expect(arc.inRange(7, 0)).toBe(true);
@ -36,20 +31,15 @@ describe('Arc element tests', function() {
});
it ('should get the tooltip position', function() {
// Mock out the arc as if the controller put it there
var arc = new Chart.elements.Arc({
_datasetIndex: 2,
_index: 1
});
// Mock out the view as if the controller put it there
arc._view = {
startAngle: 0,
endAngle: Math.PI / 2,
x: 0,
y: 0,
innerRadius: 0,
outerRadius: Math.sqrt(2),
};
});
var pos = arc.tooltipPosition();
expect(pos.x).toBeCloseTo(0.5);
@ -57,20 +47,15 @@ describe('Arc element tests', function() {
});
it ('should get the center', function() {
// Mock out the arc as if the controller put it there
var arc = new Chart.elements.Arc({
_datasetIndex: 2,
_index: 1
});
// Mock out the view as if the controller put it there
arc._view = {
startAngle: 0,
endAngle: Math.PI / 2,
x: 0,
y: 0,
innerRadius: 0,
outerRadius: Math.sqrt(2),
};
});
var center = arc.getCenterPoint();
expect(center.x).toBeCloseTo(0.5, 6);

View File

@ -13,21 +13,15 @@ describe('Chart.elements.Point', function() {
});
it ('Should correctly identify as in range', function() {
// Mock out the point as if we were made by the controller
var point = new Chart.elements.Point({
_datasetIndex: 2,
_index: 1
});
// Safely handles if these are called before the viewmodel is instantiated
expect(point.inRange(5)).toBe(false);
// Attach a view object as if we were the controller
point._view = {
radius: 2,
hitRadius: 3,
options: {
radius: 2,
hitRadius: 3,
},
x: 10,
y: 15
};
});
expect(point.inRange(10, 15)).toBe(true);
expect(point.inRange(10, 10)).toBe(false);
@ -36,18 +30,15 @@ describe('Chart.elements.Point', function() {
});
it ('should get the correct tooltip position', function() {
// Mock out the point as if we were made by the controller
var point = new Chart.elements.Point({
_datasetIndex: 2,
_index: 1
});
// Attach a view object as if we were the controller
point._view = {
radius: 2,
borderWidth: 6,
options: {
radius: 2,
borderWidth: 6,
},
x: 10,
y: 15
};
});
expect(point.tooltipPosition()).toEqual({
x: 10,
@ -57,34 +48,31 @@ describe('Chart.elements.Point', function() {
});
it('should get the correct center point', function() {
// Mock out the point as if we were made by the controller
var point = new Chart.elements.Point({
_datasetIndex: 2,
_index: 1
});
// Attach a view object as if we were the controller
point._view = {
radius: 2,
options: {
radius: 2,
},
x: 10,
y: 10
};
});
expect(point.getCenterPoint()).toEqual({x: 10, y: 10});
});
it ('should not draw if skipped', function() {
var mockContext = window.createMockContext();
var point = new Chart.elements.Point();
// Attach a view object as if we were the controller
point._view = {
radius: 2,
hitRadius: 3,
// Mock out the point as if we were made by the controller
var point = new Chart.elements.Point({
options: {
radius: 2,
hitRadius: 3,
},
x: 10,
y: 15,
ctx: mockContext,
skip: true
};
});
point.draw(mockContext);

View File

@ -14,20 +14,11 @@ describe('Rectangle element tests', function() {
it('Should correctly identify as in range', function() {
var rectangle = new Chart.elements.Rectangle({
_datasetIndex: 2,
_index: 1
});
// Safely handles if these are called before the viewmodel is instantiated
expect(rectangle.inRange(5)).toBe(false);
// Attach a view object as if we were the controller
rectangle._view = {
base: 0,
width: 4,
x: 10,
y: 15
};
});
expect(rectangle.inRange(10, 15)).toBe(true);
expect(rectangle.inRange(10, 10)).toBe(true);
@ -36,17 +27,11 @@ describe('Rectangle element tests', function() {
// Test when the y is below the base (negative bar)
var negativeRectangle = new Chart.elements.Rectangle({
_datasetIndex: 2,
_index: 1
});
// Attach a view object as if we were the controller
negativeRectangle._view = {
base: 0,
width: 4,
x: 10,
y: -15
};
});
expect(negativeRectangle.inRange(10, -16)).toBe(false);
expect(negativeRectangle.inRange(10, 1)).toBe(false);
@ -55,17 +40,11 @@ describe('Rectangle element tests', function() {
it('should get the correct tooltip position', function() {
var rectangle = new Chart.elements.Rectangle({
_datasetIndex: 2,
_index: 1
});
// Attach a view object as if we were the controller
rectangle._view = {
base: 0,
width: 4,
x: 10,
y: 15
};
});
expect(rectangle.tooltipPosition()).toEqual({
x: 10,
@ -74,17 +53,11 @@ describe('Rectangle element tests', function() {
// Test when the y is below the base (negative bar)
var negativeRectangle = new Chart.elements.Rectangle({
_datasetIndex: 2,
_index: 1
});
// Attach a view object as if we were the controller
negativeRectangle._view = {
base: -10,
width: 4,
x: 10,
y: -15
};
});
expect(negativeRectangle.tooltipPosition()).toEqual({
x: 10,
@ -94,17 +67,11 @@ describe('Rectangle element tests', function() {
it('should get the center', function() {
var rectangle = new Chart.elements.Rectangle({
_datasetIndex: 2,
_index: 1
});
// Attach a view object as if we were the controller
rectangle._view = {
base: 0,
width: 4,
x: 10,
y: 15
};
});
expect(rectangle.getCenterPoint()).toEqual({x: 10, y: 7.5});
});

View File

@ -22,8 +22,8 @@ describe('Default Configs', function() {
chart.tooltip.update();
// Title is always blank
expect(chart.tooltip._model.title).toEqual([]);
expect(chart.tooltip._model.body).toEqual([{
expect(chart.tooltip.title).toEqual([]);
expect(chart.tooltip.body).toEqual([{
before: [],
lines: ['My dataset: (10, 12, 5)'],
after: []
@ -50,8 +50,8 @@ describe('Default Configs', function() {
chart.tooltip.update();
// Title is always blank
expect(chart.tooltip._model.title).toEqual([]);
expect(chart.tooltip._model.body).toEqual([{
expect(chart.tooltip.title).toEqual([]);
expect(chart.tooltip.body).toEqual([{
before: [],
lines: ['label2: 20'],
after: []
@ -76,8 +76,8 @@ describe('Default Configs', function() {
chart.tooltip.update();
// Title is always blank
expect(chart.tooltip._model.title).toEqual([]);
expect(chart.tooltip._model.body).toEqual([{
expect(chart.tooltip.title).toEqual([]);
expect(chart.tooltip.body).toEqual([{
before: [],
lines: [
'row1: 20',
@ -196,8 +196,8 @@ describe('Default Configs', function() {
chart.tooltip.update();
// Title is always blank
expect(chart.tooltip._model.title).toEqual([]);
expect(chart.tooltip._model.body).toEqual([{
expect(chart.tooltip.title).toEqual([]);
expect(chart.tooltip.body).toEqual([{
before: [],
lines: ['label2: 20'],
after: []

View File

@ -49,163 +49,135 @@ describe('Curve helper tests', function() {
it('should spline curves with monotone cubic interpolation', function() {
var dataPoints = [
{_model: {x: 0, y: 0, skip: false}},
{_model: {x: 3, y: 6, skip: false}},
{_model: {x: 9, y: 6, skip: false}},
{_model: {x: 12, y: 60, skip: false}},
{_model: {x: 15, y: 60, skip: false}},
{_model: {x: 18, y: 120, skip: false}},
{_model: {x: null, y: null, skip: true}},
{_model: {x: 21, y: 180, skip: false}},
{_model: {x: 24, y: 120, skip: false}},
{_model: {x: 27, y: 125, skip: false}},
{_model: {x: 30, y: 105, skip: false}},
{_model: {x: 33, y: 110, skip: false}},
{_model: {x: 33, y: 110, skip: false}},
{_model: {x: 36, y: 170, skip: false}}
{x: 0, y: 0, skip: false},
{x: 3, y: 6, skip: false},
{x: 9, y: 6, skip: false},
{x: 12, y: 60, skip: false},
{x: 15, y: 60, skip: false},
{x: 18, y: 120, skip: false},
{x: null, y: null, skip: true},
{x: 21, y: 180, skip: false},
{x: 24, y: 120, skip: false},
{x: 27, y: 125, skip: false},
{x: 30, y: 105, skip: false},
{x: 33, y: 110, skip: false},
{x: 33, y: 110, skip: false},
{x: 36, y: 170, skip: false}
];
helpers.splineCurveMonotone(dataPoints);
expect(dataPoints).toEqual([{
_model: {
x: 0,
y: 0,
skip: false,
controlPointNextX: 1,
controlPointNextY: 2
}
x: 0,
y: 0,
skip: false,
controlPointNextX: 1,
controlPointNextY: 2
},
{
_model: {
x: 3,
y: 6,
skip: false,
controlPointPreviousX: 2,
controlPointPreviousY: 6,
controlPointNextX: 5,
controlPointNextY: 6
}
x: 3,
y: 6,
skip: false,
controlPointPreviousX: 2,
controlPointPreviousY: 6,
controlPointNextX: 5,
controlPointNextY: 6
},
{
_model: {
x: 9,
y: 6,
skip: false,
controlPointPreviousX: 7,
controlPointPreviousY: 6,
controlPointNextX: 10,
controlPointNextY: 6
}
x: 9,
y: 6,
skip: false,
controlPointPreviousX: 7,
controlPointPreviousY: 6,
controlPointNextX: 10,
controlPointNextY: 6
},
{
_model: {
x: 12,
y: 60,
skip: false,
controlPointPreviousX: 11,
controlPointPreviousY: 60,
controlPointNextX: 13,
controlPointNextY: 60
}
x: 12,
y: 60,
skip: false,
controlPointPreviousX: 11,
controlPointPreviousY: 60,
controlPointNextX: 13,
controlPointNextY: 60
},
{
_model: {
x: 15,
y: 60,
skip: false,
controlPointPreviousX: 14,
controlPointPreviousY: 60,
controlPointNextX: 16,
controlPointNextY: 60
}
x: 15,
y: 60,
skip: false,
controlPointPreviousX: 14,
controlPointPreviousY: 60,
controlPointNextX: 16,
controlPointNextY: 60
},
{
_model: {
x: 18,
y: 120,
skip: false,
controlPointPreviousX: 17,
controlPointPreviousY: 100
}
x: 18,
y: 120,
skip: false,
controlPointPreviousX: 17,
controlPointPreviousY: 100
},
{
_model: {
x: null,
y: null,
skip: true
}
x: null,
y: null,
skip: true
},
{
_model: {
x: 21,
y: 180,
skip: false,
controlPointNextX: 22,
controlPointNextY: 160
}
x: 21,
y: 180,
skip: false,
controlPointNextX: 22,
controlPointNextY: 160
},
{
_model: {
x: 24,
y: 120,
skip: false,
controlPointPreviousX: 23,
controlPointPreviousY: 120,
controlPointNextX: 25,
controlPointNextY: 120
}
x: 24,
y: 120,
skip: false,
controlPointPreviousX: 23,
controlPointPreviousY: 120,
controlPointNextX: 25,
controlPointNextY: 120
},
{
_model: {
x: 27,
y: 125,
skip: false,
controlPointPreviousX: 26,
controlPointPreviousY: 125,
controlPointNextX: 28,
controlPointNextY: 125
}
x: 27,
y: 125,
skip: false,
controlPointPreviousX: 26,
controlPointPreviousY: 125,
controlPointNextX: 28,
controlPointNextY: 125
},
{
_model: {
x: 30,
y: 105,
skip: false,
controlPointPreviousX: 29,
controlPointPreviousY: 105,
controlPointNextX: 31,
controlPointNextY: 105
}
x: 30,
y: 105,
skip: false,
controlPointPreviousX: 29,
controlPointPreviousY: 105,
controlPointNextX: 31,
controlPointNextY: 105
},
{
_model: {
x: 33,
y: 110,
skip: false,
controlPointPreviousX: 32,
controlPointPreviousY: 110,
controlPointNextX: 33,
controlPointNextY: 110
}
x: 33,
y: 110,
skip: false,
controlPointPreviousX: 32,
controlPointPreviousY: 110,
controlPointNextX: 33,
controlPointNextY: 110
},
{
_model: {
x: 33,
y: 110,
skip: false,
controlPointPreviousX: 33,
controlPointPreviousY: 110,
controlPointNextX: 34,
controlPointNextY: 110
}
x: 33,
y: 110,
skip: false,
controlPointPreviousX: 33,
controlPointPreviousY: 110,
controlPointNextX: 34,
controlPointNextY: 110
},
{
_model: {
x: 36,
y: 170,
skip: false,
controlPointPreviousX: 35,
controlPointPreviousY: 150
}
x: 36,
y: 170,
skip: false,
controlPointPreviousX: 35,
controlPointPreviousY: 150
}]);
});
});

View File

@ -149,7 +149,7 @@ describe('Legend block tests', function() {
datasetIndex: 1
}, {
text: 'dataset3',
fillStyle: 'green',
fillStyle: 'rgba(0,0,0,0.1)',
hidden: false,
lineCap: 'butt',
lineDash: [],
@ -198,7 +198,7 @@ describe('Legend block tests', function() {
expect(chart.legend.legendItems).toEqual([{
text: 'dataset3',
fillStyle: 'green',
fillStyle: 'rgba(0,0,0,0.1)',
hidden: false,
lineCap: 'butt',
lineDash: [],

View File

@ -113,8 +113,6 @@ function _resolveElementPoint(el) {
point = el.getCenterPoint();
} else if (el.x !== undefined && el.y !== undefined) {
point = el;
} else if (el._model && el._model.x !== undefined && el._model.y !== undefined) {
point = el._model;
}
}
return point;