diff --git a/docs/axes/cartesian/category.md b/docs/axes/cartesian/category.md index 38c306712bb..4bfb6f627cc 100644 --- a/docs/axes/cartesian/category.md +++ b/docs/axes/cartesian/category.md @@ -40,7 +40,7 @@ The category scale provides the following options for configuring tick marks. Th | Name | Type | Default | Description | -----| ---- | --------| ----------- -| labels | Array[String] | - | An array of labels to display. +| `labels` | `Array[String]` | - | An array of labels to display. | `min` | `String` | | The minimum item to display. [more...](#min-max-configuration) | `max` | `String` | | The maximum item to display. [more...](#min-max-configuration) diff --git a/docs/axes/cartesian/time.md b/docs/axes/cartesian/time.md index e087d186817..9d15b5e2674 100644 --- a/docs/axes/cartesian/time.md +++ b/docs/axes/cartesian/time.md @@ -34,7 +34,7 @@ The following options are provided by the time scale. You may also set options p | `time.isoWeekday` | `Boolean` | `false` | If true and the unit is set to 'week', then the first day of the week will be Monday. Otherwise, it will be Sunday. | `time.max` | [Time](#date-formats) | | If defined, this will override the data maximum | `time.min` | [Time](#date-formats) | | If defined, this will override the data minimum -| `time.parser` | `String` or `Function` | | Custom parser for dates. [more...](#parser) +| `time.parser` | `String/Function` | | Custom parser for dates. [more...](#parser) | `time.round` | `String` | `false` | If defined, dates will be rounded to the start of this unit. See [Time Units](#time-units) below for the allowed units. | `time.tooltipFormat` | `String` | | The moment js format string to use for the tooltip. | `time.unit` | `String` | `false` | If defined, will force the unit to be a certain type. See [Time Units](#time-units) section below for details. diff --git a/docs/axes/labelling.md b/docs/axes/labelling.md index aec8d990ff4..2e8f28c805c 100644 --- a/docs/axes/labelling.md +++ b/docs/axes/labelling.md @@ -10,12 +10,12 @@ The scale label configuration is nested under the scale configuration in the `sc | -----| ---- | --------| ----------- | `display` | `Boolean` | `false` | If true, display the axis title. | `labelString` | `String` | `''` | The text for the title. (i.e. "# of People" or "Response Choices"). -| `lineHeight` | `Number|String` | `1.2` | Height of an individual line of text (see [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/line-height)) -| `fontColor` | Color | `'#666'` | Font color for scale title. +| `lineHeight` | `Number/String` | `1.2` | Height of an individual line of text (see [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/line-height)) +| `fontColor` | `Color` | `'#666'` | Font color for scale title. | `fontFamily` | `String` | `"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"` | Font family for the scale title, follows CSS font-family options. | `fontSize` | `Number` | `12` | Font size for scale title. | `fontStyle` | `String` | `'normal'` | Font style for the scale title, follows CSS font-style options (i.e. normal, italic, oblique, initial, inherit). -| `padding` | `Number` or `Object` | `4` | Padding to apply around scale labels. Only `top` and `bottom` are implemented. +| `padding` | `Number/Object` | `4` | Padding to apply around scale labels. Only `top` and `bottom` are implemented. ## Creating Custom Tick Formats diff --git a/docs/axes/radial/linear.md b/docs/axes/radial/linear.md index f9811c6f985..1636781a84e 100644 --- a/docs/axes/radial/linear.md +++ b/docs/axes/radial/linear.md @@ -20,7 +20,7 @@ The following options are provided by the linear scale. They are all located in | Name | Type | Default | Description | -----| ---- | --------| ----------- -| `backdropColor` | Color | `'rgba(255, 255, 255, 0.75)'` | Color of label backdrops +| `backdropColor` | `Color` | `'rgba(255, 255, 255, 0.75)'` | Color of label backdrops | `backdropPaddingX` | `Number` | `2` | Horizontal padding of label backdrop. | `backdropPaddingY` | `Number` | `2` | Vertical padding of label backdrop. | `beginAtZero` | `Boolean` | `false` | if true, scale will include 0 if it is not already included. @@ -94,7 +94,7 @@ The following options are used to configure angled lines that radiate from the c | Name | Type | Default | Description | -----| ---- | --------| ----------- | `display` | `Boolean` | `true` | if true, angle lines are shown -| `color` | Color | `rgba(0, 0, 0, 0.1)` | Color of angled lines +| `color` | `Color` | `rgba(0, 0, 0, 0.1)` | Color of angled lines | `lineWidth` | `Number` | `1` | Width of angled lines ## Point Label Options @@ -104,7 +104,7 @@ The following options are used to configure the point labels that are shown on t | Name | Type | Default | Description | -----| ---- | --------| ----------- | `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` | `'#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. \ No newline at end of file +| `fontStyle` | `String` | `'normal'` | Font style to use when rendering point labels. diff --git a/docs/axes/styling.md b/docs/axes/styling.md index 79cb4e9d66f..1de31e01f6c 100644 --- a/docs/axes/styling.md +++ b/docs/axes/styling.md @@ -9,10 +9,10 @@ The grid line configuration is nested under the scale configuration in the `grid | Name | Type | Default | Description | -----| ---- | --------| ----------- | `display` | `Boolean` | `true` | If false, do not display grid lines for this axis. -| `color` | Color or Color[] | `'rgba(0, 0, 0, 0.1)'` | The color of the grid lines. If specified as an array, the first color applies to the first grid line, the second to the second grid line and so on. +| `color` | `Color/Color[]` | `'rgba(0, 0, 0, 0.1)'` | The color of the grid lines. If specified as an array, the first color applies to the first grid line, the second to the second grid line and so on. | `borderDash` | `Number[]` | `[]` | Length and spacing of dashes on grid lines. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash) | `borderDashOffset` | `Number` | `0` | Offset for line dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset) -| `lineWidth` | `Number or Number[]` | `1` | Stroke width of grid lines. +| `lineWidth` | `Number/Number[]` | `1` | Stroke width of grid lines. | `drawBorder` | `Boolean` | `true` | If true, draw border at the edge between the axis and the chart area. | `drawOnChartArea` | `Boolean` | `true` | If true, draw lines on the chart area inside the axis lines. This is useful when there are multiple axes and you need to control which grid lines are drawn. | `drawTicks` | `Boolean` | `true` | If true, draw lines beside the ticks in the axis area beside the chart. @@ -30,7 +30,7 @@ The tick configuration is nested under the scale configuration in the `ticks` ke | -----| ---- | --------| ----------- | `callback` | `Function` | | Returns the string representation of the tick value as it should be displayed on the chart. See [callback](../axes/labelling.md#creating-custom-tick-formats). | `display` | `Boolean` | `true` | If true, show tick marks -| `fontColor` | Color | `'#666'` | Font color for tick labels. +| `fontColor` | `Color` | `'#666'` | Font color for tick labels. | `fontFamily` | `String` | `"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"` | Font family for the tick labels, follows CSS font-family options. | `fontSize` | `Number` | `12` | Font size for the tick labels. | `fontStyle` | `String` | `'normal'` | Font style for the tick labels, follows CSS font-style options (i.e. normal, italic, oblique, initial, inherit). @@ -44,7 +44,7 @@ The minorTick configuration is nested under the ticks configuration in the `mino | Name | Type | Default | Description | -----| ---- | --------| ----------- | `callback` | `Function` | | Returns the string representation of the tick value as it should be displayed on the chart. See [callback](../axes/labelling.md#creating-custom-tick-formats). -| `fontColor` | Color | `'#666'` | Font color for tick labels. +| `fontColor` | `Color` | `'#666'` | Font color for tick labels. | `fontFamily` | `String` | `"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"` | Font family for the tick labels, follows CSS font-family options. | `fontSize` | `Number` | `12` | Font size for the tick labels. | `fontStyle` | `String` | `'normal'` | Font style for the tick labels, follows CSS font-style options (i.e. normal, italic, oblique, initial, inherit). @@ -55,7 +55,7 @@ The majorTick configuration is nested under the ticks configuration in the `majo | Name | Type | Default | Description | -----| ---- | --------| ----------- | `callback` | `Function` | | Returns the string representation of the tick value as it should be displayed on the chart. See [callback](../axes/labelling.md#creating-custom-tick-formats). -| `fontColor` | Color | `'#666'` | Font color for tick labels. +| `fontColor` | `Color` | `'#666'` | Font color for tick labels. | `fontFamily` | `String` | `"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"` | Font family for the tick labels, follows CSS font-family options. | `fontSize` | `Number` | `12` | Font size for the tick labels. | `fontStyle` | `String` | `'normal'` | Font style for the tick labels, follows CSS font-style options (i.e. normal, italic, oblique, initial, inherit). diff --git a/docs/charts/bar.md b/docs/charts/bar.md index 524c795214c..cd5e8c9f391 100644 --- a/docs/charts/bar.md +++ b/docs/charts/bar.md @@ -152,7 +152,7 @@ The `data` property of a dataset for a bar chart is specified as a an array of n data: [20, 10] ``` -You can also specify the dataset as x/y coordinates. +You can also specify the dataset as x/y coordinates when using the [time scale](./time.md). ```javascript data: [{x:'2016-12-25', y:20}, {x:'2016-12-26', y:10}] diff --git a/docs/charts/line.md b/docs/charts/line.md index cd4141a49a0..40441b7afe3 100644 --- a/docs/charts/line.md +++ b/docs/charts/line.md @@ -50,7 +50,7 @@ All point* properties can be specified as an array. If these are set to an array | `yAxisID` | `String` | The ID of the y axis to plot this dataset on. If not specified, this defaults to the ID of the first found y axis. | `backgroundColor` | `Color` | The fill color under the line. See [Colors](../general/colors.md#colors) | `borderColor` | `Color` | The color of the line. See [Colors](../general/colors.md#colors) -| `borderWidth` | `Number/` | The width of the line in pixels. +| `borderWidth` | `Number` | The width of the line in pixels. | `borderDash` | `Number[]` | Length and spacing of dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash) | `borderDashOffset` | `Number` | Offset for line dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset) | `borderCapStyle` | `String` | Cap style of the line. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineCap) diff --git a/docs/configuration/legend.md b/docs/configuration/legend.md index 4cad48b556e..bce69af79db 100644 --- a/docs/configuration/legend.md +++ b/docs/configuration/legend.md @@ -31,7 +31,7 @@ The legend label configuration is nested below the legend configuration using th | `boxWidth` | `Number` | `40` | width of coloured box | `fontSize` | `Number` | `12` | font size of text | `fontStyle` | `String` | `'normal'` | font style of text -| `fontColor` | Color | `'#666'` | Color of text +| `fontColor` | `Color` | `'#666'` | Color of text | `fontFamily` | `String` | `"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"` | Font family of legend text. | `padding` | `Number` | `10` | Padding between rows of colored boxes. | `generateLabels` | `Function` | | Generates legend items for each thing in the legend. Default implementation returns the text + styling for the color box. See [Legend Item](#legend-item-interface) for details. diff --git a/docs/configuration/title.md b/docs/configuration/title.md index b2c04cd8fbe..e206dfe0c55 100644 --- a/docs/configuration/title.md +++ b/docs/configuration/title.md @@ -11,11 +11,11 @@ The title configuration is passed into the `options.title` namespace. The global | `position` | `String` | `'top'` | Position of title. [more...](#position) | `fontSize` | `Number` | `12` | Font size | `fontFamily` | `String` | `"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"` | Font family for the title text. -| `fontColor` | Color | `'#666'` | Font color +| `fontColor` | `Color` | `'#666'` | Font color | `fontStyle` | `String` | `'bold'` | Font style | `padding` | `Number` | `10` | Number of pixels to add above and below the title text. -| `lineHeight` | `Number|String` | `1.2` | Height of an individual line of text (see [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/line-height)) -| `text` | `String/String[]` | `''` | Title text to display. If specified as an array, text is rendered on multiple lines. +| `lineHeight` | `Number/String` | `1.2` | Height of an individual line of text (see [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/line-height)) +| `text` | `String/String[]` | `''` | Title text to display. If specified as an array, text is rendered on multiple lines. ### Position Possible title position values are: diff --git a/docs/configuration/tooltip.md b/docs/configuration/tooltip.md index bb3b7a6aefb..51004843a33 100644 --- a/docs/configuration/tooltip.md +++ b/docs/configuration/tooltip.md @@ -14,22 +14,22 @@ The tooltip configuration is passed into the `options.tooltips` namespace. The g | `callbacks` | `Object` | | See the [callbacks section](#tooltip-callbacks) | `itemSort` | `Function` | | Sort tooltip items. [more...](#sort-callback) | `filter` | `Function` | | Filter tooltip items. [more...](#filter-callback) -| `backgroundColor` | Color | `'rgba(0,0,0,0.8)'` | Background color of the tooltip. +| `backgroundColor` | `Color` | `'rgba(0,0,0,0.8)'` | Background color of the tooltip. | `titleFontFamily` | `String` | `"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"` | title font | `titleFontSize` | `Number` | `12` | Title font size | `titleFontStyle` | `String` | `'bold'` | Title font style -| `titleFontColor` | Color | `'#fff'` | Title font color +| `titleFontColor` | `Color` | `'#fff'` | Title font color | `titleSpacing` | `Number` | `2` | Spacing to add to top and bottom of each title line. | `titleMarginBottom` | `Number` | `6` | Margin to add on bottom of title section. | `bodyFontFamily` | `String` | `"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"` | body line font | `bodyFontSize` | `Number` | `12` | Body font size | `bodyFontStyle` | `String` | `'normal'` | Body font style -| `bodyFontColor` | Color | `'#fff'` | Body font color +| `bodyFontColor` | `Color` | `'#fff'` | Body font color | `bodySpacing` | `Number` | `2` | Spacing to add to top and bottom of each tooltip item. | `footerFontFamily` | `String` | `"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"` | footer font | `footerFontSize` | `Number` | `12` | Footer font size | `footerFontStyle` | `String` | `'bold'` | Footer font style -| `footerFontColor` | Color | `'#fff'` | Footer font color +| `footerFontColor` | `Color` | `'#fff'` | Footer font color | `footerSpacing` | `Number` | `2` | Spacing to add to top and bottom of each fotter line. | `footerMarginTop` | `Number` | `6` | Margin to add before drawing the footer. | `xPadding` | `Number` | `6` | Padding to add on left and right of tooltip. @@ -37,9 +37,9 @@ The tooltip configuration is passed into the `options.tooltips` namespace. The g | `caretPadding` | `Number` | `2` | Extra distance to move the end of the tooltip arrow away from the tooltip point. | `caretSize` | `Number` | `5` | Size, in px, of the tooltip arrow. | `cornerRadius` | `Number` | `6` | Radius of tooltip corner curves. -| `multiKeyBackground` | Color | `'#fff'` | Color to draw behind the colored boxes when multiple items are in the tooltip +| `multiKeyBackground` | `Color` | `'#fff'` | Color to draw behind the colored boxes when multiple items are in the tooltip | `displayColors` | `Boolean` | `true` | if true, color boxes are shown in the tooltip -| `borderColor` | Color | `'rgba(0,0,0,0)'` | Color of the border +| `borderColor` | `Color` | `'rgba(0,0,0,0)'` | Color of the border | `borderWidth` | `Number` | `0` | Size of the border ### Position Modes @@ -51,6 +51,28 @@ The tooltip configuration is passed into the `options.tooltips` namespace. The g New modes can be defined by adding functions to the Chart.Tooltip.positioners map. +Example: +```javascript +/** + * Custom positioner + * @function Chart.Tooltip.positioners.custom + * @param elements {Chart.Element[]} the tooltip elements + * @param eventPosition {Point} the position of the event in canvas coordinates + * @returns {Point} the tooltip position + */ +Chart.Tooltip.positioners.custom = function(elements, eventPosition) { + /** @type {Chart.Tooltip} */ + var tooltip = this; + + /* ... */ + + return { + x: 0, + y: 0 + }; +} +``` + ### Sort Callback Allows sorting of [tooltip items](#tooltip-item-interface). Must implement at minimum a function that can be passed to [Array.prototype.sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort). This function can also accept a third parameter that is the data object passed to the chart. @@ -243,7 +265,7 @@ The tooltip model contains parameters that can be used to render the tooltip. // Body // The body lines that need to be rendered - // Each pbject contains 3 parameters + // Each object contains 3 parameters // before: String[] // lines of text before the line with the color square // lines: String[], // lines of text to render as the main item with color square // after: String[], // lines of text to render after the main lines diff --git a/docs/developers/README.md b/docs/developers/README.md index d2b0635b095..3826230a699 100644 --- a/docs/developers/README.md +++ b/docs/developers/README.md @@ -22,7 +22,12 @@ Latest builds are available for testing at: # Browser support -Chart.js offers support for all browsers where canvas is supported. +Chart.js offers support for the following browsers: +* Chrome 50+ +* Firefox 45+ +* Internet Explorer 11 +* Edge 14+ +* Safari 9+ Browser support for the canvas element is available in all modern & major mobile browsers. [CanIUse](http://caniuse.com/#feat=canvas) diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index dd8cfa4a790..c8291d260c2 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -3,6 +3,7 @@ Chart.js can be installed via npm or bower. It is recommended to get Chart.js th ## npm [![npm](https://img.shields.io/npm/v/chart.js.svg?style=flat-square&maxAge=600)](https://npmjs.com/package/chart.js) +[![npm](https://img.shields.io/npm/dm/chart.js.svg?style=flat-square&maxAge=600)](https://npmjs.com/package/chart.js) ```bash npm install chart.js --save @@ -16,9 +17,19 @@ bower install chart.js --save ``` ## CDN -[![cdn](https://img.shields.io/cdnjs/v/Chart.js.svg?label=cdn&style=flat-square&maxAge=600)](https://cdnjs.com/libraries/Chart.js) +### CDNJS +[![cdnjs](https://img.shields.io/cdnjs/v/Chart.js.svg?style=flat-square&maxAge=600)](https://cdnjs.com/libraries/Chart.js) -or just use these [Chart.js CDN](https://cdnjs.com/libraries/Chart.js) links. +Chart.js built files are available on [CDNJS](https://cdnjs.com/): + +https://cdnjs.com/libraries/Chart.js + +### jsDelivr +[![jsdelivr](https://img.shields.io/npm/v/chart.js.svg?label=jsdelivr&style=flat-square&maxAge=600)](https://cdn.jsdelivr.net/npm/chart.js@latest/dist/) [![jsdelivr hits](https://data.jsdelivr.com/v1/package/npm/chart.js/badge)](https://www.jsdelivr.com/package/npm/chart.js) + +Chart.js built files are also available through [jsDelivr](http://www.jsdelivr.com/): + +https://www.jsdelivr.com/package/npm/chart.js?path=dist ## Github [![github](https://img.shields.io/github/release/chartjs/Chart.js.svg?style=flat-square&maxAge=600)](https://github.com/chartjs/Chart.js/releases/latest) diff --git a/karma.conf.js b/karma.conf.js index 3ef7f49bfa7..5601cbd7212 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -14,7 +14,13 @@ module.exports = function(karma) { browserify: { debug: true - } + }, + + // These settings deal with browser disconnects. We had seen test flakiness from Firefox + // [Firefox 56.0.0 (Linux 0.0.0)]: Disconnected (1 times), because no message in 10000 ms. + // https://github.com/jasmine/jasmine/issues/1327#issuecomment-332939551 + browserNoActivityTimeout: 60000, + browserDisconnectTolerance: 3 }; // https://swizec.com/blog/how-to-run-javascript-tests-in-chrome-on-travis/swizec/6647 diff --git a/package.json b/package.json index 445dc877ab0..a7a504887fd 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "chart.js", "homepage": "http://www.chartjs.org", "description": "Simple HTML5 charts using the canvas element.", - "version": "2.7.0", + "version": "2.7.1", "license": "MIT", "main": "src/chart.js", "repository": { diff --git a/src/core/core.controller.js b/src/core/core.controller.js index 03dfedb7196..9e4984a9af6 100644 --- a/src/core/core.controller.js +++ b/src/core/core.controller.js @@ -371,6 +371,14 @@ module.exports = function(Chart) { me.updateDatasets(); + // Need to reset tooltip in case it is displayed with elements that are removed + // after update. + me.tooltip.initialize(); + + // Last active contains items that were previously in the tooltip. + // When we reset the tooltip, we need to clear it + me.lastActive = []; + // Do this before render so that any plugins that need final scale updates can use it plugins.notify(me, 'afterUpdate'); @@ -528,9 +536,7 @@ module.exports = function(Chart) { } me.drawDatasets(easingValue); - - // Finally draw the tooltip - me.tooltip.draw(); + me._drawTooltip(easingValue); plugins.notify(me, 'afterDraw', [easingValue]); }, @@ -595,6 +601,28 @@ module.exports = function(Chart) { plugins.notify(me, 'afterDatasetDraw', [args]); }, + /** + * Draws tooltip unless a plugin returns `false` to the `beforeTooltipDraw` + * hook, in which case, plugins will not be called on `afterTooltipDraw`. + * @private + */ + _drawTooltip: function(easingValue) { + var me = this; + var tooltip = me.tooltip; + var args = { + tooltip: tooltip, + easingValue: easingValue + }; + + if (plugins.notify(me, 'beforeTooltipDraw', [args]) === false) { + return; + } + + tooltip.draw(); + + plugins.notify(me, 'afterTooltipDraw', [args]); + }, + // Get the single element that was clicked on // @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw getElementAtEvent: function(e) { diff --git a/src/core/core.helpers.js b/src/core/core.helpers.js index 0b9cea52eb3..713362cd8d1 100644 --- a/src/core/core.helpers.js +++ b/src/core/core.helpers.js @@ -10,16 +10,6 @@ module.exports = function(Chart) { // -- Basic js utility methods - helpers.extend = function(base) { - var setFn = function(value, key) { - base[key] = value; - }; - for (var i = 1, ilen = arguments.length; i < ilen; i++) { - helpers.each(arguments[i], setFn); - } - return base; - }; - helpers.configMerge = function(/* objects ... */) { return helpers.merge(helpers.clone(arguments[0]), [].slice.call(arguments, 1), { merger: function(key, target, source, options) { @@ -125,29 +115,7 @@ module.exports = function(Chart) { } } }; - helpers.inherits = function(extensions) { - // Basic javascript inheritance based on the model created in Backbone.js - var me = this; - var ChartElement = (extensions && extensions.hasOwnProperty('constructor')) ? extensions.constructor : function() { - return me.apply(this, arguments); - }; - - var Surrogate = function() { - this.constructor = ChartElement; - }; - Surrogate.prototype = me.prototype; - ChartElement.prototype = new Surrogate(); - - ChartElement.extend = helpers.inherits; - - if (extensions) { - helpers.extend(ChartElement.prototype, extensions); - } - - ChartElement.__super__ = me.prototype; - return ChartElement; - }; // -- Math methods helpers.isNumber = function(n) { return !isNaN(parseFloat(n)) && isFinite(n); diff --git a/src/core/core.interaction.js b/src/core/core.interaction.js index d984126ab53..be85a080f76 100644 --- a/src/core/core.interaction.js +++ b/src/core/core.interaction.js @@ -215,7 +215,7 @@ module.exports = { * @private */ 'x-axis': function(chart, e) { - return indexMode(chart, e, {intersect: true}); + return indexMode(chart, e, {intersect: false}); }, /** diff --git a/src/core/core.plugin.js b/src/core/core.plugin.js index 399075b812c..0b7423a6904 100644 --- a/src/core/core.plugin.js +++ b/src/core/core.plugin.js @@ -323,6 +323,27 @@ module.exports = function(Chart) { * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0. * @param {Object} options - The plugin options. */ + /** + * @method IPlugin#beforeTooltipDraw + * @desc Called before drawing the `tooltip`. If any plugin returns `false`, + * the tooltip drawing is cancelled until another `render` is triggered. + * @param {Chart} chart - The chart instance. + * @param {Object} args - The call arguments. + * @param {Object} args.tooltip - The tooltip. + * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0. + * @param {Object} options - The plugin options. + * @returns {Boolean} `false` to cancel the chart tooltip drawing. + */ + /** + * @method IPlugin#afterTooltipDraw + * @desc Called after drawing the `tooltip`. Note that this hook will not + * be called if the tooltip drawing has been previously cancelled. + * @param {Chart} chart - The chart instance. + * @param {Object} args - The call arguments. + * @param {Object} args.tooltip - The tooltip. + * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0. + * @param {Object} options - The plugin options. + */ /** * @method IPlugin#beforeEvent * @desc Called before processing the specified `event`. If any plugin returns `false`, diff --git a/src/core/core.scale.js b/src/core/core.scale.js index f48b67a5ec1..ffe13cbff82 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -538,17 +538,33 @@ module.exports = function(Chart) { return rawValue; }, - // Used to get the value to display in the tooltip for the data at the given index - // function getLabelForIndex(index, datasetIndex) + /** + * Used to get the value to display in the tooltip for the data at the given index + * @param index + * @param datasetIndex + */ getLabelForIndex: helpers.noop, - // Used to get data value locations. Value can either be an index or a numerical value + /** + * Returns the location of the given data point. Value can either be an index or a numerical value + * The coordinate (0, 0) is at the upper-left corner of the canvas + * @param value + * @param index + * @param datasetIndex + */ getPixelForValue: helpers.noop, - // Used to get the data value from a given pixel. This is the inverse of getPixelForValue + /** + * Used to get the data value from a given pixel. This is the inverse of getPixelForValue + * The coordinate (0, 0) is at the upper-left corner of the canvas + * @param pixel + */ getValueForPixel: helpers.noop, - // Used for tick location, should + /** + * Returns the location of the tick at the given index + * The coordinate (0, 0) is at the upper-left corner of the canvas + */ getPixelForTick: function(index) { var me = this; var offset = me.options.offset; @@ -569,7 +585,10 @@ module.exports = function(Chart) { return me.top + (index * (innerHeight / (me._ticks.length - 1))); }, - // Utility for getting the pixel location of a percentage of scale + /** + * Utility for getting the pixel location of a percentage of scale + * The coordinate (0, 0) is at the upper-left corner of the canvas + */ getPixelForDecimal: function(decimal) { var me = this; if (me.isHorizontal()) { @@ -583,6 +602,10 @@ module.exports = function(Chart) { return me.top + (decimal * me.height); }, + /** + * Returns the pixel for the minimum chart value + * The coordinate (0, 0) is at the upper-left corner of the canvas + */ getBasePixel: function() { return this.getPixelForValue(this.getBaseValue()); }, @@ -639,7 +662,7 @@ module.exports = function(Chart) { // Since we always show the last tick,we need may need to hide the last shown one before shouldSkip = (skipRatio > 1 && i % skipRatio > 0) || (i % skipRatio === 0 && i + skipRatio >= tickCount); - if (shouldSkip && i !== tickCount - 1 || helpers.isNullOrUndef(tick.label)) { + if (shouldSkip && i !== tickCount - 1) { // leave tick in place but make sure it's not displayed (#4635) delete tick.label; } @@ -689,7 +712,7 @@ module.exports = function(Chart) { helpers.each(ticks, function(tick, index) { // autoskipper skipped this tick (#4635) - if (tick.label === undefined) { + if (helpers.isNullOrUndef(tick.label)) { return; } diff --git a/src/core/core.tooltip.js b/src/core/core.tooltip.js index 048e1a08a69..73460f8d106 100644 --- a/src/core/core.tooltip.js +++ b/src/core/core.tooltip.js @@ -384,6 +384,7 @@ module.exports = function(Chart) { Chart.Tooltip = Element.extend({ initialize: function() { this._model = getBaseModel(this._options); + this._lastActive = []; }, // Get the title @@ -495,7 +496,7 @@ module.exports = function(Chart) { var labelColors = []; var labelTextColors = []; - tooltipPosition = Chart.Tooltip.positioners[opts.position](active, me._eventPosition); + tooltipPosition = Chart.Tooltip.positioners[opts.position].call(me, active, me._eventPosition); var tooltipItems = []; for (i = 0, len = active.length; i < len; ++i) { @@ -677,6 +678,7 @@ module.exports = function(Chart) { }; // Before body lines + ctx.fillStyle = mergeOpacity(vm.bodyFontColor, opacity); helpers.each(vm.beforeBody, fillLineOfText); var drawColorBoxes = vm.displayColors; @@ -684,6 +686,8 @@ module.exports = function(Chart) { // Draw body lines now helpers.each(body, function(bodyItem, i) { + var textColor = mergeOpacity(vm.labelTextColors[i], opacity); + ctx.fillStyle = textColor; helpers.each(bodyItem.before, fillLineOfText); helpers.each(bodyItem.lines, function(line) { @@ -701,7 +705,6 @@ module.exports = function(Chart) { // Inner square ctx.fillStyle = mergeOpacity(vm.labelColors[i].backgroundColor, opacity); ctx.fillRect(pt.x + 1, pt.y + 1, bodyFontSize - 2, bodyFontSize - 2); - var textColor = mergeOpacity(vm.labelTextColors[i], opacity); ctx.fillStyle = textColor; } diff --git a/src/helpers/helpers.canvas.js b/src/helpers/helpers.canvas.js index 1429a31e6e0..13b110268b9 100644 --- a/src/helpers/helpers.canvas.js +++ b/src/helpers/helpers.canvas.js @@ -47,7 +47,7 @@ var exports = module.exports = { drawPoint: function(ctx, style, radius, x, y) { var type, edgeLength, xOffset, yOffset, height, size; - if (typeof style === 'object') { + if (style && typeof style === 'object') { type = style.toString(); if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') { ctx.drawImage(style, x - style.width / 2, y - style.height / 2, style.width, style.height); diff --git a/src/helpers/helpers.core.js b/src/helpers/helpers.core.js index 1ada7a754b1..2a4b0098ccf 100644 --- a/src/helpers/helpers.core.js +++ b/src/helpers/helpers.core.js @@ -250,6 +250,48 @@ var helpers = { */ mergeIf: function(target, source) { return helpers.merge(target, source, {merger: helpers._mergerIf}); + }, + + /** + * Applies the contents of two or more objects together into the first object. + * @param {Object} target - The target object in which all objects are merged into. + * @param {Object} arg1 - Object containing additional properties to merge in target. + * @param {Object} argN - Additional objects containing properties to merge in target. + * @returns {Object} The `target` object. + */ + extend: function(target) { + var setFn = function(value, key) { + target[key] = value; + }; + for (var i = 1, ilen = arguments.length; i < ilen; ++i) { + helpers.each(arguments[i], setFn); + } + return target; + }, + + /** + * Basic javascript inheritance based on the model created in Backbone.js + */ + inherits: function(extensions) { + var me = this; + var ChartElement = (extensions && extensions.hasOwnProperty('constructor')) ? extensions.constructor : function() { + return me.apply(this, arguments); + }; + + var Surrogate = function() { + this.constructor = ChartElement; + }; + + Surrogate.prototype = me.prototype; + ChartElement.prototype = new Surrogate(); + ChartElement.extend = helpers.inherits; + + if (extensions) { + helpers.extend(ChartElement.prototype, extensions); + } + + ChartElement.__super__ = me.prototype; + return ChartElement; } }; diff --git a/src/platforms/platform.dom.js b/src/platforms/platform.dom.js index a14119ad425..a882d22a537 100644 --- a/src/platforms/platform.dom.js +++ b/src/platforms/platform.dom.js @@ -236,6 +236,13 @@ function watchForRender(node, handler) { addEventListener(node, type, proxy); }); + // #4737: Chrome might skip the CSS animation when the CSS_RENDER_MONITOR class + // is removed then added back immediately (same animation frame?). Accessing the + // `offsetParent` property will force a reflow and re-evaluate the CSS animation. + // https://gist.github.com/paulirish/5d52fb081b3570c81e3a#box-metrics + // https://github.com/chartjs/Chart.js/issues/4737 + expando.reflow = !!node.offsetParent; + node.classList.add(CSS_RENDER_MONITOR); } diff --git a/src/scales/scale.time.js b/src/scales/scale.time.js index 27c6255fa06..4fe39b81082 100644 --- a/src/scales/scale.time.js +++ b/src/scales/scale.time.js @@ -13,47 +13,47 @@ var MAX_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; var INTERVALS = { millisecond: { - major: true, + common: true, size: 1, steps: [1, 2, 5, 10, 20, 50, 100, 250, 500] }, second: { - major: true, + common: true, size: 1000, steps: [1, 2, 5, 10, 30] }, minute: { - major: true, + common: true, size: 60000, steps: [1, 2, 5, 10, 30] }, hour: { - major: true, + common: true, size: 3600000, steps: [1, 2, 3, 6, 12] }, day: { - major: true, + common: true, size: 86400000, steps: [1, 2, 5] }, week: { - major: false, + common: false, size: 604800000, steps: [1, 2, 3, 4] }, month: { - major: true, + common: true, size: 2.628e9, steps: [1, 2, 3] }, quarter: { - major: false, + common: false, size: 7.884e9, steps: [1, 2, 3, 4] }, year: { - major: true, + common: true, size: 3.154e10 } }; @@ -253,7 +253,10 @@ function determineStepSize(min, max, unit, capacity) { return factor; } -function determineUnit(minUnit, min, max, capacity) { +/** + * Figures out what unit results in an appropriate number of auto-generated ticks + */ +function determineUnitForAutoTicks(minUnit, min, max, capacity) { var ilen = UNITS.length; var i, interval, factor; @@ -261,7 +264,7 @@ function determineUnit(minUnit, min, max, capacity) { interval = INTERVALS[UNITS[i]]; factor = interval.steps ? interval.steps[interval.steps.length - 1] : MAX_INTEGER; - if (Math.ceil((max - min) / (factor * interval.size)) <= capacity) { + if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) { return UNITS[i]; } } @@ -269,9 +272,27 @@ function determineUnit(minUnit, min, max, capacity) { return UNITS[ilen - 1]; } +/** + * Figures out what unit to format a set of ticks with + */ +function determineUnitForFormatting(ticks, minUnit, min, max) { + var duration = moment.duration(moment(max).diff(moment(min))); + var ilen = UNITS.length; + var i, unit; + + for (i = ilen - 1; i >= UNITS.indexOf(minUnit); i--) { + unit = UNITS[i]; + if (INTERVALS[unit].common && duration.as(unit) >= ticks.length) { + return unit; + } + } + + return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0]; +} + function determineMajorUnit(unit) { for (var i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i) { - if (INTERVALS[UNITS[i]].major) { + if (INTERVALS[UNITS[i]].common) { return UNITS[i]; } } @@ -283,8 +304,10 @@ function determineMajorUnit(unit) { * Important: this method can return ticks outside the min and max range, it's the * responsibility of the calling code to clamp values if needed. */ -function generate(min, max, minor, major, capacity, options) { +function generate(min, max, capacity, options) { var timeOpts = options.time; + var minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, capacity); + var major = determineMajorUnit(minor); var stepSize = helpers.valueOrDefault(timeOpts.stepSize, timeOpts.unitStepSize); var weekday = minor === 'week' ? timeOpts.isoWeekday : false; var majorTicksEnabled = options.ticks.major.enabled; @@ -481,8 +504,8 @@ module.exports = function(Chart) { var me = this; var chart = me.chart; var timeOpts = me.options.time; - var min = parse(timeOpts.min, me) || MAX_INTEGER; - var max = parse(timeOpts.max, me) || MIN_INTEGER; + var min = MAX_INTEGER; + var max = MIN_INTEGER; var timestamps = []; var datasets = []; var labels = []; @@ -529,6 +552,9 @@ module.exports = function(Chart) { max = Math.max(max, timestamps[timestamps.length - 1]); } + min = parse(timeOpts.min, me) || min; + max = parse(timeOpts.max, me) || max; + // 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; @@ -553,10 +579,6 @@ module.exports = function(Chart) { var max = me.max; var options = me.options; var timeOpts = options.time; - var formats = timeOpts.displayFormats; - var capacity = me.getLabelCapacity(min); - var unit = timeOpts.unit || determineUnit(timeOpts.minUnit, min, max, capacity); - var majorUnit = determineMajorUnit(unit); var timestamps = []; var ticks = []; var i, ilen, timestamp; @@ -570,7 +592,7 @@ module.exports = function(Chart) { break; case 'auto': default: - timestamps = generate(min, max, unit, majorUnit, capacity, options); + timestamps = generate(min, max, me.getLabelCapacity(min), options); } if (options.bounds === 'ticks' && timestamps.length) { @@ -594,14 +616,12 @@ module.exports = function(Chart) { me.max = max; // PRIVATE - me._unit = unit; - me._majorUnit = majorUnit; - me._minorFormat = formats[unit]; - me._majorFormat = formats[majorUnit]; + me._unit = timeOpts.unit || determineUnitForFormatting(ticks, timeOpts.minUnit, me.min, me.max); + me._majorUnit = determineMajorUnit(me._unit); me._table = buildLookupTable(me._timestamps.data, min, max, options.distribution); me._offsets = computeOffsets(me._table, ticks, min, max, options); - return ticksFromTimestamps(ticks, majorUnit); + return ticksFromTimestamps(ticks, me._majorUnit); }, getLabelForIndex: function(index, datasetIndex) { @@ -625,16 +645,18 @@ module.exports = function(Chart) { * Function to format an individual tick mark * @private */ - tickFormatFunction: function(tick, index, ticks) { + tickFormatFunction: function(tick, index, ticks, formatOverride) { var me = this; var options = me.options; var time = tick.valueOf(); + var formats = options.time.displayFormats; + var minorFormat = formats[me._unit]; var majorUnit = me._majorUnit; - var majorFormat = me._majorFormat; - var majorTime = tick.clone().startOf(me._majorUnit).valueOf(); + var majorFormat = formats[majorUnit]; + var majorTime = tick.clone().startOf(majorUnit).valueOf(); var majorTickOpts = options.ticks.major; var major = majorTickOpts.enabled && majorUnit && majorFormat && time === majorTime; - var label = tick.format(major ? majorFormat : me._minorFormat); + var label = tick.format(formatOverride ? formatOverride : major ? majorFormat : minorFormat); var tickOpts = major ? majorTickOpts : options.ticks.minor; var formatter = helpers.valueOrDefault(tickOpts.callback, tickOpts.userCallback); @@ -720,9 +742,9 @@ module.exports = function(Chart) { getLabelCapacity: function(exampleTime) { var me = this; - me._minorFormat = me.options.time.displayFormats.millisecond; // Pick the longest format for guestimation + var formatOverride = me.options.time.displayFormats.millisecond; // Pick the longest format for guestimation - var exampleLabel = me.tickFormatFunction(moment(exampleTime), 0, []); + var exampleLabel = me.tickFormatFunction(moment(exampleTime), 0, [], formatOverride); var tickLabelWidth = me.getLabelWidth(exampleLabel); var innerWidth = me.isHorizontal() ? me.width : me.height; diff --git a/test/jasmine.utils.js b/test/jasmine.utils.js index c94249406a2..7db35642e06 100644 --- a/test/jasmine.utils.js +++ b/test/jasmine.utils.js @@ -75,7 +75,13 @@ function acquireChart(config, options) { wrapper.appendChild(canvas); window.document.body.appendChild(wrapper); - chart = new Chart(canvas.getContext('2d'), config); + try { + chart = new Chart(canvas.getContext('2d'), config); + } catch (e) { + window.document.body.removeChild(wrapper); + throw e; + } + chart.$test = { persistent: options.persistent, wrapper: wrapper diff --git a/test/specs/core.controller.tests.js b/test/specs/core.controller.tests.js index beac21e0daa..3ec8da50b76 100644 --- a/test/specs/core.controller.tests.js +++ b/test/specs/core.controller.tests.js @@ -495,6 +495,45 @@ describe('Chart', function() { }); }); }); + + // https://github.com/chartjs/Chart.js/issues/4737 + it('should resize the canvas when re-creating the chart', function(done) { + var chart = acquireChart({ + options: { + responsive: true + } + }, { + wrapper: { + style: 'width: 320px' + } + }); + + waitForResize(chart, function() { + var canvas = chart.canvas; + expect(chart).toBeChartOfSize({ + dw: 320, dh: 320, + rw: 320, rh: 320, + }); + + chart.destroy(); + chart = new Chart(canvas, { + type: 'line', + options: { + responsive: true + } + }); + + canvas.parentNode.style.width = '455px'; + waitForResize(chart, function() { + expect(chart).toBeChartOfSize({ + dw: 455, dh: 455, + rw: 455, rh: 455, + }); + + done(); + }); + }); + }); }); describe('config.options.responsive: true (maintainAspectRatio: true)', function() { @@ -627,7 +666,7 @@ describe('Chart', function() { }); }); - describe('config.options.devicePixelRatio 3', function() { + describe('config.options.devicePixelRatio', function() { beforeEach(function() { this.devicePixelRatio = window.devicePixelRatio; window.devicePixelRatio = 1; @@ -783,6 +822,54 @@ describe('Chart', function() { expect(chart.tooltip._options).toEqual(jasmine.objectContaining(newTooltipConfig)); }); + it ('should reset the tooltip on update', function() { + var chart = acquireChart({ + type: 'line', + data: { + labels: ['A', 'B', 'C', 'D'], + datasets: [{ + data: [10, 20, 30, 100] + }] + }, + options: { + responsive: true, + tooltip: { + mode: 'nearest' + } + } + }); + + // Trigger an event over top of a point to + // put an item into the tooltip + var meta = chart.getDatasetMeta(0); + var point = meta.data[1]; + + var node = chart.canvas; + var rect = node.getBoundingClientRect(); + + var evt = new MouseEvent('mousemove', { + view: window, + bubbles: true, + cancelable: true, + clientX: rect.left + point._model.x, + clientY: 0 + }); + + // Manually trigger rather than having an async test + node.dispatchEvent(evt); + + // Check and see if tooltip was displayed + var tooltip = chart.tooltip; + + expect(chart.lastActive).toEqual([point]); + expect(tooltip._lastActive).toEqual([]); + + // Update and confirm tooltip is reset + chart.update(); + expect(chart.lastActive).toEqual([]); + expect(tooltip._lastActive).toEqual([]); + }); + it ('should update the metadata', function() { var cfg = { data: { @@ -845,6 +932,8 @@ describe('Chart', function() { 'beforeDatasetDraw', 'afterDatasetDraw', 'afterDatasetsDraw', + 'beforeTooltipDraw', + 'afterTooltipDraw', 'afterDraw', 'afterRender', ], diff --git a/test/specs/core.helpers.tests.js b/test/specs/core.helpers.tests.js index dc865fb01b2..419428bf1d6 100644 --- a/test/specs/core.helpers.tests.js +++ b/test/specs/core.helpers.tests.js @@ -6,26 +6,6 @@ describe('Core helper tests', function() { helpers = window.Chart.helpers; }); - it('should extend an object', function() { - var original = { - myProp1: 'abc', - myProp2: 56 - }; - - var extension = { - myProp3: [2, 5, 6], - myProp2: 0 - }; - - helpers.extend(original, extension); - - expect(original).toEqual({ - myProp1: 'abc', - myProp2: 0, - myProp3: [2, 5, 6], - }); - }); - it('should merge a normal config without scales', function() { var baseConfig = { valueProp: 5, diff --git a/test/specs/core.tooltip.tests.js b/test/specs/core.tooltip.tests.js index 73d943bd853..632f8121daf 100755 --- a/test/specs/core.tooltip.tests.js +++ b/test/specs/core.tooltip.tests.js @@ -822,4 +822,64 @@ describe('Core.Tooltip', function() { node.dispatchEvent(firstEvent); expect(tooltip.update).not.toHaveBeenCalled(); }); + + describe('positioners', function() { + it('Should call custom positioner with correct parameters and scope', function() { + + Chart.Tooltip.positioners.test = function() { + return {x: 0, y: 0}; + }; + + spyOn(Chart.Tooltip.positioners, 'test').and.callThrough(); + + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + label: 'Dataset 1', + data: [10, 20, 30], + pointHoverBorderColor: 'rgb(255, 0, 0)', + pointHoverBackgroundColor: 'rgb(0, 255, 0)' + }, { + label: 'Dataset 2', + data: [40, 40, 40], + pointHoverBorderColor: 'rgb(0, 0, 255)', + pointHoverBackgroundColor: 'rgb(0, 255, 255)' + }], + labels: ['Point 1', 'Point 2', 'Point 3'] + }, + options: { + tooltips: { + mode: 'nearest', + position: 'test' + } + } + }); + + // Trigger an event over top of the + var pointIndex = 1; + var datasetIndex = 0; + var meta = chart.getDatasetMeta(datasetIndex); + var point = meta.data[pointIndex]; + var node = chart.canvas; + var rect = node.getBoundingClientRect(); + var evt = new MouseEvent('mousemove', { + view: window, + bubbles: true, + cancelable: true, + clientX: rect.left + point._model.x, + clientY: rect.top + point._model.y + }); + + // Manually trigger rather than having an async test + node.dispatchEvent(evt); + + var fn = Chart.Tooltip.positioners.test; + expect(fn.calls.count()).toBe(1); + expect(fn.calls.first().args[0] instanceof Array).toBe(true); + expect(fn.calls.first().args[1].hasOwnProperty('x')).toBe(true); + expect(fn.calls.first().args[1].hasOwnProperty('y')).toBe(true); + expect(fn.calls.first().object instanceof Chart.Tooltip).toBe(true); + }); + }); }); diff --git a/test/specs/global.deprecations.tests.js b/test/specs/global.deprecations.tests.js index c5bef5b20b4..f1091464d1e 100644 --- a/test/specs/global.deprecations.tests.js +++ b/test/specs/global.deprecations.tests.js @@ -325,6 +325,45 @@ describe('Deprecations', function() { }); }); + describe('Version 2.4.0', function() { + describe('x-axis mode', function() { + it ('behaves like index mode with intersect: false', function() { + var data = { + datasets: [{ + label: 'Dataset 1', + data: [10, 20, 30], + pointHoverBorderColor: 'rgb(255, 0, 0)', + pointHoverBackgroundColor: 'rgb(0, 255, 0)' + }, { + label: 'Dataset 2', + data: [40, 40, 40], + pointHoverBorderColor: 'rgb(0, 0, 255)', + pointHoverBackgroundColor: 'rgb(0, 255, 255)' + }], + labels: ['Point 1', 'Point 2', 'Point 3'] + }; + + var chart = window.acquireChart({ + type: 'line', + data: data + }); + var meta0 = chart.getDatasetMeta(0); + var meta1 = chart.getDatasetMeta(1); + + var evt = { + type: 'click', + chart: chart, + native: true, // needed otherwise things its a DOM event + x: 0, + y: 0 + }; + + var elements = Chart.Interaction.modes['x-axis'](chart, evt); + expect(elements).toEqual([meta0.data[0], meta1.data[0]]); + }); + }); + }); + describe('Version 2.1.5', function() { // https://github.com/chartjs/Chart.js/pull/2752 describe('Chart.pluginService', function() { diff --git a/test/specs/helpers.core.tests.js b/test/specs/helpers.core.tests.js index b13b7cad2af..80b0640b2cc 100644 --- a/test/specs/helpers.core.tests.js +++ b/test/specs/helpers.core.tests.js @@ -369,4 +369,57 @@ describe('Chart.helpers.core', function() { expect(output.o.a).not.toBe(a1); }); }); + + describe('extend', function() { + it('should merge object properties in target and return target', function() { + var target = {a: 'abc', b: 56}; + var object = {b: 0, c: [2, 5, 6]}; + var result = helpers.extend(target, object); + + expect(result).toBe(target); + expect(target).toEqual({a: 'abc', b: 0, c: [2, 5, 6]}); + }); + it('should merge multiple objects properties in target', function() { + var target = {a: 0, b: 1}; + var o0 = {a: 2, c: 3, d: 4}; + var o1 = {a: 5, c: 6}; + var o2 = {a: 7, e: 8}; + + helpers.extend(target, o0, o1, o2); + + expect(target).toEqual({a: 7, b: 1, c: 6, d: 4, e: 8}); + }); + it('should not deeply merge object properties in target', function() { + var target = {a: {b: 0, c: 1}}; + var object = {a: {b: 2, d: 3}}; + + helpers.extend(target, object); + + expect(target).toEqual({a: {b: 2, d: 3}}); + expect(target.a).toBe(object.a); + }); + }); + + describe('inherits', function() { + it('should return a derived class', function() { + var A = function() {}; + A.prototype.p0 = 41; + A.prototype.p1 = function() { + return '42'; + }; + + A.inherits = helpers.inherits; + var B = A.inherits({p0: 43, p2: [44]}); + var C = A.inherits({p3: 45, p4: [46]}); + var b = new B(); + + expect(b instanceof A).toBeTruthy(); + expect(b instanceof B).toBeTruthy(); + expect(b instanceof C).toBeFalsy(); + + expect(b.p0).toBe(43); + expect(b.p1()).toBe('42'); + expect(b.p2).toEqual([44]); + }); + }); }); diff --git a/test/specs/scale.time.tests.js b/test/specs/scale.time.tests.js index 98dd774b6be..19189040ee4 100755 --- a/test/specs/scale.time.tests.js +++ b/test/specs/scale.time.tests.js @@ -376,23 +376,36 @@ describe('Time scale tests', function() { var config; beforeEach(function() { config = Chart.helpers.clone(Chart.scaleService.getScaleDefaults('time')); + config.ticks.source = 'labels'; + config.time.unit = 'day'; }); - it('should use the min option', function() { - config.time.unit = 'day'; + it('should use the min option when less than first label for building ticks', function() { config.time.min = '2014-12-29T04:00:00'; var scale = createScale(mockData, config); - expect(scale.ticks[0]).toEqual('Dec 31'); + expect(scale.ticks[0]).toEqual('Jan 1'); }); - it('should use the max option', function() { - config.time.unit = 'day'; + it('should use the min option when greater than first label for building ticks', function() { + config.time.min = '2015-01-02T04:00:00'; + + var scale = createScale(mockData, config); + expect(scale.ticks[0]).toEqual('Jan 2'); + }); + + it('should use the max option when greater than last label for building ticks', function() { config.time.max = '2015-01-05T06:00:00'; var scale = createScale(mockData, config); + expect(scale.ticks[scale.ticks.length - 1]).toEqual('Jan 3'); + }); - expect(scale.ticks[scale.ticks.length - 1]).toEqual('Jan 5'); + it('should use the max option when less than last label for building ticks', function() { + config.time.max = '2015-01-02T23:00:00'; + + var scale = createScale(mockData, config); + expect(scale.ticks[scale.ticks.length - 1]).toEqual('Jan 2'); }); });