Socket
Socket
Sign inDemoInstall

chart.js

Package Overview
Dependencies
Maintainers
4
Versions
102
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

chart.js - npm Package Compare versions

Comparing version 2.7.1 to 2.7.2

.eslintrc.yml

1

book.json
{
"root": "./docs",
"title": "Chart.js documentation",
"author": "chartjs",

@@ -4,0 +5,0 @@ "gitbook": "3.2.2",

2

bower.json

@@ -6,3 +6,3 @@ {

"license": "MIT",
"version": "2.7.1",
"version": "2.7.2",
"main": "./dist/Chart.js",

@@ -9,0 +9,0 @@ "ignore": [

@@ -6,3 +6,3 @@ gitbook.events.bind('start', function(e, config) {

gitbook.events.bind('page.change', function() {
anchors.add('h1,h2,h3,h4,h5');
anchors.add(anchors.options.selector || 'h2,h3,h4,h5');
});

@@ -23,3 +23,3 @@ # Linear Cartesian Axis

The `suggestedMax` and `suggestedMin` settings only change the data values that are used to scale the axis. These are useful for extending the range of the axis while maintaing the auto fit behaviour.
The `suggestedMax` and `suggestedMin` settings only change the data values that are used to scale the axis. These are useful for extending the range of the axis while maintaining the auto fit behaviour.

@@ -47,3 +47,3 @@ ```javascript

ticks: {
suggestedMin: 50
suggestedMin: 50,
suggestedMax: 100

@@ -50,0 +50,0 @@ }

@@ -78,3 +78,3 @@ # Cartesian Axes

data: [20, 50, 100, 75, 25, 0],
label: 'Left dataset'
label: 'Left dataset',

@@ -84,4 +84,4 @@ // This binds the dataset to the left y axis

}, {
data: [0.1, 0.5, 1.0, 2.0, 1.5, 0]
label: 'Right dataset'
data: [0.1, 0.5, 1.0, 2.0, 1.5, 0],
label: 'Right dataset',

@@ -88,0 +88,0 @@ // This binds the dataset to the right y axis

@@ -150,4 +150,4 @@ # Time Cartesian Axis

### Parser
If this property is defined as a string, it is interpreted as a custom format to be used by moment to parse the date.
If this property is defined as a string, it is interpreted as a custom format to be used by moment to parse the date.
If this is a function, it must return a moment.js object given the appropriate data value.

@@ -39,3 +39,3 @@ # Linear Radial Axis

The `suggestedMax` and `suggestedMin` settings only change the data values that are used to scale the axis. These are useful for extending the range of the axis while maintaing the auto fit behaviour.
The `suggestedMax` and `suggestedMin` settings only change the data values that are used to scale the axis. These are useful for extending the range of the axis while maintaining the auto fit behaviour.

@@ -62,3 +62,3 @@ ```javascript

ticks: {
suggestedMin: 50
suggestedMin: 50,
suggestedMax: 100

@@ -109,5 +109,5 @@ }

| `callback` | `Function` | | Callback function to transform data labels to point labels. The default implementation simply returns the current string.
| `fontColor` | `Color` | `'#666'` | Font color for point labels.
| `fontColor` | `Color/Color[]` | `'#666'` | Font color for point labels.
| `fontFamily` | `String` | `"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"` | Font family to use when rendering labels.
| `fontSize` | `Number` | 10 | font size in pixels
| `fontStyle` | `String` | `'normal'` | Font style to use when rendering point labels.

@@ -29,3 +29,3 @@ # Axes

| `beforeUpdate` | `axis` | Callback called before the update process starts.
| `beforeSetDimensions` | `axis` | Callback that runs before dimensions are set.
| `beforeSetDimensions` | `axis` | Callback that runs before dimensions are set.
| `afterSetDimensions` | `axis` | Callback that runs after dimensions are set.

@@ -37,7 +37,7 @@ | `beforeDataLimits` | `axis` | Callback that runs before data limits are determined.

| `beforeTickToLabelConversion` | `axis` | Callback that runs before ticks are converted into strings.
| `afterTickToLabelConversion` | `axis` | Callback that runs after ticks are converted into strings.
| `afterTickToLabelConversion` | `axis` | Callback that runs after ticks are converted into strings.
| `beforeCalculateTickRotation` | `axis` | Callback that runs before tick rotation is determined.
| `afterCalculateTickRotation` | `axis` | Callback that runs after tick rotation is determined.
| `beforeFit` | `axis` | Callback that runs before the scale fits to the canvas.
| `afterFit` | `axis` | Callback that runs after the scale fits to the canvas.
| `beforeFit` | `axis` | Callback that runs before the scale fits to the canvas.
| `afterFit` | `axis` | Callback that runs after the scale fits to the canvas.
| `afterUpdate` | `axis` | Callback that runs at the end of the update process.

@@ -44,0 +44,0 @@

@@ -38,4 +38,4 @@ # Styling

| `reverse` | `Boolean` | `false` | Reverses order of tick labels.
| `minor` | `object` | `{}` | Minor ticks configuration. Ommited options are inherited from options above.
| `major` | `object` | `{}` | Major ticks configuration. Ommited options are inherited from options above.
| `minor` | `object` | `{}` | Minor ticks configuration. Omitted options are inherited from options above.
| `major` | `object` | `{}` | Major ticks configuration. Omitted options are inherited from options above.

@@ -42,0 +42,0 @@ ## Minor Tick Configuration

@@ -9,8 +9,8 @@ # Bar

"labels": [
"January",
"February",
"March",
"April",
"May",
"June",
"January",
"February",
"March",
"April",
"May",
"June",
"July"

@@ -156,3 +156,3 @@ ],

You can also specify the dataset as x/y coordinates when using the [time scale](./time.md).
You can also specify the dataset as x/y coordinates when using the [time scale](../axes/cartesian/time.md#time-cartesian-axis).

@@ -159,0 +159,0 @@ ```javascript

@@ -58,3 +58,2 @@ # Doughnut and Pie

| ---- | ---- | -----------
| `label` | `String` | The label for the dataset which appears in the legend and tooltips.
| `backgroundColor` | `Color[]` | The fill color of the arcs in the dataset. See [Colors](../general/colors.md#colors)

@@ -61,0 +60,0 @@ | `borderColor` | `Color[]` | The border color of the arcs in the dataset. See [Colors](../general/colors.md#colors)

@@ -122,3 +122,3 @@ # Line

When the `data` array is an array of numbers, the x axis is generally a [category](../axes/cartesian/category.md#Category Axis). The points are placed onto the axis using their position in the array. When a line chart is created with a category axis, the `labels` property of the data object must be specified.
When the `data` array is an array of numbers, the x axis is generally a [category](../axes/cartesian/category.md#category-cartesian-axis). The points are placed onto the axis using their position in the array. When a line chart is created with a category axis, the `labels` property of the data object must be specified.

@@ -125,0 +125,0 @@ ### Point[]

@@ -44,5 +44,5 @@ # Mixed Chart Types

"labels": [
"January",
"February",
"March",
"January",
"February",
"March",
"April"

@@ -49,0 +49,0 @@ ],

@@ -49,3 +49,2 @@ # Polar Area

| ---- | ---- | -----------
| `label` | `String` | The label for the dataset which appears in the legend and tooltips.
| `backgroundColor` | `Color[]` | The fill color of the arcs in the dataset. See [Colors](../general/colors.md#colors)

@@ -52,0 +51,0 @@ | `borderColor` | `Color[]` | The border color of the arcs in the dataset. See [Colors](../general/colors.md#colors)

@@ -11,5 +11,5 @@ # Radar

"labels": [
"Eating",
"Drinking",
"Sleeping",
"Eating",
"Drinking",
"Sleeping",
"Designing",

@@ -98,3 +98,3 @@ "Coding",

* 'crossRot'
* 'dash'.
* 'dash'.
* 'line'

@@ -132,3 +132,3 @@ * 'rect'

The `data` property of a dataset for a radar chart is specified as a an array of numbers. Each point in the data array corresponds to the label at the same index on the x axis.
The `data` property of a dataset for a radar chart is specified as a an array of numbers. Each point in the data array corresponds to the label at the same index on the x axis.

@@ -135,0 +135,0 @@ ```javascript

@@ -13,3 +13,3 @@ # Legend Configuration

| `fullWidth` | `Boolean` | `true` | Marks that this box should take the full width of the canvas (pushing down other boxes). This is unlikely to need to be changed in day-to-day use.
| `onClick` | `Function` | | A callback that is called when a click event is registered on a label item
| `onClick` | `Function` | | A callback that is called when a click event is registered on a label item
| `onHover` | `Function` | | A callback that is called when a 'mousemove' event is registered on top of a label item

@@ -168,1 +168,5 @@ | `reverse` | `Boolean` | `false` | Legend will show datasets in reverse order.

```
Note that legendCallback is not called automatically and you must call `generateLegend()` yourself in code when creating a legend using this method.

@@ -9,3 +9,3 @@ # Tooltips

| -----| ---- | --------| -----------
| `enabled` | `Boolean` | `true` | Are tooltips enabled
| `enabled` | `Boolean` | `true` | Are on-canvas tooltips enabled
| `custom` | `Function` | `null` | See [custom tooltip](#external-custom-tooltips) section.

@@ -34,3 +34,3 @@ | `mode` | `String` | `'nearest'` | Sets which elements appear in the tooltip. [more...](../general/interactions/modes.md#interaction-modes).

| `footerFontColor` | `Color` | `'#fff'` | Footer font color
| `footerSpacing` | `Number` | `2` | Spacing to add to top and bottom of each fotter line.
| `footerSpacing` | `Number` | `2` | Spacing to add to top and bottom of each footer line.
| `footerMarginTop` | `Number` | `6` | Margin to add before drawing the footer.

@@ -68,5 +68,5 @@ | `xPadding` | `Number` | `6` | Padding to add on left and right of tooltip.

var tooltip = this;
/* ... */
return {

@@ -109,2 +109,28 @@ x: 0,

### Label Callback
The label callback can change the text that displays for a given data point. A common example to round data values; the following example rounds the data to two decimal places.
```javascript
var chart = new Chart(ctx, {
type: 'line',
data: data,
options: {
tooltips: {
callbacks: {
label: function(tooltipItem, data) {
var label = data.datasets[tooltipItem.datasetIndex].label || '';
if (label) {
label += ': ';
}
label += Math.round(tooltipItem.yLabel * 100) / 100;
return label;
}
}
}
}
});
```
### Label Color Callback

@@ -172,2 +198,5 @@

tooltips: {
// Disable the on-canvas tooltip
enabled: false,
custom: function(tooltipModel) {

@@ -181,3 +210,3 @@ // Tooltip Element

tooltipEl.id = 'chartjs-tooltip';
tooltipEl.innerHTML = "<table></table>"
tooltipEl.innerHTML = "<table></table>";
document.body.appendChild(tooltipEl);

@@ -221,3 +250,3 @@ }

style += '; border-width: 2px';
var span = '<span class="chartjs-tooltip-key" style="' + style + '"></span>';
var span = '<span style="' + style + '"></span>';
innerHtml += '<tr><td>' + span + body + '</td></tr>';

@@ -236,7 +265,8 @@ });

tooltipEl.style.opacity = 1;
tooltipEl.style.position = 'absolute';
tooltipEl.style.left = position.left + tooltipModel.caretX + 'px';
tooltipEl.style.top = position.top + tooltipModel.caretY + 'px';
tooltipEl.style.fontFamily = tooltipModel._fontFamily;
tooltipEl.style.fontSize = tooltipModel.fontSize;
tooltipEl.style.fontStyle = tooltipModel._fontStyle;
tooltipEl.style.fontFamily = tooltipModel._bodyFontFamily;
tooltipEl.style.fontSize = tooltipModel.bodyFontSize + 'px';
tooltipEl.style.fontStyle = tooltipModel._bodyFontStyle;
tooltipEl.style.padding = tooltipModel.yPadding + 'px ' + tooltipModel.xPadding + 'px';

@@ -249,3 +279,3 @@ }

See `samples/tooltips/line-customTooltips.html` for examples on how to get started.
See [samples](http://www.chartjs.org/samples/) for examples on how to get started with custom tooltips.

@@ -252,0 +282,0 @@ ## Tooltip Model

@@ -135,5 +135,5 @@ # Chart Prototype Methods

function clickHandler(evt) {
var item = myChart.getElementAtEvent(evt)[0];
var firstPoint = myChart.getElementAtEvent(evt)[0];
if (item) {
if (firstPoint) {
var label = myChart.data.labels[firstPoint._index];

@@ -140,0 +140,0 @@ var value = myChart.data.datasets[firstPoint._datasetIndex].data[firstPoint._index];

@@ -81,3 +81,3 @@ # New Axes

// @param index: index into the ticks array
// @param includeOffset: if true, get the pixel halway between the given tick and the next
// @param includeOffset: if true, get the pixel halfway between the given tick and the next
getPixelForTick: function(index, includeOffset) {},

@@ -89,3 +89,3 @@

// @param datasetIndex : index of the dataset the value comes from
// @param includeOffset : if true, get the pixel halway between the given tick and the next
// @param includeOffset : if true, get the pixel halfway between the given tick and the next
getPixelForValue: function(value, index, datasetIndex, includeOffset) {}

@@ -92,0 +92,0 @@

@@ -78,3 +78,3 @@ # New Charts

```javascript
// Sets the default config for 'derivedBubble' to be the same as the bubble defaults.
// Sets the default config for 'derivedBubble' to be the same as the bubble defaults.
// We look for the defaults by doing Chart.defaults[chartType]

@@ -106,3 +106,3 @@ // It looks like a bug exists when the defaults don't exist

// Chart.controllers[type]
Chart.controllers.derivedBubble = custom;
Chart.controllers.derivedBubble = custom;

@@ -109,0 +109,0 @@ // Now we can create and use our new chart type

@@ -15,3 +15,3 @@ # Contributing

Active committers and contributors are invited to introduce yourself and request commit access to this project. We have a very active Slack community that you can join [here](https://chart-js-automation.herokuapp.com/). If you think you can help, we'd love to have you!
Active committers and contributors are invited to introduce yourself and request commit access to this project. We have a very active Slack community that you can join [here](https://chartjs-slack.herokuapp.com/). If you think you can help, we'd love to have you!

@@ -18,0 +18,0 @@ # Building and Testing

# Updating Charts
It's pretty common to want to update charts after they've been created. When the chart data is changed, Chart.js will animate to the new data values.
It's pretty common to want to update charts after they've been created. When the chart data or options are changed, Chart.js will animate to the new data values and options.

@@ -17,5 +17,3 @@ ## Adding or Removing Data

}
```
```javascript
function removeData(chart) {

@@ -30,4 +28,76 @@ chart.data.labels.pop();

## Updating Options
To update the options, mutating the options property in place or passing in a new options object are supported.
- If the options are mutated in place, other option properties would be preserved, including those calculated by Chart.js.
- If created as a new object, it would be like creating a new chart with the options - old options would be discarded.
```javascript
function updateConfigByMutating(chart) {
chart.options.title.text = 'new title';
chart.update();
}
function updateConfigAsNewObject(chart) {
chart.options = {
responsive: true,
title:{
display:true,
text: 'Chart.js'
},
scales: {
xAxes: [{
display: true
}],
yAxes: [{
display: true
}]
}
}
chart.update();
}
```
Scales can be updated separately without changing other options.
To update the scales, pass in an object containing all the customization including those unchanged ones.
Variables referencing any one from `chart.scales` would be lost after updating scales with a new `id` or the changed `type`.
```javascript
function updateScales(chart) {
var xScale = chart.scales['x-axis-0'];
var yScale = chart.scales['y-axis-0'];
chart.options.scales = {
xAxes: [{
id: 'newId',
display: true
}],
yAxes: [{
display: true,
type: 'logarithmic'
}]
}
chart.update();
// need to update the reference
xScale = chart.scales['newId'];
yScale = chart.scales['y-axis-0'];
}
```
You can also update a specific scale either by specifying its index or id.
```javascript
function updateScale(chart) {
chart.options.scales.yAxes[0] = {
type: 'logarithmic'
}
chart.update();
}
```
Code sample for updating options can be found in [toggle-scale-type.html](../../samples/scales/toggle-scale-type.html).
## Preventing Animations
Sometimes when a chart updates, you may not want an animation. To achieve this you can call `update` with a duration of `0`. This will render the chart synchronously and without an animation.

@@ -9,3 +9,3 @@ # Colors

An alternative option is to pass a [CanvasPattern](https://developer.mozilla.org/en-US/docs/Web/API/CanvasPattern) or [CanvasGradient](https://developer.mozilla.org/en/docs/Web/API/CanvasGradient) object instead of a string colour.
An alternative option is to pass a [CanvasPattern](https://developer.mozilla.org/en-US/docs/Web/API/CanvasPattern) or [CanvasGradient](https://developer.mozilla.org/en/docs/Web/API/CanvasGradient) object instead of a string colour.

@@ -12,0 +12,0 @@ For example, if you wanted to fill a dataset with a pattern from an image you could do the following.

@@ -10,3 +10,3 @@ # Events

## Event Option
## Event Option
For example, to have the chart only respond to click events, you could do

@@ -13,0 +13,0 @@ ```javascript

@@ -44,3 +44,3 @@ # Interaction Modes

## index
Finds item at the same index. If the `intersect` setting is true, the first intersecting item is used to determine the index in the data. If `intersect` false the nearest item, in the x direction, is used to determine the index.
Finds item at the same index. If the `intersect` setting is true, the first intersecting item is used to determine the index in the data. If `intersect` false the nearest item, in the x direction, is used to determine the index.

@@ -47,0 +47,0 @@ ```javascript

# Interactions
The hover configuration is passed into the `options.hover` namespace. The global hover configuration is at `Chart.defaults.global.hover`. To configure which events trigger chart interactions, see [events](./events.md#events).
The hover configuration is passed into the `options.hover` namespace. The global hover configuration is at `Chart.defaults.global.hover`. To configure which events trigger chart interactions, see [events](./events.md#events).

@@ -5,0 +5,0 @@ | Name | Type | Default | Description

@@ -36,1 +36,13 @@ # Responsive Charts

```
## Printing Resizeable Charts
CSS media queries allow changing styles when printing a page. The CSS applied from these media queries may cause charts to need to resize. However, the resize won't happen automatically. To support resizing charts when printing, one needs to hook the [onbeforeprint](https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeprint) event and manually trigger resizing of each chart.
```javascript
function beforePrintHandler () {
for (var id in Chart.instances) {
Chart.instances[id].resize()
}
}
```

@@ -39,7 +39,7 @@ # Installation

If you download or clone the repository, you must [build](../developers/contributing.md#building-chartjs) Chart.js to generate the dist files. Chart.js no longer comes with prebuilt release versions, so an alternative option to downloading the repo is **strongly** advised.
If you download or clone the repository, you must [build](../developers/contributing.md#building-and-testing) Chart.js to generate the dist files. Chart.js no longer comes with prebuilt release versions, so an alternative option to downloading the repo is **strongly** advised.
# Selecting the Correct Build
Chart.js provides two different builds that are available for your use.
Chart.js provides two different builds for you to choose: `Stand-Alone Build`, `Bundled Build`.

@@ -51,3 +51,3 @@ ## Stand-Alone Build

This version only includes Chart.js. If this version is used and you require the use of the time axis, [Moment.js](http://momentjs.com/) will need to be included before Chart.js.
The stand-alone build includes Chart.js as well as the color parsing library. If this version is used, you are required to include [Moment.js](http://momentjs.com/) before Chart.js for the functionality of the time axis.

@@ -59,2 +59,2 @@ ## Bundled Build

The bundled version includes Moment.js built into the same file. This version should be used if you wish to use time axes and want a single file to include. Do not use this build if your application already includes Moment.js. If you do, Moment.js will be included twice, increasing the page load time and potentially introducing version issues.
The bundled build includes Moment.js in a single file. You should use this version if you require time axes and want to include a single file. You should not use this build if your application already included Moment.js. Otherwise, Moment.js will be included twice which results in increasing page load time and possible version compatability issues.

@@ -43,2 +43,2 @@ # Getting Started

There are many examples of Chart.js that are available in the `/samples` folder of `Chart.js.zip` that is attatched to every [release](https://github.com/chartjs/Chart.js/releases).
There are many examples of Chart.js that are available in the `/samples` folder of `Chart.js.zip` that is attached to every [release](https://github.com/chartjs/Chart.js/releases).

@@ -21,2 +21,3 @@ # Popular Extensions

- <a href="https://github.com/y-takey/chartjs-plugin-stacked100" target="_blank">chartjs-plugin-stacked100</a> - Draws 100% stacked bar chart.
- <a href="https://github.com/everestate/chartjs-plugin-waterfall" target="_blank">chartjs-plugin-waterfall</a> - Enables easy use of waterfall charts.
- <a href="https://github.com/chartjs/chartjs-plugin-zoom" target="_blank">chartjs-plugin-zoom</a> - Enables zooming and panning on charts.

@@ -28,3 +29,8 @@

### Angular
### Angular (v2+)
- <a href="https://github.com/emn178/angular2-chartjs" target="_blank">emn178/angular2-chartjs</a>
- <a href="https://github.com/valor-software/ng2-charts" target="_blank">valor-software/ng2-charts</a>
### Angular (v1)
- <a href="https://github.com/jtblin/angular-chart.js" target="_blank">angular-chart.js</a>

@@ -54,1 +60,7 @@ - <a href="https://github.com/carlcraig/tc-angular-chartjs" target="_blank">tc-angular-chartjs</a>

- <a href="https://github.com/mdewilde/chart/" target="_blank">Chart.java</a>
### GWT (Google Web toolkit)
- <a href="https://github.com/pepstock-org/Charba" target="_blank">Charba</a>
### Ember.js
- <a href="https://github.com/aomran/ember-cli-chart" target="_blank">ember-cli-chart</a>
# Chart.js
[![slack](https://img.shields.io/badge/slack-Chart.js-blue.svg?style=flat-square&maxAge=600)](https://chart-js-automation.herokuapp.com/)
[![slack](https://img.shields.io/badge/slack-chartjs-blue.svg?style=flat-square&maxAge=3600)](https://chartjs-slack.herokuapp.com/)

@@ -5,0 +5,0 @@ ## Installation

@@ -10,2 +10,3 @@ # Summary

* [Responsive](general/responsive.md)
* [Pixel Ratio](general/device-pixel-ratio.md)
* [Interactions](general/interactions/README.md)

@@ -12,0 +13,0 @@ * [Events](general/interactions/events.md)

@@ -6,3 +6,2 @@ var gulp = require('gulp');

var file = require('gulp-file');
var htmlv = require('gulp-html-validator');
var insert = require('gulp-insert');

@@ -21,6 +20,14 @@ var replace = require('gulp-replace');

var collapse = require('bundle-collapser/plugin');
var argv = require('yargs').argv
var yargs = require('yargs');
var path = require('path');
var fs = require('fs');
var htmllint = require('gulp-htmllint');
var package = require('./package.json');
var argv = yargs
.option('force-output', {default: false})
.option('silent-errors', {default: false})
.option('verbose', {default: false})
.argv
var srcDir = './src/';

@@ -34,3 +41,3 @@ var outDir = './dist/';

" *\n" +
" * Copyright 2017 Nick Downie\n" +
" * Copyright " + (new Date().getFullYear()) + " Chart.js Contributors\n" +
" * Released under the MIT license\n" +

@@ -40,2 +47,6 @@ " * https://github.com/chartjs/Chart.js/blob/master/LICENSE.md\n" +

if (argv.verbose) {
util.log("Gulp running with options: " + JSON.stringify(argv, null, 2));
}
gulp.task('bower', bowerTask);

@@ -45,8 +56,9 @@ gulp.task('build', buildTask);

gulp.task('watch', watchTask);
gulp.task('lint', lintTask);
gulp.task('lint', ['lint-html', 'lint-js']);
gulp.task('lint-html', lintHtmlTask);
gulp.task('lint-js', lintJsTask);
gulp.task('docs', docsTask);
gulp.task('test', ['lint', 'validHTML', 'unittest']);
gulp.task('test', ['lint', 'unittest']);
gulp.task('size', ['library-size', 'module-sizes']);
gulp.task('server', serverTask);
gulp.task('validHTML', validHTMLTask);
gulp.task('unittest', unittestTask);

@@ -87,5 +99,21 @@ gulp.task('library-size', librarySizeTask);

var errorHandler = function (err) {
if(argv.forceOutput) {
var browserError = 'console.error("Gulp: ' + err.toString() + '")';
['Chart', 'Chart.min', 'Chart.bundle', 'Chart.bundle.min'].forEach(function(fileName) {
fs.writeFileSync(outDir+fileName+'.js', browserError);
});
}
if(argv.silentErrors) {
util.log(util.colors.red('[Error]'), err.toString());
this.emit('end');
} else {
throw err;
}
}
var bundled = browserify('./src/chart.js', { standalone: 'Chart' })
.plugin(collapse)
.bundle()
.on('error', errorHandler)
.pipe(source('Chart.bundle.js'))

@@ -105,2 +133,3 @@ .pipe(insert.prepend(header))

.bundle()
.on('error', errorHandler)
.pipe(source('Chart.js'))

@@ -135,4 +164,5 @@ .pipe(insert.prepend(header))

function lintTask() {
function lintJsTask() {
var files = [
'samples/**/*.html',
'samples/**/*.js',

@@ -159,2 +189,9 @@ 'src/**/*.js',

function lintHtmlTask() {
return gulp.src('samples/**/*.html')
.pipe(htmllint({
failOnError: true,
}));
}
function docsTask(done) {

@@ -173,7 +210,2 @@ const script = require.resolve('gitbook-cli/bin/gitbook.js');

function validHTMLTask() {
return gulp.src('samples/*.html')
.pipe(htmlv());
}
function startTest() {

@@ -180,0 +212,0 @@ return [

The MIT License (MIT)
Copyright (c) 2013-2017 Nick Downie
Copyright (c) 2018 Chart.js Contributors

@@ -5,0 +5,0 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

@@ -5,3 +5,3 @@ {

"description": "Simple HTML5 charts using the canvas element.",
"version": "2.7.1",
"version": "2.7.2",
"license": "MIT",

@@ -14,16 +14,19 @@ "main": "src/chart.js",

"devDependencies": {
"browserify": "^14.3.0",
"browserify-istanbul": "^2.0.0",
"bundle-collapser": "^1.2.1",
"browserify": "^14.5.0",
"browserify-istanbul": "^3.0.1",
"bundle-collapser": "^1.3.0",
"child-process-promise": "^2.2.1",
"coveralls": "^2.13.1",
"gitbook-cli": "^2.3.0",
"coveralls": "^3.0.0",
"eslint": "^4.9.0",
"eslint-config-chartjs": "^0.1.0",
"eslint-plugin-html": "^4.0.2",
"gitbook-cli": "^2.3.2",
"gulp": "3.9.x",
"gulp-concat": "~2.6.x",
"gulp-connect": "~5.0.0",
"gulp-eslint": "^3.0.1",
"gulp-eslint": "^4.0.0",
"gulp-file": "^0.3.0",
"gulp-html-validator": "^0.0.5",
"gulp-htmllint": "^0.0.15",
"gulp-insert": "~0.5.0",
"gulp-replace": "^0.5.4",
"gulp-replace": "^0.6.1",
"gulp-size": "~2.1.0",

@@ -34,7 +37,7 @@ "gulp-streamify": "^1.0.2",

"gulp-zip": "~4.0.0",
"jasmine": "^2.6.0",
"jasmine-core": "^2.6.2",
"karma": "^1.7.0",
"jasmine": "^2.8.0",
"jasmine-core": "^2.8.0",
"karma": "^1.7.1",
"karma-browserify": "^5.1.1",
"karma-chrome-launcher": "^2.1.1",
"karma-chrome-launcher": "^2.2.0",
"karma-coverage": "^1.1.1",

@@ -48,3 +51,3 @@ "karma-firefox-launcher": "^1.0.1",

"watchify": "^3.9.0",
"yargs": "^8.0.1"
"yargs": "^9.0.1"
},

@@ -55,5 +58,5 @@ "spm": {

"dependencies": {
"chartjs-color": "~2.2.0",
"moment": "~2.18.0"
"chartjs-color": "^2.1.0",
"moment": "^2.10.2"
}
}
# Chart.js
[![travis](https://img.shields.io/travis/chartjs/Chart.js.svg?style=flat-square&maxAge=60)](https://travis-ci.org/chartjs/Chart.js) [![codeclimate](https://img.shields.io/codeclimate/github/chartjs/Chart.js.svg?style=flat-square&maxAge=600)](https://codeclimate.com/github/chartjs/Chart.js) [![coveralls](https://img.shields.io/coveralls/chartjs/Chart.js.svg?style=flat-square&maxAge=600)](https://coveralls.io/github/chartjs/Chart.js?branch=master) [![slack](https://img.shields.io/badge/slack-Chart.js-blue.svg?style=flat-square&maxAge=3600)](https://chart-js-automation.herokuapp.com/)
[![travis](https://img.shields.io/travis/chartjs/Chart.js.svg?style=flat-square&maxAge=60)](https://travis-ci.org/chartjs/Chart.js) [![coveralls](https://img.shields.io/coveralls/chartjs/Chart.js.svg?style=flat-square&maxAge=600)](https://coveralls.io/github/chartjs/Chart.js?branch=master) [![codeclimate](https://img.shields.io/codeclimate/maintainability/chartjs/Chart.js.svg?style=flat-square&maxAge=600)](https://codeclimate.com/github/chartjs/Chart.js) [![slack](https://img.shields.io/badge/slack-chartjs-blue.svg?style=flat-square&maxAge=3600)](https://chartjs-slack.herokuapp.com/)

@@ -22,8 +22,20 @@ *Simple HTML5 Charts using the canvas element* [chartjs.org](http://www.chartjs.org)

#### Selecting the Correct Build
### Selecting the Correct Build
Chart.js provides two different builds that are available for your use. The `Chart.js` and `Chart.min.js` files include Chart.js and the accompanying color parsing library. If this version is used and you require the use of the time axis, [Moment.js](http://momentjs.com/) will need to be included before Chart.js.
Chart.js provides two different builds for you to choose: `Stand-Alone Build`, `Bundled Build`.
The `Chart.bundle.js` and `Chart.bundle.min.js` builds include Moment.js in a single file. This version should be used if you require time axes and want a single file to include, select this version. Do not use this build if your application already includes Moment.js. If you do, Moment.js will be included twice, increasing the page load time and potentially introducing version issues.
#### Stand-Alone Build
Files:
* `dist/Chart.js`
* `dist/Chart.min.js`
The stand-alone build includes Chart.js as well as the color parsing library. If this version is used, you are required to include [Moment.js](http://momentjs.com/) before Chart.js for the functionality of the time axis.
#### Bundled Build
Files:
* `dist/Chart.bundle.js`
* `dist/Chart.bundle.min.js`
The bundled build includes Moment.js in a single file. You should use this version if you require time axes and want to include a single file. You should not use this build if your application already included Moment.js. Otherwise, Moment.js will be included twice which results in increasing page load time and possible version compatability issues.
## Documentation

@@ -30,0 +42,0 @@

@@ -7,3 +7,3 @@ /* global Chart */

Chart.plugins.register({
id: 'samples_filler_analyser',
id: 'samples-filler-analyser',

@@ -10,0 +10,0 @@ beforeInit: function(chart, options) {

@@ -139,2 +139,5 @@ (function(global) {

path: 'scales/non-numeric-y.html'
}, {
title: 'Toggle Scale Type',
path: 'scales/toggle-scale-type.html'
}]

@@ -141,0 +144,0 @@ }, {

@@ -15,9 +15,10 @@ /**

Chart.Interaction = require('./core/core.interaction');
Chart.layouts = require('./core/core.layouts');
Chart.platform = require('./platforms/platform');
Chart.plugins = require('./core/core.plugins');
Chart.Ticks = require('./core/core.ticks');
require('./core/core.plugin')(Chart);
require('./core/core.animation')(Chart);
require('./core/core.controller')(Chart);
require('./core/core.datasetController')(Chart);
require('./core/core.layoutService')(Chart);
require('./core/core.scaleService')(Chart);

@@ -53,12 +54,9 @@ require('./core/core.scale')(Chart);

// Loading built-it plugins
var plugins = [];
var plugins = require('./plugins');
for (var k in plugins) {
if (plugins.hasOwnProperty(k)) {
Chart.plugins.register(plugins[k]);
}
}
plugins.push(
require('./plugins/plugin.filler')(Chart),
require('./plugins/plugin.legend')(Chart),
require('./plugins/plugin.title')(Chart)
);
Chart.plugins.register(plugins);
Chart.platform.initialize();

@@ -74,2 +72,39 @@

/**
* Provided for backward compatibility, not available anymore
* @namespace Chart.Legend
* @deprecated since version 2.1.5
* @todo remove at version 3
* @private
*/
Chart.Legend = plugins.legend._element;
/**
* Provided for backward compatibility, not available anymore
* @namespace Chart.Title
* @deprecated since version 2.1.5
* @todo remove at version 3
* @private
*/
Chart.Title = plugins.title._element;
/**
* Provided for backward compatibility, use Chart.plugins instead
* @namespace Chart.pluginService
* @deprecated since version 2.1.5
* @todo remove at version 3
* @private
*/
Chart.pluginService = Chart.plugins;
/**
* Provided for backward compatibility, inheriting from Chart.PlugingBase has no
* effect, instead simply create/register plugins via plain JavaScript objects.
* @interface Chart.PluginBase
* @deprecated since version 2.5.0
* @todo remove at version 3
* @private
*/
Chart.PluginBase = Chart.Element.extend({});
/**
* Provided for backward compatibility, use Chart.helpers.canvas instead.

@@ -82,1 +117,10 @@ * @namespace Chart.canvasHelpers

Chart.canvasHelpers = Chart.helpers.canvas;
/**
* Provided for backward compatibility, use Chart.layouts instead.
* @namespace Chart.layoutService
* @deprecated since version 2.8.0
* @todo remove at version 3
* @private
*/
Chart.layoutService = Chart.layouts;

@@ -98,2 +98,89 @@ 'use strict';

/**
* Computes the "optimal" sample size to maintain bars equally sized while preventing overlap.
* @private
*/
function computeMinSampleSize(scale, pixels) {
var min = scale.isHorizontal() ? scale.width : scale.height;
var ticks = scale.getTicks();
var prev, curr, i, ilen;
for (i = 1, ilen = pixels.length; i < ilen; ++i) {
min = Math.min(min, pixels[i] - pixels[i - 1]);
}
for (i = 0, ilen = ticks.length; i < ilen; ++i) {
curr = scale.getPixelForTick(i);
min = i > 0 ? Math.min(min, curr - prev) : min;
prev = curr;
}
return min;
}
/**
* Computes an "ideal" category based on the absolute bar thickness or, if undefined or null,
* uses the smallest interval (see computeMinSampleSize) that prevents bar overlapping. This
* mode currently always generates bars equally sized (until we introduce scriptable options?).
* @private
*/
function computeFitCategoryTraits(index, ruler, options) {
var thickness = options.barThickness;
var count = ruler.stackCount;
var curr = ruler.pixels[index];
var size, ratio;
if (helpers.isNullOrUndef(thickness)) {
size = ruler.min * options.categoryPercentage;
ratio = options.barPercentage;
} else {
// When bar thickness is enforced, category and bar percentages are ignored.
// Note(SB): we could add support for relative bar thickness (e.g. barThickness: '50%')
// and deprecate barPercentage since this value is ignored when thickness is absolute.
size = thickness * count;
ratio = 1;
}
return {
chunk: size / count,
ratio: ratio,
start: curr - (size / 2)
};
}
/**
* Computes an "optimal" category that globally arranges bars side by side (no gap when
* percentage options are 1), based on the previous and following categories. This mode
* generates bars with different widths when data are not evenly spaced.
* @private
*/
function computeFlexCategoryTraits(index, ruler, options) {
var pixels = ruler.pixels;
var curr = pixels[index];
var prev = index > 0 ? pixels[index - 1] : null;
var next = index < pixels.length - 1 ? pixels[index + 1] : null;
var percent = options.categoryPercentage;
var start, size;
if (prev === null) {
// first data: its size is double based on the next point or,
// if it's also the last data, we use the scale end extremity.
prev = curr - (next === null ? ruler.end - curr : next - curr);
}
if (next === null) {
// last data: its size is also double based on the previous point.
next = curr + curr - prev;
}
start = curr - ((curr - prev) / 2) * percent;
size = ((next - prev) / 2) * percent;
return {
chunk: size / ruler.stackCount,
ratio: options.barPercentage,
start: start
};
}
module.exports = function(Chart) {

@@ -205,6 +292,8 @@

/**
* Returns the effective number of stacks based on groups and bar visibility.
* Returns the stacks based on groups and bar visibility.
* @param {Number} [last] - The dataset index
* @returns {Array} The stack list
* @private
*/
getStackCount: function(last) {
_getStacks: function(last) {
var me = this;

@@ -228,11 +317,29 @@ var chart = me.chart;

return stacks.length;
return stacks;
},
/**
* Returns the effective number of stacks based on groups and bar visibility.
* @private
*/
getStackCount: function() {
return this._getStacks().length;
},
/**
* Returns the stack index for the given dataset based on groups and bar visibility.
* @param {Number} [datasetIndex] - The dataset index
* @param {String} [name] - The stack name to find
* @returns {Number} The stack index
* @private
*/
getStackIndex: function(datasetIndex) {
return this.getStackCount(datasetIndex) - 1;
getStackIndex: function(datasetIndex, name) {
var stacks = this._getStacks(datasetIndex);
var index = (name !== undefined)
? stacks.indexOf(name)
: -1; // indexOf returns -1 if element is not present
return (index === -1)
? stacks.length - 1
: index;
},

@@ -248,7 +355,7 @@

var datasetIndex = me.index;
var pixels = [];
var isHorizontal = scale.isHorizontal();
var start = isHorizontal ? scale.left : scale.top;
var end = start + (isHorizontal ? scale.width : scale.height);
var i, ilen;
var pixels = [];
var i, ilen, min;

@@ -259,3 +366,8 @@ for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) {

min = helpers.isNullOrUndef(scale.options.barThickness)
? computeMinSampleSize(scale, pixels)
: -1;
return {
min: min,
pixels: pixels,

@@ -320,46 +432,17 @@ start: start,

var options = ruler.scale.options;
var stackIndex = me.getStackIndex(datasetIndex);
var pixels = ruler.pixels;
var base = pixels[index];
var length = pixels.length;
var start = ruler.start;
var end = ruler.end;
var leftSampleSize, rightSampleSize, leftCategorySize, rightCategorySize, fullBarSize, size;
var range = options.barThickness === 'flex'
? computeFlexCategoryTraits(index, ruler, options)
: computeFitCategoryTraits(index, ruler, options);
if (length === 1) {
leftSampleSize = base > start ? base - start : end - base;
rightSampleSize = base < end ? end - base : base - start;
} else {
if (index > 0) {
leftSampleSize = (base - pixels[index - 1]) / 2;
if (index === length - 1) {
rightSampleSize = leftSampleSize;
}
}
if (index < length - 1) {
rightSampleSize = (pixels[index + 1] - base) / 2;
if (index === 0) {
leftSampleSize = rightSampleSize;
}
}
}
var stackIndex = me.getStackIndex(datasetIndex, me.getMeta().stack);
var center = range.start + (range.chunk * stackIndex) + (range.chunk / 2);
var size = Math.min(
helpers.valueOrDefault(options.maxBarThickness, Infinity),
range.chunk * range.ratio);
leftCategorySize = leftSampleSize * options.categoryPercentage;
rightCategorySize = rightSampleSize * options.categoryPercentage;
fullBarSize = (leftCategorySize + rightCategorySize) / ruler.stackCount;
size = fullBarSize * options.barPercentage;
size = Math.min(
helpers.valueOrDefault(options.barThickness, size),
helpers.valueOrDefault(options.maxBarThickness, Infinity));
base -= leftCategorySize;
base += fullBarSize * stackIndex;
base += (fullBarSize - size) / 2;
return {
size: size,
base: base,
head: base + size,
center: base + size / 2
base: center - size / 2,
head: center + size / 2,
center: center,
size: size
};

@@ -366,0 +449,0 @@ },

@@ -276,3 +276,3 @@ 'use strict';

if (total > 0 && !isNaN(value)) {
return (Math.PI * 2.0) * (value / total);
return (Math.PI * 2.0) * (Math.abs(value) / total);
}

@@ -279,0 +279,0 @@ return 0;

@@ -6,6 +6,7 @@ 'use strict';

var Interaction = require('./core.interaction');
var layouts = require('./core.layouts');
var platform = require('../platforms/platform');
var plugins = require('./core.plugins');
module.exports = function(Chart) {
var plugins = Chart.plugins;

@@ -49,13 +50,17 @@ // Create a dictionary of chart types, to allow for extension of existing types

// Update Scale(s) with options
if (newOptions.scale) {
chart.scale.options = newOptions.scale;
} else if (newOptions.scales) {
newOptions.scales.xAxes.concat(newOptions.scales.yAxes).forEach(function(scaleOptions) {
chart.scales[scaleOptions.id].options = scaleOptions;
});
}
helpers.each(chart.scales, function(scale) {
layouts.removeBox(chart, scale);
});
newOptions = helpers.configMerge(
Chart.defaults.global,
Chart.defaults[chart.config.type],
newOptions);
chart.options = chart.config.options = newOptions;
chart.ensureScalesHaveIDs();
chart.buildOrUpdateScales();
// Tooltip
chart.tooltip._options = newOptions.tooltips;
chart.tooltip.initialize();
}

@@ -148,3 +153,3 @@

me.ensureScalesHaveIDs();
me.buildScales();
me.buildOrUpdateScales();
me.initToolTip();

@@ -229,7 +234,11 @@

*/
buildScales: function() {
buildOrUpdateScales: function() {
var me = this;
var options = me.options;
var scales = me.scales = {};
var scales = me.scales || {};
var items = [];
var updated = Object.keys(scales).reduce(function(obj, id) {
obj[id] = false;
return obj;
}, {});

@@ -258,7 +267,4 @@ if (options.scales) {

var scaleOptions = item.options;
var id = scaleOptions.id;
var scaleType = helpers.valueOrDefault(scaleOptions.type, item.dtype);
var scaleClass = Chart.scaleService.getScaleConstructor(scaleType);
if (!scaleClass) {
return;
}

@@ -269,10 +275,24 @@ if (positionIsHorizontal(scaleOptions.position) !== positionIsHorizontal(item.dposition)) {

var scale = new scaleClass({
id: scaleOptions.id,
options: scaleOptions,
ctx: me.ctx,
chart: me
});
updated[id] = true;
var scale = null;
if (id in scales && scales[id].type === scaleType) {
scale = scales[id];
scale.options = scaleOptions;
scale.ctx = me.ctx;
scale.chart = me;
} else {
var scaleClass = Chart.scaleService.getScaleConstructor(scaleType);
if (!scaleClass) {
return;
}
scale = new scaleClass({
id: id,
type: scaleType,
options: scaleOptions,
ctx: me.ctx,
chart: me
});
scales[scale.id] = scale;
}
scales[scale.id] = scale;
scale.mergeTicksOptions();

@@ -287,3 +307,11 @@

});
// clear up discarded scales
helpers.each(updated, function(hasUpdated, id) {
if (!hasUpdated) {
delete scales[id];
}
});
me.scales = scales;
Chart.scaleService.addScalesToLayout(this);

@@ -311,2 +339,3 @@ },

meta.controller.updateIndex(datasetIndex);
meta.controller.linkScales();
} else {

@@ -358,2 +387,6 @@ var ControllerClass = Chart.controllers[meta.type];

// plugins options references might have change, let's invalidate the cache
// https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167
plugins._invalidate(me);
if (plugins.notify(me, 'beforeUpdate') === false) {

@@ -377,5 +410,7 @@ return;

// Can only reset the new controllers after the scales have been updated
helpers.each(newControllers, function(controller) {
controller.reset();
});
if (me.options.animation && me.options.animation.duration) {
helpers.each(newControllers, function(controller) {
controller.reset();
});
}

@@ -418,3 +453,3 @@ me.updateDatasets();

Chart.layoutService.update(this, this.width, this.height);
layouts.update(this, this.width, this.height);

@@ -833,3 +868,11 @@ /**

var changed = me.handleEvent(e);
changed |= tooltip && tooltip.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);
}

@@ -836,0 +879,0 @@ plugins.notify(me, 'afterEvent', [e]);

@@ -114,6 +114,6 @@ 'use strict';

if (meta.xAxisID === null) {
if (meta.xAxisID === null || !(meta.xAxisID in me.chart.scales)) {
meta.xAxisID = dataset.xAxisID || me.chart.options.scales.xAxes[0].id;
}
if (meta.yAxisID === null) {
if (meta.yAxisID === null || !(meta.yAxisID in me.chart.scales)) {
meta.yAxisID = dataset.yAxisID || me.chart.options.scales.yAxes[0].id;

@@ -120,0 +120,0 @@ }

@@ -162,3 +162,9 @@ /* global window: false */

function(x) {
return Math.log(x) / Math.LN10;
var exponent = Math.log(x) * Math.LOG10E; // Math.LOG10E = 1 / Math.LN10.
// Check for whole powers of 10,
// which due to floating point rounding error should be corrected.
var powerOf10 = Math.round(exponent);
var isPowerOf10 = x === Math.pow(10, powerOf10);
return isPowerOf10 ? powerOf10 : exponent;
};

@@ -516,4 +522,6 @@ helpers.toRadians = function(degrees) {

// See https://github.com/chartjs/Chart.js/issues/3575
canvas.style.height = height + 'px';
canvas.style.width = width + 'px';
if (!canvas.style.height && !canvas.style.width) {
canvas.style.height = height + 'px';
canvas.style.width = width + 'px';
}
};

@@ -520,0 +528,0 @@ // -- Canvas methods

@@ -708,6 +708,7 @@ 'use strict';

var xTickStart = options.position === 'right' ? me.left : me.right - tl;
var xTickEnd = options.position === 'right' ? me.left + tl : me.right;
var yTickStart = options.position === 'bottom' ? me.top : me.bottom - tl;
var yTickEnd = options.position === 'bottom' ? me.top + tl : me.bottom;
var axisWidth = me.options.gridLines.lineWidth;
var xTickStart = options.position === 'right' ? me.right : me.right - axisWidth - tl;
var xTickEnd = options.position === 'right' ? me.right + tl : me.right;
var yTickStart = options.position === 'bottom' ? me.top + axisWidth : me.bottom - tl - axisWidth;
var yTickEnd = options.position === 'bottom' ? me.top + axisWidth + tl : me.bottom + axisWidth;

@@ -768,3 +769,3 @@ helpers.each(ticks, function(tick, index) {

y1 = chartArea.top;
y2 = chartArea.bottom;
y2 = chartArea.bottom + axisWidth;
} else {

@@ -795,3 +796,3 @@ var isLeft = options.position === 'left';

x1 = chartArea.left;
x2 = chartArea.right;
x2 = chartArea.right + axisWidth;
ty1 = ty2 = y1 = y2 = yLineValue;

@@ -862,7 +863,11 @@ }

if (helpers.isArray(label)) {
for (var i = 0, y = 0; i < label.length; ++i) {
var lineCount = label.length;
var lineHeight = tickFont.size * 1.5;
var y = me.isHorizontal() ? 0 : -lineHeight * (lineCount - 1) / 2;
for (var i = 0; i < lineCount; ++i) {
// We just make sure the multiline element is a string here..
context.fillText('' + label[i], 0, y);
// apply same lineSpacing as calculated @ L#320
y += (tickFont.size * 1.5);
y += lineHeight;
}

@@ -913,5 +918,5 @@ } else {

var x1 = me.left;
var x2 = me.right;
var x2 = me.right + axisWidth;
var y1 = me.top;
var y2 = me.bottom;
var y2 = me.bottom + axisWidth;

@@ -918,0 +923,0 @@ var aliasPixel = helpers.aliasPixel(context.lineWidth);

@@ -5,2 +5,3 @@ 'use strict';

var helpers = require('../helpers/index');
var layouts = require('./core.layouts');

@@ -42,3 +43,3 @@ module.exports = function(Chart) {

scale.weight = scale.options.weight;
Chart.layoutService.addBox(chart, scale);
layouts.addBox(chart, scale);
});

@@ -45,0 +46,0 @@ }

@@ -11,136 +11,2 @@ 'use strict';

/**
* Namespace to hold generators for different types of ticks
* @namespace Chart.Ticks.generators
*/
generators: {
/**
* Interface for the options provided to the numeric tick generator
* @interface INumericTickGenerationOptions
*/
/**
* The maximum number of ticks to display
* @name INumericTickGenerationOptions#maxTicks
* @type Number
*/
/**
* The distance between each tick.
* @name INumericTickGenerationOptions#stepSize
* @type Number
* @optional
*/
/**
* Forced minimum for the ticks. If not specified, the minimum of the data range is used to calculate the tick minimum
* @name INumericTickGenerationOptions#min
* @type Number
* @optional
*/
/**
* The maximum value of the ticks. If not specified, the maximum of the data range is used to calculate the tick maximum
* @name INumericTickGenerationOptions#max
* @type Number
* @optional
*/
/**
* Generate a set of linear ticks
* @method Chart.Ticks.generators.linear
* @param generationOptions {INumericTickGenerationOptions} the options used to generate the ticks
* @param dataRange {IRange} the range of the data
* @returns {Array<Number>} array of tick values
*/
linear: function(generationOptions, dataRange) {
var ticks = [];
// To get a "nice" value for the tick spacing, we will use the appropriately named
// "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks
// for details.
var spacing;
if (generationOptions.stepSize && generationOptions.stepSize > 0) {
spacing = generationOptions.stepSize;
} else {
var niceRange = helpers.niceNum(dataRange.max - dataRange.min, false);
spacing = helpers.niceNum(niceRange / (generationOptions.maxTicks - 1), true);
}
var niceMin = Math.floor(dataRange.min / spacing) * spacing;
var niceMax = Math.ceil(dataRange.max / spacing) * spacing;
// If min, max and stepSize is set and they make an evenly spaced scale use it.
if (generationOptions.min && generationOptions.max && generationOptions.stepSize) {
// If very close to our whole number, use it.
if (helpers.almostWhole((generationOptions.max - generationOptions.min) / generationOptions.stepSize, spacing / 1000)) {
niceMin = generationOptions.min;
niceMax = generationOptions.max;
}
}
var numSpaces = (niceMax - niceMin) / spacing;
// If very close to our rounded value, use it.
if (helpers.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) {
numSpaces = Math.round(numSpaces);
} else {
numSpaces = Math.ceil(numSpaces);
}
// Put the values into the ticks array
ticks.push(generationOptions.min !== undefined ? generationOptions.min : niceMin);
for (var j = 1; j < numSpaces; ++j) {
ticks.push(niceMin + (j * spacing));
}
ticks.push(generationOptions.max !== undefined ? generationOptions.max : niceMax);
return ticks;
},
/**
* Generate a set of logarithmic ticks
* @method Chart.Ticks.generators.logarithmic
* @param generationOptions {INumericTickGenerationOptions} the options used to generate the ticks
* @param dataRange {IRange} the range of the data
* @returns {Array<Number>} array of tick values
*/
logarithmic: function(generationOptions, dataRange) {
var ticks = [];
var valueOrDefault = helpers.valueOrDefault;
// Figure out what the max number of ticks we can support it is based on the size of
// the axis area. For now, we say that the minimum tick spacing in pixels must be 50
// We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
// the graph
var tickVal = valueOrDefault(generationOptions.min, Math.pow(10, Math.floor(helpers.log10(dataRange.min))));
var endExp = Math.floor(helpers.log10(dataRange.max));
var endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp));
var exp, significand;
if (tickVal === 0) {
exp = Math.floor(helpers.log10(dataRange.minNotZero));
significand = Math.floor(dataRange.minNotZero / Math.pow(10, exp));
ticks.push(tickVal);
tickVal = significand * Math.pow(10, exp);
} else {
exp = Math.floor(helpers.log10(tickVal));
significand = Math.floor(tickVal / Math.pow(10, exp));
}
do {
ticks.push(tickVal);
++significand;
if (significand === 10) {
significand = 1;
++exp;
}
tickVal = significand * Math.pow(10, exp);
} while (exp < endExp || (exp === endExp && significand < endSignificand));
var lastTick = valueOrDefault(generationOptions.max, tickVal);
ticks.push(lastTick);
return ticks;
}
},
/**
* Namespace to hold formatters for different types of ticks

@@ -147,0 +13,0 @@ * @namespace Chart.Ticks.formatters

@@ -302,6 +302,6 @@ 'use strict';

olf = function(x) {
return x + size.width > chart.width;
return x + size.width + model.caretSize + model.caretPadding > chart.width;
};
orf = function(x) {
return x - size.width < 0;
return x - size.width - model.caretSize - model.caretPadding < 0;
};

@@ -340,3 +340,3 @@ yf = function(y) {

*/
function getBackgroundPoint(vm, size, alignment) {
function getBackgroundPoint(vm, size, alignment, chart) {
// Background Position

@@ -358,2 +358,8 @@ var x = vm.x;

x -= (size.width / 2);
if (x + size.width > chart.width) {
x = chart.width - size.width;
}
if (x < 0) {
x = 0;
}
}

@@ -551,3 +557,3 @@

// Final Size and Position
backgroundPoint = getBackgroundPoint(model, tooltipSize, alignment);
backgroundPoint = getBackgroundPoint(model, tooltipSize, alignment, me._chart);
} else {

@@ -624,3 +630,3 @@ model.opacity = 0;

} else {
x2 = ptX + (width / 2);
x2 = vm.caretX;
x1 = x2 - caretSize;

@@ -854,21 +860,15 @@ x3 = x2 + caretSize;

// If tooltip didn't change, do not handle the target event
if (!changed) {
return false;
}
// Only handle target event on tooltip change
if (changed) {
me._lastActive = me._active;
me._lastActive = me._active;
if (options.enabled || options.custom) {
me._eventPosition = {
x: e.x,
y: e.y
};
if (options.enabled || options.custom) {
me._eventPosition = {
x: e.x,
y: e.y
};
var model = me._model;
me.update(true);
me.pivot();
// See if our tooltip position changed
changed |= (model.x !== me._model.x) || (model.y !== me._model.y);
me.update(true);
me.pivot();
}
}

@@ -875,0 +875,0 @@

@@ -27,3 +27,3 @@ 'use strict';

var vm = this._view;
return vm ? (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false;
return vm ? (Math.abs(mouseX - vm.x) < vm.radius + vm.hitRadius) : false;
}

@@ -33,3 +33,3 @@

var vm = this._view;
return vm ? (Math.pow(mouseY - vm.y, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false;
return vm ? (Math.abs(mouseY - vm.y) < vm.radius + vm.hitRadius) : false;
}

@@ -36,0 +36,0 @@

@@ -21,302 +21,299 @@ /**

module.exports = function() {
var mappers = {
dataset: function(source) {
var index = source.fill;
var chart = source.chart;
var meta = chart.getDatasetMeta(index);
var visible = meta && chart.isDatasetVisible(index);
var points = (visible && meta.dataset._children) || [];
var length = points.length || 0;
var mappers = {
dataset: function(source) {
var index = source.fill;
var chart = source.chart;
var meta = chart.getDatasetMeta(index);
var visible = meta && chart.isDatasetVisible(index);
var points = (visible && meta.dataset._children) || [];
var length = points.length || 0;
return !length ? null : function(point, i) {
return (i < length && points[i]._view) || null;
};
},
return !length ? null : function(point, i) {
return (i < length && points[i]._view) || null;
boundary: function(source) {
var boundary = source.boundary;
var x = boundary ? boundary.x : null;
var y = boundary ? boundary.y : null;
return function(point) {
return {
x: x === null ? point.x : x,
y: y === null ? point.y : y,
};
},
};
}
};
boundary: function(source) {
var boundary = source.boundary;
var x = boundary ? boundary.x : null;
var y = boundary ? boundary.y : null;
// @todo if (fill[0] === '#')
function decodeFill(el, index, count) {
var model = el._model || {};
var fill = model.fill;
var target;
return function(point) {
return {
x: x === null ? point.x : x,
y: y === null ? point.y : y,
};
};
}
};
if (fill === undefined) {
fill = !!model.backgroundColor;
}
// @todo if (fill[0] === '#')
function decodeFill(el, index, count) {
var model = el._model || {};
var fill = model.fill;
var target;
if (fill === false || fill === null) {
return false;
}
if (fill === undefined) {
fill = !!model.backgroundColor;
if (fill === true) {
return 'origin';
}
target = parseFloat(fill, 10);
if (isFinite(target) && Math.floor(target) === target) {
if (fill[0] === '-' || fill[0] === '+') {
target = index + target;
}
if (fill === false || fill === null) {
if (target === index || target < 0 || target >= count) {
return false;
}
if (fill === true) {
return 'origin';
}
return target;
}
target = parseFloat(fill, 10);
if (isFinite(target) && Math.floor(target) === target) {
if (fill[0] === '-' || fill[0] === '+') {
target = index + target;
}
switch (fill) {
// compatibility
case 'bottom':
return 'start';
case 'top':
return 'end';
case 'zero':
return 'origin';
// supported boundaries
case 'origin':
case 'start':
case 'end':
return fill;
// invalid fill values
default:
return false;
}
}
if (target === index || target < 0 || target >= count) {
return false;
}
function computeBoundary(source) {
var model = source.el._model || {};
var scale = source.el._scale || {};
var fill = source.fill;
var target = null;
var horizontal;
if (isFinite(fill)) {
return null;
}
// Backward compatibility: until v3, we still need to support boundary values set on
// the model (scaleTop, scaleBottom and scaleZero) because some external plugins and
// controllers might still use it (e.g. the Smith chart).
if (fill === 'start') {
target = model.scaleBottom === undefined ? scale.bottom : model.scaleBottom;
} else if (fill === 'end') {
target = model.scaleTop === undefined ? scale.top : model.scaleTop;
} else if (model.scaleZero !== undefined) {
target = model.scaleZero;
} else if (scale.getBasePosition) {
target = scale.getBasePosition();
} else if (scale.getBasePixel) {
target = scale.getBasePixel();
}
if (target !== undefined && target !== null) {
if (target.x !== undefined && target.y !== undefined) {
return target;
}
switch (fill) {
// compatibility
case 'bottom':
return 'start';
case 'top':
return 'end';
case 'zero':
return 'origin';
// supported boundaries
case 'origin':
case 'start':
case 'end':
return fill;
// invalid fill values
default:
return false;
if (typeof target === 'number' && isFinite(target)) {
horizontal = scale.isHorizontal();
return {
x: horizontal ? target : null,
y: horizontal ? null : target
};
}
}
function computeBoundary(source) {
var model = source.el._model || {};
var scale = source.el._scale || {};
var fill = source.fill;
var target = null;
var horizontal;
return null;
}
if (isFinite(fill)) {
return null;
}
function resolveTarget(sources, index, propagate) {
var source = sources[index];
var fill = source.fill;
var visited = [index];
var target;
// Backward compatibility: until v3, we still need to support boundary values set on
// the model (scaleTop, scaleBottom and scaleZero) because some external plugins and
// controllers might still use it (e.g. the Smith chart).
if (!propagate) {
return fill;
}
if (fill === 'start') {
target = model.scaleBottom === undefined ? scale.bottom : model.scaleBottom;
} else if (fill === 'end') {
target = model.scaleTop === undefined ? scale.top : model.scaleTop;
} else if (model.scaleZero !== undefined) {
target = model.scaleZero;
} else if (scale.getBasePosition) {
target = scale.getBasePosition();
} else if (scale.getBasePixel) {
target = scale.getBasePixel();
while (fill !== false && visited.indexOf(fill) === -1) {
if (!isFinite(fill)) {
return fill;
}
if (target !== undefined && target !== null) {
if (target.x !== undefined && target.y !== undefined) {
return target;
}
if (typeof target === 'number' && isFinite(target)) {
horizontal = scale.isHorizontal();
return {
x: horizontal ? target : null,
y: horizontal ? null : target
};
}
target = sources[fill];
if (!target) {
return false;
}
return null;
}
function resolveTarget(sources, index, propagate) {
var source = sources[index];
var fill = source.fill;
var visited = [index];
var target;
if (!propagate) {
if (target.visible) {
return fill;
}
while (fill !== false && visited.indexOf(fill) === -1) {
if (!isFinite(fill)) {
return fill;
}
visited.push(fill);
fill = target.fill;
}
target = sources[fill];
if (!target) {
return false;
}
return false;
}
if (target.visible) {
return fill;
}
function createMapper(source) {
var fill = source.fill;
var type = 'dataset';
visited.push(fill);
fill = target.fill;
}
if (fill === false) {
return null;
}
return false;
if (!isFinite(fill)) {
type = 'boundary';
}
function createMapper(source) {
var fill = source.fill;
var type = 'dataset';
return mappers[type](source);
}
if (fill === false) {
return null;
}
function isDrawable(point) {
return point && !point.skip;
}
if (!isFinite(fill)) {
type = 'boundary';
}
function drawArea(ctx, curve0, curve1, len0, len1) {
var i;
return mappers[type](source);
if (!len0 || !len1) {
return;
}
function isDrawable(point) {
return point && !point.skip;
// building first area curve (normal)
ctx.moveTo(curve0[0].x, curve0[0].y);
for (i = 1; i < len0; ++i) {
helpers.canvas.lineTo(ctx, curve0[i - 1], curve0[i]);
}
function drawArea(ctx, curve0, curve1, len0, len1) {
var i;
// joining the two area curves
ctx.lineTo(curve1[len1 - 1].x, curve1[len1 - 1].y);
if (!len0 || !len1) {
return;
}
// building first area curve (normal)
ctx.moveTo(curve0[0].x, curve0[0].y);
for (i = 1; i < len0; ++i) {
helpers.canvas.lineTo(ctx, curve0[i - 1], curve0[i]);
}
// joining the two area curves
ctx.lineTo(curve1[len1 - 1].x, curve1[len1 - 1].y);
// building opposite area curve (reverse)
for (i = len1 - 1; i > 0; --i) {
helpers.canvas.lineTo(ctx, curve1[i], curve1[i - 1], true);
}
// building opposite area curve (reverse)
for (i = len1 - 1; i > 0; --i) {
helpers.canvas.lineTo(ctx, curve1[i], curve1[i - 1], true);
}
}
function doFill(ctx, points, mapper, view, color, loop) {
var count = points.length;
var span = view.spanGaps;
var curve0 = [];
var curve1 = [];
var len0 = 0;
var len1 = 0;
var i, ilen, index, p0, p1, d0, d1;
function doFill(ctx, points, mapper, view, color, loop) {
var count = points.length;
var span = view.spanGaps;
var curve0 = [];
var curve1 = [];
var len0 = 0;
var len1 = 0;
var i, ilen, index, p0, p1, d0, d1;
ctx.beginPath();
ctx.beginPath();
for (i = 0, ilen = (count + !!loop); i < ilen; ++i) {
index = i % count;
p0 = points[index]._view;
p1 = mapper(p0, index, view);
d0 = isDrawable(p0);
d1 = isDrawable(p1);
for (i = 0, ilen = (count + !!loop); i < ilen; ++i) {
index = i % count;
p0 = points[index]._view;
p1 = mapper(p0, index, view);
d0 = isDrawable(p0);
d1 = isDrawable(p1);
if (d0 && d1) {
len0 = curve0.push(p0);
len1 = curve1.push(p1);
} else if (len0 && len1) {
if (!span) {
drawArea(ctx, curve0, curve1, len0, len1);
len0 = len1 = 0;
curve0 = [];
curve1 = [];
} else {
if (d0) {
curve0.push(p0);
}
if (d1) {
curve1.push(p1);
}
if (d0 && d1) {
len0 = curve0.push(p0);
len1 = curve1.push(p1);
} else if (len0 && len1) {
if (!span) {
drawArea(ctx, curve0, curve1, len0, len1);
len0 = len1 = 0;
curve0 = [];
curve1 = [];
} else {
if (d0) {
curve0.push(p0);
}
if (d1) {
curve1.push(p1);
}
}
}
}
drawArea(ctx, curve0, curve1, len0, len1);
drawArea(ctx, curve0, curve1, len0, len1);
ctx.closePath();
ctx.fillStyle = color;
ctx.fill();
}
ctx.closePath();
ctx.fillStyle = color;
ctx.fill();
}
return {
id: 'filler',
module.exports = {
id: 'filler',
afterDatasetsUpdate: function(chart, options) {
var count = (chart.data.datasets || []).length;
var propagate = options.propagate;
var sources = [];
var meta, i, el, source;
afterDatasetsUpdate: function(chart, options) {
var count = (chart.data.datasets || []).length;
var propagate = options.propagate;
var sources = [];
var meta, i, el, source;
for (i = 0; i < count; ++i) {
meta = chart.getDatasetMeta(i);
el = meta.dataset;
source = null;
for (i = 0; i < count; ++i) {
meta = chart.getDatasetMeta(i);
el = meta.dataset;
source = null;
if (el && el._model && el instanceof elements.Line) {
source = {
visible: chart.isDatasetVisible(i),
fill: decodeFill(el, i, count),
chart: chart,
el: el
};
}
meta.$filler = source;
sources.push(source);
if (el && el._model && el instanceof elements.Line) {
source = {
visible: chart.isDatasetVisible(i),
fill: decodeFill(el, i, count),
chart: chart,
el: el
};
}
for (i = 0; i < count; ++i) {
source = sources[i];
if (!source) {
continue;
}
meta.$filler = source;
sources.push(source);
}
source.fill = resolveTarget(sources, i, propagate);
source.boundary = computeBoundary(source);
source.mapper = createMapper(source);
for (i = 0; i < count; ++i) {
source = sources[i];
if (!source) {
continue;
}
},
beforeDatasetDraw: function(chart, args) {
var meta = args.meta.$filler;
if (!meta) {
return;
}
source.fill = resolveTarget(sources, i, propagate);
source.boundary = computeBoundary(source);
source.mapper = createMapper(source);
}
},
var ctx = chart.ctx;
var el = meta.el;
var view = el._view;
var points = el._children || [];
var mapper = meta.mapper;
var color = view.backgroundColor || defaults.global.defaultColor;
beforeDatasetDraw: function(chart, args) {
var meta = args.meta.$filler;
if (!meta) {
return;
}
if (mapper && color && points.length) {
helpers.canvas.clipArea(ctx, chart.chartArea);
doFill(ctx, points, mapper, view, color, el._loop);
helpers.canvas.unclipArea(ctx);
}
var ctx = chart.ctx;
var el = meta.el;
var view = el._view;
var points = el._children || [];
var mapper = meta.mapper;
var color = view.backgroundColor || defaults.global.defaultColor;
if (mapper && color && points.length) {
helpers.canvas.clipArea(ctx, chart.chartArea);
doFill(ctx, points, mapper, view, color, el._loop);
helpers.canvas.unclipArea(ctx);
}
};
}
};

@@ -6,3 +6,6 @@ 'use strict';

var helpers = require('../helpers/index');
var layouts = require('../core/core.layouts');
var noop = helpers.noop;
defaults._set('global', {

@@ -83,487 +86,493 @@ legend: {

module.exports = function(Chart) {
/**
* Helper function to get the box width based on the usePointStyle option
* @param labelopts {Object} the label options on the legend
* @param fontSize {Number} the label font size
* @return {Number} width of the color box area
*/
function getBoxWidth(labelOpts, fontSize) {
return labelOpts.usePointStyle ?
fontSize * Math.SQRT2 :
labelOpts.boxWidth;
}
var layout = Chart.layoutService;
var noop = helpers.noop;
/**
* IMPORTANT: this class is exposed publicly as Chart.Legend, backward compatibility required!
*/
var Legend = Element.extend({
/**
* Helper function to get the box width based on the usePointStyle option
* @param labelopts {Object} the label options on the legend
* @param fontSize {Number} the label font size
* @return {Number} width of the color box area
*/
function getBoxWidth(labelOpts, fontSize) {
return labelOpts.usePointStyle ?
fontSize * Math.SQRT2 :
labelOpts.boxWidth;
}
initialize: function(config) {
helpers.extend(this, config);
Chart.Legend = Element.extend({
// Contains hit boxes for each dataset (in dataset order)
this.legendHitBoxes = [];
initialize: function(config) {
helpers.extend(this, config);
// Are we in doughnut mode which has a different data type
this.doughnutMode = false;
},
// Contains hit boxes for each dataset (in dataset order)
this.legendHitBoxes = [];
// These methods are ordered by lifecycle. Utilities then follow.
// Any function defined here is inherited by all legend types.
// Any function can be extended by the legend type
// Are we in doughnut mode which has a different data type
this.doughnutMode = false;
},
beforeUpdate: noop,
update: function(maxWidth, maxHeight, margins) {
var me = this;
// These methods are ordered by lifecycle. Utilities then follow.
// Any function defined here is inherited by all legend types.
// Any function can be extended by the legend type
// Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
me.beforeUpdate();
beforeUpdate: noop,
update: function(maxWidth, maxHeight, margins) {
var me = this;
// Absorb the master measurements
me.maxWidth = maxWidth;
me.maxHeight = maxHeight;
me.margins = margins;
// Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
me.beforeUpdate();
// Dimensions
me.beforeSetDimensions();
me.setDimensions();
me.afterSetDimensions();
// Labels
me.beforeBuildLabels();
me.buildLabels();
me.afterBuildLabels();
// Absorb the master measurements
me.maxWidth = maxWidth;
me.maxHeight = maxHeight;
me.margins = margins;
// Fit
me.beforeFit();
me.fit();
me.afterFit();
//
me.afterUpdate();
// Dimensions
me.beforeSetDimensions();
me.setDimensions();
me.afterSetDimensions();
// Labels
me.beforeBuildLabels();
me.buildLabels();
me.afterBuildLabels();
return me.minSize;
},
afterUpdate: noop,
// Fit
me.beforeFit();
me.fit();
me.afterFit();
//
me.afterUpdate();
//
return me.minSize;
},
afterUpdate: noop,
beforeSetDimensions: noop,
setDimensions: function() {
var me = this;
// Set the unconstrained dimension before label rotation
if (me.isHorizontal()) {
// Reset position before calculating rotation
me.width = me.maxWidth;
me.left = 0;
me.right = me.width;
} else {
me.height = me.maxHeight;
//
// Reset position before calculating rotation
me.top = 0;
me.bottom = me.height;
}
beforeSetDimensions: noop,
setDimensions: function() {
var me = this;
// Set the unconstrained dimension before label rotation
if (me.isHorizontal()) {
// Reset position before calculating rotation
me.width = me.maxWidth;
me.left = 0;
me.right = me.width;
} else {
me.height = me.maxHeight;
// Reset padding
me.paddingLeft = 0;
me.paddingTop = 0;
me.paddingRight = 0;
me.paddingBottom = 0;
// Reset position before calculating rotation
me.top = 0;
me.bottom = me.height;
}
// Reset minSize
me.minSize = {
width: 0,
height: 0
};
},
afterSetDimensions: noop,
// Reset padding
me.paddingLeft = 0;
me.paddingTop = 0;
me.paddingRight = 0;
me.paddingBottom = 0;
//
// Reset minSize
me.minSize = {
width: 0,
height: 0
};
},
afterSetDimensions: noop,
beforeBuildLabels: noop,
buildLabels: function() {
var me = this;
var labelOpts = me.options.labels || {};
var legendItems = helpers.callback(labelOpts.generateLabels, [me.chart], me) || [];
//
if (labelOpts.filter) {
legendItems = legendItems.filter(function(item) {
return labelOpts.filter(item, me.chart.data);
});
}
beforeBuildLabels: noop,
buildLabels: function() {
var me = this;
var labelOpts = me.options.labels || {};
var legendItems = helpers.callback(labelOpts.generateLabels, [me.chart], me) || [];
if (me.options.reverse) {
legendItems.reverse();
}
if (labelOpts.filter) {
legendItems = legendItems.filter(function(item) {
return labelOpts.filter(item, me.chart.data);
});
}
me.legendItems = legendItems;
},
afterBuildLabels: noop,
if (me.options.reverse) {
legendItems.reverse();
}
//
me.legendItems = legendItems;
},
afterBuildLabels: noop,
beforeFit: noop,
fit: function() {
var me = this;
var opts = me.options;
var labelOpts = opts.labels;
var display = opts.display;
//
var ctx = me.ctx;
beforeFit: noop,
fit: function() {
var me = this;
var opts = me.options;
var labelOpts = opts.labels;
var display = opts.display;
var globalDefault = defaults.global;
var valueOrDefault = helpers.valueOrDefault;
var fontSize = valueOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize);
var fontStyle = valueOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle);
var fontFamily = valueOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily);
var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily);
var ctx = me.ctx;
// Reset hit boxes
var hitboxes = me.legendHitBoxes = [];
var globalDefault = defaults.global;
var valueOrDefault = helpers.valueOrDefault;
var fontSize = valueOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize);
var fontStyle = valueOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle);
var fontFamily = valueOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily);
var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily);
var minSize = me.minSize;
var isHorizontal = me.isHorizontal();
// Reset hit boxes
var hitboxes = me.legendHitBoxes = [];
if (isHorizontal) {
minSize.width = me.maxWidth; // fill all the width
minSize.height = display ? 10 : 0;
} else {
minSize.width = display ? 10 : 0;
minSize.height = me.maxHeight; // fill all the height
}
var minSize = me.minSize;
var isHorizontal = me.isHorizontal();
// Increase sizes here
if (display) {
ctx.font = labelFont;
if (isHorizontal) {
minSize.width = me.maxWidth; // fill all the width
minSize.height = display ? 10 : 0;
} else {
minSize.width = display ? 10 : 0;
minSize.height = me.maxHeight; // fill all the height
}
// Labels
// Increase sizes here
if (display) {
ctx.font = labelFont;
// Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one
var lineWidths = me.lineWidths = [0];
var totalHeight = me.legendItems.length ? fontSize + (labelOpts.padding) : 0;
if (isHorizontal) {
// Labels
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
// Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one
var lineWidths = me.lineWidths = [0];
var totalHeight = me.legendItems.length ? fontSize + (labelOpts.padding) : 0;
helpers.each(me.legendItems, function(legendItem, i) {
var boxWidth = getBoxWidth(labelOpts, fontSize);
var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
if (lineWidths[lineWidths.length - 1] + width + labelOpts.padding >= me.width) {
totalHeight += fontSize + (labelOpts.padding);
lineWidths[lineWidths.length] = me.left;
}
helpers.each(me.legendItems, function(legendItem, i) {
var boxWidth = getBoxWidth(labelOpts, fontSize);
var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
// Store the hitbox width and height here. Final position will be updated in `draw`
hitboxes[i] = {
left: 0,
top: 0,
width: width,
height: fontSize
};
if (lineWidths[lineWidths.length - 1] + width + labelOpts.padding >= me.width) {
totalHeight += fontSize + (labelOpts.padding);
lineWidths[lineWidths.length] = me.left;
}
lineWidths[lineWidths.length - 1] += width + labelOpts.padding;
});
// Store the hitbox width and height here. Final position will be updated in `draw`
hitboxes[i] = {
left: 0,
top: 0,
width: width,
height: fontSize
};
minSize.height += totalHeight;
lineWidths[lineWidths.length - 1] += width + labelOpts.padding;
});
} else {
var vPadding = labelOpts.padding;
var columnWidths = me.columnWidths = [];
var totalWidth = labelOpts.padding;
var currentColWidth = 0;
var currentColHeight = 0;
var itemHeight = fontSize + vPadding;
minSize.height += totalHeight;
helpers.each(me.legendItems, function(legendItem, i) {
var boxWidth = getBoxWidth(labelOpts, fontSize);
var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
} else {
var vPadding = labelOpts.padding;
var columnWidths = me.columnWidths = [];
var totalWidth = labelOpts.padding;
var currentColWidth = 0;
var currentColHeight = 0;
var itemHeight = fontSize + vPadding;
// If too tall, go to new column
if (currentColHeight + itemHeight > minSize.height) {
totalWidth += currentColWidth + labelOpts.padding;
columnWidths.push(currentColWidth); // previous column width
helpers.each(me.legendItems, function(legendItem, i) {
var boxWidth = getBoxWidth(labelOpts, fontSize);
var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
currentColWidth = 0;
currentColHeight = 0;
}
// If too tall, go to new column
if (currentColHeight + itemHeight > minSize.height) {
totalWidth += currentColWidth + labelOpts.padding;
columnWidths.push(currentColWidth); // previous column width
// Get max width
currentColWidth = Math.max(currentColWidth, itemWidth);
currentColHeight += itemHeight;
currentColWidth = 0;
currentColHeight = 0;
}
// Store the hitbox width and height here. Final position will be updated in `draw`
hitboxes[i] = {
left: 0,
top: 0,
width: itemWidth,
height: fontSize
};
});
// Get max width
currentColWidth = Math.max(currentColWidth, itemWidth);
currentColHeight += itemHeight;
totalWidth += currentColWidth;
columnWidths.push(currentColWidth);
minSize.width += totalWidth;
}
}
// Store the hitbox width and height here. Final position will be updated in `draw`
hitboxes[i] = {
left: 0,
top: 0,
width: itemWidth,
height: fontSize
};
});
me.width = minSize.width;
me.height = minSize.height;
},
afterFit: noop,
totalWidth += currentColWidth;
columnWidths.push(currentColWidth);
minSize.width += totalWidth;
}
}
// Shared Methods
isHorizontal: function() {
return this.options.position === 'top' || this.options.position === 'bottom';
},
me.width = minSize.width;
me.height = minSize.height;
},
afterFit: noop,
// Actually draw the legend on the canvas
draw: function() {
var me = this;
var opts = me.options;
var labelOpts = opts.labels;
var globalDefault = defaults.global;
var lineDefault = globalDefault.elements.line;
var legendWidth = me.width;
var lineWidths = me.lineWidths;
// Shared Methods
isHorizontal: function() {
return this.options.position === 'top' || this.options.position === 'bottom';
},
if (opts.display) {
var ctx = me.ctx;
var valueOrDefault = helpers.valueOrDefault;
var fontColor = valueOrDefault(labelOpts.fontColor, globalDefault.defaultFontColor);
var fontSize = valueOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize);
var fontStyle = valueOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle);
var fontFamily = valueOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily);
var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily);
var cursor;
// Actually draw the legend on the canvas
draw: function() {
var me = this;
var opts = me.options;
var labelOpts = opts.labels;
var globalDefault = defaults.global;
var lineDefault = globalDefault.elements.line;
var legendWidth = me.width;
var lineWidths = me.lineWidths;
// Canvas setup
ctx.textAlign = 'left';
ctx.textBaseline = 'middle';
ctx.lineWidth = 0.5;
ctx.strokeStyle = fontColor; // for strikethrough effect
ctx.fillStyle = fontColor; // render in correct colour
ctx.font = labelFont;
if (opts.display) {
var ctx = me.ctx;
var valueOrDefault = helpers.valueOrDefault;
var fontColor = valueOrDefault(labelOpts.fontColor, globalDefault.defaultFontColor);
var fontSize = valueOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize);
var fontStyle = valueOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle);
var fontFamily = valueOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily);
var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily);
var cursor;
var boxWidth = getBoxWidth(labelOpts, fontSize);
var hitboxes = me.legendHitBoxes;
// Canvas setup
ctx.textAlign = 'left';
ctx.textBaseline = 'middle';
ctx.lineWidth = 0.5;
ctx.strokeStyle = fontColor; // for strikethrough effect
ctx.fillStyle = fontColor; // render in correct colour
ctx.font = labelFont;
// current position
var drawLegendBox = function(x, y, legendItem) {
if (isNaN(boxWidth) || boxWidth <= 0) {
return;
}
var boxWidth = getBoxWidth(labelOpts, fontSize);
var hitboxes = me.legendHitBoxes;
// Set the ctx for the box
ctx.save();
// current position
var drawLegendBox = function(x, y, legendItem) {
if (isNaN(boxWidth) || boxWidth <= 0) {
return;
}
ctx.fillStyle = valueOrDefault(legendItem.fillStyle, globalDefault.defaultColor);
ctx.lineCap = valueOrDefault(legendItem.lineCap, lineDefault.borderCapStyle);
ctx.lineDashOffset = valueOrDefault(legendItem.lineDashOffset, lineDefault.borderDashOffset);
ctx.lineJoin = valueOrDefault(legendItem.lineJoin, lineDefault.borderJoinStyle);
ctx.lineWidth = valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth);
ctx.strokeStyle = valueOrDefault(legendItem.strokeStyle, globalDefault.defaultColor);
var isLineWidthZero = (valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth) === 0);
// Set the ctx for the box
ctx.save();
if (ctx.setLineDash) {
// IE 9 and 10 do not support line dash
ctx.setLineDash(valueOrDefault(legendItem.lineDash, lineDefault.borderDash));
}
ctx.fillStyle = valueOrDefault(legendItem.fillStyle, globalDefault.defaultColor);
ctx.lineCap = valueOrDefault(legendItem.lineCap, lineDefault.borderCapStyle);
ctx.lineDashOffset = valueOrDefault(legendItem.lineDashOffset, lineDefault.borderDashOffset);
ctx.lineJoin = valueOrDefault(legendItem.lineJoin, lineDefault.borderJoinStyle);
ctx.lineWidth = valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth);
ctx.strokeStyle = valueOrDefault(legendItem.strokeStyle, globalDefault.defaultColor);
var isLineWidthZero = (valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth) === 0);
if (opts.labels && opts.labels.usePointStyle) {
// Recalculate x and y for drawPoint() because its expecting
// x and y to be center of figure (instead of top left)
var radius = fontSize * Math.SQRT2 / 2;
var offSet = radius / Math.SQRT2;
var centerX = x + offSet;
var centerY = y + offSet;
if (ctx.setLineDash) {
// IE 9 and 10 do not support line dash
ctx.setLineDash(valueOrDefault(legendItem.lineDash, lineDefault.borderDash));
// Draw pointStyle as legend symbol
helpers.canvas.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY);
} else {
// Draw box as legend symbol
if (!isLineWidthZero) {
ctx.strokeRect(x, y, boxWidth, fontSize);
}
ctx.fillRect(x, y, boxWidth, fontSize);
}
if (opts.labels && opts.labels.usePointStyle) {
// Recalculate x and y for drawPoint() because its expecting
// x and y to be center of figure (instead of top left)
var radius = fontSize * Math.SQRT2 / 2;
var offSet = radius / Math.SQRT2;
var centerX = x + offSet;
var centerY = y + offSet;
ctx.restore();
};
var fillText = function(x, y, legendItem, textWidth) {
var halfFontSize = fontSize / 2;
var xLeft = boxWidth + halfFontSize + x;
var yMiddle = y + halfFontSize;
// Draw pointStyle as legend symbol
helpers.canvas.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY);
} else {
// Draw box as legend symbol
if (!isLineWidthZero) {
ctx.strokeRect(x, y, boxWidth, fontSize);
}
ctx.fillRect(x, y, boxWidth, fontSize);
}
ctx.fillText(legendItem.text, xLeft, yMiddle);
ctx.restore();
if (legendItem.hidden) {
// Strikethrough the text if hidden
ctx.beginPath();
ctx.lineWidth = 2;
ctx.moveTo(xLeft, yMiddle);
ctx.lineTo(xLeft + textWidth, yMiddle);
ctx.stroke();
}
};
// Horizontal
var isHorizontal = me.isHorizontal();
if (isHorizontal) {
cursor = {
x: me.left + ((legendWidth - lineWidths[0]) / 2),
y: me.top + labelOpts.padding,
line: 0
};
var fillText = function(x, y, legendItem, textWidth) {
var halfFontSize = fontSize / 2;
var xLeft = boxWidth + halfFontSize + x;
var yMiddle = y + halfFontSize;
} else {
cursor = {
x: me.left + labelOpts.padding,
y: me.top + labelOpts.padding,
line: 0
};
}
ctx.fillText(legendItem.text, xLeft, yMiddle);
var itemHeight = fontSize + labelOpts.padding;
helpers.each(me.legendItems, function(legendItem, i) {
var textWidth = ctx.measureText(legendItem.text).width;
var width = boxWidth + (fontSize / 2) + textWidth;
var x = cursor.x;
var y = cursor.y;
if (legendItem.hidden) {
// Strikethrough the text if hidden
ctx.beginPath();
ctx.lineWidth = 2;
ctx.moveTo(xLeft, yMiddle);
ctx.lineTo(xLeft + textWidth, yMiddle);
ctx.stroke();
}
};
// Horizontal
var isHorizontal = me.isHorizontal();
if (isHorizontal) {
cursor = {
x: me.left + ((legendWidth - lineWidths[0]) / 2),
y: me.top + labelOpts.padding,
line: 0
};
} else {
cursor = {
x: me.left + labelOpts.padding,
y: me.top + labelOpts.padding,
line: 0
};
}
var itemHeight = fontSize + labelOpts.padding;
helpers.each(me.legendItems, function(legendItem, i) {
var textWidth = ctx.measureText(legendItem.text).width;
var width = boxWidth + (fontSize / 2) + textWidth;
var x = cursor.x;
var y = cursor.y;
if (isHorizontal) {
if (x + width >= legendWidth) {
y = cursor.y += itemHeight;
cursor.line++;
x = cursor.x = me.left + ((legendWidth - lineWidths[cursor.line]) / 2);
}
} else if (y + itemHeight > me.bottom) {
x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding;
y = cursor.y = me.top + labelOpts.padding;
if (x + width >= legendWidth) {
y = cursor.y += itemHeight;
cursor.line++;
x = cursor.x = me.left + ((legendWidth - lineWidths[cursor.line]) / 2);
}
} else if (y + itemHeight > me.bottom) {
x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding;
y = cursor.y = me.top + labelOpts.padding;
cursor.line++;
}
drawLegendBox(x, y, legendItem);
drawLegendBox(x, y, legendItem);
hitboxes[i].left = x;
hitboxes[i].top = y;
hitboxes[i].left = x;
hitboxes[i].top = y;
// Fill the actual label
fillText(x, y, legendItem, textWidth);
// Fill the actual label
fillText(x, y, legendItem, textWidth);
if (isHorizontal) {
cursor.x += width + (labelOpts.padding);
} else {
cursor.y += itemHeight;
}
if (isHorizontal) {
cursor.x += width + (labelOpts.padding);
} else {
cursor.y += itemHeight;
}
});
}
},
});
}
},
/**
* Handle an event
* @private
* @param {IEvent} event - The event to handle
* @return {Boolean} true if a change occured
*/
handleEvent: function(e) {
var me = this;
var opts = me.options;
var type = e.type === 'mouseup' ? 'click' : e.type;
var changed = false;
/**
* Handle an event
* @private
* @param {IEvent} event - The event to handle
* @return {Boolean} true if a change occured
*/
handleEvent: function(e) {
var me = this;
var opts = me.options;
var type = e.type === 'mouseup' ? 'click' : e.type;
var changed = false;
if (type === 'mousemove') {
if (!opts.onHover) {
return;
}
} else if (type === 'click') {
if (!opts.onClick) {
return;
}
} else {
if (type === 'mousemove') {
if (!opts.onHover) {
return;
}
} else if (type === 'click') {
if (!opts.onClick) {
return;
}
} else {
return;
}
// Chart event already has relative position in it
var x = e.x;
var y = e.y;
// Chart event already has relative position in it
var x = e.x;
var y = e.y;
if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) {
// See if we are touching one of the dataset boxes
var lh = me.legendHitBoxes;
for (var i = 0; i < lh.length; ++i) {
var hitBox = lh[i];
if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) {
// See if we are touching one of the dataset boxes
var lh = me.legendHitBoxes;
for (var i = 0; i < lh.length; ++i) {
var hitBox = lh[i];
if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) {
// Touching an element
if (type === 'click') {
// use e.native for backwards compatibility
opts.onClick.call(me, e.native, me.legendItems[i]);
changed = true;
break;
} else if (type === 'mousemove') {
// use e.native for backwards compatibility
opts.onHover.call(me, e.native, me.legendItems[i]);
changed = true;
break;
}
if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) {
// Touching an element
if (type === 'click') {
// use e.native for backwards compatibility
opts.onClick.call(me, e.native, me.legendItems[i]);
changed = true;
break;
} else if (type === 'mousemove') {
// use e.native for backwards compatibility
opts.onHover.call(me, e.native, me.legendItems[i]);
changed = true;
break;
}
}
}
}
return changed;
}
return changed;
}
});
function createNewLegendAndAttach(chart, legendOpts) {
var legend = new Legend({
ctx: chart.ctx,
options: legendOpts,
chart: chart
});
function createNewLegendAndAttach(chart, legendOpts) {
var legend = new Chart.Legend({
ctx: chart.ctx,
options: legendOpts,
chart: chart
});
layouts.configure(chart, legend, legendOpts);
layouts.addBox(chart, legend);
chart.legend = legend;
}
layout.configure(chart, legend, legendOpts);
layout.addBox(chart, legend);
chart.legend = legend;
}
module.exports = {
id: 'legend',
return {
id: 'legend',
/**
* Backward compatibility: since 2.1.5, the legend is registered as a plugin, making
* Chart.Legend obsolete. To avoid a breaking change, we export the Legend as part of
* the plugin, which one will be re-exposed in the chart.js file.
* https://github.com/chartjs/Chart.js/pull/2640
* @private
*/
_element: Legend,
beforeInit: function(chart) {
var legendOpts = chart.options.legend;
beforeInit: function(chart) {
var legendOpts = chart.options.legend;
if (legendOpts) {
createNewLegendAndAttach(chart, legendOpts);
}
},
if (legendOpts) {
createNewLegendAndAttach(chart, legendOpts);
}
},
beforeUpdate: function(chart) {
var legendOpts = chart.options.legend;
var legend = chart.legend;
beforeUpdate: function(chart) {
var legendOpts = chart.options.legend;
var legend = chart.legend;
if (legendOpts) {
helpers.mergeIf(legendOpts, defaults.global.legend);
if (legendOpts) {
helpers.mergeIf(legendOpts, defaults.global.legend);
if (legend) {
layout.configure(chart, legend, legendOpts);
legend.options = legendOpts;
} else {
createNewLegendAndAttach(chart, legendOpts);
}
} else if (legend) {
layout.removeBox(chart, legend);
delete chart.legend;
}
},
afterEvent: function(chart, e) {
var legend = chart.legend;
if (legend) {
legend.handleEvent(e);
layouts.configure(chart, legend, legendOpts);
legend.options = legendOpts;
} else {
createNewLegendAndAttach(chart, legendOpts);
}
} else if (legend) {
layouts.removeBox(chart, legend);
delete chart.legend;
}
};
},
afterEvent: function(chart, e) {
var legend = chart.legend;
if (legend) {
legend.handleEvent(e);
}
}
};

@@ -6,3 +6,6 @@ 'use strict';

var helpers = require('../helpers/index');
var layouts = require('../core/core.layouts');
var noop = helpers.noop;
defaults._set('global', {

@@ -21,225 +24,231 @@ title: {

module.exports = function(Chart) {
/**
* IMPORTANT: this class is exposed publicly as Chart.Legend, backward compatibility required!
*/
var Title = Element.extend({
initialize: function(config) {
var me = this;
helpers.extend(me, config);
var layout = Chart.layoutService;
var noop = helpers.noop;
// Contains hit boxes for each dataset (in dataset order)
me.legendHitBoxes = [];
},
Chart.Title = Element.extend({
initialize: function(config) {
var me = this;
helpers.extend(me, config);
// These methods are ordered by lifecycle. Utilities then follow.
// Contains hit boxes for each dataset (in dataset order)
me.legendHitBoxes = [];
},
beforeUpdate: noop,
update: function(maxWidth, maxHeight, margins) {
var me = this;
// These methods are ordered by lifecycle. Utilities then follow.
// Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
me.beforeUpdate();
beforeUpdate: noop,
update: function(maxWidth, maxHeight, margins) {
var me = this;
// Absorb the master measurements
me.maxWidth = maxWidth;
me.maxHeight = maxHeight;
me.margins = margins;
// Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
me.beforeUpdate();
// Dimensions
me.beforeSetDimensions();
me.setDimensions();
me.afterSetDimensions();
// Labels
me.beforeBuildLabels();
me.buildLabels();
me.afterBuildLabels();
// Absorb the master measurements
me.maxWidth = maxWidth;
me.maxHeight = maxHeight;
me.margins = margins;
// Fit
me.beforeFit();
me.fit();
me.afterFit();
//
me.afterUpdate();
// Dimensions
me.beforeSetDimensions();
me.setDimensions();
me.afterSetDimensions();
// Labels
me.beforeBuildLabels();
me.buildLabels();
me.afterBuildLabels();
return me.minSize;
// Fit
me.beforeFit();
me.fit();
me.afterFit();
//
me.afterUpdate();
},
afterUpdate: noop,
return me.minSize;
//
},
afterUpdate: noop,
beforeSetDimensions: noop,
setDimensions: function() {
var me = this;
// Set the unconstrained dimension before label rotation
if (me.isHorizontal()) {
// Reset position before calculating rotation
me.width = me.maxWidth;
me.left = 0;
me.right = me.width;
} else {
me.height = me.maxHeight;
//
// Reset position before calculating rotation
me.top = 0;
me.bottom = me.height;
}
beforeSetDimensions: noop,
setDimensions: function() {
var me = this;
// Set the unconstrained dimension before label rotation
if (me.isHorizontal()) {
// Reset position before calculating rotation
me.width = me.maxWidth;
me.left = 0;
me.right = me.width;
} else {
me.height = me.maxHeight;
// Reset padding
me.paddingLeft = 0;
me.paddingTop = 0;
me.paddingRight = 0;
me.paddingBottom = 0;
// Reset position before calculating rotation
me.top = 0;
me.bottom = me.height;
}
// Reset minSize
me.minSize = {
width: 0,
height: 0
};
},
afterSetDimensions: noop,
// Reset padding
me.paddingLeft = 0;
me.paddingTop = 0;
me.paddingRight = 0;
me.paddingBottom = 0;
//
// Reset minSize
me.minSize = {
width: 0,
height: 0
};
},
afterSetDimensions: noop,
beforeBuildLabels: noop,
buildLabels: noop,
afterBuildLabels: noop,
//
//
beforeBuildLabels: noop,
buildLabels: noop,
afterBuildLabels: noop,
beforeFit: noop,
fit: function() {
var me = this;
var valueOrDefault = helpers.valueOrDefault;
var opts = me.options;
var display = opts.display;
var fontSize = valueOrDefault(opts.fontSize, defaults.global.defaultFontSize);
var minSize = me.minSize;
var lineCount = helpers.isArray(opts.text) ? opts.text.length : 1;
var lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize);
var textSize = display ? (lineCount * lineHeight) + (opts.padding * 2) : 0;
//
if (me.isHorizontal()) {
minSize.width = me.maxWidth; // fill all the width
minSize.height = textSize;
} else {
minSize.width = textSize;
minSize.height = me.maxHeight; // fill all the height
}
beforeFit: noop,
fit: function() {
var me = this;
var valueOrDefault = helpers.valueOrDefault;
var opts = me.options;
var display = opts.display;
var fontSize = valueOrDefault(opts.fontSize, defaults.global.defaultFontSize);
var minSize = me.minSize;
var lineCount = helpers.isArray(opts.text) ? opts.text.length : 1;
var lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize);
var textSize = display ? (lineCount * lineHeight) + (opts.padding * 2) : 0;
me.width = minSize.width;
me.height = minSize.height;
if (me.isHorizontal()) {
minSize.width = me.maxWidth; // fill all the width
minSize.height = textSize;
} else {
minSize.width = textSize;
minSize.height = me.maxHeight; // fill all the height
}
},
afterFit: noop,
me.width = minSize.width;
me.height = minSize.height;
// Shared Methods
isHorizontal: function() {
var pos = this.options.position;
return pos === 'top' || pos === 'bottom';
},
},
afterFit: noop,
// Actually draw the title block on the canvas
draw: function() {
var me = this;
var ctx = me.ctx;
var valueOrDefault = helpers.valueOrDefault;
var opts = me.options;
var globalDefaults = defaults.global;
// Shared Methods
isHorizontal: function() {
var pos = this.options.position;
return pos === 'top' || pos === 'bottom';
},
if (opts.display) {
var fontSize = valueOrDefault(opts.fontSize, globalDefaults.defaultFontSize);
var fontStyle = valueOrDefault(opts.fontStyle, globalDefaults.defaultFontStyle);
var fontFamily = valueOrDefault(opts.fontFamily, globalDefaults.defaultFontFamily);
var titleFont = helpers.fontString(fontSize, fontStyle, fontFamily);
var lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize);
var offset = lineHeight / 2 + opts.padding;
var rotation = 0;
var top = me.top;
var left = me.left;
var bottom = me.bottom;
var right = me.right;
var maxWidth, titleX, titleY;
// Actually draw the title block on the canvas
draw: function() {
var me = this;
var ctx = me.ctx;
var valueOrDefault = helpers.valueOrDefault;
var opts = me.options;
var globalDefaults = defaults.global;
ctx.fillStyle = valueOrDefault(opts.fontColor, globalDefaults.defaultFontColor); // render in correct colour
ctx.font = titleFont;
if (opts.display) {
var fontSize = valueOrDefault(opts.fontSize, globalDefaults.defaultFontSize);
var fontStyle = valueOrDefault(opts.fontStyle, globalDefaults.defaultFontStyle);
var fontFamily = valueOrDefault(opts.fontFamily, globalDefaults.defaultFontFamily);
var titleFont = helpers.fontString(fontSize, fontStyle, fontFamily);
var lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize);
var offset = lineHeight / 2 + opts.padding;
var rotation = 0;
var top = me.top;
var left = me.left;
var bottom = me.bottom;
var right = me.right;
var maxWidth, titleX, titleY;
// Horizontal
if (me.isHorizontal()) {
titleX = left + ((right - left) / 2); // midpoint of the width
titleY = top + offset;
maxWidth = right - left;
} else {
titleX = opts.position === 'left' ? left + offset : right - offset;
titleY = top + ((bottom - top) / 2);
maxWidth = bottom - top;
rotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5);
}
ctx.fillStyle = valueOrDefault(opts.fontColor, globalDefaults.defaultFontColor); // render in correct colour
ctx.font = titleFont;
ctx.save();
ctx.translate(titleX, titleY);
ctx.rotate(rotation);
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
// Horizontal
if (me.isHorizontal()) {
titleX = left + ((right - left) / 2); // midpoint of the width
titleY = top + offset;
maxWidth = right - left;
} else {
titleX = opts.position === 'left' ? left + offset : right - offset;
titleY = top + ((bottom - top) / 2);
maxWidth = bottom - top;
rotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5);
var text = opts.text;
if (helpers.isArray(text)) {
var y = 0;
for (var i = 0; i < text.length; ++i) {
ctx.fillText(text[i], 0, y, maxWidth);
y += lineHeight;
}
} else {
ctx.fillText(text, 0, 0, maxWidth);
}
ctx.save();
ctx.translate(titleX, titleY);
ctx.rotate(rotation);
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.restore();
}
}
});
var text = opts.text;
if (helpers.isArray(text)) {
var y = 0;
for (var i = 0; i < text.length; ++i) {
ctx.fillText(text[i], 0, y, maxWidth);
y += lineHeight;
}
} else {
ctx.fillText(text, 0, 0, maxWidth);
}
ctx.restore();
}
}
function createNewTitleBlockAndAttach(chart, titleOpts) {
var title = new Title({
ctx: chart.ctx,
options: titleOpts,
chart: chart
});
function createNewTitleBlockAndAttach(chart, titleOpts) {
var title = new Chart.Title({
ctx: chart.ctx,
options: titleOpts,
chart: chart
});
layouts.configure(chart, title, titleOpts);
layouts.addBox(chart, title);
chart.titleBlock = title;
}
layout.configure(chart, title, titleOpts);
layout.addBox(chart, title);
chart.titleBlock = title;
}
module.exports = {
id: 'title',
return {
id: 'title',
/**
* Backward compatibility: since 2.1.5, the title is registered as a plugin, making
* Chart.Title obsolete. To avoid a breaking change, we export the Title as part of
* the plugin, which one will be re-exposed in the chart.js file.
* https://github.com/chartjs/Chart.js/pull/2640
* @private
*/
_element: Title,
beforeInit: function(chart) {
var titleOpts = chart.options.title;
beforeInit: function(chart) {
var titleOpts = chart.options.title;
if (titleOpts) {
createNewTitleBlockAndAttach(chart, titleOpts);
}
},
if (titleOpts) {
createNewTitleBlockAndAttach(chart, titleOpts);
}
},
beforeUpdate: function(chart) {
var titleOpts = chart.options.title;
var titleBlock = chart.titleBlock;
beforeUpdate: function(chart) {
var titleOpts = chart.options.title;
var titleBlock = chart.titleBlock;
if (titleOpts) {
helpers.mergeIf(titleOpts, defaults.global.title);
if (titleOpts) {
helpers.mergeIf(titleOpts, defaults.global.title);
if (titleBlock) {
layout.configure(chart, titleBlock, titleOpts);
titleBlock.options = titleOpts;
} else {
createNewTitleBlockAndAttach(chart, titleOpts);
}
} else if (titleBlock) {
Chart.layoutService.removeBox(chart, titleBlock);
delete chart.titleBlock;
if (titleBlock) {
layouts.configure(chart, titleBlock, titleOpts);
titleBlock.options = titleOpts;
} else {
createNewTitleBlockAndAttach(chart, titleOpts);
}
} else if (titleBlock) {
layouts.removeBox(chart, titleBlock);
delete chart.titleBlock;
}
};
}
};

@@ -173,7 +173,6 @@ 'use strict';

pixel = me.left + (me.width / range * (rightValue - start));
return Math.round(pixel);
} else {
pixel = me.bottom - (me.height / range * (rightValue - start));
}
pixel = me.bottom - (me.height / range * (rightValue - start));
return Math.round(pixel);
return pixel;
},

@@ -180,0 +179,0 @@ getValueForPixel: function(pixel) {

'use strict';
var helpers = require('../helpers/index');
var Ticks = require('../core/core.ticks');
/**
* Generate a set of linear ticks
* @param generationOptions the options used to generate the ticks
* @param dataRange the range of the data
* @returns {Array<Number>} array of tick values
*/
function generateTicks(generationOptions, dataRange) {
var ticks = [];
// To get a "nice" value for the tick spacing, we will use the appropriately named
// "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks
// for details.
var spacing;
if (generationOptions.stepSize && generationOptions.stepSize > 0) {
spacing = generationOptions.stepSize;
} else {
var niceRange = helpers.niceNum(dataRange.max - dataRange.min, false);
spacing = helpers.niceNum(niceRange / (generationOptions.maxTicks - 1), true);
}
var niceMin = Math.floor(dataRange.min / spacing) * spacing;
var niceMax = Math.ceil(dataRange.max / spacing) * spacing;
// If min, max and stepSize is set and they make an evenly spaced scale use it.
if (generationOptions.min && generationOptions.max && generationOptions.stepSize) {
// If very close to our whole number, use it.
if (helpers.almostWhole((generationOptions.max - generationOptions.min) / generationOptions.stepSize, spacing / 1000)) {
niceMin = generationOptions.min;
niceMax = generationOptions.max;
}
}
var numSpaces = (niceMax - niceMin) / spacing;
// If very close to our rounded value, use it.
if (helpers.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) {
numSpaces = Math.round(numSpaces);
} else {
numSpaces = Math.ceil(numSpaces);
}
var precision = 1;
if (spacing < 1) {
precision = Math.pow(10, spacing.toString().length - 2);
niceMin = Math.round(niceMin * precision) / precision;
niceMax = Math.round(niceMax * precision) / precision;
}
ticks.push(generationOptions.min !== undefined ? generationOptions.min : niceMin);
for (var j = 1; j < numSpaces; ++j) {
ticks.push(Math.round((niceMin + j * spacing) * precision) / precision);
}
ticks.push(generationOptions.max !== undefined ? generationOptions.max : niceMax);
return ticks;
}
module.exports = function(Chart) {

@@ -105,3 +159,3 @@

};
var ticks = me.ticks = Ticks.generators.linear(numericGeneratorOptions, me);
var ticks = me.ticks = generateTicks(numericGeneratorOptions, me);

@@ -108,0 +162,0 @@ me.handleDirectionalChanges();

@@ -6,2 +6,54 @@ 'use strict';

/**
* Generate a set of logarithmic ticks
* @param generationOptions the options used to generate the ticks
* @param dataRange the range of the data
* @returns {Array<Number>} array of tick values
*/
function generateTicks(generationOptions, dataRange) {
var ticks = [];
var valueOrDefault = helpers.valueOrDefault;
// Figure out what the max number of ticks we can support it is based on the size of
// the axis area. For now, we say that the minimum tick spacing in pixels must be 50
// We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
// the graph
var tickVal = valueOrDefault(generationOptions.min, Math.pow(10, Math.floor(helpers.log10(dataRange.min))));
var endExp = Math.floor(helpers.log10(dataRange.max));
var endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp));
var exp, significand;
if (tickVal === 0) {
exp = Math.floor(helpers.log10(dataRange.minNotZero));
significand = Math.floor(dataRange.minNotZero / Math.pow(10, exp));
ticks.push(tickVal);
tickVal = significand * Math.pow(10, exp);
} else {
exp = Math.floor(helpers.log10(tickVal));
significand = Math.floor(tickVal / Math.pow(10, exp));
}
var precision = exp < 0 ? Math.pow(10, Math.abs(exp)) : 1;
do {
ticks.push(tickVal);
++significand;
if (significand === 10) {
significand = 1;
++exp;
precision = exp >= 0 ? 1 : precision;
}
tickVal = Math.round(significand * Math.pow(10, exp) * precision) / precision;
} while (exp < endExp || (exp === endExp && significand < endSignificand));
var lastTick = valueOrDefault(generationOptions.max, tickVal);
ticks.push(lastTick);
return ticks;
}
module.exports = function(Chart) {

@@ -22,7 +74,5 @@

var opts = me.options;
var tickOpts = opts.ticks;
var chart = me.chart;
var data = chart.data;
var datasets = data.datasets;
var valueOrDefault = helpers.valueOrDefault;
var isHorizontal = me.isHorizontal();

@@ -73,14 +123,8 @@ function IDMatches(meta) {

var value = +me.getRightValue(rawValue);
if (isNaN(value) || meta.data[index].hidden) {
// invalid, hidden and negative values are ignored
if (isNaN(value) || meta.data[index].hidden || value < 0) {
return;
}
values[index] = values[index] || 0;
if (opts.relativePoints) {
values[index] = 100;
} else {
// Don't need to split positive and negative since the log scale can't handle a 0 crossing
values[index] += value;
}
values[index] += value;
});

@@ -91,6 +135,8 @@ }

helpers.each(valuesPerStack, function(valuesForType) {
var minVal = helpers.min(valuesForType);
var maxVal = helpers.max(valuesForType);
me.min = me.min === null ? minVal : Math.min(me.min, minVal);
me.max = me.max === null ? maxVal : Math.max(me.max, maxVal);
if (valuesForType.length > 0) {
var minVal = helpers.min(valuesForType);
var maxVal = helpers.max(valuesForType);
me.min = me.min === null ? minVal : Math.min(me.min, minVal);
me.max = me.max === null ? maxVal : Math.max(me.max, maxVal);
}
});

@@ -104,3 +150,4 @@

var value = +me.getRightValue(rawValue);
if (isNaN(value) || meta.data[index].hidden) {
// invalid, hidden and negative values are ignored
if (isNaN(value) || meta.data[index].hidden || value < 0) {
return;

@@ -129,2 +176,13 @@ }

// Common base implementation to handle ticks.min, ticks.max
this.handleTickRangeOptions();
},
handleTickRangeOptions: function() {
var me = this;
var opts = me.options;
var tickOpts = opts.ticks;
var valueOrDefault = helpers.valueOrDefault;
var DEFAULT_MIN = 1;
var DEFAULT_MAX = 10;
me.min = valueOrDefault(tickOpts.min, me.min);

@@ -138,6 +196,23 @@ me.max = valueOrDefault(tickOpts.max, me.max);

} else {
me.min = 1;
me.max = 10;
me.min = DEFAULT_MIN;
me.max = DEFAULT_MAX;
}
}
if (me.min === null) {
me.min = Math.pow(10, Math.floor(helpers.log10(me.max)) - 1);
}
if (me.max === null) {
me.max = me.min !== 0
? Math.pow(10, Math.floor(helpers.log10(me.min)) + 1)
: DEFAULT_MAX;
}
if (me.minNotZero === null) {
if (me.min > 0) {
me.minNotZero = me.min;
} else if (me.max < 1) {
me.minNotZero = Math.pow(10, Math.floor(helpers.log10(me.max)));
} else {
me.minNotZero = DEFAULT_MIN;
}
}
},

@@ -148,2 +223,3 @@ buildTicks: function() {

var tickOpts = opts.ticks;
var reverse = !me.isHorizontal();

@@ -154,9 +230,4 @@ var generationOptions = {

};
var ticks = me.ticks = Ticks.generators.logarithmic(generationOptions, me);
var ticks = me.ticks = generateTicks(generationOptions, me);
if (!me.isHorizontal()) {
// We are in a vertical orientation. The top value is the highest. So reverse the array
ticks.reverse();
}
// At this point, we need to update our max and min given the tick values since we have expanded the

@@ -168,4 +239,3 @@ // range of the scale

if (tickOpts.reverse) {
ticks.reverse();
reverse = !reverse;
me.start = me.max;

@@ -177,2 +247,5 @@ me.end = me.min;

}
if (reverse) {
ticks.reverse();
}
},

@@ -191,46 +264,53 @@ convertTicksToLabels: function() {

},
/**
* Returns the value of the first tick.
* @param {Number} value - The minimum not zero value.
* @return {Number} The first tick value.
* @private
*/
_getFirstTickValue: function(value) {
var exp = Math.floor(helpers.log10(value));
var significand = Math.floor(value / Math.pow(10, exp));
return significand * Math.pow(10, exp);
},
getPixelForValue: function(value) {
var me = this;
var start = me.start;
var newVal = +me.getRightValue(value);
var opts = me.options;
var tickOpts = opts.ticks;
var innerDimension, pixel, range;
var reverse = me.options.ticks.reverse;
var log10 = helpers.log10;
var firstTickValue = me._getFirstTickValue(me.minNotZero);
var offset = 0;
var innerDimension, pixel, start, end, sign;
value = +me.getRightValue(value);
if (reverse) {
start = me.end;
end = me.start;
sign = -1;
} else {
start = me.start;
end = me.end;
sign = 1;
}
if (me.isHorizontal()) {
range = helpers.log10(me.end) - helpers.log10(start); // todo: if start === 0
if (newVal === 0) {
pixel = me.left;
} else {
innerDimension = me.width;
pixel = me.left + (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start)));
}
innerDimension = me.width;
pixel = reverse ? me.right : me.left;
} else {
// Bottom - top since pixels increase downward on a screen
innerDimension = me.height;
if (start === 0 && !tickOpts.reverse) {
range = helpers.log10(me.end) - helpers.log10(me.minNotZero);
if (newVal === start) {
pixel = me.bottom;
} else if (newVal === me.minNotZero) {
pixel = me.bottom - innerDimension * 0.02;
} else {
pixel = me.bottom - innerDimension * 0.02 - (innerDimension * 0.98 / range * (helpers.log10(newVal) - helpers.log10(me.minNotZero)));
}
} else if (me.end === 0 && tickOpts.reverse) {
range = helpers.log10(me.start) - helpers.log10(me.minNotZero);
if (newVal === me.end) {
pixel = me.top;
} else if (newVal === me.minNotZero) {
pixel = me.top + innerDimension * 0.02;
} else {
pixel = me.top + innerDimension * 0.02 + (innerDimension * 0.98 / range * (helpers.log10(newVal) - helpers.log10(me.minNotZero)));
}
} else if (newVal === 0) {
pixel = tickOpts.reverse ? me.top : me.bottom;
} else {
range = helpers.log10(me.end) - helpers.log10(start);
innerDimension = me.height;
pixel = me.bottom - (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start)));
sign *= -1; // invert, since the upper-left corner of the canvas is at pixel (0, 0)
pixel = reverse ? me.top : me.bottom;
}
if (value !== start) {
if (start === 0) { // include zero tick
offset = helpers.getValueOrDefault(
me.options.ticks.fontSize,
Chart.defaults.global.defaultFontSize
);
innerDimension -= offset;
start = firstTickValue;
}
if (value !== 0) {
offset += innerDimension / (log10(end) - log10(start)) * (log10(value) - log10(start));
}
pixel += sign * offset;
}

@@ -241,12 +321,35 @@ return pixel;

var me = this;
var range = helpers.log10(me.end) - helpers.log10(me.start);
var value, innerDimension;
var reverse = me.options.ticks.reverse;
var log10 = helpers.log10;
var firstTickValue = me._getFirstTickValue(me.minNotZero);
var innerDimension, start, end, value;
if (reverse) {
start = me.end;
end = me.start;
} else {
start = me.start;
end = me.end;
}
if (me.isHorizontal()) {
innerDimension = me.width;
value = me.start * Math.pow(10, (pixel - me.left) * range / innerDimension);
} else { // todo: if start === 0
value = reverse ? me.right - pixel : pixel - me.left;
} else {
innerDimension = me.height;
value = Math.pow(10, (me.bottom - pixel) * range / innerDimension) / me.start;
value = reverse ? pixel - me.top : me.bottom - pixel;
}
if (value !== start) {
if (start === 0) { // include zero tick
var offset = helpers.getValueOrDefault(
me.options.ticks.fontSize,
Chart.defaults.global.defaultFontSize
);
value -= offset;
innerDimension -= offset;
start = firstTickValue;
}
value *= log10(end) - log10(start);
value /= innerDimension;
value = Math.pow(10, log10(start) + value);
}
return value;

@@ -253,0 +356,0 @@ }

@@ -240,3 +240,2 @@ 'use strict';

var ctx = scale.ctx;
var valueOrDefault = helpers.valueOrDefault;
var opts = scale.options;

@@ -271,3 +270,3 @@ var angleLineOpts = opts.angleLines;

// Keep this in loop since we may support array properties here
var pointLabelFontColor = valueOrDefault(pointLabelOpts.fontColor, globalDefaults.defaultFontColor);
var pointLabelFontColor = helpers.valueAtIndexOrDefault(pointLabelOpts.fontColor, i, globalDefaults.defaultFontColor);
ctx.font = plFont.font;

@@ -274,0 +273,0 @@ ctx.fillStyle = pointLabelFontColor;

@@ -243,3 +243,3 @@ /* global window: false */

if (!steps) {
return Math.ceil(range / ((capacity || 1) * milliseconds));
return Math.ceil(range / (capacity * milliseconds));
}

@@ -407,2 +407,23 @@

function determineLabelFormat(data, timeOpts) {
var i, momentDate, hasTime;
var ilen = data.length;
// find the label with the most parts (milliseconds, minutes, etc.)
// format all labels with the same level of detail as the most specific label
for (i = 0; i < ilen; i++) {
momentDate = momentify(data[i], timeOpts);
if (momentDate.millisecond() !== 0) {
return 'MMM D, YYYY h:mm:ss.SSS a';
}
if (momentDate.second() !== 0 || momentDate.minute() !== 0 || momentDate.hour() !== 0) {
hasTime = true;
}
}
if (hasTime) {
return 'MMM D, YYYY h:mm:ss a';
}
return 'MMM D, YYYY';
}
module.exports = function(Chart) {

@@ -509,2 +530,3 @@

var timeOpts = me.options.time;
var unit = timeOpts.unit || 'day';
var min = MAX_INTEGER;

@@ -561,5 +583,5 @@ var max = MIN_INTEGER;

// In case there is no valid min/max, let's use today limits
min = min === MAX_INTEGER ? +moment().startOf('day') : min;
max = max === MIN_INTEGER ? +moment().endOf('day') + 1 : max;
// In case there is no valid min/max, set limits based on unit time option
min = min === MAX_INTEGER ? +moment().startOf(unit) : min;
max = max === MIN_INTEGER ? +moment().endOf(unit) + 1 : max;

@@ -627,2 +649,3 @@ // Make sure that max is strictly higher than min (required by the lookup table)

me._offsets = computeOffsets(me._table, ticks, min, max, options);
me._labelFormat = determineLabelFormat(me._timestamps.data, timeOpts);

@@ -643,6 +666,9 @@ return ticksFromTimestamps(ticks, me._majorUnit);

if (timeOpts.tooltipFormat) {
label = momentify(label, timeOpts).format(timeOpts.tooltipFormat);
return momentify(label, timeOpts).format(timeOpts.tooltipFormat);
}
if (typeof label === 'string') {
return label;
}
return label;
return momentify(label, timeOpts).format(me._labelFormat);
},

@@ -757,3 +783,4 @@

return Math.floor(innerWidth / tickLabelWidth);
var capacity = Math.floor(innerWidth / tickLabelWidth);
return capacity > 0 ? capacity : 1;
}

@@ -760,0 +787,0 @@ });

@@ -121,5 +121,2 @@ /* global __karma__ */

fail('Missing PNG comparison file for ' + inputs.json);
if (!json.debug) {
releaseChart(chart);
}
done();

@@ -126,0 +123,0 @@ }

describe('Chart.controllers.bar', function() {
describe('auto', jasmine.specsFromFixtures('controller.bar'));
it('should be constructed', function() {

@@ -1633,82 +1635,2 @@ var chart = window.acquireChart({

});
describe('Bar thickness with a time scale', function() {
['auto', 'data', 'labels'].forEach(function(source) {
['series', 'linear'].forEach(function(distribution) {
describe('When ticks.source is "' + source + '", distribution is "' + distribution + '"', function() {
beforeEach(function() {
this.chart = window.acquireChart({
type: 'bar',
data: {
datasets: [{
data: [1, 2, 3]
}, {
data: [1, 2, 3]
}],
labels: ['2017', '2018', '2020']
},
options: {
legend: false,
title: false,
scales: {
xAxes: [{
id: 'x',
type: 'time',
time: {
unit: 'year',
parser: 'YYYY'
},
ticks: {
source: source
},
offset: true,
distribution: distribution
}],
yAxes: [{
type: 'linear'
}]
}
}
});
});
it('should correctly set bar width', function() {
var chart = this.chart;
var scale = chart.scales.x;
var options = chart.options.scales.xAxes[0];
var categoryPercentage = options.categoryPercentage;
var barPercentage = options.barPercentage;
var firstInterval = scale.getPixelForValue('2018') - scale.getPixelForValue('2017');
var firstExpected = firstInterval * categoryPercentage / 2 * barPercentage;
var lastInterval = scale.getPixelForValue('2020') - scale.getPixelForValue('2018');
var lastExpected = lastInterval * categoryPercentage / 2 * barPercentage;
var i, ilen, meta;
for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) {
meta = chart.getDatasetMeta(i);
expect(meta.data[0]._model.width).toBeCloseToPixel(firstExpected);
expect(meta.data[1]._model.width).toBeCloseToPixel((firstExpected + lastExpected) / 2);
expect(meta.data[2]._model.width).toBeCloseToPixel(lastExpected);
}
});
it('should correctly set bar width if maxBarThickness is specified', function() {
var chart = this.chart;
var options = chart.options.scales.xAxes[0];
var i, ilen, meta;
options.maxBarThickness = 10;
chart.update();
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[2]._model.width).toBeCloseToPixel(10);
}
});
});
});
});
});
});

@@ -208,2 +208,42 @@ describe('Chart.controllers.doughnut', function() {

it('should treat negative values as positive', function() {
var chart = window.acquireChart({
type: 'doughnut',
data: {
datasets: [{
data: [-1, -3]
}],
labels: ['label0', 'label1']
},
options: {
legend: false,
title: false,
cutoutPercentage: 50,
rotation: Math.PI,
circumference: Math.PI * 0.5,
elements: {
arc: {
backgroundColor: 'rgb(255, 0, 0)',
borderColor: 'rgb(0, 0, 255)',
borderWidth: 2
}
}
}
});
var meta = chart.getDatasetMeta(0);
expect(meta.data.length).toBe(2);
// Only startAngle, endAngle and circumference should be different.
[
{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);
});
});
it ('should draw all arcs', function() {

@@ -210,0 +250,0 @@ var chart = window.acquireChart({

@@ -762,4 +762,4 @@ describe('Chart', function() {

// then we will reset and see that they moved
expect(meta.data[0]._model.y).toBe(333);
expect(meta.data[1]._model.y).toBe(183);
expect(meta.data[0]._model.y).toBeCloseToPixel(333);
expect(meta.data[1]._model.y).toBeCloseToPixel(183);
expect(meta.data[2]._model.y).toBe(32);

@@ -779,2 +779,34 @@ expect(meta.data[3]._model.y).toBe(484);

describe('config update', function() {
it ('should update options', function() {
var chart = acquireChart({
type: 'line',
data: {
labels: ['A', 'B', 'C', 'D'],
datasets: [{
data: [10, 20, 30, 100]
}]
},
options: {
responsive: true
}
});
chart.options = {
responsive: false,
scales: {
yAxes: [{
ticks: {
min: 0,
max: 10
}
}]
}
};
chart.update();
var yScale = chart.scales['y-axis-0'];
expect(yScale.options.ticks.min).toBe(0);
expect(yScale.options.ticks.max).toBe(10);
});
it ('should update scales options', function() {

@@ -803,2 +835,75 @@ var chart = acquireChart({

it ('should update scales options from new object', function() {
var chart = acquireChart({
type: 'line',
data: {
labels: ['A', 'B', 'C', 'D'],
datasets: [{
data: [10, 20, 30, 100]
}]
},
options: {
responsive: true
}
});
var newScalesConfig = {
yAxes: [{
ticks: {
min: 0,
max: 10
}
}]
};
chart.options.scales = newScalesConfig;
chart.update();
var yScale = chart.scales['y-axis-0'];
expect(yScale.options.ticks.min).toBe(0);
expect(yScale.options.ticks.max).toBe(10);
});
it ('should remove discarded scale', function() {
var chart = acquireChart({
type: 'line',
data: {
labels: ['A', 'B', 'C', 'D'],
datasets: [{
data: [10, 20, 30, 100]
}]
},
options: {
responsive: true,
scales: {
yAxes: [{
id: 'yAxis0',
ticks: {
min: 0,
max: 10
}
}]
}
}
});
var newScalesConfig = {
yAxes: [{
ticks: {
min: 0,
max: 10
}
}]
};
chart.options.scales = newScalesConfig;
chart.update();
var yScale = chart.scales.yAxis0;
expect(yScale).toBeUndefined();
var newyScale = chart.scales['y-axis-0'];
expect(newyScale.options.ticks.min).toBe(0);
expect(newyScale.options.ticks.max).toBe(10);
});
it ('should update tooltip options', function() {

@@ -805,0 +910,0 @@ var chart = acquireChart({

@@ -197,4 +197,8 @@ describe('Core helper tests', function() {

expect(helpers.log10(0)).toBe(-Infinity);
expect(helpers.log10(1)).toBe(0);
expect(helpers.log10(1000)).toBeCloseTo(3, 1e-9);
// Check all allowed powers of 10, which should return integer values
var maxPowerOf10 = Math.floor(helpers.log10(Number.MAX_VALUE));
for (var i = 0; i < maxPowerOf10; i += 1) {
expect(helpers.log10(Math.pow(10, i))).toBe(i);
}
});

@@ -729,2 +733,19 @@

it ('should leave styled height and width on canvas if explicitly set', function() {
var chart = window.acquireChart({}, {
canvas: {
height: 200,
width: 200,
style: 'height: 400px; width: 400px;'
}
});
helpers.retinaScale(chart, true);
var canvas = chart.canvas;
expect(canvas.style.height).toBe('400px');
expect(canvas.style.width).toBe('400px');
});
describe('Color helper', function() {

@@ -731,0 +752,0 @@ function isColorInstance(obj) {

@@ -342,4 +342,37 @@ describe('Chart.plugins', function() {

expect(plugin.hook.calls.first().args[1]).toEqual({a: 'foobar'});
delete Chart.defaults.global.plugins.a;
});
// https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167
it('should invalidate cache when update plugin options', function() {
var plugin = {id: 'a', hook: function() {}};
var chart = window.acquireChart({
plugins: [plugin],
options: {
plugins: {
a: {
foo: 'foo'
}
}
},
});
spyOn(plugin, 'hook');
Chart.plugins.notify(chart, 'hook');
expect(plugin.hook).toHaveBeenCalled();
expect(plugin.hook.calls.first().args[1]).toEqual({foo: 'foo'});
chart.options.plugins.a = {bar: 'bar'};
chart.update();
plugin.hook.calls.reset();
Chart.plugins.notify(chart, 'hook');
expect(plugin.hook).toHaveBeenCalled();
expect(plugin.hook.calls.first().args[1]).toEqual({bar: 'bar'});
});
});
});

@@ -767,3 +767,3 @@ // Test the rectangle element

var chart = window.acquireChart({
type: 'bar',
type: 'line',
data: {

@@ -886,2 +886,69 @@ datasets: [{

});
it('Should avoid tooltip truncation in x axis if there is enough space to show tooltip without truncation', function() {
var chart = window.acquireChart({
type: 'pie',
data: {
datasets: [{
data: [
50,
50
],
backgroundColor: [
'rgb(255, 0, 0)',
'rgb(0, 255, 0)'
],
label: 'Dataset 1'
}],
labels: [
'Red long tooltip text to avoid unnecessary loop steps',
'Green long tooltip text to avoid unnecessary loop steps'
]
},
options: {
responsive: true,
animation: {
// without this slice center point is calculated wrong
animateRotate: false
}
}
});
// Trigger an event over top of the slice
for (var slice = 0; slice < 2; slice++) {
var meta = chart.getDatasetMeta(0);
var point = meta.data[slice].getCenterPoint();
var tooltipPosition = meta.data[slice].tooltipPosition();
var node = chart.canvas;
var rect = node.getBoundingClientRect();
var mouseMoveEvent = new MouseEvent('mousemove', {
view: window,
bubbles: true,
cancelable: true,
clientX: rect.left + point.x,
clientY: rect.top + point.y
});
var mouseOutEvent = new MouseEvent('mouseout');
// Lets cycle while tooltip is narrower than chart area
var infiniteCycleDefense = 70;
for (var i = 0; i < infiniteCycleDefense; i++) {
chart.config.data.labels[slice] = chart.config.data.labels[slice] + 'l';
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);
}
expect(model.caretX).toBe(tooltipPosition.x);
// if tooltip is longer than chart area then all tests done
if (model.width > chart.width) {
break;
}
}
}
});
});
describe('Deprecations', function() {
describe('Version 2.8.0', function() {
describe('Chart.layoutService', function() {
it('should be defined and an alias of Chart.layouts', function() {
expect(Chart.layoutService).toBeDefined();
expect(Chart.layoutService).toBe(Chart.layouts);
});
});
});
describe('Version 2.7.0', function() {

@@ -305,4 +314,4 @@ describe('Chart.Controller.update(duration, lazy)', function() {

var override = Chart.layoutService.update;
Chart.layoutService.update = function() {
var override = Chart.layouts.update;
Chart.layouts.update = function() {
sequence.push('layoutUpdate');

@@ -376,3 +385,21 @@ override.apply(this, arguments);

});
describe('Chart.Legend', function() {
it('should be defined and an instance of Chart.Element', function() {
var legend = new Chart.Legend({});
expect(Chart.Legend).toBeDefined();
expect(legend).not.toBe(undefined);
expect(legend instanceof Chart.Element).toBeTruthy();
});
});
describe('Chart.Title', function() {
it('should be defined and an instance of Chart.Element', function() {
var title = new Chart.Title({});
expect(Chart.Title).toBeDefined();
expect(title).not.toBe(undefined);
expect(title instanceof Chart.Element).toBeTruthy();
});
});
});
});
// Test the rectangle element
describe('Legend block tests', function() {
it('Should be constructed', function() {
var legend = new Chart.Legend({});
expect(legend).not.toBe(undefined);
});
it('should have the correct default config', function() {

@@ -9,0 +4,0 @@ expect(Chart.defaults.global.legend).toEqual({

// Test the rectangle element
describe('Title block tests', function() {
it('Should be constructed', function() {
var title = new Chart.Title({});
expect(title).not.toBe(undefined);
});
it('Should have the correct default config', function() {

@@ -10,0 +5,0 @@ expect(Chart.defaults.global.title).toEqual({

@@ -693,90 +693,433 @@ describe('Logarithmic Scale tests', function() {

it('should get the correct pixel value for a point', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
xAxisID: 'xScale', // for the horizontal scale
yAxisID: 'yScale',
data: [{x: 10, y: 10}, {x: 5, y: 5}, {x: 1, y: 1}, {x: 25, y: 25}, {x: 78, y: 78}]
}],
describe('when', function() {
var data = [
{
data: [1, 39],
stack: 'stack'
},
options: {
scales: {
xAxes: [{
id: 'xScale',
type: 'logarithmic'
}],
{
data: [1, 39],
stack: 'stack'
},
];
var dataWithEmptyStacks = [
{
data: []
},
{
data: []
}
].concat(data);
var config = [
{
axis: 'y',
firstTick: 1, // start of the axis (minimum)
describe: 'all stacks are defined'
},
{
axis: 'y',
data: dataWithEmptyStacks,
firstTick: 1,
describe: 'not all stacks are defined'
},
{
axis: 'y',
scale: {
yAxes: [{
id: 'yScale',
type: 'logarithmic'
ticks: {
min: 0
}
}]
}
},
firstTick: 0,
describe: 'all stacks are defined and ticks.min: 0'
},
{
axis: 'y',
data: dataWithEmptyStacks,
scale: {
yAxes: [{
ticks: {
min: 0
}
}]
},
firstTick: 0,
describe: 'not stacks are defined and ticks.min: 0'
},
{
axis: 'x',
firstTick: 1,
describe: 'all stacks are defined'
},
{
axis: 'x',
data: dataWithEmptyStacks,
firstTick: 1,
describe: 'not all stacks are defined'
},
{
axis: 'x',
scale: {
xAxes: [{
ticks: {
min: 0
}
}]
},
firstTick: 0,
describe: 'all stacks are defined and ticks.min: 0'
},
{
axis: 'x',
data: dataWithEmptyStacks,
scale: {
xAxes: [{
ticks: {
min: 0
}
}]
},
firstTick: 0,
describe: 'not all stacks are defined and ticks.min: 0'
},
];
config.forEach(function(setup) {
var scaleConfig = {};
var type, chartStart, chartEnd;
if (setup.axis === 'x') {
type = 'horizontalBar';
chartStart = 'left';
chartEnd = 'right';
} else {
type = 'bar';
chartStart = 'bottom';
chartEnd = 'top';
}
});
scaleConfig[setup.axis + 'Axes'] = [{
type: 'logarithmic'
}];
Chart.helpers.extend(scaleConfig, setup.scale);
var description = 'dataset has stack option and ' + setup.describe
+ ' and axis is "' + setup.axis + '";';
describe(description, function() {
it('should define the correct axis limits', function() {
var chart = window.acquireChart({
type: type,
data: {
labels: ['category 1', 'category 2'],
datasets: setup.data || data,
},
options: {
scales: scaleConfig
}
});
var xScale = chart.scales.xScale;
expect(xScale.getPixelForValue(80, 0, 0)).toBeCloseToPixel(495); // right - paddingRight
expect(xScale.getPixelForValue(1, 0, 0)).toBeCloseToPixel(37 + 6); // left + paddingLeft + lineSpace
expect(xScale.getPixelForValue(10, 0, 0)).toBeCloseToPixel(278 + 6 / 2); // halfway
expect(xScale.getPixelForValue(0, 0, 0)).toBeCloseToPixel(37 + 6); // 0 is invalid, put it on the left.
var axisID = setup.axis + '-axis-0';
var scale = chart.scales[axisID];
var firstTick = setup.firstTick;
var lastTick = 80; // last tick (should be first available tick after: 2 * 39)
var start = chart.chartArea[chartStart];
var end = chart.chartArea[chartEnd];
expect(xScale.getValueForPixel(495)).toBeCloseToPixel(80);
expect(xScale.getValueForPixel(48)).toBeCloseTo(1, 1e-4);
expect(xScale.getValueForPixel(278)).toBeCloseTo(10, 1e-4);
expect(scale.getPixelForValue(firstTick, 0, 0)).toBe(start);
expect(scale.getPixelForValue(lastTick, 0, 0)).toBe(end);
var yScale = chart.scales.yScale;
expect(yScale.getPixelForValue(80, 0, 0)).toBeCloseToPixel(32); // top + paddingTop
expect(yScale.getPixelForValue(1, 0, 0)).toBeCloseToPixel(484); // bottom - paddingBottom
expect(yScale.getPixelForValue(10, 0, 0)).toBeCloseToPixel(246); // halfway
expect(yScale.getPixelForValue(0, 0, 0)).toBeCloseToPixel(484); // 0 is invalid. force it on bottom
expect(scale.getValueForPixel(start)).toBeCloseTo(firstTick, 4);
expect(scale.getValueForPixel(end)).toBeCloseTo(lastTick, 4);
expect(yScale.getValueForPixel(32)).toBeCloseTo(80, 1e-4);
expect(yScale.getValueForPixel(484)).toBeCloseTo(1, 1e-4);
expect(yScale.getValueForPixel(246)).toBeCloseTo(10, 1e-4);
chart.scales[axisID].options.ticks.reverse = true; // Reverse mode
chart.update();
// chartArea might have been resized in update
start = chart.chartArea[chartEnd];
end = chart.chartArea[chartStart];
expect(scale.getPixelForValue(firstTick, 0, 0)).toBe(start);
expect(scale.getPixelForValue(lastTick, 0, 0)).toBe(end);
expect(scale.getValueForPixel(start)).toBeCloseTo(firstTick, 4);
expect(scale.getValueForPixel(end)).toBeCloseTo(lastTick, 4);
});
});
});
});
it('should get the correct pixel value for a point when 0 values are present', function() {
var chart = window.acquireChart({
type: 'bar',
data: {
datasets: [{
yAxisID: 'yScale',
data: [0.063, 4, 0, 63, 10, 0.5]
}],
labels: []
describe('when', function() {
var config = [
{
dataset: [],
firstTick: 1, // value of the first tick
lastTick: 10, // value of the last tick
describe: 'empty dataset, without ticks.min/max'
},
options: {
scales: {
yAxes: [{
id: 'yScale',
type: 'logarithmic',
ticks: {
reverse: false
}
}]
{
dataset: [],
scale: {stacked: true},
firstTick: 1,
lastTick: 10,
describe: 'empty dataset, without ticks.min/max, with stacked: true'
},
{
data: {
datasets: [
{data: [], stack: 'stack'},
{data: [], stack: 'stack'},
],
},
type: 'bar',
firstTick: 1,
lastTick: 10,
describe: 'empty dataset with stack option, without ticks.min/max'
},
{
data: {
datasets: [
{data: [], stack: 'stack'},
{data: [], stack: 'stack'},
],
},
type: 'horizontalBar',
firstTick: 1,
lastTick: 10,
describe: 'empty dataset with stack option, without ticks.min/max'
},
{
dataset: [],
scale: {ticks: {min: 1}},
firstTick: 1,
lastTick: 10,
describe: 'empty dataset, ticks.min: 1, without ticks.max'
},
{
dataset: [],
scale: {ticks: {max: 80}},
firstTick: 1,
lastTick: 80,
describe: 'empty dataset, ticks.max: 80, without ticks.min'
},
{
dataset: [],
scale: {ticks: {max: 0.8}},
firstTick: 0.01,
lastTick: 0.8,
describe: 'empty dataset, ticks.max: 0.8, without ticks.min'
},
{
dataset: [{x: 10, y: 10}, {x: 5, y: 5}, {x: 1, y: 1}, {x: 25, y: 25}, {x: 78, y: 78}],
firstTick: 1,
lastTick: 80,
describe: 'dataset min point {x: 1, y: 1}, max point {x:78, y:78}'
},
];
config.forEach(function(setup) {
var axes = [
{
id: 'x', // horizontal scale
start: 'left',
end: 'right'
},
{
id: 'y', // vertical scale
start: 'bottom',
end: 'top'
}
}
];
axes.forEach(function(axis) {
var expectation = 'min = ' + setup.firstTick + ', max = ' + setup.lastTick;
describe(setup.describe + ' and axis is "' + axis.id + '"; expect: ' + expectation + ';', function() {
beforeEach(function() {
var xScaleConfig = {
type: 'logarithmic',
};
var yScaleConfig = {
type: 'logarithmic',
};
var data = setup.data || {
datasets: [{
data: setup.dataset
}],
};
Chart.helpers.extend(xScaleConfig, setup.scale);
Chart.helpers.extend(yScaleConfig, setup.scale);
Chart.helpers.extend(data, setup.data || {});
this.chart = window.acquireChart({
type: 'line',
data: data,
options: {
scales: {
xAxes: [xScaleConfig],
yAxes: [yScaleConfig]
}
}
});
});
it('should get the correct pixel value for a point', function() {
var chart = this.chart;
var axisID = axis.id + '-axis-0';
var scale = chart.scales[axisID];
var firstTick = setup.firstTick;
var lastTick = setup.lastTick;
var start = chart.chartArea[axis.start];
var end = chart.chartArea[axis.end];
expect(scale.getPixelForValue(firstTick, 0, 0)).toBe(start);
expect(scale.getPixelForValue(lastTick, 0, 0)).toBe(end);
expect(scale.getPixelForValue(0, 0, 0)).toBe(start); // 0 is invalid, put it at the start.
expect(scale.getValueForPixel(start)).toBeCloseTo(firstTick, 4);
expect(scale.getValueForPixel(end)).toBeCloseTo(lastTick, 4);
chart.scales[axisID].options.ticks.reverse = true; // Reverse mode
chart.update();
// chartArea might have been resized in update
start = chart.chartArea[axis.end];
end = chart.chartArea[axis.start];
expect(scale.getPixelForValue(firstTick, 0, 0)).toBe(start);
expect(scale.getPixelForValue(lastTick, 0, 0)).toBe(end);
expect(scale.getValueForPixel(start)).toBeCloseTo(firstTick, 4);
expect(scale.getValueForPixel(end)).toBeCloseTo(lastTick, 4);
});
});
});
});
});
var yScale = chart.scales.yScale;
expect(yScale.getPixelForValue(70, 0, 0)).toBeCloseToPixel(32); // top + paddingTop
expect(yScale.getPixelForValue(0, 0, 0)).toBeCloseToPixel(484); // bottom - paddingBottom
expect(yScale.getPixelForValue(0.063, 0, 0)).toBeCloseToPixel(475); // minNotZero 2% from range
expect(yScale.getPixelForValue(0.5, 0, 0)).toBeCloseToPixel(344);
expect(yScale.getPixelForValue(4, 0, 0)).toBeCloseToPixel(213);
expect(yScale.getPixelForValue(10, 0, 0)).toBeCloseToPixel(155);
expect(yScale.getPixelForValue(63, 0, 0)).toBeCloseToPixel(38.5);
describe('when', function() {
var config = [
{
dataset: [],
scale: {ticks: {min: 0}},
firstTick: 1, // value of the first tick
lastTick: 10, // value of the last tick
describe: 'empty dataset, ticks.min: 0, without ticks.max'
},
{
dataset: [],
scale: {ticks: {min: 0, max: 80}},
firstTick: 1,
lastTick: 80,
describe: 'empty dataset, ticks.min: 0, ticks.max: 80'
},
{
dataset: [],
scale: {ticks: {min: 0, max: 0.8}},
firstTick: 0.1,
lastTick: 0.8,
describe: 'empty dataset, ticks.min: 0, ticks.max: 0.8'
},
{
dataset: [{x: 0, y: 0}, {x: 10, y: 10}, {x: 1.2, y: 1.2}, {x: 25, y: 25}, {x: 78, y: 78}],
firstTick: 1,
lastTick: 80,
describe: 'dataset min point {x: 0, y: 0}, max point {x:78, y:78}, minNotZero {x: 1.2, y: 1.2}'
},
{
dataset: [{x: 0, y: 0}, {x: 10, y: 10}, {x: 6.3, y: 6.3}, {x: 25, y: 25}, {x: 78, y: 78}],
firstTick: 6,
lastTick: 80,
describe: 'dataset min point {x: 0, y: 0}, max point {x:78, y:78}, minNotZero {x: 6.3, y: 6.3}'
},
{
dataset: [{x: 10, y: 10}, {x: 1.2, y: 1.2}, {x: 25, y: 25}, {x: 78, y: 78}],
scale: {ticks: {min: 0}},
firstTick: 1,
lastTick: 80,
describe: 'dataset min point {x: 1.2, y: 1.2}, max point {x:78, y:78}, ticks.min: 0'
},
{
dataset: [{x: 10, y: 10}, {x: 6.3, y: 6.3}, {x: 25, y: 25}, {x: 78, y: 78}],
scale: {ticks: {min: 0}},
firstTick: 6,
lastTick: 80,
describe: 'dataset min point {x: 6.3, y: 6.3}, max point {x:78, y:78}, ticks.min: 0'
},
];
config.forEach(function(setup) {
var axes = [
{
id: 'x', // horizontal scale
start: 'left',
end: 'right'
},
{
id: 'y', // vertical scale
start: 'bottom',
end: 'top'
}
];
axes.forEach(function(axis) {
var expectation = 'min = 0, max = ' + setup.lastTick + ', first tick = ' + setup.firstTick;
describe(setup.describe + ' and axis is "' + axis.id + '"; expect: ' + expectation + ';', function() {
beforeEach(function() {
var xScaleConfig = {
type: 'logarithmic',
};
var yScaleConfig = {
type: 'logarithmic',
};
var data = setup.data || {
datasets: [{
data: setup.dataset
}],
};
Chart.helpers.extend(xScaleConfig, setup.scale);
Chart.helpers.extend(yScaleConfig, setup.scale);
Chart.helpers.extend(data, setup.data || {});
this.chart = window.acquireChart({
type: 'line',
data: data,
options: {
scales: {
xAxes: [xScaleConfig],
yAxes: [yScaleConfig]
}
}
});
});
chart.options.scales.yAxes[0].ticks.reverse = true; // Reverse mode
chart.update();
it('should get the correct pixel value for a point', function() {
var chart = this.chart;
var axisID = axis.id + '-axis-0';
var scale = chart.scales[axisID];
var firstTick = setup.firstTick;
var lastTick = setup.lastTick;
var fontSize = chart.options.defaultFontSize;
var start = chart.chartArea[axis.start];
var end = chart.chartArea[axis.end];
var sign = scale.isHorizontal() ? 1 : -1;
expect(yScale.getPixelForValue(70, 0, 0)).toBeCloseToPixel(484); // bottom - paddingBottom
expect(yScale.getPixelForValue(0, 0, 0)).toBeCloseToPixel(32); // top + paddingTop
expect(yScale.getPixelForValue(0.063, 0, 0)).toBeCloseToPixel(41); // minNotZero 2% from range
expect(yScale.getPixelForValue(0.5, 0, 0)).toBeCloseToPixel(172);
expect(yScale.getPixelForValue(4, 0, 0)).toBeCloseToPixel(303);
expect(yScale.getPixelForValue(10, 0, 0)).toBeCloseToPixel(361);
expect(yScale.getPixelForValue(63, 0, 0)).toBeCloseToPixel(477);
expect(scale.getPixelForValue(0, 0, 0)).toBe(start);
expect(scale.getPixelForValue(lastTick, 0, 0)).toBe(end);
expect(scale.getPixelForValue(firstTick, 0, 0)).toBe(start + sign * fontSize);
expect(scale.getValueForPixel(start)).toBeCloseTo(0, 4);
expect(scale.getValueForPixel(end)).toBeCloseTo(lastTick, 4);
expect(scale.getValueForPixel(start + sign * fontSize)).toBeCloseTo(firstTick, 4);
chart.scales[axisID].options.ticks.reverse = true; // Reverse mode
chart.update();
// chartArea might have been resized in update
start = chart.chartArea[axis.end];
end = chart.chartArea[axis.start];
expect(scale.getPixelForValue(0, 0, 0)).toBe(start);
expect(scale.getPixelForValue(lastTick, 0, 0)).toBe(end);
expect(scale.getPixelForValue(firstTick, 0, 0)).toBe(start - sign * fontSize, 4);
expect(scale.getValueForPixel(start)).toBeCloseTo(0, 4);
expect(scale.getValueForPixel(end)).toBeCloseTo(lastTick, 4);
expect(scale.getValueForPixel(start - sign * fontSize)).toBeCloseTo(firstTick, 4);
});
});
});
});
});
});

@@ -587,2 +587,111 @@ // Time scale tests

it('should get the correct label when time is specified as a string', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
xAxisID: 'xScale0',
data: [{t: '2015-01-01T20:00:00', y: 10}, {t: '2015-01-02T21:00:00', y: 3}]
}],
},
options: {
scales: {
xAxes: [{
id: 'xScale0',
type: 'time',
position: 'bottom'
}],
}
}
});
var xScale = chart.scales.xScale0;
expect(xScale.getLabelForIndex(0, 0)).toBeTruthy();
expect(xScale.getLabelForIndex(0, 0)).toBe('2015-01-01T20:00:00');
});
it('should get the correct label for a timestamp with milliseconds', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
xAxisID: 'xScale0',
data: [
{t: +new Date('2018-01-08 05:14:23.234'), y: 10},
{t: +new Date('2018-01-09 06:17:43.426'), y: 3}
]
}],
},
options: {
scales: {
xAxes: [{
id: 'xScale0',
type: 'time',
position: 'bottom'
}],
}
}
});
var xScale = chart.scales.xScale0;
var label = xScale.getLabelForIndex(0, 0);
expect(label).toEqual('Jan 8, 2018 5:14:23.234 am');
});
it('should get the correct label for a timestamp with time', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
xAxisID: 'xScale0',
data: [
{t: +new Date('2018-01-08 05:14:23'), y: 10},
{t: +new Date('2018-01-09 06:17:43'), y: 3}
]
}],
},
options: {
scales: {
xAxes: [{
id: 'xScale0',
type: 'time',
position: 'bottom'
}],
}
}
});
var xScale = chart.scales.xScale0;
var label = xScale.getLabelForIndex(0, 0);
expect(label).toEqual('Jan 8, 2018 5:14:23 am');
});
it('should get the correct label for a timestamp representing a date', function() {
var chart = window.acquireChart({
type: 'line',
data: {
datasets: [{
xAxisID: 'xScale0',
data: [
{t: +new Date('2018-01-08 00:00:00'), y: 10},
{t: +new Date('2018-01-09 00:00:00'), y: 3}
]
}],
},
options: {
scales: {
xAxes: [{
id: 'xScale0',
type: 'time',
position: 'bottom'
}],
}
}
});
var xScale = chart.scales.xScale0;
var label = xScale.getLabelForIndex(0, 0);
expect(label).toEqual('Jan 8, 2018');
});
it('should get the correct pixel for only one data in the dataset', function() {

@@ -707,3 +816,3 @@ var chart = window.acquireChart({

});
it ('should correctly handle empty `data.labels`', function() {
it ('should correctly handle empty `data.labels` using "day" if `time.unit` is undefined`', function() {
var chart = this.chart;

@@ -719,2 +828,15 @@ var scale = chart.scales.x;

});
it ('should correctly handle empty `data.labels` using `time.unit`', function() {
var chart = this.chart;
var scale = chart.scales.x;
var options = chart.options.scales.xAxes[0];
options.time.unit = 'year';
chart.data.labels = [];
chart.update();
expect(scale.min).toEqual(+moment().startOf('year'));
expect(scale.max).toEqual(+moment().endOf('year') + 1);
expect(getTicksLabels(scale)).toEqual([]);
});
});

@@ -790,3 +912,3 @@

});
it ('should correctly handle empty `data.labels`', function() {
it ('should correctly handle empty `data.labels` using "day" if `time.unit` is undefined`', function() {
var chart = this.chart;

@@ -803,2 +925,17 @@ var scale = chart.scales.x;

});
it ('should correctly handle empty `data.labels` and hidden datasets using `time.unit`', function() {
var chart = this.chart;
var scale = chart.scales.x;
var options = chart.options.scales.xAxes[0];
options.time.unit = 'year';
chart.data.labels = [];
var meta = chart.getDatasetMeta(1);
meta.hidden = true;
chart.update();
expect(scale.min).toEqual(+moment().startOf('year'));
expect(scale.max).toEqual(+moment().endOf('year') + 1);
expect(getTicksLabels(scale)).toEqual([]);
});
});

@@ -805,0 +942,0 @@ });

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc