extends: chartjs
env:
+ es6: true
browser: true
node: true
| ---- | ---- | ------- | -----------
| `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|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'`
## 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',
// 0 opacity is a hidden tooltip
opacity: number,
- legendColorBackground: Color,
+ multiKeyBackground: Color,
displayColors: boolean,
borderColor: Color,
borderWidth: number
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.
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
```javascript
var meta = myChart.getDatasetMeta(0);
-var x = meta.data[0]._model.x;
+var x = meta.data[0].x;
```
// 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();
}
});
## 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.
| `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.
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
}
});
```
| 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.
* `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
* `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`
* `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`
* `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
--- /dev/null
+<!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>
--- /dev/null
+<!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>
--- /dev/null
+<!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>
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(),
}, {
title: 'Other charts',
items: [{
+ title: 'Bubble',
+ path: 'charts/bubble.html'
+ }, {
title: 'Scatter',
path: 'charts/scatter/basic.html'
}, {
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: [{
datasets: {
bar: {
categoryPercentage: 0.8,
- barPercentage: 0.9
+ barPercentage: 0.9,
+ animation: {
+ numbers: {
+ type: 'number',
+ properties: ['x', 'y', 'base', 'width', 'height']
+ }
+ }
}
}
});
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);
},
/**
'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',
'backgroundColor',
'borderColor',
'borderWidth',
- 'hoverBackgroundColor',
- 'hoverBorderColor',
- 'hoverBorderWidth',
- 'hoverRadius',
'hitRadius',
+ 'radius',
'pointStyle',
'rotation'
],
* @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;
},
/**
/**
* @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);
- /**
- * @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
- };
+ if (reset) {
+ properties.options.radius = 0;
+ }
+ }
- 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._updateElement(point, i, properties, mode);
+ }
+
+ me._updateSharedOptions(sharedOptions, mode);
},
/**
* @private
*/
- _resolveDataElementOptions: function(index) {
+ _resolveDataElementOptions: function(index, mode) {
var me = this;
var chart = me.chart;
var dataset = me.getDataset();
};
// 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
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
return ringIndex;
},
- update: function(reset) {
+ update: function(mode) {
var me = this;
var chart = me.chart;
var chartArea = chart.chartArea;
}
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();
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);
}
},
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
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;
}
}
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
const valueOrDefault = helpers.valueOrDefault;
const resolve = helpers.options.resolve;
-const isPointInArea = helpers.canvas._isPointInArea;
defaults._set('line', {
showLines: true,
'borderDashOffset',
'borderJoinStyle',
'borderWidth',
+ 'capBezierPoints',
'cubicInterpolationMode',
'fill'
],
borderColor: 'pointBorderColor',
borderWidth: 'pointBorderWidth',
hitRadius: 'pointHitRadius',
+ hoverHitRadius: 'pointHitRadius',
hoverBackgroundColor: 'pointHoverBackgroundColor',
hoverBorderColor: 'pointHoverBorderColor',
hoverBorderWidth: 'pointHoverBorderWidth',
rotation: 'pointRotation'
},
- update: function(reset) {
+ update: function(mode) {
const me = this;
const meta = me._cachedMeta;
const line = meta.dataset;
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();
- }
-
- // Update Points
- me.updateElements(points, 0, points.length, reset);
+ const properties = {
+ _children: points,
+ options: me._resolveDatasetElementOptions()
+ };
- if (showLine && line._model.tension !== 0) {
- me.updateBezierControlPoints();
+ me._updateElement(line, undefined, properties, mode);
}
- // Now pivot the point for animation
- for (i = 0, ilen = points.length; i < ilen; ++i) {
- points[i].pivot(me.chart._animationsDisabled);
+ // Update Points
+ 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]);
+ const properties = {
+ x,
+ y,
+ skip: isNaN(x) || isNaN(y)
+ };
- // Utility
- point._options = options;
+ if (includeOptions) {
+ properties.options = i === start ? firstOpts
+ : me._resolveDataElementOptions(i, mode);
+ }
- // 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
- };
+ me._updateElement(point, i, properties, mode);
}
+
+ me._updateSharedOptions(sharedOptions, mode);
},
/**
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;
let i = 0;
if (me._showLine) {
- meta.dataset.draw(ctx);
+ meta.dataset.draw(ctx, area);
}
// Draw the points
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);
- },
});
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',
}
},
- // 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');
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);
},
/**
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;
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;
+
+ me._cachedMeta.count = me.countVisibleElements();
- for (i = 0; i < start + count; i++) {
+ 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]);
-
- const resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[i]);
- const options = arc._options = me._resolveDataElementOptions(i);
-
- arc._model = {
- backgroundColor: options.backgroundColor,
- borderColor: options.borderColor,
- borderWidth: options.borderWidth,
- borderAlign: options.borderAlign,
+ let startAngle = angle;
+ let endAngle = angle + me._computeAngle(i);
+ let outerRadius = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[i]);
+ angle = endAngle;
+
+ if (reset) {
+ if (animationOpts.animateScale) {
+ outerRadius = 0;
+ }
+ if (animationOpts.animateRotate) {
+ startAngle = datasetStartAngle;
+ endAngle = datasetStartAngle;
+ }
+ }
+
+ 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);
}
},
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
*/
}
});
-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,
};
},
- 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);
-
- // Update bezier control points
- me.updateBezierControlPoints();
+ me.updateElements(points, 0, points.length, mode);
- // 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);
}
},
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);
}
});
'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;
+ }
+};
+
+class Animation {
+ constructor(cfg, target, prop, to) {
+ const me = this;
+ let from = cfg.from;
+
+ if (from === undefined) {
+ from = target[prop];
+ }
+ if (to === undefined) {
+ to = target[prop];
+ }
+
+ if (from === undefined) {
+ from = to;
+ } else if (to === undefined) {
+ to = from;
+ }
- 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
+ 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;
+ }
- 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);
+ 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;
'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,
-
- /**
- * @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;
-
- animation.chart = chart;
- animation.startTime = Date.now();
- animation.duration = duration;
+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;
+}
+
+class Animations {
+ constructor(chart, animations) {
+ this._chart = chart;
+ this._properties = new Map();
+ this.configure(animations);
+ }
- if (!lazy) {
- chart.animating = true;
- }
+ configure(animations) {
+ const animatedProps = this._properties;
+ const animDefaults = Object.fromEntries(Object.entries(animations).filter(({1: value}) => !helpers.isObject(value)));
- 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;
+ }
+ 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));
+ }
}
}
+ }
- 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;
- });
+ /**
+ * 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 (index !== -1) {
- this.animations.splice(index, 1);
- chart.animating = false;
+ if (!newOptions) {
+ return animations;
}
- },
-
- 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();
- });
+ 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
*/
- startDigest: function() {
- var me = this;
+ _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];
- me.advance();
+ const cfg = animatedProps.get(prop);
+ if (!cfg || !cfg.duration) {
+ // not animated, set directly to new value
+ target[prop] = value;
+ continue;
+ }
- // Do we have more stuff to animate?
- if (me.animations.length > 0) {
- me.requestAnimationFrame();
+ let animation = running[prop];
+ if (animation) {
+ animation.cancel();
+ }
+ running[prop] = animation = new Animation(cfg, target, prop, value);
+ animations.push(animation);
}
- },
+ return animations;
+ }
+
/**
- * @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;
- }
+ * 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;
--- /dev/null
+'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;
'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');
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) {
}
function isAnimationDisabled(config) {
- return !config.animation || !(
- config.animation.duration ||
- (config.hover && config.hover.animationDuration) ||
- config.responsiveAnimationDuration
- );
+ return !config.animation;
}
function updateConfig(chart) {
chart.ensureScalesHaveIDs();
chart.buildOrUpdateScales();
- // Tooltip
- chart.tooltip._options = newOptions.tooltips;
chart.tooltip.initialize();
}
};
}
+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;
return;
}
+ Animator.listen(me, 'complete', onAnimationsComplete);
+ Animator.listen(me, 'progress', onAnimationProgress);
+
me.initialize();
me.update();
},
},
stop: function() {
- // Stops any current animation loop occurring
- animations.cancelAnimation(this);
+ Animator.stop(this);
return this;
},
}
me.stop();
- me.update({
- duration: options.responsiveAnimationDuration
- });
+ me.update('resize');
}
},
this.tooltip.initialize();
},
- update: function(config) {
+ update: function(mode) {
var me = this;
var i, ilen;
- config = config || {};
+ me._updating = true;
updateConfig(me);
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();
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;
},
/**
* 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) {
}
for (var i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
- me.updateDataset(i);
+ me.updateDataset(i, mode);
}
plugins.notify(me, 'afterDatasetsUpdate');
* 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;
}
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');
},
/**
* 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');
},
/**
* 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;
var args = {
meta: meta,
index: meta.index,
- easingValue: easingValue
};
if (plugins.notify(me, 'beforeDatasetDraw', [args]) === false) {
bottom: clip.bottom === false ? canvas.height : area.bottom + clip.bottom
});
- meta.controller.draw(easingValue);
+ meta.controller.draw();
helpers.canvas.unclipArea(ctx);
* 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]);
},
},
initToolTip: function() {
- var me = this;
- me.tooltip = new Tooltip({
- _chart: me,
- _data: me.data,
- _options: me.options.tooltips
- });
+ this.tooltip = new Tooltip({_chart: this});
},
/**
* @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;
},
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
}
}
- me._updateHoverStyles();
changed = !helpers._elementsEqual(me.active, me.lastActive);
+ if (changed) {
+ me._updateHoverStyles();
+ }
// Remember Last Actives
me.lastActive = me.active;
'use strict';
var helpers = require('../helpers/index');
+var Animations = require('./core.animations');
var resolve = helpers.options.resolve;
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();
},
reset: function() {
- this._update(true);
+ this._update('reset');
},
/**
},
/**
- * Returns the merged user-supplied and default dataset-level options
+ * Merges user-supplied and default dataset-level options
* @private
*/
_configure: function() {
/**
* @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;
}
},
+ _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;
},
/**
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;
/**
* @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;
},
- setHoverStyle: function(element, datasetIndex, index) {
- const dataset = this.chart.data.datasets[datasetIndex];
- const model = element._model;
- const getHoverColor = helpers.getHoverColor;
+ /**
+ * 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};
+ }
+ },
- element.$previousStyle = {
- backgroundColor: model.backgroundColor,
- borderColor: model.borderColor,
- borderWidth: model.borderWidth
- };
+ /**
+ * 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);
+ },
- 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);
+ setHoverStyle: function(element, datasetIndex, index) {
+ this._setStyle(element, index, 'active', true);
},
/**
const element = this._cachedMeta.dataset;
if (element) {
- this.removeHoverStyle(element);
+ this._setStyle(element, undefined, 'active', false);
}
},
*/
_setDatasetHoverStyle: function() {
const element = this._cachedMeta.dataset;
- const prev = {};
- let i, ilen, key, keys, hoverOptions, model;
- if (!element) {
- return;
- }
-
- 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];
+ if (element) {
+ this._setStyle(element, undefined, 'active', true);
}
-
- element.$previousStyle = prev;
},
/**
}
me._parse(start, count);
- me.updateElements(data, start, count);
+ me.updateElements(data, start, count, 'reset');
},
/**
'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;
({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);
}
}
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);
}
}
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});
}
});
*/
/**
* @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.
*/
/**
* @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.
*/
* @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.
*/
/**
* @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.
*/
* @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.
*/
/**
* @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.
*/
* @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.
*/
/**
const defaults = require('./core.defaults');
const Element = require('./core.element');
const helpers = require('../helpers/index');
+const Animations = require('./core.animations');
const valueOrDefault = helpers.valueOrDefault;
const getRtlHelper = helpers.rtl.getRtlAdapter;
displayColors: true,
borderColor: 'rgba(0,0,0,0)',
borderWidth: 0,
+ animation: {
+ duration: 400,
+ easing: 'easeOutQuart',
+ numbers: {
+ type: 'number',
+ properties: ['x', 'y', 'width', 'height'],
+ },
+ opacity: {
+ easing: 'linear',
+ duration: 200
+ }
+ },
callbacks: {
// Args are: (tooltipItems, data)
beforeTitle: helpers.noop,
},
labelColor: function(tooltipItem, chart) {
var meta = chart.getDatasetMeta(tooltipItem.datasetIndex);
- var activeElement = meta.data[tooltipItem.index];
- var view = activeElement.$previousStyle || activeElement._view;
+ var options = meta.controller.getStyle(tooltipItem.index);
return {
- borderColor: view.borderColor,
- backgroundColor: view.backgroundColor
+ borderColor: options.borderColor,
+ backgroundColor: options.backgroundColor
};
},
labelTextColor: function() {
- return this._options.bodyFontColor;
+ return this.options.bodyFontColor;
},
afterLabel: helpers.noop,
/**
* Helper to get the reset model for the tooltip
- * @param tooltipOpts {object} the tooltip options
+ * @param options {object} the tooltip options
*/
-function getBaseModel(tooltipOpts) {
+function resolveOptions(options) {
var globalDefaults = defaults.global;
- return {
- // Positioning
- xPadding: tooltipOpts.xPadding,
- yPadding: tooltipOpts.yPadding,
- xAlign: tooltipOpts.xAlign,
- yAlign: tooltipOpts.yAlign,
-
- // Drawing direction and text direction
- rtl: tooltipOpts.rtl,
- textDirection: tooltipOpts.textDirection,
-
- // Body
- bodyFontColor: tooltipOpts.bodyFontColor,
- _bodyFontFamily: valueOrDefault(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily),
- _bodyFontStyle: valueOrDefault(tooltipOpts.bodyFontStyle, globalDefaults.defaultFontStyle),
- _bodyAlign: tooltipOpts.bodyAlign,
- bodyFontSize: valueOrDefault(tooltipOpts.bodyFontSize, globalDefaults.defaultFontSize),
- bodySpacing: tooltipOpts.bodySpacing,
-
- // Title
- titleFontColor: tooltipOpts.titleFontColor,
- _titleFontFamily: valueOrDefault(tooltipOpts.titleFontFamily, globalDefaults.defaultFontFamily),
- _titleFontStyle: valueOrDefault(tooltipOpts.titleFontStyle, globalDefaults.defaultFontStyle),
- titleFontSize: valueOrDefault(tooltipOpts.titleFontSize, globalDefaults.defaultFontSize),
- _titleAlign: tooltipOpts.titleAlign,
- titleSpacing: tooltipOpts.titleSpacing,
- titleMarginBottom: tooltipOpts.titleMarginBottom,
-
- // Footer
- footerFontColor: tooltipOpts.footerFontColor,
- _footerFontFamily: valueOrDefault(tooltipOpts.footerFontFamily, globalDefaults.defaultFontFamily),
- _footerFontStyle: valueOrDefault(tooltipOpts.footerFontStyle, globalDefaults.defaultFontStyle),
- footerFontSize: valueOrDefault(tooltipOpts.footerFontSize, globalDefaults.defaultFontSize),
- _footerAlign: tooltipOpts.footerAlign,
- footerSpacing: tooltipOpts.footerSpacing,
- footerMarginTop: tooltipOpts.footerMarginTop,
-
- // Appearance
- caretSize: tooltipOpts.caretSize,
- cornerRadius: tooltipOpts.cornerRadius,
- backgroundColor: tooltipOpts.backgroundColor,
- opacity: 0,
- legendColorBackground: tooltipOpts.multiKeyBackground,
- displayColors: tooltipOpts.displayColors,
- borderColor: tooltipOpts.borderColor,
- borderWidth: tooltipOpts.borderWidth
- };
+ options = helpers.extend({}, globalDefaults.tooltips, options);
+
+ options.bodyFontFamily = valueOrDefault(options.bodyFontFamily, globalDefaults.defaultFontFamily);
+ options.bodyFontStyle = valueOrDefault(options.bodyFontStyle, globalDefaults.defaultFontStyle);
+ options.bodyFontSize = valueOrDefault(options.bodyFontSize, globalDefaults.defaultFontSize);
+
+ options.titleFontFamily = valueOrDefault(options.titleFontFamily, globalDefaults.defaultFontFamily);
+ options.titleFontStyle = valueOrDefault(options.titleFontStyle, globalDefaults.defaultFontStyle);
+ options.titleFontSize = valueOrDefault(options.titleFontSize, globalDefaults.defaultFontSize);
+
+ options.footerFontFamily = valueOrDefault(options.footerFontFamily, globalDefaults.defaultFontFamily);
+ options.footerFontStyle = valueOrDefault(options.footerFontStyle, globalDefaults.defaultFontStyle);
+ options.footerFontSize = valueOrDefault(options.footerFontSize, globalDefaults.defaultFontSize);
+
+ return options;
}
/**
* Get the size of the tooltip
*/
-function getTooltipSize(tooltip, model) {
- var ctx = tooltip._chart.ctx;
+function getTooltipSize(tooltip) {
+ const ctx = tooltip._chart.ctx;
+ const {body, footer, options, title} = tooltip;
+ const {bodyFontSize, footerFontSize, titleFontSize} = options;
+ const titleLineCount = title.length;
+ const footerLineCount = footer.length;
- var height = model.yPadding * 2; // Tooltip Padding
- var width = 0;
+ let height = options.yPadding * 2; // Tooltip Padding
+ let width = 0;
// Count of all lines in the body
- var body = model.body;
var combinedBodyLength = body.reduce(function(count, bodyItem) {
return count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length;
}, 0);
- combinedBodyLength += model.beforeBody.length + model.afterBody.length;
-
- var titleLineCount = model.title.length;
- var footerLineCount = model.footer.length;
- var titleFontSize = model.titleFontSize;
- var bodyFontSize = model.bodyFontSize;
- var footerFontSize = model.footerFontSize;
-
- height += titleLineCount * titleFontSize; // Title Lines
- height += titleLineCount ? (titleLineCount - 1) * model.titleSpacing : 0; // Title Line Spacing
- height += titleLineCount ? model.titleMarginBottom : 0; // Title's bottom Margin
- height += combinedBodyLength * bodyFontSize; // Body Lines
- height += combinedBodyLength ? (combinedBodyLength - 1) * model.bodySpacing : 0; // Body Line Spacing
- height += footerLineCount ? model.footerMarginTop : 0; // Footer Margin
- height += footerLineCount * (footerFontSize); // Footer Lines
- height += footerLineCount ? (footerLineCount - 1) * model.footerSpacing : 0; // Footer Line Spacing
+ combinedBodyLength += tooltip.beforeBody.length + tooltip.afterBody.length;
+
+ if (titleLineCount) {
+ height += titleLineCount * titleFontSize
+ + (titleLineCount - 1) * options.titleSpacing
+ + options.titleMarginBottom;
+ }
+ if (combinedBodyLength) {
+ height += combinedBodyLength * bodyFontSize
+ + (combinedBodyLength - 1) * options.bodySpacing;
+ }
+ if (footerLineCount) {
+ height += options.footerMarginTop
+ + footerLineCount * footerFontSize
+ + (footerLineCount - 1) * options.footerSpacing;
+ }
// Title width
var widthPadding = 0;
width = Math.max(width, ctx.measureText(line).width + widthPadding);
};
- ctx.font = helpers.fontString(titleFontSize, model._titleFontStyle, model._titleFontFamily);
- helpers.each(model.title, maxLineWidth);
+ ctx.font = helpers.fontString(titleFontSize, options.titleFontStyle, options.titleFontFamily);
+ helpers.each(tooltip.title, maxLineWidth);
// Body width
- ctx.font = helpers.fontString(bodyFontSize, model._bodyFontStyle, model._bodyFontFamily);
- helpers.each(model.beforeBody.concat(model.afterBody), maxLineWidth);
+ ctx.font = helpers.fontString(bodyFontSize, options.bodyFontStyle, options.bodyFontFamily);
+ helpers.each(tooltip.beforeBody.concat(tooltip.afterBody), maxLineWidth);
// Body lines may include some extra width due to the color box
- widthPadding = model.displayColors ? (bodyFontSize + 2) : 0;
+ widthPadding = options.displayColors ? (bodyFontSize + 2) : 0;
helpers.each(body, function(bodyItem) {
helpers.each(bodyItem.before, maxLineWidth);
helpers.each(bodyItem.lines, maxLineWidth);
widthPadding = 0;
// Footer width
- ctx.font = helpers.fontString(footerFontSize, model._footerFontStyle, model._footerFontFamily);
- helpers.each(model.footer, maxLineWidth);
+ ctx.font = helpers.fontString(footerFontSize, options.footerFontStyle, options.footerFontFamily);
+ helpers.each(tooltip.footer, maxLineWidth);
// Add padding
- width += 2 * model.xPadding;
+ width += 2 * options.xPadding;
- return {
- width: width,
- height: height
- };
+ return {width, height};
}
/**
* Helper to get the alignment of a tooltip given the size
*/
-function determineAlignment(tooltip, size) {
- var model = tooltip._model;
- var chart = tooltip._chart;
+function determineAlignment(chart, options, size) {
+ const {x, y, width, height} = size;
var chartArea = chart.chartArea;
var xAlign = 'center';
var yAlign = 'center';
- if (model.y < size.height) {
+ if (y < height) {
yAlign = 'top';
- } else if (model.y > (chart.height - size.height)) {
+ } else if (y > (chart.height - height)) {
yAlign = 'bottom';
}
var midY = (chartArea.top + chartArea.bottom) / 2;
if (yAlign === 'center') {
- lf = function(x) {
- return x <= midX;
- };
- rf = function(x) {
- return x > midX;
- };
+ lf = (value) => value <= midX;
+ rf = (value) => value > midX;
} else {
- lf = function(x) {
- return x <= (size.width / 2);
- };
- rf = function(x) {
- return x >= (chart.width - (size.width / 2));
- };
+ lf = (value) => value <= (width / 2);
+ rf = (value) => value >= (chart.width - (width / 2));
}
- olf = function(x) {
- return x + size.width + model.caretSize + model.caretPadding > chart.width;
- };
- orf = function(x) {
- return x - size.width - model.caretSize - model.caretPadding < 0;
- };
- yf = function(y) {
- return y <= midY ? 'top' : 'bottom';
- };
+ olf = (value) => value + width + options.caretSize + options.caretPadding > chart.width;
+ orf = (value) => value - width - options.caretSize - options.caretPadding < 0;
+ yf = (value) => value <= midY ? 'top' : 'bottom';
- if (lf(model.x)) {
+ if (lf(x)) {
xAlign = 'left';
// Is tooltip too wide and goes over the right side of the chart.?
- if (olf(model.x)) {
+ if (olf(x)) {
xAlign = 'center';
- yAlign = yf(model.y);
+ yAlign = yf(y);
}
- } else if (rf(model.x)) {
+ } else if (rf(x)) {
xAlign = 'right';
// Is tooltip too wide and goes outside left edge of canvas?
- if (orf(model.x)) {
+ if (orf(x)) {
xAlign = 'center';
- yAlign = yf(model.y);
+ yAlign = yf(y);
}
}
- var opts = tooltip._options;
return {
- xAlign: opts.xAlign ? opts.xAlign : xAlign,
- yAlign: opts.yAlign ? opts.yAlign : yAlign
+ xAlign: options.xAlign ? options.xAlign : xAlign,
+ yAlign: options.yAlign ? options.yAlign : yAlign
};
}
-/**
- * Helper to get the location a tooltip needs to be placed at given the initial position (via the vm) and the size and alignment
- */
-function getBackgroundPoint(vm, size, alignment, chart) {
- // Background Position
- var x = vm.x;
- var y = vm.y;
-
- var caretSize = vm.caretSize;
- var caretPadding = vm.caretPadding;
- var cornerRadius = vm.cornerRadius;
- var xAlign = alignment.xAlign;
- var yAlign = alignment.yAlign;
- var paddingAndSize = caretSize + caretPadding;
- var radiusAndPadding = cornerRadius + caretPadding;
-
+function alignX(size, xAlign, chartWidth) {
+ let {x, width} = size;
if (xAlign === 'right') {
- x -= size.width;
+ x -= width;
} else if (xAlign === 'center') {
- x -= (size.width / 2);
- if (x + size.width > chart.width) {
- x = chart.width - size.width;
+ x -= (width / 2);
+ if (x + width > chartWidth) {
+ x = chartWidth - width;
}
if (x < 0) {
x = 0;
}
}
+ return x;
+}
+function alignY(size, yAlign, paddingAndSize) {
+ let {y, height} = size;
if (yAlign === 'top') {
y += paddingAndSize;
} else if (yAlign === 'bottom') {
- y -= size.height + paddingAndSize;
+ y -= height + paddingAndSize;
} else {
- y -= (size.height / 2);
+ y -= (height / 2);
}
+ return y;
+}
+
+/**
+ * Helper to get the location a tooltip needs to be placed at given the initial position (via the vm) and the size and alignment
+ */
+function getBackgroundPoint(options, size, alignment, chart) {
+ const {caretSize, caretPadding, cornerRadius} = options;
+ const {xAlign, yAlign} = alignment;
+ const paddingAndSize = caretSize + caretPadding;
+ const radiusAndPadding = cornerRadius + caretPadding;
+
+ let x = alignX(size, xAlign, chart.width);
+ let y = alignY(size, yAlign, paddingAndSize);
if (yAlign === 'center') {
if (xAlign === 'left') {
x += radiusAndPadding;
}
- return {
- x: x,
- y: y
- };
+ return {x, y};
}
-function getAlignedX(vm, align) {
+function getAlignedX(tooltip, align) {
+ const options = tooltip.options;
return align === 'center'
- ? vm.x + vm.width / 2
+ ? tooltip.x + tooltip.width / 2
: align === 'right'
- ? vm.x + vm.width - vm.xPadding
- : vm.x + vm.xPadding;
+ ? tooltip.x + tooltip.width - options.xPadding
+ : tooltip.x + options.xPadding;
}
/**
constructor(config) {
super(config);
- this.initialize();
+ const me = this;
+ me.opacity = 0;
+ me._active = [];
+ me._lastActive = [];
+ me.initialize();
}
initialize() {
- var me = this;
- me._model = getBaseModel(me._options);
- me._view = {};
- me._lastActive = [];
+ const me = this;
+ me.options = resolveOptions(me._chart.options.tooltips);
}
- transition(easingValue) {
- var me = this;
- var options = me._options;
-
- if (me._lastEvent && me._chart.animating) {
- // Let's react to changes during animation
- me._active = me._chart.getElementsAtEventForMode(me._lastEvent, options.mode, options);
- me.update(true);
- me.pivot();
- me._lastActive = me.active;
+ /**
+ * @private
+ */
+ _resolveAnimations() {
+ const me = this;
+ const cached = me._cachedAnimations;
+
+ if (cached) {
+ return cached;
}
- Element.prototype.transition.call(me, easingValue);
+ const chart = me._chart;
+ const opts = chart.options.animation && me.options.animation;
+ const animations = new Animations(me._chart, opts);
+ me._cachedAnimations = Object.freeze(animations);
+
+ return animations;
}
// Get the title
// Args are: (tooltipItem, data)
getTitle() {
var me = this;
- var opts = me._options;
+ var opts = me.options;
var callbacks = opts.callbacks;
var beforeTitle = callbacks.beforeTitle.apply(me, arguments);
// Args are: (tooltipItem, data)
getBeforeBody() {
- return getBeforeAfterBodyLines(this._options.callbacks.beforeBody.apply(this, arguments));
+ return getBeforeAfterBodyLines(this.options.callbacks.beforeBody.apply(this, arguments));
}
// Args are: (tooltipItem, data)
getBody(tooltipItems, data) {
var me = this;
- var callbacks = me._options.callbacks;
+ var callbacks = me.options.callbacks;
var bodyItems = [];
helpers.each(tooltipItems, function(tooltipItem) {
// Args are: (tooltipItem, data)
getAfterBody() {
- return getBeforeAfterBodyLines(this._options.callbacks.afterBody.apply(this, arguments));
+ return getBeforeAfterBodyLines(this.options.callbacks.afterBody.apply(this, arguments));
}
// Get the footer and beforeFooter and afterFooter lines
// Args are: (tooltipItem, data)
getFooter() {
var me = this;
- var callbacks = me._options.callbacks;
+ var callbacks = me.options.callbacks;
var beforeFooter = callbacks.beforeFooter.apply(me, arguments);
var footer = callbacks.footer.apply(me, arguments);
return lines;
}
- update(changed) {
- var me = this;
- var opts = me._options;
-
- // Need to regenerate the model because its faster than using extend and it is necessary due to the optimization in Chart.Element.transition
- // that does _view = _model if ease === 1. This causes the 2nd tooltip update to set properties in both the view and model at the same time
- // which breaks any animations.
- var existingModel = me._model;
- var model = me._model = getBaseModel(opts);
- var active = me._active;
-
- var data = me._data;
-
- // In the case where active.length === 0 we need to keep these at existing values for good animations
- var alignment = {
- xAlign: existingModel.xAlign,
- yAlign: existingModel.yAlign
- };
- var backgroundPoint = {
- x: existingModel.x,
- y: existingModel.y
- };
- var tooltipSize = {
- width: existingModel.width,
- height: existingModel.height
- };
- var tooltipPosition = {
- x: existingModel.caretX,
- y: existingModel.caretY
- };
-
- var i, len;
+ /**
+ * @private
+ */
+ _createItems() {
+ const me = this;
+ const active = me._active;
+ const options = me.options;
+ const data = me._chart.data;
+ const labelColors = [];
+ const labelTextColors = [];
+ let tooltipItems = [];
+ let i, len;
+
+ for (i = 0, len = active.length; i < len; ++i) {
+ tooltipItems.push(createTooltipItem(me._chart, active[i]));
+ }
- if (active.length) {
- model.opacity = 1;
+ // If the user provided a filter function, use it to modify the tooltip items
+ if (options.filter) {
+ tooltipItems = tooltipItems.filter(function(a) {
+ return options.filter(a, data);
+ });
+ }
- var labelColors = [];
- var labelTextColors = [];
- tooltipPosition = positioners[opts.position].call(me, active, me._eventPosition);
+ // If the user provided a sorting function, use it to modify the tooltip items
+ if (options.itemSort) {
+ tooltipItems = tooltipItems.sort(function(a, b) {
+ return options.itemSort(a, b, data);
+ });
+ }
- var tooltipItems = [];
- for (i = 0, len = active.length; i < len; ++i) {
- tooltipItems.push(createTooltipItem(me._chart, active[i]));
- }
+ // Determine colors for boxes
+ helpers.each(tooltipItems, function(tooltipItem) {
+ labelColors.push(options.callbacks.labelColor.call(me, tooltipItem, me._chart));
+ labelTextColors.push(options.callbacks.labelTextColor.call(me, tooltipItem, me._chart));
+ });
- // If the user provided a filter function, use it to modify the tooltip items
- if (opts.filter) {
- tooltipItems = tooltipItems.filter(function(a) {
- return opts.filter(a, data);
- });
- }
+ me.labelColors = labelColors;
+ me.labelTextColors = labelTextColors;
+ me.dataPoints = tooltipItems;
+ return tooltipItems;
+ }
- // If the user provided a sorting function, use it to modify the tooltip items
- if (opts.itemSort) {
- tooltipItems = tooltipItems.sort(function(a, b) {
- return opts.itemSort(a, b, data);
- });
+ update(changed) {
+ const me = this;
+ const options = me.options;
+ const active = me._active;
+ let properties;
+
+ if (!active.length) {
+ if (me.opacity !== 0) {
+ properties = {
+ opacity: 0
+ };
}
-
- // Determine colors for boxes
- helpers.each(tooltipItems, function(tooltipItem) {
- labelColors.push(opts.callbacks.labelColor.call(me, tooltipItem, me._chart));
- labelTextColors.push(opts.callbacks.labelTextColor.call(me, tooltipItem, me._chart));
- });
-
-
- // Build the Text Lines
- model.title = me.getTitle(tooltipItems, data);
- model.beforeBody = me.getBeforeBody(tooltipItems, data);
- model.body = me.getBody(tooltipItems, data);
- model.afterBody = me.getAfterBody(tooltipItems, data);
- model.footer = me.getFooter(tooltipItems, data);
-
- // Initial positioning and colors
- model.x = tooltipPosition.x;
- model.y = tooltipPosition.y;
- model.caretPadding = opts.caretPadding;
- model.labelColors = labelColors;
- model.labelTextColors = labelTextColors;
-
- // data points
- model.dataPoints = tooltipItems;
-
- // We need to determine alignment of the tooltip
- tooltipSize = getTooltipSize(this, model);
- alignment = determineAlignment(this, tooltipSize);
- // Final Size and Position
- backgroundPoint = getBackgroundPoint(model, tooltipSize, alignment, me._chart);
} else {
- model.opacity = 0;
+ const data = me._chart.data;
+ const position = positioners[options.position].call(me, active, me._eventPosition);
+ const tooltipItems = me._createItems();
+
+ me.title = me.getTitle(tooltipItems, data);
+ me.beforeBody = me.getBeforeBody(tooltipItems, data);
+ me.body = me.getBody(tooltipItems, data);
+ me.afterBody = me.getAfterBody(tooltipItems, data);
+ me.footer = me.getFooter(tooltipItems, data);
+
+ const size = me._size = getTooltipSize(me);
+ const positionAndSize = helpers.extend({}, position, size);
+ const alignment = determineAlignment(me._chart, options, positionAndSize);
+ const backgroundPoint = getBackgroundPoint(options, positionAndSize, alignment, me._chart);
+
+ me.xAlign = alignment.xAlign;
+ me.yAlign = alignment.yAlign;
+
+ properties = {
+ opacity: 1,
+ x: backgroundPoint.x,
+ y: backgroundPoint.y,
+ width: size.width,
+ height: size.height,
+ caretX: position.x,
+ caretY: position.y
+ };
}
- model.xAlign = alignment.xAlign;
- model.yAlign = alignment.yAlign;
- model.x = backgroundPoint.x;
- model.y = backgroundPoint.y;
- model.width = tooltipSize.width;
- model.height = tooltipSize.height;
-
- // Point where the caret on the tooltip points to
- model.caretX = tooltipPosition.x;
- model.caretY = tooltipPosition.y;
-
- me._model = model;
-
- if (changed && opts.custom) {
- opts.custom.call(me, model);
+ if (properties) {
+ me._resolveAnimations().update(me, properties);
}
- return me;
+ if (changed && options.custom) {
+ options.custom.call(me);
+ }
}
- drawCaret(tooltipPoint, size) {
- var ctx = this._chart.ctx;
- var vm = this._view;
- var caretPosition = this.getCaretPosition(tooltipPoint, size, vm);
+ drawCaret(tooltipPoint, ctx, size) {
+ var caretPosition = this.getCaretPosition(tooltipPoint, size);
ctx.lineTo(caretPosition.x1, caretPosition.y1);
ctx.lineTo(caretPosition.x2, caretPosition.y2);
ctx.lineTo(caretPosition.x3, caretPosition.y3);
}
- getCaretPosition(tooltipPoint, size, vm) {
- var x1, x2, x3, y1, y2, y3;
- var caretSize = vm.caretSize;
- var cornerRadius = vm.cornerRadius;
- var xAlign = vm.xAlign;
- var yAlign = vm.yAlign;
- var ptX = tooltipPoint.x;
- var ptY = tooltipPoint.y;
- var width = size.width;
- var height = size.height;
+ getCaretPosition(tooltipPoint, size) {
+ const {xAlign, yAlign, options} = this;
+ const {cornerRadius, caretSize} = options;
+ const {x: ptX, y: ptY} = tooltipPoint;
+ const {width, height} = size;
+ let x1, x2, x3, y1, y2, y3;
if (yAlign === 'center') {
y2 = ptY + (height / 2);
if (xAlign === 'left') {
x1 = ptX;
x2 = x1 - caretSize;
- x3 = x1;
-
- y1 = y2 + caretSize;
- y3 = y2 - caretSize;
} else {
x1 = ptX + width;
x2 = x1 + caretSize;
- x3 = x1;
-
- y1 = y2 - caretSize;
- y3 = y2 + caretSize;
}
+ x3 = x1;
+ y1 = y2 + caretSize;
+ y3 = y2 - caretSize;
} else {
if (xAlign === 'left') {
x2 = ptX + cornerRadius + (caretSize);
- x1 = x2 - caretSize;
- x3 = x2 + caretSize;
} else if (xAlign === 'right') {
x2 = ptX + width - cornerRadius - caretSize;
- x1 = x2 - caretSize;
- x3 = x2 + caretSize;
} else {
- x2 = vm.caretX;
- x1 = x2 - caretSize;
- x3 = x2 + caretSize;
+ x2 = this.caretX;
}
+ x1 = x2 - caretSize;
+ x3 = x2 + caretSize;
if (yAlign === 'top') {
y1 = ptY;
y2 = y1 - caretSize;
- y3 = y1;
} else {
y1 = ptY + height;
y2 = y1 + caretSize;
- y3 = y1;
- // invert drawing order
- var tmp = x3;
- x3 = x1;
- x1 = tmp;
}
+ y3 = y1;
}
- return {x1: x1, x2: x2, x3: x3, y1: y1, y2: y2, y3: y3};
+ return {x1, x2, x3, y1, y2, y3};
}
- drawTitle(pt, vm, ctx) {
- var title = vm.title;
+ drawTitle(pt, ctx) {
+ const me = this;
+ const options = me.options;
+ var title = me.title;
var length = title.length;
var titleFontSize, titleSpacing, i;
if (length) {
- var rtlHelper = getRtlHelper(vm.rtl, vm.x, vm.width);
+ var rtlHelper = getRtlHelper(options.rtl, me.x, me.width);
- pt.x = getAlignedX(vm, vm._titleAlign);
+ pt.x = getAlignedX(me, options.titleAlign);
- ctx.textAlign = rtlHelper.textAlign(vm._titleAlign);
+ ctx.textAlign = rtlHelper.textAlign(options.titleAlign);
ctx.textBaseline = 'middle';
- titleFontSize = vm.titleFontSize;
- titleSpacing = vm.titleSpacing;
+ titleFontSize = options.titleFontSize;
+ titleSpacing = options.titleSpacing;
- ctx.fillStyle = vm.titleFontColor;
- ctx.font = helpers.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily);
+ ctx.fillStyle = options.titleFontColor;
+ ctx.font = helpers.fontString(titleFontSize, options.titleFontStyle, options.titleFontFamily);
for (i = 0; i < length; ++i) {
ctx.fillText(title[i], rtlHelper.x(pt.x), pt.y + titleFontSize / 2);
pt.y += titleFontSize + titleSpacing; // Line Height and spacing
if (i + 1 === length) {
- pt.y += vm.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing
+ pt.y += options.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing
}
}
}
}
- drawBody(pt, vm, ctx) {
- var bodyFontSize = vm.bodyFontSize;
- var bodySpacing = vm.bodySpacing;
- var bodyAlign = vm._bodyAlign;
- var body = vm.body;
- var drawColorBoxes = vm.displayColors;
+ _drawColorBox(ctx, pt, i, rtlHelper) {
+ const me = this;
+ const options = me.options;
+ const labelColors = me.labelColors[i];
+ const bodyFontSize = options.bodyFontSize;
+ const colorX = getAlignedX(me, 'left');
+ const rtlColorX = rtlHelper.x(colorX);
+
+ // Fill a white rect so that colours merge nicely if the opacity is < 1
+ ctx.fillStyle = options.multiKeyBackground;
+ ctx.fillRect(rtlHelper.leftForLtr(rtlColorX, bodyFontSize), pt.y, bodyFontSize, bodyFontSize);
+
+ // Border
+ ctx.lineWidth = 1;
+ ctx.strokeStyle = labelColors.borderColor;
+ ctx.strokeRect(rtlHelper.leftForLtr(rtlColorX, bodyFontSize), pt.y, bodyFontSize, bodyFontSize);
+
+ // Inner square
+ ctx.fillStyle = labelColors.backgroundColor;
+ ctx.fillRect(rtlHelper.leftForLtr(rtlHelper.xPlus(rtlColorX, 1), bodyFontSize - 2), pt.y + 1, bodyFontSize - 2, bodyFontSize - 2);
+
+ // restore fillStyle
+ ctx.fillStyle = me.labelTextColors[i];
+ }
+
+ drawBody(pt, ctx) {
+ const me = this;
+ const {body, options} = me;
+ const {bodyFontSize, bodySpacing, bodyAlign, displayColors} = options;
var xLinePadding = 0;
- var colorX = drawColorBoxes ? getAlignedX(vm, 'left') : 0;
- var rtlHelper = getRtlHelper(vm.rtl, vm.x, vm.width);
+ var rtlHelper = getRtlHelper(options.rtl, me.x, me.width);
var fillLineOfText = function(line) {
ctx.fillText(line, rtlHelper.x(pt.x + xLinePadding), pt.y + bodyFontSize / 2);
pt.y += bodyFontSize + bodySpacing;
};
- var bodyItem, textColor, labelColors, lines, i, j, ilen, jlen;
var bodyAlignForCalculation = rtlHelper.textAlign(bodyAlign);
+ var bodyItem, textColor, lines, i, j, ilen, jlen;
ctx.textAlign = bodyAlign;
ctx.textBaseline = 'middle';
- ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
+ ctx.font = helpers.fontString(bodyFontSize, options.bodyFontStyle, options.bodyFontFamily);
- pt.x = getAlignedX(vm, bodyAlignForCalculation);
+ pt.x = getAlignedX(me, bodyAlignForCalculation);
// Before body lines
- ctx.fillStyle = vm.bodyFontColor;
- helpers.each(vm.beforeBody, fillLineOfText);
+ ctx.fillStyle = options.bodyFontColor;
+ helpers.each(me.beforeBody, fillLineOfText);
- xLinePadding = drawColorBoxes && bodyAlignForCalculation !== 'right'
+ xLinePadding = displayColors && bodyAlignForCalculation !== 'right'
? bodyAlign === 'center' ? (bodyFontSize / 2 + 1) : (bodyFontSize + 2)
: 0;
// Draw body lines now
for (i = 0, ilen = body.length; i < ilen; ++i) {
bodyItem = body[i];
- textColor = vm.labelTextColors[i];
- labelColors = vm.labelColors[i];
+ textColor = me.labelTextColors[i];
ctx.fillStyle = textColor;
helpers.each(bodyItem.before, fillLineOfText);
lines = bodyItem.lines;
for (j = 0, jlen = lines.length; j < jlen; ++j) {
// Draw Legend-like boxes if needed
- if (drawColorBoxes) {
- var rtlColorX = rtlHelper.x(colorX);
-
- // Fill a white rect so that colours merge nicely if the opacity is < 1
- ctx.fillStyle = vm.legendColorBackground;
- ctx.fillRect(rtlHelper.leftForLtr(rtlColorX, bodyFontSize), pt.y, bodyFontSize, bodyFontSize);
-
- // Border
- ctx.lineWidth = 1;
- ctx.strokeStyle = labelColors.borderColor;
- ctx.strokeRect(rtlHelper.leftForLtr(rtlColorX, bodyFontSize), pt.y, bodyFontSize, bodyFontSize);
-
- // Inner square
- ctx.fillStyle = labelColors.backgroundColor;
- ctx.fillRect(rtlHelper.leftForLtr(rtlHelper.xPlus(rtlColorX, 1), bodyFontSize - 2), pt.y + 1, bodyFontSize - 2, bodyFontSize - 2);
- ctx.fillStyle = textColor;
+ if (displayColors) {
+ me._drawColorBox(ctx, pt, i, rtlHelper);
}
fillLineOfText(lines[j]);
xLinePadding = 0;
// After body lines
- helpers.each(vm.afterBody, fillLineOfText);
+ helpers.each(me.afterBody, fillLineOfText);
pt.y -= bodySpacing; // Remove last body spacing
}
- drawFooter(pt, vm, ctx) {
- var footer = vm.footer;
+ drawFooter(pt, ctx) {
+ const me = this;
+ const options = me.options;
+ var footer = me.footer;
var length = footer.length;
var footerFontSize, i;
if (length) {
- var rtlHelper = getRtlHelper(vm.rtl, vm.x, vm.width);
+ var rtlHelper = getRtlHelper(options.rtl, me.x, me.width);
- pt.x = getAlignedX(vm, vm._footerAlign);
- pt.y += vm.footerMarginTop;
+ pt.x = getAlignedX(me, options.footerAlign);
+ pt.y += options.footerMarginTop;
- ctx.textAlign = rtlHelper.textAlign(vm._footerAlign);
+ ctx.textAlign = rtlHelper.textAlign(options.footerAlign);
ctx.textBaseline = 'middle';
- footerFontSize = vm.footerFontSize;
+ footerFontSize = options.footerFontSize;
- ctx.fillStyle = vm.footerFontColor;
- ctx.font = helpers.fontString(footerFontSize, vm._footerFontStyle, vm._footerFontFamily);
+ ctx.fillStyle = options.footerFontColor;
+ ctx.font = helpers.fontString(footerFontSize, options.footerFontStyle, options.footerFontFamily);
for (i = 0; i < length; ++i) {
ctx.fillText(footer[i], rtlHelper.x(pt.x), pt.y + footerFontSize / 2);
- pt.y += footerFontSize + vm.footerSpacing;
+ pt.y += footerFontSize + options.footerSpacing;
}
}
}
- drawBackground(pt, vm, ctx, tooltipSize) {
- ctx.fillStyle = vm.backgroundColor;
- ctx.strokeStyle = vm.borderColor;
- ctx.lineWidth = vm.borderWidth;
- var xAlign = vm.xAlign;
- var yAlign = vm.yAlign;
- var x = pt.x;
- var y = pt.y;
- var width = tooltipSize.width;
- var height = tooltipSize.height;
- var radius = vm.cornerRadius;
+ drawBackground(pt, ctx, tooltipSize) {
+ const {xAlign, yAlign, options} = this;
+ const {x, y} = pt;
+ const {width, height} = tooltipSize;
+ const radius = options.cornerRadius;
+
+ ctx.fillStyle = options.backgroundColor;
+ ctx.strokeStyle = options.borderColor;
+ ctx.lineWidth = options.borderWidth;
ctx.beginPath();
ctx.moveTo(x + radius, y);
if (yAlign === 'top') {
- this.drawCaret(pt, tooltipSize);
+ this.drawCaret(pt, ctx, tooltipSize);
}
ctx.lineTo(x + width - radius, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
if (yAlign === 'center' && xAlign === 'right') {
- this.drawCaret(pt, tooltipSize);
+ this.drawCaret(pt, ctx, tooltipSize);
}
ctx.lineTo(x + width, y + height - radius);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
if (yAlign === 'bottom') {
- this.drawCaret(pt, tooltipSize);
+ this.drawCaret(pt, ctx, tooltipSize);
}
ctx.lineTo(x + radius, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
if (yAlign === 'center' && xAlign === 'left') {
- this.drawCaret(pt, tooltipSize);
+ this.drawCaret(pt, ctx, tooltipSize);
}
ctx.lineTo(x, y + radius);
ctx.quadraticCurveTo(x, y, x + radius, y);
ctx.fill();
- if (vm.borderWidth > 0) {
+ if (options.borderWidth > 0) {
ctx.stroke();
}
}
- draw() {
- var ctx = this._chart.ctx;
- var vm = this._view;
+ /**
+ * Update x/y animation targets when _active elements are animating too
+ * @private
+ */
+ _updateAnimationTarget() {
+ const me = this;
+ const chart = me._chart;
+ const options = me.options;
+ const anims = me.$animations;
+ const animX = anims && anims.x;
+ const animY = anims && anims.y;
+ if (animX && animX.active() || animY && animY.active()) {
+ const position = positioners[options.position].call(me, me._active, me._eventPosition);
+ if (!position) {
+ return;
+ }
+ const positionAndSize = helpers.extend({}, position, me._size);
+ const alignment = determineAlignment(chart, options, positionAndSize);
+ const point = getBackgroundPoint(options, positionAndSize, alignment, chart);
+ if (animX._to !== point.x || animY._to !== point.y) {
+ me._resolveAnimations().update(me, point);
+ }
+ }
+ }
+
+ draw(ctx) {
+ const me = this;
+ const options = me.options;
+ let opacity = me.opacity;
- if (vm.opacity === 0) {
+ if (!opacity) {
return;
}
+ me._updateAnimationTarget();
+
var tooltipSize = {
- width: vm.width,
- height: vm.height
+ width: me.width,
+ height: me.height
};
var pt = {
- x: vm.x,
- y: vm.y
+ x: me.x,
+ y: me.y
};
// IE11/Edge does not like very small opacities, so snap to 0
- var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity;
+ opacity = Math.abs(opacity < 1e-3) ? 0 : opacity;
// Truthy/falsey value for empty tooltip
- var hasTooltipContent = vm.title.length || vm.beforeBody.length || vm.body.length || vm.afterBody.length || vm.footer.length;
+ var hasTooltipContent = me.title.length || me.beforeBody.length || me.body.length || me.afterBody.length || me.footer.length;
- if (this._options.enabled && hasTooltipContent) {
+ if (options.enabled && hasTooltipContent) {
ctx.save();
ctx.globalAlpha = opacity;
// Draw Background
- this.drawBackground(pt, vm, ctx, tooltipSize);
+ me.drawBackground(pt, ctx, tooltipSize);
- // Draw Title, Body, and Footer
- pt.y += vm.yPadding;
+ helpers.rtl.overrideTextDirection(ctx, options.textDirection);
- helpers.rtl.overrideTextDirection(ctx, vm.textDirection);
+ pt.y += options.yPadding;
// Titles
- this.drawTitle(pt, vm, ctx);
+ me.drawTitle(pt, ctx);
// Body
- this.drawBody(pt, vm, ctx);
+ me.drawBody(pt, ctx);
// Footer
- this.drawFooter(pt, vm, ctx);
+ me.drawFooter(pt, ctx);
- helpers.rtl.restoreTextDirection(ctx, vm.textDirection);
+ helpers.rtl.restoreTextDirection(ctx, options.textDirection);
ctx.restore();
}
*/
handleEvent(e) {
var me = this;
- var options = me._options;
+ var options = me.options;
var changed = false;
me._lastActive = me._lastActive || [];
// Find Active Elements for tooltips
if (e.type === 'mouseout') {
me._active = [];
- me._lastEvent = null;
} else {
me._active = me._chart.getElementsAtEventForMode(e, options.mode, options);
- if (e.type !== 'click') {
- me._lastEvent = e.type === 'click' ? null : e;
- }
if (options.reverse) {
me._active.reverse();
}
};
me.update(true);
- me.pivot();
+ // me.pivot();
}
}
}
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';
}
}
inRange(chartX, chartY) {
- var vm = this._view;
-
- if (vm) {
- var pointRelativePosition = getAngleFromPoint(vm, {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;
- }
+ var me = this;
- // Check if within the range of the open/close angle
- var betweenAngles = (angle >= startAngle && angle <= endAngle);
- var withinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius);
+ var pointRelativePosition = getAngleFromPoint(me, {x: chartX, y: chartY});
+ var angle = pointRelativePosition.angle;
+ var distance = pointRelativePosition.distance;
- return (betweenAngles && withinRadius);
+ // Sanitise angle range
+ var startAngle = me.startAngle;
+ var endAngle = me.endAngle;
+ while (endAngle < startAngle) {
+ endAngle += TAU;
+ }
+ while (angle > endAngle) {
+ angle -= TAU;
}
- return false;
+ 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;
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();
ctx.closePath();
ctx.fill();
- if (vm.borderWidth) {
- drawBorder(ctx, vm, arc);
+ if (options.borderWidth) {
+ drawBorder(ctx, me, arc);
}
ctx.restore();
import helpers from '../helpers';
const defaultColor = defaults.global.defaultColor;
+const isPointInArea = helpers.canvas._isPointInArea;
defaults._set('global', {
elements: {
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;
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);
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.
}
}
-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 {
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;
closePath = points.closePath;
}
- ctx.save();
+ if (useFastPath(options)) {
+ fastPath(ctx, points, spanGaps);
+ } else {
+ me.updateControlPoints(area);
+ normalPath(ctx, points, spanGaps, options);
+ }
- setStyle(ctx, vm);
+ return closePath;
+ }
- ctx.beginPath();
+ draw(ctx, area) {
+ const me = this;
- if (useFastPath(vm)) {
- fastPath(ctx, points, spanGaps);
- } else {
- normalPath(ctx, points, spanGaps, vm);
+ if (!me._children.length) {
+ return;
}
- if (closePath) {
+ ctx.save();
+
+ setStyle(ctx, me.options);
+
+ ctx.beginPath();
+
+ if (me.drawPath(ctx, area)) {
ctx.closePath();
}
}
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;
-
- if (vm.skip || radius <= 0) {
+ const me = this;
+ const options = me.options;
+ const radius = options.radius;
+
+ 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);
}
}
}
}
});
-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 {
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');
}
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)) {
};
}
-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: {
};
}
-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)
}
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) {
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
};
}
}
var pointsWithTangents = (points || []).map(function(point) {
return {
- model: point._model,
+ model: point,
deltaK: 0,
mK: 0
};
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');
var length = points.length || 0;
return !length ? null : function(point, i) {
- return (i < length && points[i]._view) || null;
+ return (i < length && points[i]) || null;
};
},
// @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;
}
function computeLinearBoundary(source) {
- var model = source.el._model || {};
+ var model = source.el || {};
var scale = source.scale || {};
var fill = source.fill;
var target = null;
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;
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);
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),
},
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;
}
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') {
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);
}
}
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,
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);
}
}
}
{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',
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() {
{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);
{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);
});
});
{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);
{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);
});
});
{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);
{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);
});
});
{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);
{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);
});
});
{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);
{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);
});
});
{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);
});
});
{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);
});
});
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() {
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)'];
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() {
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;
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;
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);
}
});
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);
}
});
});
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() {
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);
});
});
{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
}));
});
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
}));
}
});
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() {
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() {
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);
});
});
});
legend: false,
title: false,
animation: {
+ duration: 0,
animateRotate: true,
animateScale: false
},
{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
{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
{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);
});
});
{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);
});
});
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() {
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() {
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);
});
});
});
{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',
}));
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'];
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'];
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'];
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() {
{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);
{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);
});
});
{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);
{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);
});
});
{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);
{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);
});
});
{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);
{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);
});
});
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() {
}
});
- 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() {
}
});
- 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');
});
});
}
});
- 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() {
}
});
- 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() {
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() {
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() {
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() {
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);
});
});
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() {
});
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);
});
});
{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
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() {
{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
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() {
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() {
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);
});
});
});
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)',
{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,
}));
});
{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,
}));
});
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)',
{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'
}));
});
});
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() {
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() {
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);
});
});
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() {
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() {
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() {
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';
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';
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;
var chart = acquireChart({
type: 'line',
options: {
- responsiveAnimationDuration: 4242,
spanGaps: false,
hover: {
mode: 'dataset',
});
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;
// 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);
});
});
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() {
]);
});
});
-
- 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
- );
- });
- });
});
+++ /dev/null
-// 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);
- });
-});
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);
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);
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});
// 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 = {
// 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 = {
// 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 = {
// '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 = {
// '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 = {
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
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]);
// 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 = {
// 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 = {
// 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 = {
// 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 = {
// 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 = {
// 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: window,
bubbles: true,
cancelable: true,
- clientX: rect.left + point._model.x,
+ clientX: rect.left + point.x,
clientY: 0
});
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'],
}],
afterBody: [],
footer: [],
- caretPadding: 2,
labelColors: [{
borderColor: globalDefaults.defaultColor,
backgroundColor: globalDefaults.defaultColor
}]
}));
- 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() {
view: window,
bubbles: true,
cancelable: true,
- clientX: rect.left + point._model.x,
+ clientX: rect.left + point.x,
clientY: 0
});
// 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,
}));
});
});
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
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'],
}],
afterBody: [],
footer: [],
- caretPadding: 2,
labelTextColors: ['#fff'],
labelColors: [{
borderColor: globalDefaults.defaultColor,
}]
}));
- 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() {
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
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'],
}],
afterBody: ['afterBody'],
footer: ['beforeFooter', 'footer', 'afterFooter'],
- caretPadding: 2,
labelTextColors: ['labelTextColor', 'labelTextColor'],
labelColors: [{
borderColor: globalDefaults.defaultColor,
}]
}));
- 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() {
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
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',
}]
}));
- 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() {
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
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',
}]
}));
- 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() {
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
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',
}]
}));
- 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() {
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
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',
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
// 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,
}));
// 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);
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;
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
animation: {
// without this slice center point is calculated wrong
animateRotate: false
+ },
+ tooltips: {
+ animation: false
}
}
});
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;
}
}
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
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'],
}],
afterBody: ['afterBody', 'newline'],
footer: ['beforeFooter', 'newline', 'footer', 'newline', 'afterFooter', 'newline'],
- caretPadding: 2,
labelTextColors: ['labelTextColor', 'labelTextColor'],
labelColors: [{
borderColor: globalDefaults.defaultColor,
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,
-
- // Title
- titleFontColor: '#fff',
- _titleFontFamily: globalDefaults.defaultFontFamily,
- _titleFontStyle: 'bold',
- titleFontSize: globalDefaults.defaultFontSize,
- _titleAlign: title,
- titleSpacing: 2,
- titleMarginBottom: 6,
-
- // Footer
- footerFontColor: '#fff',
- _footerFontFamily: globalDefaults.defaultFontFamily,
- _footerFontStyle: 'bold',
- footerFontSize: globalDefaults.defaultFontSize,
- _footerAlign: footer,
- footerSpacing: 2,
- footerMarginTop: 6,
+ options: {
+ xPadding: 5,
+ yPadding: 5,
+
+ // Body
+ bodyFontColor: '#fff',
+ bodyFontFamily: globalDefaults.defaultFontFamily,
+ bodyFontStyle: globalDefaults.defaultFontStyle,
+ bodyAlign: body,
+ bodyFontSize: globalDefaults.defaultFontSize,
+ bodySpacing: 2,
+
+ // Title
+ titleFontColor: '#fff',
+ titleFontFamily: globalDefaults.defaultFontFamily,
+ titleFontStyle: 'bold',
+ titleFontSize: globalDefaults.defaultFontSize,
+ titleAlign: title,
+ titleSpacing: 2,
+ titleMarginBottom: 6,
+
+ // 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
- // Appearance
- caretSize: 5,
- cornerRadius: 6,
- borderColor: '#aaa',
- borderWidth: 1,
- backgroundColor: 'rgba(0,0,0,0.8)',
+ },
opacity: 1,
- legendColorBackground: '#fff',
// Text
title: ['title'],
}],
afterBody: [],
footer: ['footer'],
- caretPadding: 2,
labelTextColors: ['#fff'],
labelColors: [{
borderColor: 'rgb(255, 0, 0)',
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']},
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']},
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']},
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']},
});
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);
});
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);
});
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);
});
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);
});
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,
});
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);
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);
// 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);
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,
// 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,
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});
});
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: []
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: []
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',
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: []
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
}]);
});
});
datasetIndex: 1
}, {
text: 'dataset3',
- fillStyle: 'green',
+ fillStyle: 'rgba(0,0,0,0.1)',
hidden: false,
lineCap: 'butt',
lineDash: [],
expect(chart.legend.legendItems).toEqual([{
text: 'dataset3',
- fillStyle: 'green',
+ fillStyle: 'rgba(0,0,0,0.1)',
hidden: false,
lineCap: 'butt',
lineDash: [],
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;