Comparing version 2.7.1 to 2.7.2
{ | ||
"root": "./docs", | ||
"title": "Chart.js documentation", | ||
"author": "chartjs", | ||
@@ -4,0 +5,0 @@ "gitbook": "3.2.2", |
@@ -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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
7007870
367
87483
58
36
27
+ Addedchartjs-color@2.4.1(transitive)
+ Addedchartjs-color-string@0.6.0(transitive)
+ Addedcolor-convert@1.9.3(transitive)
+ Addedcolor-name@1.1.3(transitive)
+ Addedmoment@2.30.1(transitive)
- Removedchartjs-color@2.2.0(transitive)
- Removedchartjs-color-string@0.5.0(transitive)
- Removedcolor-convert@0.5.3(transitive)
- Removedmoment@2.18.1(transitive)
Updatedchartjs-color@^2.1.0
Updatedmoment@^2.10.2