From 6ecad0b33cfbf7a449b1d957aaadae5ff93d2c76 Mon Sep 17 00:00:00 2001 From: Simon Brunel Date: Thu, 25 May 2017 15:53:14 +0200 Subject: [PATCH 001/112] Attempt to fix the failing deploy step --- .travis.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index f0290da2a..34e8a8b3b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,16 +38,16 @@ addons: # https://github.com/travis-ci/travis-ci/issues/5538#issuecomment-225025939 deploy: -- provider: script - script: ./scripts/release.sh - skip_cleanup: true - on: - branch: release - provider: script script: ./scripts/deploy.sh skip_cleanup: true on: all_branches: true +- provider: script + script: ./scripts/release.sh + skip_cleanup: true + on: + branch: release - provider: releases api_key: $GITHUB_AUTH_TOKEN file: From ea725c0d20a4f227e2a1c97ef4bce776c3ae3235 Mon Sep 17 00:00:00 2001 From: Ben McCann Date: Thu, 25 May 2017 08:52:39 -0700 Subject: [PATCH 002/112] Fix code climate badge and link (#4277) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d2ed99631..c4b7c5e39 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Chart.js -[![Build Status](https://travis-ci.org/chartjs/Chart.js.svg?branch=master)](https://travis-ci.org/chartjs/Chart.js) [![Code Climate](https://codeclimate.com/github/nnnick/Chart.js/badges/gpa.svg)](https://codeclimate.com/github/nnnick/Chart.js) [![Coverage Status](https://coveralls.io/repos/github/chartjs/Chart.js/badge.svg?branch=master)](https://coveralls.io/github/chartjs/Chart.js?branch=master) +[![Build Status](https://travis-ci.org/chartjs/Chart.js.svg?branch=master)](https://travis-ci.org/chartjs/Chart.js) [![Code Climate](https://codeclimate.com/github/chartjs/Chart.js/badges/gpa.svg)](https://codeclimate.com/github/chartjs/Chart.js) [![Coverage Status](https://coveralls.io/repos/github/chartjs/Chart.js/badge.svg?branch=master)](https://coveralls.io/github/chartjs/Chart.js?branch=master) [![Chart.js on Slack](https://img.shields.io/badge/slack-Chart.js-blue.svg)](https://chart-js-automation.herokuapp.com/) From 04ab523dd46e648a9555ba859d678ac25173cd26 Mon Sep 17 00:00:00 2001 From: Ben McCann Date: Thu, 25 May 2017 08:53:37 -0700 Subject: [PATCH 003/112] Upgrade dependencies (#4272) --- package.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 7963c6e58..7e4c19297 100644 --- a/package.json +++ b/package.json @@ -10,25 +10,25 @@ "url": "https://github.com/chartjs/Chart.js.git" }, "devDependencies": { - "browserify": "^13.0.0", - "browserify-istanbul": "^0.2.1", + "browserify": "^14.3.0", + "browserify-istanbul": "^2.0.0", "bundle-collapser": "^1.2.1", "child-process-promise": "^2.2.0", "coveralls": "^2.11.6", "gitbook-cli": "^2.3.0", "gulp": "3.9.x", - "gulp-concat": "~2.1.x", - "gulp-connect": "~2.0.5", + "gulp-concat": "~2.6.x", + "gulp-connect": "~5.0.0", "gulp-eslint": "^3.0.0", "gulp-file": "^0.3.0", - "gulp-html-validator": "^0.0.2", + "gulp-html-validator": "^0.0.5", "gulp-insert": "~0.5.0", "gulp-replace": "^0.5.4", - "gulp-size": "~0.4.0", + "gulp-size": "~2.1.0", "gulp-streamify": "^1.0.2", - "gulp-uglify": "~2.0.x", - "gulp-util": "~2.2.x", - "gulp-zip": "~3.2.0", + "gulp-uglify": "~2.1.x", + "gulp-util": "~3.0.x", + "gulp-zip": "~4.0.0", "jasmine": "^2.5.0", "jasmine-core": "^2.5.0", "karma": "^1.5.0", @@ -42,7 +42,7 @@ "pixelmatch": "^4.0.2", "vinyl-source-stream": "^1.1.0", "watchify": "^3.7.0", - "yargs": "^5.0.0" + "yargs": "^8.0.1" }, "spm": { "main": "Chart.js" From 1cbba830fdef5ac25f997deb5ae0683db9321a79 Mon Sep 17 00:00:00 2001 From: Ben McCann Date: Thu, 4 May 2017 21:34:23 -0700 Subject: [PATCH 004/112] Refactor time scale methods into a common location --- src/chart.js | 2 + src/core/core.ticks.js | 4 +- src/helpers/helpers.time.js | 196 +++++++++++++++++++++++++++++++++++ src/scales/scale.time.js | 200 ++---------------------------------- 4 files changed, 211 insertions(+), 191 deletions(-) create mode 100644 src/helpers/helpers.time.js diff --git a/src/chart.js b/src/chart.js index 4771b6d65..f36d240c2 100644 --- a/src/chart.js +++ b/src/chart.js @@ -4,6 +4,8 @@ var Chart = require('./core/core.js')(); require('./core/core.helpers')(Chart); +require('./helpers/helpers.time')(Chart); + require('./platforms/platform.js')(Chart); require('./core/core.canvasHelpers')(Chart); require('./core/core.element')(Chart); diff --git a/src/core/core.ticks.js b/src/core/core.ticks.js index 8d84f1f23..6a6794192 100644 --- a/src/core/core.ticks.js +++ b/src/core/core.ticks.js @@ -141,7 +141,9 @@ module.exports = function(Chart) { ticks.push(lastTick); return ticks; - } + }, + + time: helpers.time.generateTicks }, /** diff --git a/src/helpers/helpers.time.js b/src/helpers/helpers.time.js new file mode 100644 index 000000000..e055fb2b1 --- /dev/null +++ b/src/helpers/helpers.time.js @@ -0,0 +1,196 @@ +'use strict'; + +var moment = require('moment'); +moment = typeof(moment) === 'function' ? moment : window.moment; + +module.exports = function(Chart) { + + var interval = { + millisecond: { + size: 1, + steps: [1, 2, 5, 10, 20, 50, 100, 250, 500] + }, + second: { + size: 1000, + steps: [1, 2, 5, 10, 30] + }, + minute: { + size: 60000, + steps: [1, 2, 5, 10, 30] + }, + hour: { + size: 3600000, + steps: [1, 2, 3, 6, 12] + }, + day: { + size: 86400000, + steps: [1, 2, 5] + }, + week: { + size: 604800000, + maxStep: 4 + }, + month: { + size: 2.628e9, + maxStep: 3 + }, + quarter: { + size: 7.884e9, + maxStep: 4 + }, + year: { + size: 3.154e10, + maxStep: false + } + }; + + /** + * Helper for generating axis labels. + * @param options {ITimeGeneratorOptions} the options for generation + * @param dataRange {IRange} the data range + * @param niceRange {IRange} the pretty range to display + * @return {Number[]} ticks + */ + function generateTicksNiceRange(options, dataRange, niceRange) { + var ticks = []; + if (options.maxTicks) { + var stepSize = options.stepSize; + ticks.push(options.min !== undefined ? options.min : niceRange.min); + var cur = moment(niceRange.min); + while (cur.add(stepSize, options.unit).valueOf() < niceRange.max) { + ticks.push(cur.valueOf()); + } + var realMax = options.max || niceRange.max; + if (ticks[ticks.length - 1] !== realMax) { + ticks.push(realMax); + } + } + return ticks; + } + + Chart.helpers = Chart.helpers || {}; + + Chart.helpers.time = { + + /** + * Helper function to parse time to a moment object + * @param axis {TimeAxis} the time axis + * @param label {Date|string|number|Moment} The thing to parse + * @return {Moment} parsed time + */ + parseTime: function(axis, label) { + var timeOpts = axis.options.time; + if (typeof timeOpts.parser === 'string') { + return moment(label, timeOpts.parser); + } + if (typeof timeOpts.parser === 'function') { + return timeOpts.parser(label); + } + if (typeof label.getMonth === 'function' || typeof label === 'number') { + // Date objects + return moment(label); + } + if (label.isValid && label.isValid()) { + // Moment support + return label; + } + var format = timeOpts.format; + if (typeof format !== 'string' && format.call) { + // Custom parsing (return an instance of moment) + console.warn('options.time.format is deprecated and replaced by options.time.parser.'); + return format(label); + } + // Moment format parsing + return moment(label, format); + }, + + /** + * Figure out which is the best unit for the scale + * @param minUnit {String} minimum unit to use + * @param min {Number} scale minimum + * @param max {Number} scale maximum + * @return {String} the unit to use + */ + determineUnit: function(minUnit, min, max, maxTicks) { + var units = Object.keys(interval); + var unit; + var numUnits = units.length; + + for (var i = units.indexOf(minUnit); i < numUnits; i++) { + unit = units[i]; + var unitDetails = interval[unit]; + var steps = (unitDetails.steps && unitDetails.steps[unitDetails.steps.length - 1]) || unitDetails.maxStep; + if (steps === undefined || Math.ceil((max - min) / (steps * unitDetails.size)) <= maxTicks) { + break; + } + } + + return unit; + }, + + /** + * Determines how we scale the unit + * @param min {Number} the scale minimum + * @param max {Number} the scale maximum + * @param unit {String} the unit determined by the {@see determineUnit} method + * @return {Number} the axis step size as a multiple of unit + */ + determineStepSize: function(min, max, unit, maxTicks) { + // Using our unit, figure out what we need to scale as + var unitDefinition = interval[unit]; + var unitSizeInMilliSeconds = unitDefinition.size; + var sizeInUnits = Math.ceil((max - min) / unitSizeInMilliSeconds); + var multiplier = 1; + var range = max - min; + + if (unitDefinition.steps) { + // Have an array of steps + var numSteps = unitDefinition.steps.length; + for (var i = 0; i < numSteps && sizeInUnits > maxTicks; i++) { + multiplier = unitDefinition.steps[i]; + sizeInUnits = Math.ceil(range / (unitSizeInMilliSeconds * multiplier)); + } + } else { + while (sizeInUnits > maxTicks && maxTicks > 0) { + ++multiplier; + sizeInUnits = Math.ceil(range / (unitSizeInMilliSeconds * multiplier)); + } + } + + return multiplier; + }, + + /** + * @function generateTicks + * @param options {ITimeGeneratorOptions} the options for generation + * @param dataRange {IRange} the data range + * @return {Number[]} ticks + */ + generateTicks: function(options, dataRange) { + var niceMin; + var niceMax; + var isoWeekday = options.isoWeekday; + if (options.unit === 'week' && isoWeekday !== false) { + niceMin = moment(dataRange.min).startOf('isoWeek').isoWeekday(isoWeekday).valueOf(); + niceMax = moment(dataRange.max).startOf('isoWeek').isoWeekday(isoWeekday); + if (dataRange.max - niceMax > 0) { + niceMax.add(1, 'week'); + } + niceMax = niceMax.valueOf(); + } else { + niceMin = moment(dataRange.min).startOf(options.unit).valueOf(); + niceMax = moment(dataRange.max).startOf(options.unit); + if (dataRange.max - niceMax > 0) { + niceMax.add(1, options.unit); + } + niceMax = niceMax.valueOf(); + } + return generateTicksNiceRange(options, dataRange, { + min: niceMin, + max: niceMax + }); + } + + }; + +}; diff --git a/src/scales/scale.time.js b/src/scales/scale.time.js index 0b356b3c1..1d726e1b3 100644 --- a/src/scales/scale.time.js +++ b/src/scales/scale.time.js @@ -7,44 +7,7 @@ moment = typeof(moment) === 'function' ? moment : window.moment; module.exports = function(Chart) { var helpers = Chart.helpers; - var interval = { - millisecond: { - size: 1, - steps: [1, 2, 5, 10, 20, 50, 100, 250, 500] - }, - second: { - size: 1000, - steps: [1, 2, 5, 10, 30] - }, - minute: { - size: 60000, - steps: [1, 2, 5, 10, 30] - }, - hour: { - size: 3600000, - steps: [1, 2, 3, 6, 12] - }, - day: { - size: 86400000, - steps: [1, 2, 5] - }, - week: { - size: 604800000, - maxStep: 4 - }, - month: { - size: 2.628e9, - maxStep: 3 - }, - quarter: { - size: 7.884e9, - maxStep: 4 - }, - year: { - size: 3.154e10, - maxStep: false - } - }; + var timeHelpers = helpers.time; var defaultConfig = { position: 'bottom', @@ -76,149 +39,6 @@ module.exports = function(Chart) { } }; - /** - * Helper function to parse time to a moment object - * @param axis {TimeAxis} the time axis - * @param label {Date|string|number|Moment} The thing to parse - * @return {Moment} parsed time - */ - function parseTime(axis, label) { - var timeOpts = axis.options.time; - if (typeof timeOpts.parser === 'string') { - return moment(label, timeOpts.parser); - } - if (typeof timeOpts.parser === 'function') { - return timeOpts.parser(label); - } - if (typeof label.getMonth === 'function' || typeof label === 'number') { - // Date objects - return moment(label); - } - if (label.isValid && label.isValid()) { - // Moment support - return label; - } - var format = timeOpts.format; - if (typeof format !== 'string' && format.call) { - // Custom parsing (return an instance of moment) - console.warn('options.time.format is deprecated and replaced by options.time.parser.'); - return format(label); - } - // Moment format parsing - return moment(label, format); - } - - /** - * Figure out which is the best unit for the scale - * @param minUnit {String} minimum unit to use - * @param min {Number} scale minimum - * @param max {Number} scale maximum - * @return {String} the unit to use - */ - function determineUnit(minUnit, min, max, maxTicks) { - var units = Object.keys(interval); - var unit; - var numUnits = units.length; - - for (var i = units.indexOf(minUnit); i < numUnits; i++) { - unit = units[i]; - var unitDetails = interval[unit]; - var steps = (unitDetails.steps && unitDetails.steps[unitDetails.steps.length - 1]) || unitDetails.maxStep; - if (steps === undefined || Math.ceil((max - min) / (steps * unitDetails.size)) <= maxTicks) { - break; - } - } - - return unit; - } - - /** - * Determines how we scale the unit - * @param min {Number} the scale minimum - * @param max {Number} the scale maximum - * @param unit {String} the unit determined by the {@see determineUnit} method - * @return {Number} the axis step size as a multiple of unit - */ - function determineStepSize(min, max, unit, maxTicks) { - // Using our unit, figoure out what we need to scale as - var unitDefinition = interval[unit]; - var unitSizeInMilliSeconds = unitDefinition.size; - var sizeInUnits = Math.ceil((max - min) / unitSizeInMilliSeconds); - var multiplier = 1; - var range = max - min; - - if (unitDefinition.steps) { - // Have an array of steps - var numSteps = unitDefinition.steps.length; - for (var i = 0; i < numSteps && sizeInUnits > maxTicks; i++) { - multiplier = unitDefinition.steps[i]; - sizeInUnits = Math.ceil(range / (unitSizeInMilliSeconds * multiplier)); - } - } else { - while (sizeInUnits > maxTicks && maxTicks > 0) { - ++multiplier; - sizeInUnits = Math.ceil(range / (unitSizeInMilliSeconds * multiplier)); - } - } - - return multiplier; - } - - /** - * Helper for generating axis labels. - * @param options {ITimeGeneratorOptions} the options for generation - * @param dataRange {IRange} the data range - * @param niceRange {IRange} the pretty range to display - * @return {Number[]} ticks - */ - function generateTicks(options, dataRange, niceRange) { - var ticks = []; - if (options.maxTicks) { - var stepSize = options.stepSize; - ticks.push(options.min !== undefined ? options.min : niceRange.min); - var cur = moment(niceRange.min); - while (cur.add(stepSize, options.unit).valueOf() < niceRange.max) { - ticks.push(cur.valueOf()); - } - var realMax = options.max || niceRange.max; - if (ticks[ticks.length - 1] !== realMax) { - ticks.push(realMax); - } - } - return ticks; - } - - /** - * @function Chart.Ticks.generators.time - * @param options {ITimeGeneratorOptions} the options for generation - * @param dataRange {IRange} the data range - * @return {Number[]} ticks - */ - Chart.Ticks.generators.time = function(options, dataRange) { - var niceMin; - var niceMax; - var isoWeekday = options.isoWeekday; - if (options.unit === 'week' && isoWeekday !== false) { - niceMin = moment(dataRange.min).startOf('isoWeek').isoWeekday(isoWeekday).valueOf(); - niceMax = moment(dataRange.max).startOf('isoWeek').isoWeekday(isoWeekday); - if (dataRange.max - niceMax > 0) { - niceMax.add(1, 'week'); - } - niceMax = niceMax.valueOf(); - } else { - niceMin = moment(dataRange.min).startOf(options.unit).valueOf(); - niceMax = moment(dataRange.max).startOf(options.unit); - if (dataRange.max - niceMax > 0) { - niceMax.add(1, options.unit); - } - niceMax = niceMax.valueOf(); - } - return generateTicks(options, dataRange, { - min: niceMin, - max: niceMax - }); - }; - var TimeScale = Chart.Scale.extend({ initialize: function() { if (!moment) { @@ -244,7 +64,7 @@ module.exports = function(Chart) { var timestamp; helpers.each(chartData.labels, function(label, labelIndex) { - var labelMoment = parseTime(me, label); + var labelMoment = timeHelpers.parseTime(me, label); if (labelMoment.isValid()) { // We need to round the time @@ -267,7 +87,7 @@ module.exports = function(Chart) { if (typeof dataset.data[0] === 'object' && dataset.data[0] !== null && me.chart.isDatasetVisible(datasetIndex)) { // We have potential point data, so we need to parse this helpers.each(dataset.data, function(value, dataIndex) { - var dataMoment = parseTime(me, me.getRightValue(value)); + var dataMoment = timeHelpers.parseTime(me, me.getRightValue(value)); if (dataMoment.isValid()) { if (timeOpts.round) { @@ -302,7 +122,7 @@ module.exports = function(Chart) { var dataMax = me.dataMax; if (timeOpts.min) { - var minMoment = parseTime(me, timeOpts.min); + var minMoment = timeHelpers.parseTime(me, timeOpts.min); if (timeOpts.round) { minMoment.round(timeOpts.round); } @@ -310,15 +130,15 @@ module.exports = function(Chart) { } if (timeOpts.max) { - maxTimestamp = parseTime(me, timeOpts.max).valueOf(); + maxTimestamp = timeHelpers.parseTime(me, timeOpts.max).valueOf(); } var maxTicks = me.getLabelCapacity(minTimestamp || dataMin); - var unit = timeOpts.unit || determineUnit(timeOpts.minUnit, minTimestamp || dataMin, maxTimestamp || dataMax, maxTicks); + var unit = timeOpts.unit || timeHelpers.determineUnit(timeOpts.minUnit, minTimestamp || dataMin, maxTimestamp || dataMax, maxTicks); me.displayFormat = timeOpts.displayFormats[unit]; - var stepSize = timeOpts.stepSize || determineStepSize(minTimestamp || dataMin, maxTimestamp || dataMax, unit, maxTicks); - me.ticks = Chart.Ticks.generators.time({ + var stepSize = timeOpts.stepSize || timeHelpers.determineStepSize(minTimestamp || dataMin, maxTimestamp || dataMax, unit, maxTicks); + me.ticks = timeHelpers.generateTicks({ maxTicks: maxTicks, min: minTimestamp, max: maxTimestamp, @@ -347,7 +167,7 @@ module.exports = function(Chart) { // Format nicely if (me.options.time.tooltipFormat) { - label = parseTime(me, label).format(me.options.time.tooltipFormat); + label = timeHelpers.parseTime(me, label).format(me.options.time.tooltipFormat); } return label; @@ -393,7 +213,7 @@ module.exports = function(Chart) { if (offset === null) { if (!value || !value.isValid) { // not already a moment object - value = parseTime(me, me.getRightValue(value)); + value = timeHelpers.parseTime(me, me.getRightValue(value)); } if (value && value.isValid && value.isValid()) { From 75791988d3d7187a747a26a25051ca41835b24ad Mon Sep 17 00:00:00 2001 From: Ben McCann Date: Thu, 25 May 2017 09:55:21 -0700 Subject: [PATCH 005/112] Upgrade dependencies --- gulpfile.js | 4 +--- package.json | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 884242f05..22ba3b304 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -216,9 +216,7 @@ function librarySizeTask() { function moduleSizesTask() { return gulp.src(srcDir + '**/*.js') - .pipe(uglify({ - preserveComments: 'some' - })) + .pipe(uglify()) .pipe(size({ showFiles: true, gzip: true diff --git a/package.json b/package.json index 7e4c19297..1a78ade9c 100644 --- a/package.json +++ b/package.json @@ -13,35 +13,35 @@ "browserify": "^14.3.0", "browserify-istanbul": "^2.0.0", "bundle-collapser": "^1.2.1", - "child-process-promise": "^2.2.0", - "coveralls": "^2.11.6", + "child-process-promise": "^2.2.1", + "coveralls": "^2.13.1", "gitbook-cli": "^2.3.0", "gulp": "3.9.x", "gulp-concat": "~2.6.x", "gulp-connect": "~5.0.0", - "gulp-eslint": "^3.0.0", + "gulp-eslint": "^3.0.1", "gulp-file": "^0.3.0", "gulp-html-validator": "^0.0.5", "gulp-insert": "~0.5.0", "gulp-replace": "^0.5.4", "gulp-size": "~2.1.0", "gulp-streamify": "^1.0.2", - "gulp-uglify": "~2.1.x", + "gulp-uglify": "~3.0.x", "gulp-util": "~3.0.x", "gulp-zip": "~4.0.0", - "jasmine": "^2.5.0", - "jasmine-core": "^2.5.0", - "karma": "^1.5.0", - "karma-browserify": "^5.1.0", - "karma-chrome-launcher": "^2.0.0", - "karma-coverage": "^1.1.0", - "karma-firefox-launcher": "^1.0.0", + "jasmine": "^2.6.0", + "jasmine-core": "^2.6.2", + "karma": "^1.7.0", + "karma-browserify": "^5.1.1", + "karma-chrome-launcher": "^2.1.1", + "karma-coverage": "^1.1.1", + "karma-firefox-launcher": "^1.0.1", "karma-jasmine": "^1.1.0", "karma-jasmine-html-reporter": "^0.2.2", - "merge-stream": "^1.0.0", + "merge-stream": "^1.0.1", "pixelmatch": "^4.0.2", "vinyl-source-stream": "^1.1.0", - "watchify": "^3.7.0", + "watchify": "^3.9.0", "yargs": "^8.0.1" }, "spm": { @@ -49,6 +49,6 @@ }, "dependencies": { "chartjs-color": "^2.1.0", - "moment": "^2.10.6" + "moment": "^2.18.1" } } From 2258199c22c9369db629b312b1ece51a77adfc42 Mon Sep 17 00:00:00 2001 From: Stephen Boissiere Date: Sat, 27 May 2017 15:58:18 +0100 Subject: [PATCH 006/112] Add hard coded integer constants for *_SAFE_INTEGER which are not available on IE --- src/scales/scale.time.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/scales/scale.time.js b/src/scales/scale.time.js index 1d726e1b3..f99452eef 100644 --- a/src/scales/scale.time.js +++ b/src/scales/scale.time.js @@ -52,8 +52,9 @@ module.exports = function(Chart) { var timeOpts = me.options.time; // We store the data range as unix millisecond timestamps so dataMin and dataMax will always be integers. - var dataMin = Number.MAX_SAFE_INTEGER; - var dataMax = Number.MIN_SAFE_INTEGER; + // Integer constants are from the ES6 spec. + var dataMin = Number.MAX_SAFE_INTEGER || 9007199254740991; + var dataMax = Number.MIN_SAFE_INTEGER || -9007199254740991; var chartData = me.chart.data; var parsedData = { From 394382b931789a0861a5a7daf153faa5a4498af2 Mon Sep 17 00:00:00 2001 From: ApoorvA Date: Sun, 28 May 2017 18:09:29 +0530 Subject: [PATCH 007/112] Add tooltip textLabelColor callback (#4199) Add a new tooltip callback `labelTextColor` that returns the colour for each item in the body of the tooltip. Fixes issue #4191 --- docs/configuration/tooltip.md | 4 ++++ src/core/core.tooltip.js | 12 ++++++++---- test/specs/core.tooltip.tests.js | 5 +++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/docs/configuration/tooltip.md b/docs/configuration/tooltip.md index 21387f140..8947d2463 100644 --- a/docs/configuration/tooltip.md +++ b/docs/configuration/tooltip.md @@ -74,6 +74,7 @@ All functions are called with the same arguments: a [tooltip item](#chart-config | `beforeLabel` | `tooltipItem, data` | Returns text to render before an individual label. This will be called for each item in the tooltip. | `label` | `tooltipItem, data` | Returns text to render for an individual item in the tooltip. | `labelColor` | `tooltipItem, chart` | Returns the colors to render for the tooltip item. [more...](#label-color-callback) +| `labelTextColor` | `tooltipItem, chart` | Returns the colors for the text of the label for the tooltip item. | `afterLabel` | `tooltipItem, data` | Returns text to render after an individual label. | `afterBody` | `Array[tooltipItem], data` | Returns text to render after the body section | `beforeFooter` | `Array[tooltipItem], data` | Returns text to render before the footer section. @@ -95,6 +96,9 @@ var chart = new Chart(ctx, { borderColor: 'rgb(255, 0, 0)', backgroundColor: 'rgb(255, 0, 0)' } + }, + labelTextColor:function(tooltipItem, chart){ + return '#543453'; } } } diff --git a/src/core/core.tooltip.js b/src/core/core.tooltip.js index 3428f4ac9..cb21a9bc9 100644 --- a/src/core/core.tooltip.js +++ b/src/core/core.tooltip.js @@ -87,6 +87,9 @@ module.exports = function(Chart) { backgroundColor: view.backgroundColor }; }, + labelTextColor: function() { + return this._options.bodyFontColor; + }, afterLabel: helpers.noop, // Args are: (tooltipItems, data) @@ -487,6 +490,7 @@ module.exports = function(Chart) { model.opacity = 1; var labelColors = []; + var labelTextColors = []; tooltipPosition = Chart.Tooltip.positioners[opts.position](active, me._eventPosition); var tooltipItems = []; @@ -511,8 +515,10 @@ module.exports = function(Chart) { // Determine colors for boxes helpers.each(tooltipItems, function(tooltipItem) { labelColors.push(opts.callbacks.labelColor.call(me, tooltipItem, me._chart)); + labelTextColors.push(opts.callbacks.labelTextColor.call(me, tooltipItem, me._chart)); }); + // Build the Text Lines model.title = me.getTitle(tooltipItems, data); model.beforeBody = me.getBeforeBody(tooltipItems, data); @@ -525,6 +531,7 @@ module.exports = function(Chart) { model.y = Math.round(tooltipPosition.y); model.caretPadding = opts.caretPadding; model.labelColors = labelColors; + model.labelTextColors = labelTextColors; // data points model.dataPoints = tooltipItems; @@ -657,9 +664,6 @@ module.exports = function(Chart) { ctx.textAlign = vm._bodyAlign; ctx.textBaseline = 'top'; - - var textColor = mergeOpacity(vm.bodyFontColor, opacity); - ctx.fillStyle = textColor; ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily); // Before Body @@ -693,7 +697,7 @@ 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/test/specs/core.tooltip.tests.js b/test/specs/core.tooltip.tests.js index 886e43a2f..0fbc46799 100755 --- a/test/specs/core.tooltip.tests.js +++ b/test/specs/core.tooltip.tests.js @@ -330,6 +330,7 @@ describe('Core.Tooltip', function() { afterBody: [], footer: [], caretPadding: 2, + labelTextColors: ['#fff'], labelColors: [{ borderColor: 'rgb(255, 0, 0)', backgroundColor: 'rgb(0, 255, 0)' @@ -393,6 +394,9 @@ describe('Core.Tooltip', function() { }, afterFooter: function() { return 'afterFooter'; + }, + labelTextColor: function() { + return 'labelTextColor'; } } } @@ -476,6 +480,7 @@ describe('Core.Tooltip', function() { afterBody: ['afterBody'], footer: ['beforeFooter', 'footer', 'afterFooter'], caretPadding: 2, + labelTextColors: ['labelTextColor', 'labelTextColor'], labelColors: [{ borderColor: 'rgb(255, 0, 0)', backgroundColor: 'rgb(0, 255, 0)' From dab0a7f699f18cf73bd2af19760bbe0b4d0d703c Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Sun, 28 May 2017 22:23:20 +0100 Subject: [PATCH 008/112] Fix onHover event not being triggered (#4297) Fix onHover event not being triggered The core controller was looking at the wrong object (options.hover) to find the function to be called on hover. The function is provided on the top level options object (options.onHover). By using the helper function, there's no need to verify if the callback is defined, as the helper already does that. Fixes #4296 --- src/core/core.controller.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/core/core.controller.js b/src/core/core.controller.js index 71eb3bb62..f8c471f3d 100644 --- a/src/core/core.controller.js +++ b/src/core/core.controller.js @@ -808,11 +808,9 @@ module.exports = function(Chart) { me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions); } - // On Hover hook - if (hoverOptions.onHover) { - // Need to call with native event here to not break backwards compatibility - hoverOptions.onHover.call(me, e.native, me.active); - } + // Invoke onHover hook + // Need to call with native event here to not break backwards compatibility + helpers.callback(options.onHover || options.hover.onHover, [e.native, me.active], me); if (e.type === 'mouseup' || e.type === 'click') { if (options.onClick) { From f7f177f5ade62a0969596bdfff773b404425047d Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 30 May 2017 01:40:10 +0300 Subject: [PATCH 009/112] Implemented aligment by senior unit in time axis. (#4267) Implemented alignment by major unit in the time scale. This allows showing the first tick of a larger unit like days in a special way and is part of the basis of the time series scale. --- samples/scales/time/line-point-data.html | 8 +-- samples/scales/time/line.html | 38 +++++++-------- src/helpers/helpers.time.js | 42 +++++++++++++--- src/scales/scale.time.js | 9 +++- test/specs/scale.time.tests.js | 62 +++++++++++------------- 5 files changed, 93 insertions(+), 66 deletions(-) diff --git a/samples/scales/time/line-point-data.html b/samples/scales/time/line-point-data.html index 0a221624b..e28f883f2 100644 --- a/samples/scales/time/line-point-data.html +++ b/samples/scales/time/line-point-data.html @@ -77,10 +77,10 @@ }, options: { responsive: true, - title:{ - display:true, - text:"Chart.js Time Point Data" - }, + title:{ + display:true, + text:"Chart.js Time Point Data" + }, scales: { xAxes: [{ type: "time", diff --git a/samples/scales/time/line.html b/samples/scales/time/line.html index 221354f9e..0cca931e0 100644 --- a/samples/scales/time/line.html +++ b/samples/scales/time/line.html @@ -28,7 +28,7 @@ ``` Now, we can create a chart. We add a script to our page: From ecca3373b2aac74860fa35de79727fab8bf28494 Mon Sep 17 00:00:00 2001 From: Simon Brunel Date: Sat, 1 Jul 2017 09:55:11 +0200 Subject: [PATCH 043/112] Increase ESLint complexity and add config for tests (#4421) Raise the cyclomatic complexity to 10 which seems to better match the project coding style and still reasonable (6 being quite low). Also move unit tests specific eslint rules in the cascaded `./test/.eslintrc` file (previously in `gulp.js`). --- .eslintrc | 2 +- gulpfile.js | 20 ++------------------ test/.eslintrc | 12 ++++++++++++ 3 files changed, 15 insertions(+), 19 deletions(-) create mode 100644 test/.eslintrc diff --git a/.eslintrc b/.eslintrc index 7acbf600a..fdf1951dd 100644 --- a/.eslintrc +++ b/.eslintrc @@ -44,7 +44,7 @@ rules: accessor-pairs: 2 array-callback-return: 0 block-scoped-var: 0 - complexity: [2, 6] + complexity: [2, 10] consistent-return: 0 curly: [2, all] default-case: 2 diff --git a/gulpfile.js b/gulpfile.js index 22ba3b304..ff3cce5b6 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -137,25 +137,9 @@ function lintTask() { // to fix, let's turn them as warnings and rewrite code later progressively. var options = { rules: { - 'complexity': [1, 6], + 'complexity': [1, 10], 'max-statements': [1, 30] - }, - globals: [ - 'Chart', - 'acquireChart', - 'afterAll', - 'afterEach', - 'beforeAll', - 'beforeEach', - 'describe', - 'expect', - 'fail', - 'it', - 'jasmine', - 'moment', - 'spyOn', - 'xit' - ] + } }; return gulp.src(files) diff --git a/test/.eslintrc b/test/.eslintrc new file mode 100644 index 000000000..5a281785c --- /dev/null +++ b/test/.eslintrc @@ -0,0 +1,12 @@ +env: + jasmine: true + +globals: + acquireChart: true + Chart: true + moment: true + +# http://eslint.org/docs/rules/ +rules: + # Best Practices + complexity: 0 From e5a431e724b589e907935090eeaad8030058731f Mon Sep 17 00:00:00 2001 From: Let Aurn IV Date: Sat, 1 Jul 2017 09:59:26 +0200 Subject: [PATCH 044/112] Remove `.js` extensions when requiring a file (#4427) --- src/chart.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/chart.js b/src/chart.js index 517224c93..22aa7d893 100644 --- a/src/chart.js +++ b/src/chart.js @@ -1,22 +1,22 @@ /** * @namespace Chart */ -var Chart = require('./core/core.js')(); +var Chart = require('./core/core')(); require('./helpers/helpers.core')(Chart); require('./core/core.helpers')(Chart); require('./helpers/helpers.time')(Chart); require('./helpers/helpers.canvas')(Chart); -require('./platforms/platform.js')(Chart); +require('./platforms/platform')(Chart); require('./core/core.element')(Chart); -require('./core/core.plugin.js')(Chart); +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); -require('./core/core.ticks.js')(Chart); +require('./core/core.ticks')(Chart); require('./core/core.scale')(Chart); require('./core/core.interaction')(Chart); require('./core/core.tooltip')(Chart); @@ -26,7 +26,7 @@ require('./elements/element.line')(Chart); require('./elements/element.point')(Chart); require('./elements/element.rectangle')(Chart); -require('./scales/scale.linearbase.js')(Chart); +require('./scales/scale.linearbase')(Chart); require('./scales/scale.category')(Chart); require('./scales/scale.linear')(Chart); require('./scales/scale.logarithmic')(Chart); @@ -54,9 +54,9 @@ require('./charts/Chart.Scatter')(Chart); var plugins = []; plugins.push( - require('./plugins/plugin.filler.js')(Chart), - require('./plugins/plugin.legend.js')(Chart), - require('./plugins/plugin.title.js')(Chart) + require('./plugins/plugin.filler')(Chart), + require('./plugins/plugin.legend')(Chart), + require('./plugins/plugin.title')(Chart) ); Chart.plugins.register(plugins); From 6f317135a3b87b6ff62660adccd1ee063240d46f Mon Sep 17 00:00:00 2001 From: Simon Brunel Date: Sat, 1 Jul 2017 14:08:20 +0200 Subject: [PATCH 045/112] Clamp radius when drawing rounded rectangle (#4448) --- src/helpers/helpers.canvas.js | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/helpers/helpers.canvas.js b/src/helpers/helpers.canvas.js index 917b29615..22bf58f31 100644 --- a/src/helpers/helpers.canvas.js +++ b/src/helpers/helpers.canvas.js @@ -24,20 +24,22 @@ module.exports = function(Chart) { * @param {Number} width - The rectangle's width. * @param {Number} height - The rectangle's height. * @param {Number} radius - The rounded amount (in pixels) for the four corners. - * @todo handler `radius` as top-left, top-right, bottom-right, bottom-left array/object? - * @todo clamp `radius` to the maximum "correct" value. + * @todo handle `radius` as top-left, top-right, bottom-right, bottom-left array/object? */ roundedRect: function(ctx, x, y, width, height, radius) { if (radius) { - ctx.moveTo(x + radius, y); - ctx.lineTo(x + width - radius, y); - ctx.quadraticCurveTo(x + width, y, x + width, y + radius); - ctx.lineTo(x + width, y + height - radius); - ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); - ctx.lineTo(x + radius, y + height); - ctx.quadraticCurveTo(x, y + height, x, y + height - radius); - ctx.lineTo(x, y + radius); - ctx.quadraticCurveTo(x, y, x + radius, y); + var rx = Math.min(radius, width/2); + var ry = Math.min(radius, height/2); + + ctx.moveTo(x + rx, y); + ctx.lineTo(x + width - rx, y); + ctx.quadraticCurveTo(x + width, y, x + width, y + ry); + ctx.lineTo(x + width, y + height - ry); + ctx.quadraticCurveTo(x + width, y + height, x + width - rx, y + height); + ctx.lineTo(x + rx, y + height); + ctx.quadraticCurveTo(x, y + height, x, y + height - ry); + ctx.lineTo(x, y + ry); + ctx.quadraticCurveTo(x, y, x + rx, y); } else { ctx.rect(x, y, width, height); } From 225bfd36f3daae2cfd0e2869658144ee6bfd988b Mon Sep 17 00:00:00 2001 From: Simon Brunel Date: Sat, 1 Jul 2017 14:51:38 +0200 Subject: [PATCH 046/112] Rewrite the clone and merge helpers (#4422) The `clone` method now accepts any type of input but also recursively perform a deep copy of the array items. Rewrite the `configMerge` and `scaleMerge` helpers which now rely on a new generic and customizable `merge` method, that one accepts a target object in which multiple sources are deep copied. Note that the target (first argument) is not cloned and will be modified after calling `merge(target, sources)`. Add a `mergeIf` helper which merge the source properties only if they do not exist in the target object. --- src/core/core.helpers.js | 121 +++++++++++----------------- src/core/core.scaleService.js | 2 +- src/helpers/helpers.core.js | 104 ++++++++++++++++++++++++ src/plugins/plugin.legend.js | 2 +- src/plugins/plugin.title.js | 2 +- test/specs/core.helpers.tests.js | 19 ----- test/specs/helpers.core.tests.js | 133 +++++++++++++++++++++++++++++++ 7 files changed, 287 insertions(+), 96 deletions(-) diff --git a/src/core/core.helpers.js b/src/core/core.helpers.js index 08470fa7b..e66d339d3 100644 --- a/src/core/core.helpers.js +++ b/src/core/core.helpers.js @@ -8,19 +8,7 @@ module.exports = function(Chart) { var helpers = Chart.helpers; // -- Basic js utility methods - helpers.clone = function(obj) { - var objClone = {}; - helpers.each(obj, function(value, key) { - if (helpers.isArray(value)) { - objClone[key] = value.slice(0); - } else if (typeof value === 'object' && value !== null) { - objClone[key] = helpers.clone(value); - } else { - objClone[key] = value; - } - }); - return objClone; - }; + helpers.extend = function(base) { var setFn = function(value, key) { base[key] = value; @@ -30,75 +18,60 @@ module.exports = function(Chart) { } return base; }; - // Need a special merge function to chart configs since they are now grouped - helpers.configMerge = function(_base) { - var base = helpers.clone(_base); - helpers.each(Array.prototype.slice.call(arguments, 1), function(extension) { - helpers.each(extension, function(value, key) { - var baseHasProperty = base.hasOwnProperty(key); - var baseVal = baseHasProperty ? base[key] : {}; + + helpers.configMerge = function(/* objects ... */) { + return helpers.merge(helpers.clone(arguments[0]), [].slice.call(arguments, 1), { + merger: function(key, target, source, options) { + var tval = target[key] || {}; + var sval = source[key]; if (key === 'scales') { - // Scale config merging is complex. Add our own function here for that - base[key] = helpers.scaleMerge(baseVal, value); + // scale config merging is complex. Add our own function here for that + target[key] = helpers.scaleMerge(tval, sval); } else if (key === 'scale') { - // Used in polar area & radar charts since there is only one scale - base[key] = helpers.configMerge(baseVal, Chart.scaleService.getScaleDefaults(value.type), value); - } else if (baseHasProperty - && typeof baseVal === 'object' - && !helpers.isArray(baseVal) - && baseVal !== null - && typeof value === 'object' - && !helpers.isArray(value)) { - // If we are overwriting an object with an object, do a merge of the properties. - base[key] = helpers.configMerge(baseVal, value); + // used in polar area & radar charts since there is only one scale + target[key] = helpers.merge(tval, [Chart.scaleService.getScaleDefaults(sval.type), sval]); } else { - // can just overwrite the value in this case - base[key] = value; + helpers._merger(key, target, source, options); } - }); - }); - - return base; - }; - helpers.scaleMerge = function(_base, extension) { - var base = helpers.clone(_base); - - helpers.each(extension, function(value, key) { - if (key === 'xAxes' || key === 'yAxes') { - // These properties are arrays of items - if (base.hasOwnProperty(key)) { - helpers.each(value, function(valueObj, index) { - var axisType = helpers.valueOrDefault(valueObj.type, key === 'xAxes' ? 'category' : 'linear'); - var axisDefaults = Chart.scaleService.getScaleDefaults(axisType); - if (index >= base[key].length || !base[key][index].type) { - base[key].push(helpers.configMerge(axisDefaults, valueObj)); - } else if (valueObj.type && valueObj.type !== base[key][index].type) { - // Type changed. Bring in the new defaults before we bring in valueObj so that valueObj can override the correct scale defaults - base[key][index] = helpers.configMerge(base[key][index], axisDefaults, valueObj); - } else { - // Type is the same - base[key][index] = helpers.configMerge(base[key][index], valueObj); - } - }); - } else { - base[key] = []; - helpers.each(value, function(valueObj) { - var axisType = helpers.valueOrDefault(valueObj.type, key === 'xAxes' ? 'category' : 'linear'); - base[key].push(helpers.configMerge(Chart.scaleService.getScaleDefaults(axisType), valueObj)); - }); - } - } else if (base.hasOwnProperty(key) && typeof base[key] === 'object' && base[key] !== null && typeof value === 'object') { - // If we are overwriting an object with an object, do a merge of the properties. - base[key] = helpers.configMerge(base[key], value); - - } else { - // can just overwrite the value in this case - base[key] = value; } }); + }; - return base; + helpers.scaleMerge = function(/* objects ... */) { + return helpers.merge(helpers.clone(arguments[0]), [].slice.call(arguments, 1), { + merger: function(key, target, source, options) { + if (key === 'xAxes' || key === 'yAxes') { + var slen = source[key].length; + var i, type, scale, defaults; + + if (!target[key]) { + target[key] = []; + } + + for (i = 0; i < slen; ++i) { + scale = source[key][i]; + type = helpers.valueOrDefault(scale.type, key === 'xAxes'? 'category' : 'linear'); + defaults = Chart.scaleService.getScaleDefaults(type); + + if (i >= target[key].length) { + target[key].push({}); + } + + if (!target[key][i].type || (scale.type && scale.type !== target[key][i].type)) { + // new/untyped scale or type changed: let's apply the new defaults + // then merge source scale to correctly overwrite the defaults. + helpers.merge(target[key][i], [defaults, scale]); + } else { + // scales type are the same + helpers.merge(target[key][i], scale); + } + } + } else { + helpers._merger(key, target, source, options); + } + } + }); }; helpers.where = function(collection, filterCallback) { diff --git a/src/core/core.scaleService.js b/src/core/core.scaleService.js index 92e813650..58eafc9cb 100644 --- a/src/core/core.scaleService.js +++ b/src/core/core.scaleService.js @@ -22,7 +22,7 @@ module.exports = function(Chart) { }, getScaleDefaults: function(type) { // Return the scale defaults merged with the global settings so that we always use the latest ones - return this.defaults.hasOwnProperty(type) ? helpers.scaleMerge(Chart.defaults.scale, this.defaults[type]) : {}; + return this.defaults.hasOwnProperty(type) ? helpers.merge({}, [Chart.defaults.scale, this.defaults[type]]) : {}; }, updateScaleDefaults: function(type, additions) { var defaults = this.defaults; diff --git a/src/helpers/helpers.core.js b/src/helpers/helpers.core.js index 269851fe9..d0e4b1426 100644 --- a/src/helpers/helpers.core.js +++ b/src/helpers/helpers.core.js @@ -147,6 +147,110 @@ module.exports = function(Chart) { } return true; + }, + + /** + * Returns a deep copy of `source` without keeping references on objects and arrays. + * @param {*} source - The value to clone. + * @returns {*} + */ + clone: function(source) { + if (helpers.isArray(source)) { + return source.map(helpers.clone); + } + + if (helpers.isObject(source)) { + var target = {}; + var keys = Object.keys(source); + var klen = keys.length; + var k = 0; + + for (; k Date: Mon, 3 Jul 2017 21:32:10 -0400 Subject: [PATCH 047/112] Support an array for line chart pointBorderWidth --- src/controllers/controller.line.js | 2 +- test/specs/controller.line.tests.js | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/controllers/controller.line.js b/src/controllers/controller.line.js index 6975b1ebb..97982bded 100644 --- a/src/controllers/controller.line.js +++ b/src/controllers/controller.line.js @@ -137,7 +137,7 @@ module.exports = function(Chart) { if (!isNaN(custom.borderWidth)) { borderWidth = custom.borderWidth; - } else if (!isNaN(dataset.pointBorderWidth)) { + } else if (!isNaN(dataset.pointBorderWidth) || helpers.isArray(dataset.pointBorderWidth)) { borderWidth = helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, borderWidth); } else if (!isNaN(dataset.borderWidth)) { borderWidth = dataset.borderWidth; diff --git a/test/specs/controller.line.tests.js b/test/specs/controller.line.tests.js index f1fe61fac..4c9471db9 100644 --- a/test/specs/controller.line.tests.js +++ b/test/specs/controller.line.tests.js @@ -730,4 +730,24 @@ describe('Line controller tests', function() { expect(point._model.borderWidth).toBe(0); }); + + it('should allow an array as the point border width setting', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [10, 15, 0, -4], + label: 'dataset1', + pointBorderWidth: [1, 2, 3, 4] + }], + labels: ['label1', 'label2', 'label3', 'label4'] + } + }); + + var meta = chart.getDatasetMeta(0); + expect(meta.data[0]._model.borderWidth).toBe(1); + expect(meta.data[1]._model.borderWidth).toBe(2); + expect(meta.data[2]._model.borderWidth).toBe(3); + expect(meta.data[3]._model.borderWidth).toBe(4); + }); }); From 7d60857819eaab2c16aea4ac7eb527a6ce8df844 Mon Sep 17 00:00:00 2001 From: etimberg Date: Mon, 3 Jul 2017 19:50:08 -0400 Subject: [PATCH 048/112] Use proper reverse option in radial linear scale --- src/scales/scale.radialLinear.js | 7 ++++--- test/specs/scale.radialLinear.tests.js | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/scales/scale.radialLinear.js b/src/scales/scale.radialLinear.js index 845dc5ded..90cb52cd0 100644 --- a/src/scales/scale.radialLinear.js +++ b/src/scales/scale.radialLinear.js @@ -244,7 +244,7 @@ module.exports = function(Chart) { ctx.lineWidth = angleLineOpts.lineWidth; ctx.strokeStyle = angleLineOpts.color; - var outerDistance = scale.getDistanceFromCenterForValue(opts.reverse ? scale.min : scale.max); + var outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max); // Point Label Font var plFont = getPointLabelFontOptions(scale); @@ -366,6 +366,7 @@ module.exports = function(Chart) { }, convertTicksToLabels: function() { var me = this; + Chart.LinearScaleBase.prototype.convertTicksToLabels.call(me); // Point labels @@ -433,7 +434,7 @@ module.exports = function(Chart) { // Take into account half font size + the yPadding of the top value var scalingFactor = me.drawingArea / (me.max - me.min); - if (me.options.reverse) { + if (me.options.ticks.reverse) { return (me.max - value) * scalingFactor; } return (value - me.min) * scalingFactor; @@ -480,7 +481,7 @@ module.exports = function(Chart) { helpers.each(me.ticks, function(label, index) { // Don't draw a centre value (if it is minimum) - if (index > 0 || opts.reverse) { + if (index > 0 || tickOpts.reverse) { var yCenterOffset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]); var yHeight = me.yCenter - yCenterOffset; diff --git a/test/specs/scale.radialLinear.tests.js b/test/specs/scale.radialLinear.tests.js index e966e1229..a8ac68ff1 100644 --- a/test/specs/scale.radialLinear.tests.js +++ b/test/specs/scale.radialLinear.tests.js @@ -401,7 +401,7 @@ describe('Test the radial linear scale', function() { y: 275, }); - chart.scale.options.reverse = true; + chart.scale.options.ticks.reverse = true; chart.update(); expect(chart.scale.getDistanceFromCenterForValue(chart.scale.min)).toBe(233); From 9ec78cee1c18c596c1550b24c90b90eaa9ac17de Mon Sep 17 00:00:00 2001 From: etimberg Date: Mon, 3 Jul 2017 18:00:03 -0400 Subject: [PATCH 049/112] Add a note on how to use getElementAtEvent in a click handler --- docs/developers/api.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/developers/api.md b/docs/developers/api.md index 2875375ed..a5a0a5133 100644 --- a/docs/developers/api.md +++ b/docs/developers/api.md @@ -128,6 +128,19 @@ myLineChart.getElementAtEvent(e); // => returns the first element at the event point. ``` +To get an item that was clicked on, `getElementAtEvent` can be used. + +```javascript +function clickHandler(evt) { + var item = myChart.getElementAtEvent(evt)[0]; + + if (item) { + var label = myChart.data.labels[firstPoint._index]; + var value = myChart.data.datasets[firstPoint._datasetIndex].data[firstPoint._index]; + } +} +``` + ## .getElementsAtEvent(e) Looks for the element under the event point, then returns all elements at the same data index. This is used internally for 'label' mode highlighting. From 6e54bc594ace713ddc75a45bdfb7f1e8d11e2432 Mon Sep 17 00:00:00 2001 From: Akihiko Kusanagi Date: Sun, 2 Jul 2017 15:40:00 +0900 Subject: [PATCH 050/112] Clip chart area before filling - Add clip and unclip around doFill() call - This fixes #4450 --- src/plugins/plugin.filler.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/plugins/plugin.filler.js b/src/plugins/plugin.filler.js index 9354e4ed5..2a91d38e8 100644 --- a/src/plugins/plugin.filler.js +++ b/src/plugins/plugin.filler.js @@ -296,6 +296,7 @@ module.exports = function(Chart) { return; } + var ctx = chart.ctx; var el = meta.el; var view = el._view; var points = el._children || []; @@ -303,7 +304,9 @@ module.exports = function(Chart) { var color = view.backgroundColor || defaults.global.defaultColor; if (mapper && color && points.length) { - doFill(chart.ctx, points, mapper, view, color, el._loop); + helpers.canvas.clipArea(ctx, chart.chartArea); + doFill(ctx, points, mapper, view, color, el._loop); + helpers.canvas.unclipArea(ctx); } } }; From e8ebb46aadd5185e7b8a531b9d7d07b988bb7f71 Mon Sep 17 00:00:00 2001 From: Alexander Paterson Date: Wed, 5 Jul 2017 20:11:29 +0800 Subject: [PATCH 051/112] Update link to documentation for previous versions --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c4b7c5e39..1a6e61e63 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ The `Chart.bundle.js` and `Chart.bundle.min.js` builds include Moment.js in a si ## Documentation -You can find documentation at [www.chartjs.org/docs](http://www.chartjs.org/docs). The markdown files that build the site are available under `/docs`. Previous version documentation is available at [www.chartjs.org/docs/#notes-previous-versions](http://www.chartjs.org/docs/#notes-previous-versions). +You can find documentation at [www.chartjs.org/docs](http://www.chartjs.org/docs). The markdown files that build the site are available under `/docs`. Previous version documentation is available at [www.chartjs.org/docs/latest/developers/#previous-versions](http://www.chartjs.org/docs/latest/developers/#previous-versions). ## Contributing From 56050dc9b7a17130efdbdcd3b8de46b728f25b84 Mon Sep 17 00:00:00 2001 From: Simon Brunel Date: Sat, 8 Jul 2017 09:44:00 +0200 Subject: [PATCH 052/112] Move easing effects in separate file + unit tests --- src/chart.js | 1 + src/core/core.controller.js | 2 +- src/core/core.helpers.js | 197 ------------------ src/helpers/helpers.easing.js | 252 ++++++++++++++++++++++++ test/specs/global.deprecations.tests.js | 7 + test/specs/helpers.easing.tests.js | 61 ++++++ 6 files changed, 322 insertions(+), 198 deletions(-) create mode 100644 src/helpers/helpers.easing.js create mode 100644 test/specs/helpers.easing.tests.js diff --git a/src/chart.js b/src/chart.js index 22aa7d893..8026cc856 100644 --- a/src/chart.js +++ b/src/chart.js @@ -4,6 +4,7 @@ var Chart = require('./core/core')(); require('./helpers/helpers.core')(Chart); +require('./helpers/helpers.easing')(Chart); require('./core/core.helpers')(Chart); require('./helpers/helpers.time')(Chart); require('./helpers/helpers.canvas')(Chart); diff --git a/src/core/core.controller.js b/src/core/core.controller.js index 7f8050ad9..c367c36ae 100644 --- a/src/core/core.controller.js +++ b/src/core/core.controller.js @@ -484,7 +484,7 @@ module.exports = function(Chart) { easing: config.easing || animationOptions.easing, render: function(chart, animationObject) { - var easingFunction = helpers.easingEffects[animationObject.easing]; + var easingFunction = helpers.easing.effects[animationObject.easing]; var currentStep = animationObject.currentStep; var stepDecimal = currentStep / animationObject.numSteps; diff --git a/src/core/core.helpers.js b/src/core/core.helpers.js index e66d339d3..854a55575 100644 --- a/src/core/core.helpers.js +++ b/src/core/core.helpers.js @@ -388,203 +388,6 @@ module.exports = function(Chart) { return niceFraction * Math.pow(10, exponent); }; - // Easing functions adapted from Robert Penner's easing equations - // http://www.robertpenner.com/easing/ - var easingEffects = helpers.easingEffects = { - linear: function(t) { - return t; - }, - easeInQuad: function(t) { - return t * t; - }, - easeOutQuad: function(t) { - return -1 * t * (t - 2); - }, - easeInOutQuad: function(t) { - if ((t /= 1 / 2) < 1) { - return 1 / 2 * t * t; - } - return -1 / 2 * ((--t) * (t - 2) - 1); - }, - easeInCubic: function(t) { - return t * t * t; - }, - easeOutCubic: function(t) { - return 1 * ((t = t / 1 - 1) * t * t + 1); - }, - easeInOutCubic: function(t) { - if ((t /= 1 / 2) < 1) { - return 1 / 2 * t * t * t; - } - return 1 / 2 * ((t -= 2) * t * t + 2); - }, - easeInQuart: function(t) { - return t * t * t * t; - }, - easeOutQuart: function(t) { - return -1 * ((t = t / 1 - 1) * t * t * t - 1); - }, - easeInOutQuart: function(t) { - if ((t /= 1 / 2) < 1) { - return 1 / 2 * t * t * t * t; - } - return -1 / 2 * ((t -= 2) * t * t * t - 2); - }, - easeInQuint: function(t) { - return 1 * (t /= 1) * t * t * t * t; - }, - easeOutQuint: function(t) { - return 1 * ((t = t / 1 - 1) * t * t * t * t + 1); - }, - easeInOutQuint: function(t) { - if ((t /= 1 / 2) < 1) { - return 1 / 2 * t * t * t * t * t; - } - return 1 / 2 * ((t -= 2) * t * t * t * t + 2); - }, - easeInSine: function(t) { - return -1 * Math.cos(t / 1 * (Math.PI / 2)) + 1; - }, - easeOutSine: function(t) { - return 1 * Math.sin(t / 1 * (Math.PI / 2)); - }, - easeInOutSine: function(t) { - return -1 / 2 * (Math.cos(Math.PI * t / 1) - 1); - }, - easeInExpo: function(t) { - return (t === 0) ? 1 : 1 * Math.pow(2, 10 * (t / 1 - 1)); - }, - easeOutExpo: function(t) { - return (t === 1) ? 1 : 1 * (-Math.pow(2, -10 * t / 1) + 1); - }, - easeInOutExpo: function(t) { - if (t === 0) { - return 0; - } - if (t === 1) { - return 1; - } - if ((t /= 1 / 2) < 1) { - return 1 / 2 * Math.pow(2, 10 * (t - 1)); - } - return 1 / 2 * (-Math.pow(2, -10 * --t) + 2); - }, - easeInCirc: function(t) { - if (t >= 1) { - return t; - } - return -1 * (Math.sqrt(1 - (t /= 1) * t) - 1); - }, - easeOutCirc: function(t) { - return 1 * Math.sqrt(1 - (t = t / 1 - 1) * t); - }, - easeInOutCirc: function(t) { - if ((t /= 1 / 2) < 1) { - return -1 / 2 * (Math.sqrt(1 - t * t) - 1); - } - return 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1); - }, - easeInElastic: function(t) { - var s = 1.70158; - var p = 0; - var a = 1; - if (t === 0) { - return 0; - } - if ((t /= 1) === 1) { - return 1; - } - if (!p) { - p = 1 * 0.3; - } - if (a < Math.abs(1)) { - a = 1; - s = p / 4; - } else { - s = p / (2 * Math.PI) * Math.asin(1 / a); - } - return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p)); - }, - easeOutElastic: function(t) { - var s = 1.70158; - var p = 0; - var a = 1; - if (t === 0) { - return 0; - } - if ((t /= 1) === 1) { - return 1; - } - if (!p) { - p = 1 * 0.3; - } - if (a < Math.abs(1)) { - a = 1; - s = p / 4; - } else { - s = p / (2 * Math.PI) * Math.asin(1 / a); - } - return a * Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1; - }, - easeInOutElastic: function(t) { - var s = 1.70158; - var p = 0; - var a = 1; - if (t === 0) { - return 0; - } - if ((t /= 1 / 2) === 2) { - return 1; - } - if (!p) { - p = 1 * (0.3 * 1.5); - } - if (a < Math.abs(1)) { - a = 1; - s = p / 4; - } else { - s = p / (2 * Math.PI) * Math.asin(1 / a); - } - if (t < 1) { - return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p)); - } - return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) * 0.5 + 1; - }, - easeInBack: function(t) { - var s = 1.70158; - return 1 * (t /= 1) * t * ((s + 1) * t - s); - }, - easeOutBack: function(t) { - var s = 1.70158; - return 1 * ((t = t / 1 - 1) * t * ((s + 1) * t + s) + 1); - }, - easeInOutBack: function(t) { - var s = 1.70158; - if ((t /= 1 / 2) < 1) { - return 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)); - } - return 1 / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2); - }, - easeInBounce: function(t) { - return 1 - easingEffects.easeOutBounce(1 - t); - }, - easeOutBounce: function(t) { - if ((t /= 1) < (1 / 2.75)) { - return 1 * (7.5625 * t * t); - } else if (t < (2 / 2.75)) { - return 1 * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75); - } else if (t < (2.5 / 2.75)) { - return 1 * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375); - } - return 1 * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375); - }, - easeInOutBounce: function(t) { - if (t < 1 / 2) { - return easingEffects.easeInBounce(t * 2) * 0.5; - } - return easingEffects.easeOutBounce(t * 2 - 1) * 0.5 + 1 * 0.5; - } - }; // Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/ helpers.requestAnimFrame = (function() { if (typeof window === 'undefined') { diff --git a/src/helpers/helpers.easing.js b/src/helpers/helpers.easing.js new file mode 100644 index 000000000..975cc9288 --- /dev/null +++ b/src/helpers/helpers.easing.js @@ -0,0 +1,252 @@ +'use strict'; + +/** + * Easing functions adapted from Robert Penner's easing equations. + * http://www.robertpenner.com/easing/ + */ +var effects = { + linear: function(t) { + return t; + }, + + easeInQuad: function(t) { + return t * t; + }, + + easeOutQuad: function(t) { + return -t * (t - 2); + }, + + easeInOutQuad: function(t) { + if ((t /= 0.5) < 1) { + return 0.5 * t * t; + } + return -0.5 * ((--t) * (t - 2) - 1); + }, + + easeInCubic: function(t) { + return t * t * t; + }, + + easeOutCubic: function(t) { + return (t = t - 1) * t * t + 1; + }, + + easeInOutCubic: function(t) { + if ((t /= 0.5) < 1) { + return 0.5 * t * t * t; + } + return 0.5 * ((t -= 2) * t * t + 2); + }, + + easeInQuart: function(t) { + return t * t * t * t; + }, + + easeOutQuart: function(t) { + return -((t = t - 1) * t * t * t - 1); + }, + + easeInOutQuart: function(t) { + if ((t /= 0.5) < 1) { + return 0.5 * t * t * t * t; + } + return -0.5 * ((t -= 2) * t * t * t - 2); + }, + + easeInQuint: function(t) { + return t * t * t * t * t; + }, + + easeOutQuint: function(t) { + return (t = t - 1) * t * t * t * t + 1; + }, + + easeInOutQuint: function(t) { + if ((t /= 0.5) < 1) { + return 0.5 * t * t * t * t * t; + } + return 0.5 * ((t -= 2) * t * t * t * t + 2); + }, + + easeInSine: function(t) { + return -Math.cos(t * (Math.PI / 2)) + 1; + }, + + easeOutSine: function(t) { + return Math.sin(t * (Math.PI / 2)); + }, + + easeInOutSine: function(t) { + return -0.5 * (Math.cos(Math.PI * t) - 1); + }, + + easeInExpo: function(t) { + return (t === 0) ? 0 : Math.pow(2, 10 * (t - 1)); + }, + + easeOutExpo: function(t) { + return (t === 1) ? 1 : -Math.pow(2, -10 * t) + 1; + }, + + easeInOutExpo: function(t) { + if (t === 0) { + return 0; + } + if (t === 1) { + return 1; + } + if ((t /= 0.5) < 1) { + return 0.5 * Math.pow(2, 10 * (t - 1)); + } + return 0.5 * (-Math.pow(2, -10 * --t) + 2); + }, + + easeInCirc: function(t) { + if (t >= 1) { + return t; + } + return -(Math.sqrt(1 - t * t) - 1); + }, + + easeOutCirc: function(t) { + return Math.sqrt(1 - (t = t - 1) * t); + }, + + easeInOutCirc: function(t) { + if ((t /= 0.5) < 1) { + return -0.5 * (Math.sqrt(1 - t * t) - 1); + } + return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1); + }, + + easeInElastic: function(t) { + var s = 1.70158; + var p = 0; + var a = 1; + if (t === 0) { + return 0; + } + if (t === 1) { + return 1; + } + if (!p) { + p = 1 * 0.3; + } + if (a < Math.abs(1)) { + a = 1; + s = p / 4; + } else { + s = p / (2 * Math.PI) * Math.asin(1 / a); + } + return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p)); + }, + + easeOutElastic: function(t) { + var s = 1.70158; + var p = 0; + var a = 1; + if (t === 0) { + return 0; + } + if (t === 1) { + return 1; + } + if (!p) { + p = 1 * 0.3; + } + if (a < Math.abs(1)) { + a = 1; + s = p / 4; + } else { + s = p / (2 * Math.PI) * Math.asin(1 / a); + } + return a * Math.pow(2, -10 * t) * Math.sin((t - s) * (2 * Math.PI) / p) + 1; + }, + + easeInOutElastic: function(t) { + var s = 1.70158; + var p = 0; + var a = 1; + if (t === 0) { + return 0; + } + if ((t /= 0.5) === 2) { + return 1; + } + if (!p) { + p = 0.3 * 1.5; + } + if (a < Math.abs(1)) { + a = 1; + s = p / 4; + } else { + s = p / (2 * Math.PI) * Math.asin(1 / a); + } + if (t < 1) { + return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p)); + } + return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p) * 0.5 + 1; + }, + easeInBack: function(t) { + var s = 1.70158; + return t * t * ((s + 1) * t - s); + }, + + easeOutBack: function(t) { + var s = 1.70158; + return (t = t - 1) * t * ((s + 1) * t + s) + 1; + }, + + easeInOutBack: function(t) { + var s = 1.70158; + if ((t /= 0.5) < 1) { + return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s)); + } + return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2); + }, + + easeInBounce: function(t) { + return 1 - effects.easeOutBounce(1 - t); + }, + + easeOutBounce: function(t) { + if (t < (1 / 2.75)) { + return 7.5625 * t * t; + } + if (t < (2 / 2.75)) { + return 7.5625 * (t -= (1.5 / 2.75)) * t + 0.75; + } + if (t < (2.5 / 2.75)) { + return 7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375; + } + return 7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375; + }, + + easeInOutBounce: function(t) { + if (t < 0.5) { + return effects.easeInBounce(t * 2) * 0.5; + } + return effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5; + } +}; + +module.exports = function(Chart) { + /** + * @namespace Chart.helpers.easing.effects + */ + Chart.helpers.easing = { + effects: effects + }; + + /** + * Provided for backward compatibility, use Chart.helpers.easing.effects instead. + * @function Chart.helpers.easingEffects + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ + Chart.helpers.easingEffects = effects; + + return Chart.helpers.easing; +}; diff --git a/test/specs/global.deprecations.tests.js b/test/specs/global.deprecations.tests.js index 0f9feb894..8c9608694 100644 --- a/test/specs/global.deprecations.tests.js +++ b/test/specs/global.deprecations.tests.js @@ -84,6 +84,13 @@ describe('Deprecations', function() { }); }); + describe('Chart.helpers.easingEffects', function() { + it('should be defined and an alias of Chart.helpers.easing.effects', function() { + expect(Chart.helpers.easingEffects).toBeDefined(); + expect(Chart.helpers.easingEffects).toBe(Chart.helpers.easing.effects); + }); + }); + describe('Chart.helpers.drawRoundedRectangle', function() { it('should be defined and a function', function() { expect(Chart.helpers.drawRoundedRectangle).toBeDefined(); diff --git a/test/specs/helpers.easing.tests.js b/test/specs/helpers.easing.tests.js new file mode 100644 index 000000000..b27655c9b --- /dev/null +++ b/test/specs/helpers.easing.tests.js @@ -0,0 +1,61 @@ +'use strict'; + +describe('Chart.helpers.easing', function() { + var easing = Chart.helpers.easing; + + describe('effects', function() { + var expected = { + easeInOutBack: [-0, -0.03751855, -0.09255566, -0.07883348, 0.08992579, 0.5, 0.91007421, 1.07883348, 1.09255566, 1.03751855, 1], + easeInOutBounce: [0, 0.03, 0.11375, 0.045, 0.34875, 0.5, 0.65125, 0.955, 0.88625, 0.97, 1], + easeInOutCirc: [-0, 0.01010205, 0.04174243, 0.1, 0.2, 0.5, 0.8, 0.9, 0.95825757, 0.98989795, 1], + easeInOutCubic: [0, 0.004, 0.032, 0.108, 0.256, 0.5, 0.744, 0.892, 0.968, 0.996, 1], + easeInOutElastic: [0, 0.00033916, -0.00390625, 0.02393889, -0.11746158, 0.5, 1.11746158, 0.97606111, 1.00390625, 0.99966084, 1], + easeInOutExpo: [0, 0.00195313, 0.0078125, 0.03125, 0.125, 0.5, 0.875, 0.96875, 0.9921875, 0.99804688, 1], + easeInOutQuad: [0, 0.02, 0.08, 0.18, 0.32, 0.5, 0.68, 0.82, 0.92, 0.98, 1], + easeInOutQuart: [0, 0.0008, 0.0128, 0.0648, 0.2048, 0.5, 0.7952, 0.9352, 0.9872, 0.9992, 1], + easeInOutQuint: [0, 0.00016, 0.00512, 0.03888, 0.16384, 0.5, 0.83616, 0.96112, 0.99488, 0.99984, 1], + easeInOutSine: [-0, 0.02447174, 0.0954915, 0.20610737, 0.3454915, 0.5, 0.6545085, 0.79389263, 0.9045085, 0.97552826, 1], + easeInBack: [-0, -0.01431422, -0.04645056, -0.08019954, -0.09935168, -0.0876975, -0.02902752, 0.09286774, 0.29419776, 0.59117202, 1], + easeInBounce: [0, 0.011875, 0.06, 0.069375, 0.2275, 0.234375, 0.09, 0.319375, 0.6975, 0.924375, 1], + easeInCirc: [-0, 0.00501256, 0.0202041, 0.0460608, 0.08348486, 0.1339746, 0.2, 0.28585716, 0.4, 0.56411011, 1], + easeInCubic: [0, 0.001, 0.008, 0.027, 0.064, 0.125, 0.216, 0.343, 0.512, 0.729, 1], + easeInExpo: [0, 0.00195313, 0.00390625, 0.0078125, 0.015625, 0.03125, 0.0625, 0.125, 0.25, 0.5, 1], + easeInElastic: [0, 0.00195313, -0.00195313, -0.00390625, 0.015625, -0.015625, -0.03125, 0.125, -0.125, -0.25, 1], + easeInQuad: [0, 0.01, 0.04, 0.09, 0.16, 0.25, 0.36, 0.49, 0.64, 0.81, 1], + easeInQuart: [0, 0.0001, 0.0016, 0.0081, 0.0256, 0.0625, 0.1296, 0.2401, 0.4096, 0.6561, 1], + easeInQuint: [0, 0.00001, 0.00032, 0.00243, 0.01024, 0.03125, 0.07776, 0.16807, 0.32768, 0.59049, 1], + easeInSine: [0, 0.01231166, 0.04894348, 0.10899348, 0.19098301, 0.29289322, 0.41221475, 0.5460095, 0.69098301, 0.84356553, 1], + easeOutBack: [0, 0.40882798, 0.70580224, 0.90713226, 1.02902752, 1.0876975, 1.09935168, 1.08019954, 1.04645056, 1.01431422, 1], + easeOutBounce: [0, 0.075625, 0.3025, 0.680625, 0.91, 0.765625, 0.7725, 0.930625, 0.94, 0.988125, 1], + easeOutCirc: [0, 0.43588989, 0.6, 0.71414284, 0.8, 0.8660254, 0.91651514, 0.9539392, 0.9797959, 0.99498744, 1], + easeOutElastic: [0, 1.25, 1.125, 0.875, 1.03125, 1.015625, 0.984375, 1.00390625, 1.00195313, 0.99804688, 1], + easeOutExpo: [0, 0.5, 0.75, 0.875, 0.9375, 0.96875, 0.984375, 0.9921875, 0.99609375, 0.99804688, 1], + easeOutCubic: [0, 0.271, 0.488, 0.657, 0.784, 0.875, 0.936, 0.973, 0.992, 0.999, 1], + easeOutQuad: [0, 0.19, 0.36, 0.51, 0.64, 0.75, 0.84, 0.91, 0.96, 0.99, 1], + easeOutQuart: [-0, 0.3439, 0.5904, 0.7599, 0.8704, 0.9375, 0.9744, 0.9919, 0.9984, 0.9999, 1], + easeOutQuint: [0, 0.40951, 0.67232, 0.83193, 0.92224, 0.96875, 0.98976, 0.99757, 0.99968, 0.99999, 1], + easeOutSine: [0, 0.15643447, 0.30901699, 0.4539905, 0.58778525, 0.70710678, 0.80901699, 0.89100652, 0.95105652, 0.98768834, 1], + linear: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1] + }; + + function generate(method) { + var fn = easing.effects[method]; + var accuracy = Math.pow(10, 8); + var count = 10; + var values = []; + var i; + + for (i=0; i<=count; ++i) { + values.push(Math.round(accuracy * fn(i/count)) / accuracy); + } + + return values; + } + + Object.keys(easing.effects).forEach(function(method) { + it ('"' + method + '" should return expected values', function() { + expect(generate(method)).toEqual(expected[method]); + }); + }); + }); +}); From 463a8dd77899cde1a2c542c6c90d0c7614285f16 Mon Sep 17 00:00:00 2001 From: Simon Brunel Date: Sat, 8 Jul 2017 15:38:40 +0200 Subject: [PATCH 053/112] Simplify formulas based on code review --- src/helpers/helpers.easing.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/helpers/helpers.easing.js b/src/helpers/helpers.easing.js index 975cc9288..23ca6fff0 100644 --- a/src/helpers/helpers.easing.js +++ b/src/helpers/helpers.easing.js @@ -131,9 +131,9 @@ var effects = { return 1; } if (!p) { - p = 1 * 0.3; + p = 0.3; } - if (a < Math.abs(1)) { + if (a < 1) { a = 1; s = p / 4; } else { @@ -153,9 +153,9 @@ var effects = { return 1; } if (!p) { - p = 1 * 0.3; + p = 0.3; } - if (a < Math.abs(1)) { + if (a < 1) { a = 1; s = p / 4; } else { @@ -175,9 +175,9 @@ var effects = { return 1; } if (!p) { - p = 0.3 * 1.5; + p = 0.45; } - if (a < Math.abs(1)) { + if (a < 1) { a = 1; s = p / 4; } else { From f16d8a32e2641bae8483a5194b7ddb1ec56ab5fa Mon Sep 17 00:00:00 2001 From: Adrian Liaw Date: Sun, 9 Jul 2017 20:58:18 +0800 Subject: [PATCH 054/112] Fix links in documentations (#4477) * Fix relative links in documentation of configurations * Fix relative link of 'Time Units' * Fix relative links for chart types documentations * Fix the release version link in developers/plugins --- docs/axes/cartesian/time.md | 4 ++-- docs/charts/bar.md | 12 ++++++------ docs/charts/bubble.md | 4 ++-- docs/charts/line.md | 4 ++-- docs/charts/polar.md | 4 ++-- docs/charts/radar.md | 2 +- docs/configuration/legend.md | 6 +++--- docs/configuration/tooltip.md | 8 ++++---- docs/developers/plugins.md | 2 +- 9 files changed, 23 insertions(+), 23 deletions(-) diff --git a/docs/axes/cartesian/time.md b/docs/axes/cartesian/time.md index 6b6aaa64f..dc4a2bb42 100644 --- a/docs/axes/cartesian/time.md +++ b/docs/axes/cartesian/time.md @@ -13,9 +13,9 @@ The following options are provided by the time scale. They are all located in th | `max` | [Time](#date-formats) | | If defined, this will override the data maximum | `min` | [Time](#date-formats) | | If defined, this will override the data minimum | `parser` | `String` or `Function` | | Custom parser for dates. [more...](#parser) -| `round` | `String` | `false` | If defined, dates will be rounded to the start of this unit. See [Time Units](#scales-time-units) below for the allowed units. +| `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. | `tooltipFormat` | `String` | | The moment js format string to use for the tooltip. -| `unit` | `String` | `false` | If defined, will force the unit to be a certain type. See [Time Units](#scales-time-units) section below for details. +| `unit` | `String` | `false` | If defined, will force the unit to be a certain type. See [Time Units](#time-units) section below for details. | `stepSize` | `Number` | `1` | The number of units between grid lines. | `minUnit` | `String` | `'millisecond'` | The minimum display format to be used for a time unit. diff --git a/docs/charts/bar.md b/docs/charts/bar.md index 32cc32e9e..4d5697ec8 100644 --- a/docs/charts/bar.md +++ b/docs/charts/bar.md @@ -73,7 +73,7 @@ Some properties can be specified as an array. If these are set to an array value | `backgroundColor` | `Color/Color[]` | The fill color of the bar. See [Colors](../general/colors.md#colors) | `borderColor` | `Color/Color[]` | The color of the bar border. See [Colors](../general/colors.md#colors) | `borderWidth` | `Number/Number[]` | The stroke width of the bar in pixels. -| `borderSkipped` | `String` | Which edge to skip drawing the border for. [more...](#borderSkipped) +| `borderSkipped` | `String` | Which edge to skip drawing the border for. [more...](#borderskipped) | `hoverBackgroundColor` | `Color/Color[]` | The fill colour of the bars when hovered. | `hoverBorderColor` | `Color/Color[]` | The stroke colour of the bars when hovered. | `hoverBorderWidth` | `Number/Number[]` | The stroke width of the bars when hovered. @@ -93,11 +93,11 @@ The bar chart defines the following configuration options. These options are mer | Name | Type | Default | Description | ---- | ---- | ------- | ----------- -| `barPercentage` | `Number` | `0.9` | Percent (0-1) of the available width each bar should be within the category percentage. 1.0 will take the whole category width and put the bars right next to each other. [more...](#bar-chart-barpercentage-vs-categorypercentage) -| `categoryPercentage` | `Number` | `0.8` | Percent (0-1) of the available width (the space between the gridlines for small datasets) for each data-point to use for the bars. [more...](#bar-chart-barpercentage-vs-categorypercentage) +| `barPercentage` | `Number` | `0.9` | Percent (0-1) of the available width each bar should be within the category percentage. 1.0 will take the whole category width and put the bars right next to each other. [more...](#barpercentage-vs-categorypercentage) +| `categoryPercentage` | `Number` | `0.8` | Percent (0-1) of the available width (the space between the gridlines for small datasets) for each data-point to use for the bars. [more...](#barpercentage-vs-categorypercentage) | `barThickness` | `Number` | | Manually set width of each bar in pixels. If not set, the bars are sized automatically using `barPercentage` and `categoryPercentage`; | `maxBarThickness` | `Number` | | Set this to ensure that the automatically sized bars are not sized thicker than this. Only works if barThickness is not set (automatic sizing is enabled). -| `gridLines.offsetGridLines` | `Boolean` | `true` | If true, the bars for a particular data point fall between the grid lines. If false, the grid line will go right down the middle of the bars. [more...](#offsetGridLines) +| `gridLines.offsetGridLines` | `Boolean` | `true` | If true, the bars for a particular data point fall between the grid lines. If false, the grid line will go right down the middle of the bars. [more...](#offsetgridlines) ### offsetGridLines If true, the bars for a particular data point fall between the grid lines. If false, the grid line will go right down the middle of the bars. It is unlikely that this will ever need to be changed in practice. It exists more as a way to reuse the axis code by configuring the existing axis slightly differently. @@ -235,6 +235,6 @@ var myBarChart = new Chart(ctx, { ``` ## Config Options -The configuration options for the horizontal bar chart are the same as for the [bar chart](../bar/config-options.md#config-options). However, any options specified on the x axis in a bar chart, are applied to the y axis in a horizontal bar chart. +The configuration options for the horizontal bar chart are the same as for the [bar chart](#configuration-options). However, any options specified on the x axis in a bar chart, are applied to the y axis in a horizontal bar chart. -The default horizontal bar configuration is specified in `Chart.defaults.horizontalBar`. \ No newline at end of file +The default horizontal bar configuration is specified in `Chart.defaults.horizontalBar`. diff --git a/docs/charts/bubble.md b/docs/charts/bubble.md index d36f7eea4..df96911c6 100644 --- a/docs/charts/bubble.md +++ b/docs/charts/bubble.md @@ -53,7 +53,7 @@ All properties, except `label` can be specified as an array. If these are set to ## Config Options -The bubble chart has no unique configuration options. To configure options common to all of the bubbles, the [point element options](../configuration/elements/point.md#point-configuration) are used. +The bubble chart has no unique configuration options. To configure options common to all of the bubbles, the [point element options](../configuration/elements.md#point-configuration) are used. ## Default Options @@ -78,4 +78,4 @@ Data for the bubble chart is passed in the form of an object. The object must im // Radius of bubble. This is not scaled. r: } -``` \ No newline at end of file +``` diff --git a/docs/charts/line.md b/docs/charts/line.md index b390d1187..499191130 100644 --- a/docs/charts/line.md +++ b/docs/charts/line.md @@ -55,14 +55,14 @@ All point* properties can be specified as an array. If these are set to an array | `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) | `borderJoinStyle` | `String` | Line joint style. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin) -| `cubicInterpolationMode` | `String` | Algorithm used to interpolate a smooth curve from the discrete data points. [more...](#cubicInterpolationMode) +| `cubicInterpolationMode` | `String` | Algorithm used to interpolate a smooth curve from the discrete data points. [more...](#cubicinterpolationmode) | `fill` | `Boolean/String` | How to fill the area under the line. See [area charts](area.md) | `lineTension` | `Number` | Bezier curve tension of the line. Set to 0 to draw straightlines. This option is ignored if monotone cubic interpolation is used. | `pointBackgroundColor` | `Color/Color[]` | The fill color for points. | `pointBorderColor` | `Color/Color[]` | The border color for points. | `pointBorderWidth` | `Number/Number[]` | The width of the point border in pixels. | `pointRadius` | `Number/Number[]` | The radius of the point shape. If set to 0, the point is not rendered. -| `pointStyle` | `String/String[]/Image/Image[]` | Style of the point. [more...](#pointStyle) +| `pointStyle` | `String/String[]/Image/Image[]` | Style of the point. [more...](#pointstyle) | `pointHitRadius` | `Number/Number[]` | The pixel size of the non-displayed point that reacts to mouse events. | `pointHoverBackgroundColor` | `Color/Color[]` | Point background color when hovered. | `pointHoverBorderColor` | `Color/Color[]` | Point border color when hovered. diff --git a/docs/charts/polar.md b/docs/charts/polar.md index 4c55556f1..29952cff6 100644 --- a/docs/charts/polar.md +++ b/docs/charts/polar.md @@ -56,7 +56,7 @@ The following options can be included in a polar area chart dataset to configure ## Config Options -These are the customisation options specific to Polar Area charts. These options are merged with the [global chart configuration options](#global-chart-configuration), and form the options of the chart. +These are the customisation options specific to Polar Area charts. These options are merged with the [global chart default options](#default-options), and form the options of the chart. | Name | Type | Default | Description | ---- | ---- | ------- | ----------- @@ -92,4 +92,4 @@ data = { 'Blue' ] }; -``` \ No newline at end of file +``` diff --git a/docs/charts/radar.md b/docs/charts/radar.md index dd5d47b27..592bb94ef 100644 --- a/docs/charts/radar.md +++ b/docs/charts/radar.md @@ -82,7 +82,7 @@ All point* properties can be specified as an array. If these are set to an array | `pointBorderColor` | `Color/Color[]` | The border color for points. | `pointBorderWidth` | `Number/Number[]` | The width of the point border in pixels. | `pointRadius` | `Number/Number[]` | The radius of the point shape. If set to 0, the point is not rendered. -| `pointStyle` | `String/String[]/Image/Image[]` | Style of the point. [more...](#pointStyle) +| `pointStyle` | `String/String[]/Image/Image[]` | Style of the point. [more...](#pointstyle) | `pointHitRadius` | `Number/Number[]` | The pixel size of the non-displayed point that reacts to mouse events. | `pointHoverBackgroundColor` | `Color/Color[]` | Point background color when hovered. | `pointHoverBorderColor` | `Color/Color[]` | Point border color when hovered. diff --git a/docs/configuration/legend.md b/docs/configuration/legend.md index c398908b4..4cad48b55 100644 --- a/docs/configuration/legend.md +++ b/docs/configuration/legend.md @@ -34,8 +34,8 @@ The legend label configuration is nested below the legend configuration using th | `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](#chart-configuration-legend-item-interface) for details. -| `filter` | `Function` | `null` | Filters legend items out of the legend. Receives 2 parameters, a [Legend Item](#chart-configuration-legend-item-interface) and the chart data. +| `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. +| `filter` | `Function` | `null` | Filters legend items out of the legend. Receives 2 parameters, a [Legend Item](#legend-item-interface) and the chart data. | `usePointStyle` | `Boolean` | `false` | Label style will match corresponding point style (size is based on fontSize, boxWidth is not used in this case). ## Legend Item Interface @@ -163,4 +163,4 @@ var chart = new Chart(ctx, { } } }); -``` \ No newline at end of file +``` diff --git a/docs/configuration/tooltip.md b/docs/configuration/tooltip.md index 8947d2463..bb3b7a6ae 100644 --- a/docs/configuration/tooltip.md +++ b/docs/configuration/tooltip.md @@ -7,7 +7,7 @@ The tooltip configuration is passed into the `options.tooltips` namespace. The g | Name | Type | Default | Description | -----| ---- | --------| ----------- | `enabled` | `Boolean` | `true` | Are tooltips enabled -| `custom` | `Function` | `null` | See [custom tooltip](#custom-tooltips) section. +| `custom` | `Function` | `null` | See [custom tooltip](#external-custom-tooltips) section. | `mode` | `String` | `'nearest'` | Sets which elements appear in the tooltip. [more...](../general/interactions/modes.md#interaction-modes). | `intersect` | `Boolean` | `true` | if true, the tooltip mode applies only when the mouse position intersects with an element. If false, the mode will be applied at all times. | `position` | `String` | `'average'` | The mode for positioning the tooltip. [more...](#position-modes) @@ -53,17 +53,17 @@ New modes can be defined by adding functions to the Chart.Tooltip.positioners ma ### Sort Callback -Allows sorting of [tooltip items](#chart-configuration-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. +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. ### Filter Callback -Allows filtering of [tooltip items](#chart-configuration-tooltip-item-interface). Must implement at minimum a function that can be passed to [Array.prototype.filter](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/filter). This function can also accept a second parameter that is the data object passed to the chart. +Allows filtering of [tooltip items](#tooltip-item-interface). Must implement at minimum a function that can be passed to [Array.prototype.filter](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/filter). This function can also accept a second parameter that is the data object passed to the chart. ## Tooltip Callbacks The tooltip label configuration is nested below the tooltip configuration using the `callbacks` key. The tooltip has the following callbacks for providing text. For all functions, 'this' will be the tooltip object created from the Chart.Tooltip constructor. -All functions are called with the same arguments: a [tooltip item](#chart-configuration-tooltip-item-interface) and the data object passed to the chart. All functions must return either a string or an array of strings. Arrays of strings are treated as multiple lines of text. +All functions are called with the same arguments: a [tooltip item](#tooltip-item-interface) and the data object passed to the chart. All functions must return either a string or an array of strings. Arrays of strings are treated as multiple lines of text. | Name | Arguments | Description | ---- | --------- | ----------- diff --git a/docs/developers/plugins.md b/docs/developers/plugins.md index f0a1d9566..cc4284f8f 100644 --- a/docs/developers/plugins.md +++ b/docs/developers/plugins.md @@ -1,6 +1,6 @@ # Plugins -Plugins are the most efficient way to customize or change the default behavior of a chart. They have been introduced at [version 2.1.0](https://github.com/chartjs/Chart.js/releases/tag/2.1.0) (global plugins only) and extended at [version 2.5.0](https://github.com/chartjs/Chart.js/releases/tag/2.5.0) (per chart plugins and options). +Plugins are the most efficient way to customize or change the default behavior of a chart. They have been introduced at [version 2.1.0](https://github.com/chartjs/Chart.js/releases/tag/2.1.0) (global plugins only) and extended at [version 2.5.0](https://github.com/chartjs/Chart.js/releases/tag/v2.5.0) (per chart plugins and options). ## Using plugins From b03ab1ca452b0e613d3c3f495f38d78937aa52df Mon Sep 17 00:00:00 2001 From: Suhaib Khan Date: Sat, 15 Jul 2017 13:49:16 +0530 Subject: [PATCH 055/112] Fix labelOffset not working for vertical axes (#4249) --- src/core/core.scale.js | 2 +- .../label-offset-vertical-axes.json | 41 ++++++++++++++++++ .../core.scale/label-offset-vertical-axes.png | Bin 0 -> 4434 bytes test/specs/core.scale.tests.js | 3 ++ 4 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 test/fixtures/core.scale/label-offset-vertical-axes.json create mode 100644 test/fixtures/core.scale/label-offset-vertical-axes.png create mode 100644 test/specs/core.scale.tests.js diff --git a/src/core/core.scale.js b/src/core/core.scale.js index 5d8d82576..2938385cd 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -647,7 +647,7 @@ module.exports = function(Chart) { var yLineValue = me.getPixelForTick(index); // xvalues for grid lines yLineValue += helpers.aliasPixel(lineWidth); - labelY = me.getPixelForTick(index, gridLines.offsetGridLines); + labelY = me.getPixelForTick(index, gridLines.offsetGridLines) + optionTicks.labelOffset; tx1 = xTickStart; tx2 = xTickEnd; diff --git a/test/fixtures/core.scale/label-offset-vertical-axes.json b/test/fixtures/core.scale/label-offset-vertical-axes.json new file mode 100644 index 000000000..dbd979ecd --- /dev/null +++ b/test/fixtures/core.scale/label-offset-vertical-axes.json @@ -0,0 +1,41 @@ +{ + "config": { + "type": "horizontalBar", + "data": { + "labels": ["\u25C0", "\u25A0", "\u25C6", "\u25CF"], + "datasets": [{ + "data": [12, 19, 3, 5] + }] + }, + "options": { + "legend": false, + "title": false, + "scales": { + "xAxes": [{ + "ticks": { + "display": false + }, + "gridLines":{ + "display": false, + "drawBorder": false + } + }], + "yAxes": [{ + "ticks": { + "labelOffset": 25 + }, + "gridLines":{ + "display": false, + "drawBorder": false + } + }] + } + } + }, + "options": { + "canvas": { + "height": 256, + "width": 512 + } + } +} diff --git a/test/fixtures/core.scale/label-offset-vertical-axes.png b/test/fixtures/core.scale/label-offset-vertical-axes.png new file mode 100644 index 0000000000000000000000000000000000000000..2f6b18c8c23939f2b79177a937ba2458298f826b GIT binary patch literal 4434 zcmeHLSy)qN8b0SV!9cKt9gEnoC`3d+78gJdlr>6(&_}5SO`(dQECmXvG$dLTw}O%? zB0@%%!GeHVh=7=gQk8;Qz=dKU>VPN)u!yXZ`EriEotr*0H`A-+KYaW5zVCm+p&*Xm zT&+*E5CoYU;P1^v5DdPe2u%Y%nzQdlA&Bn00B_IF(>5!gNVCkM3^f$-DQ31^d##%E zL^rKiVy|=O5_OC3>zgO&<$s#Gk!GG2A3Mf78x!GFwhtP|Eq?4R zeB9${deOEj>G|nBNiQ0r?z&e>>v~t-b8El0Lm3w`V~Gn86r(J%zP~OmlCIArjy~v! z9z*?AsHtUx=xfVcC^R_Z=*Y+a<)!PF+l7;Lb2b#c>AGT>kCOK3+o!C=2lKk(@h$ak{1HGsDY z8cV)Z`%;5yAej5CArBGm2VdiTI2EOeu}ikFFE)(p@ULHCb{7X1M%F;aZ=0hi)yCq|C0T74tS2lB1u_FFlaL1U}F=uX}T`83{y z2MT0f;CsYVyjOq)pyc8EC!;-#1&ZJycJ-);>*Hy3U6S|G6s_Y2+2H|xr+E|6Jk&KH~Gyfoo{s$znk(Oqlr5;(fOyLQXzdoMD~`16+&L`5+d3dc}TWIv;` zdP32B;lhPeY*v<;ju`38Y%(KAEiRDxQ56Gp)n_^$Z_k!S^LM{6=2OL+8F3bb7PwIJ z8XFtq%@!<}y&9c&Yt73D)ki^2da1gEK0Kx78E;8{a~vWU4bK#^O7g!*F2Nkq8F8eN zxKKHK4aMY(!Zp&y#`nRx+w=N6*Z*OQxLAH;46*~f+^mR{Qb(HT_U=BNUOzUpqpI=U z-Rh!=WVJIB7oWtV&@z|IJ?aCf*>1qD$ByM>=?-wnwbAw>^zRh_ajZh_O&pqlkYAm1 zpphj2p*t_9=o1H;r5O4CP!sdd$<@Cz*@EIbBY=HW4OlLxNTc}(>ly)D`oNT(%lgQT&$zNL3Fo+ZbfWP`<`Q-Rs!Z4DLZ^~3l zG%XnrBlqQytt3PB_5OBb!S%qjI7@4y0DKh?C%o8*+@2iFB**oMdYNZI8kTbDWczVI z#_rpiL^atQAhYrL{bT@%0J|UiFA7nOKXhMwq|=LR_)8oea82AxR8tHR(e4vt{&zcn zWjjT7h?>+x?>{V@VnmzY$&LQOzCP{Ev#6Am6n@fpb705J){G35b&W=%4zU%c!&O-T z`dBXa*vQ@$b^S+acW-Y-#6<8D0&5gnC+Ja3K*K>|Q4}~}`@b?E78SG>H0w_gGW>Cr zW^f>wT)Ukx2Mc0HFAcAMTi0OER|x0K?wS0fFLlSJTL-LWLpz6Vvc(D;nRYCV83JYGWLjsb^E6KxXtuRsmIZIt^GH1Go2nMXK~ zz$>!_+Kr`{68+N5fl$>U?5@fyU7pPiVLl{nXdm7JE|et|JqxcS5g!edaSq<}JyGIY z;~-?}%as8i>}`OCww_Xz8%TH|9%htF>0)bgB`v%>f zlA2m2;-+L7$9BKV8keW`=2Tt%qh-$*rzGUnmiv<|7+o=SSC!b|yob){S(Vh^x?ChU ze{I3LMUX~Vf%)}LO2gQbvmQq2gK*W)OpOgbQso$4-%Q|$7lXC+^z?MZcF)#}F4b(G zge%ao@Lq_qct-9#Ssdwq7_*jAs~Z}o<=mE< z=X&>RmA{To&ddq%zUV}N`}Wd=SHs9_rP3ocEp4Kl+tN3g&aWz@-yVusLt=cBA~HiW z!S&2{v8xMJb)xpto+pW9>?Uk*Hbkah`tvFxlt~u6Bo$PwV-rk!sD4d7);@=1{2So) z^E)lYq+=|+BK>aj9eLUPXE&htYZ15h)@A@@fdl(X{`I#HM z`0vF3Pow?-&5nVcamV+yg)6tIp_#i^1}}sLt*`wF7WmpH{DinqzrI}9sG6O&KgBJu zDVZsX&KnMI+&vaw)Z0#Q4Bq8g!VoIY^-SY$&TY$wSG*aP^zx_a@z*B%?MNaE@C9m% z)YS2_+?G1GM{e%!GwtP7*QUnZZM(^fj&Wr=1L5~Nx9xWe?U`2W(^;*W8my9LsR0U= z-L_0fMJEzjeUiWq_x4)lG<-;;Qk8GPx3T1vLz}Oe{#t@84ZD)iKQlSHuPvYj2QGj=v2jaF8&A{V1|E Date: Sat, 15 Jul 2017 15:13:56 +0200 Subject: [PATCH 056/112] Make `Chart.helpers` importable (#4479) Properly export helpers and remove dependencies to `Chart.helpers`. Helpers can now be accessed from `src/helpers/index.js` (`var helpers = require('path/to/helpers/index')`, instead of `var helpers = Chart.helpers`). --- src/chart.js | 18 +- src/controllers/controller.bar.js | 4 +- src/controllers/controller.bubble.js | 4 +- src/controllers/controller.doughnut.js | 5 +- src/controllers/controller.line.js | 8 +- src/controllers/controller.polarArea.js | 4 +- src/controllers/controller.radar.js | 4 +- src/core/core.animation.js | 4 +- src/core/core.controller.js | 4 +- src/core/core.datasetController.js | 4 +- src/core/core.element.js | 3 +- src/core/core.helpers.js | 2 +- src/core/core.interaction.js | 3 +- src/core/core.layoutService.js | 4 +- src/core/core.plugin.js | 4 +- src/core/core.scale.js | 4 +- src/core/core.scaleService.js | 4 +- src/core/core.ticks.js | 4 +- src/core/core.tooltip.js | 4 +- src/elements/element.arc.js | 5 +- src/elements/element.line.js | 3 +- src/elements/element.point.js | 9 +- src/helpers/helpers.canvas.js | 159 ++++---- src/helpers/helpers.core.js | 502 ++++++++++++------------ src/helpers/helpers.easing.js | 36 +- src/helpers/helpers.time.js | 409 ++++++++++--------- src/helpers/index.js | 6 + src/platforms/platform.dom.js | 5 +- src/platforms/platform.js | 6 +- src/plugins/plugin.filler.js | 3 +- src/plugins/plugin.legend.js | 5 +- src/plugins/plugin.title.js | 3 +- src/scales/scale.linear.js | 4 +- src/scales/scale.linearbase.js | 5 +- src/scales/scale.logarithmic.js | 4 +- src/scales/scale.radialLinear.js | 3 +- src/scales/scale.time.js | 3 +- 37 files changed, 638 insertions(+), 623 deletions(-) create mode 100644 src/helpers/index.js diff --git a/src/chart.js b/src/chart.js index 8026cc856..493568686 100644 --- a/src/chart.js +++ b/src/chart.js @@ -3,11 +3,10 @@ */ var Chart = require('./core/core')(); -require('./helpers/helpers.core')(Chart); -require('./helpers/helpers.easing')(Chart); +Chart.helpers = require('./helpers/index'); + +// @todo dispatch these helpers into appropriated helpers/helpers.* file and write unit tests! require('./core/core.helpers')(Chart); -require('./helpers/helpers.time')(Chart); -require('./helpers/helpers.canvas')(Chart); require('./platforms/platform')(Chart); require('./core/core.element')(Chart); @@ -66,3 +65,14 @@ module.exports = Chart; if (typeof window !== 'undefined') { window.Chart = Chart; } + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use Chart.helpers.canvas instead. + * @namespace Chart.canvasHelpers + * @deprecated since version 2.6.0 + * @todo remove at version 3 + * @private + */ +Chart.canvasHelpers = Chart.helpers.canvas; diff --git a/src/controllers/controller.bar.js b/src/controllers/controller.bar.js index c89631cb4..93d285323 100644 --- a/src/controllers/controller.bar.js +++ b/src/controllers/controller.bar.js @@ -1,8 +1,8 @@ 'use strict'; -module.exports = function(Chart) { +var helpers = require('../helpers/index'); - var helpers = Chart.helpers; +module.exports = function(Chart) { Chart.defaults.bar = { hover: { diff --git a/src/controllers/controller.bubble.js b/src/controllers/controller.bubble.js index 3152cd09c..b91b15f12 100644 --- a/src/controllers/controller.bubble.js +++ b/src/controllers/controller.bubble.js @@ -1,8 +1,8 @@ 'use strict'; -module.exports = function(Chart) { +var helpers = require('../helpers/index'); - var helpers = Chart.helpers; +module.exports = function(Chart) { Chart.defaults.bubble = { hover: { diff --git a/src/controllers/controller.doughnut.js b/src/controllers/controller.doughnut.js index e86eea26e..c87c59bbc 100644 --- a/src/controllers/controller.doughnut.js +++ b/src/controllers/controller.doughnut.js @@ -1,9 +1,10 @@ 'use strict'; +var helpers = require('../helpers/index'); + module.exports = function(Chart) { - var helpers = Chart.helpers, - defaults = Chart.defaults; + var defaults = Chart.defaults; defaults.doughnut = { animation: { diff --git a/src/controllers/controller.line.js b/src/controllers/controller.line.js index 97982bded..c31e73f23 100644 --- a/src/controllers/controller.line.js +++ b/src/controllers/controller.line.js @@ -1,8 +1,8 @@ 'use strict'; -module.exports = function(Chart) { +var helpers = require('../helpers/index'); - var helpers = Chart.helpers; +module.exports = function(Chart) { Chart.defaults.line = { showLines: true, @@ -285,13 +285,13 @@ module.exports = function(Chart) { var ilen = points.length; var i = 0; - Chart.helpers.canvas.clipArea(chart.ctx, area); + helpers.canvas.clipArea(chart.ctx, area); if (lineEnabled(me.getDataset(), chart.options)) { meta.dataset.draw(); } - Chart.helpers.canvas.unclipArea(chart.ctx); + helpers.canvas.unclipArea(chart.ctx); // Draw the points for (; i= 0; i--) { - fn.call(thisArg, loopable[i], i); - } - } else { - for (i = 0; i < len; i++) { - fn.call(thisArg, loopable[i], i); - } + /** + * Note(SB) for performance sake, this method should only be used when loopable type + * is unknown or in none intensive code (not called often and small loopable). Else + * it's preferable to use a regular for() loop and save extra function calls. + * @param {Object|Array} loopable - The object or array to be iterated. + * @param {Function} fn - The function to call for each item. + * @param {Object} [thisArg] - The value of `this` provided for the call to `fn`. + * @param {Boolean} [reverse] - If true, iterates backward on the loopable. + */ + each: function(loopable, fn, thisArg, reverse) { + var i, len, keys; + if (helpers.isArray(loopable)) { + len = loopable.length; + if (reverse) { + for (i = len - 1; i >= 0; i--) { + fn.call(thisArg, loopable[i], i); } - } else if (helpers.isObject(loopable)) { - keys = Object.keys(loopable); - len = keys.length; + } else { for (i = 0; i < len; i++) { - fn.call(thisArg, loopable[keys[i]], keys[i]); + fn.call(thisArg, loopable[i], i); } } - }, - - /** - * Returns true if the `a0` and `a1` arrays have the same content, else returns false. - * @see http://stackoverflow.com/a/14853974 - * @param {Array} a0 - The array to compare - * @param {Array} a1 - The array to compare - * @returns {Boolean} - */ - arrayEquals: function(a0, a1) { - var i, ilen, v0, v1; - - if (!a0 || !a1 || a0.length !== a1.length) { - return false; + } else if (helpers.isObject(loopable)) { + keys = Object.keys(loopable); + len = keys.length; + for (i = 0; i < len; i++) { + fn.call(thisArg, loopable[keys[i]], keys[i]); } + } + }, - for (i = 0, ilen=a0.length; i < ilen; ++i) { - v0 = a0[i]; - v1 = a1[i]; + /** + * Returns true if the `a0` and `a1` arrays have the same content, else returns false. + * @see http://stackoverflow.com/a/14853974 + * @param {Array} a0 - The array to compare + * @param {Array} a1 - The array to compare + * @returns {Boolean} + */ + arrayEquals: function(a0, a1) { + var i, ilen, v0, v1; - if (v0 instanceof Array && v1 instanceof Array) { - if (!helpers.arrayEquals(v0, v1)) { - return false; - } - } else if (v0 !== v1) { - // NOTE: two different object instances will never be equal: {x:20} != {x:20} + if (!a0 || !a1 || a0.length !== a1.length) { + return false; + } + + for (i = 0, ilen=a0.length; i < ilen; ++i) { + v0 = a0[i]; + v1 = a1[i]; + + if (v0 instanceof Array && v1 instanceof Array) { + if (!helpers.arrayEquals(v0, v1)) { return false; } + } else if (v0 !== v1) { + // NOTE: two different object instances will never be equal: {x:20} != {x:20} + return false; } + } - return true; - }, + return true; + }, - /** - * Returns a deep copy of `source` without keeping references on objects and arrays. - * @param {*} source - The value to clone. - * @returns {*} - */ - clone: function(source) { - if (helpers.isArray(source)) { - return source.map(helpers.clone); - } + /** + * Returns a deep copy of `source` without keeping references on objects and arrays. + * @param {*} source - The value to clone. + * @returns {*} + */ + clone: function(source) { + if (helpers.isArray(source)) { + return source.map(helpers.clone); + } - if (helpers.isObject(source)) { - var target = {}; - var keys = Object.keys(source); - var klen = keys.length; - var k = 0; + if (helpers.isObject(source)) { + var target = {}; + var keys = Object.keys(source); + var klen = keys.length; + var k = 0; - for (; k maxTicks; i++) { - multiplier = unitDefinition.steps[i]; - sizeInUnits = Math.ceil(range / (unitSizeInMilliSeconds * multiplier)); - } - } else { - while (sizeInUnits > maxTicks && maxTicks > 0) { - ++multiplier; - sizeInUnits = Math.ceil(range / (unitSizeInMilliSeconds * multiplier)); - } - } - - return multiplier; - }, - - /** - * @function generateTicks - * @param options {ITimeGeneratorOptions} the options for generation - * @param dataRange {IRange} the data range - * @return {Number[]} ticks - */ - generateTicks: function(options, dataRange) { - var niceMin; - var niceMax; - var isoWeekday = options.timeOpts.isoWeekday; - if (options.unit === 'week' && isoWeekday !== false) { - niceMin = moment(dataRange.min).startOf('isoWeek').isoWeekday(isoWeekday).valueOf(); - niceMax = moment(dataRange.max).startOf('isoWeek').isoWeekday(isoWeekday); - if (dataRange.max - niceMax > 0) { - niceMax.add(1, 'week'); - } - niceMax = niceMax.valueOf(); - } else { - niceMin = moment(dataRange.min).startOf(options.unit).valueOf(); - niceMax = moment(dataRange.max).startOf(options.unit); - if (dataRange.max - niceMax > 0) { - niceMax.add(1, options.unit); - } - niceMax = niceMax.valueOf(); - } - return generateTicksNiceRange(options, dataRange, { - min: niceMin, - max: niceMax - }); } - }; + return unit; + }, + /** + * Determine major unit accordingly to passed unit + * @param unit {String} relative unit + * @return {String} major unit + */ + determineMajorUnit: function(unit) { + var units = Object.keys(interval); + var unitIndex = units.indexOf(unit); + while (unitIndex < units.length) { + var majorUnit = units[++unitIndex]; + // exclude 'week' and 'quarter' units + if (majorUnit !== 'week' && majorUnit !== 'quarter') { + return majorUnit; + } + } + + return null; + }, + + /** + * Determines how we scale the unit + * @param min {Number} the scale minimum + * @param max {Number} the scale maximum + * @param unit {String} the unit determined by the {@see determineUnit} method + * @return {Number} the axis step size as a multiple of unit + */ + determineStepSize: function(min, max, unit, maxTicks) { + // Using our unit, figure out what we need to scale as + var unitDefinition = interval[unit]; + var unitSizeInMilliSeconds = unitDefinition.size; + var sizeInUnits = Math.ceil((max - min) / unitSizeInMilliSeconds); + var multiplier = 1; + var range = max - min; + + if (unitDefinition.steps) { + // Have an array of steps + var numSteps = unitDefinition.steps.length; + for (var i = 0; i < numSteps && sizeInUnits > maxTicks; i++) { + multiplier = unitDefinition.steps[i]; + sizeInUnits = Math.ceil(range / (unitSizeInMilliSeconds * multiplier)); + } + } else { + while (sizeInUnits > maxTicks && maxTicks > 0) { + ++multiplier; + sizeInUnits = Math.ceil(range / (unitSizeInMilliSeconds * multiplier)); + } + } + + return multiplier; + }, + + /** + * @function generateTicks + * @param options {ITimeGeneratorOptions} the options for generation + * @param dataRange {IRange} the data range + * @return {Number[]} ticks + */ + generateTicks: function(options, dataRange) { + var niceMin; + var niceMax; + var isoWeekday = options.timeOpts.isoWeekday; + if (options.unit === 'week' && isoWeekday !== false) { + niceMin = moment(dataRange.min).startOf('isoWeek').isoWeekday(isoWeekday).valueOf(); + niceMax = moment(dataRange.max).startOf('isoWeek').isoWeekday(isoWeekday); + if (dataRange.max - niceMax > 0) { + niceMax.add(1, 'week'); + } + niceMax = niceMax.valueOf(); + } else { + niceMin = moment(dataRange.min).startOf(options.unit).valueOf(); + niceMax = moment(dataRange.max).startOf(options.unit); + if (dataRange.max - niceMax > 0) { + niceMax.add(1, options.unit); + } + niceMax = niceMax.valueOf(); + } + return generateTicksNiceRange(options, dataRange, { + min: niceMin, + max: niceMax + }); + } }; diff --git a/src/helpers/index.js b/src/helpers/index.js new file mode 100644 index 000000000..a21c99aca --- /dev/null +++ b/src/helpers/index.js @@ -0,0 +1,6 @@ +'use strict'; + +module.exports = require('./helpers.core'); +module.exports.easing = require('./helpers.easing'); +module.exports.canvas = require('./helpers.canvas'); +module.exports.time = require('./helpers.time'); diff --git a/src/platforms/platform.dom.js b/src/platforms/platform.dom.js index a5345d1e7..f8e328954 100644 --- a/src/platforms/platform.dom.js +++ b/src/platforms/platform.dom.js @@ -1,8 +1,9 @@ 'use strict'; +var helpers = require('../helpers/index'); + // Chart.Platform implementation for targeting a web browser -module.exports = function(Chart) { - var helpers = Chart.helpers; +module.exports = function() { // DOM event types -> Chart.js event types. // Note: only events with different types are mapped. diff --git a/src/platforms/platform.js b/src/platforms/platform.js index 0f27e5868..3464d5ff5 100644 --- a/src/platforms/platform.js +++ b/src/platforms/platform.js @@ -1,8 +1,10 @@ 'use strict'; +var helpers = require('../helpers/index'); + // By default, select the browser (DOM) platform. // @TODO Make possible to select another platform at build time. -var implementation = require('./platform.dom.js'); +var implementation = require('./platform.dom'); module.exports = function(Chart) { /** @@ -65,5 +67,5 @@ module.exports = function(Chart) { * @prop {Number} y - The mouse y position, relative to the canvas (null for incompatible events) */ - Chart.helpers.extend(Chart.platform, implementation(Chart)); + helpers.extend(Chart.platform, implementation(Chart)); }; diff --git a/src/plugins/plugin.filler.js b/src/plugins/plugin.filler.js index 2a91d38e8..4f49064f0 100644 --- a/src/plugins/plugin.filler.js +++ b/src/plugins/plugin.filler.js @@ -1,5 +1,7 @@ 'use strict'; +var helpers = require('../helpers/index'); + module.exports = function(Chart) { /** * Plugin based on discussion from the following Chart.js issues: @@ -11,7 +13,6 @@ module.exports = function(Chart) { }; var defaults = Chart.defaults; - var helpers = Chart.helpers; var mappers = { dataset: function(source) { var index = source.fill; diff --git a/src/plugins/plugin.legend.js b/src/plugins/plugin.legend.js index 0e3b1e665..3eb0f57df 100644 --- a/src/plugins/plugin.legend.js +++ b/src/plugins/plugin.legend.js @@ -1,8 +1,9 @@ 'use strict'; +var helpers = require('../helpers/index'); + module.exports = function(Chart) { - var helpers = Chart.helpers; var layout = Chart.layoutService; var noop = helpers.noop; @@ -360,7 +361,7 @@ module.exports = function(Chart) { var centerY = y + offSet; // Draw pointStyle as legend symbol - Chart.helpers.canvas.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY); + helpers.canvas.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY); } else { // Draw box as legend symbol if (!isLineWidthZero) { diff --git a/src/plugins/plugin.title.js b/src/plugins/plugin.title.js index c8f815d76..42ad1b0d3 100644 --- a/src/plugins/plugin.title.js +++ b/src/plugins/plugin.title.js @@ -1,8 +1,9 @@ 'use strict'; +var helpers = require('../helpers/index'); + module.exports = function(Chart) { - var helpers = Chart.helpers; var layout = Chart.layoutService; var noop = helpers.noop; diff --git a/src/scales/scale.linear.js b/src/scales/scale.linear.js index c9a6af41e..34223c283 100644 --- a/src/scales/scale.linear.js +++ b/src/scales/scale.linear.js @@ -1,8 +1,8 @@ 'use strict'; -module.exports = function(Chart) { +var helpers = require('../helpers/index'); - var helpers = Chart.helpers; +module.exports = function(Chart) { var defaultConfig = { position: 'left', diff --git a/src/scales/scale.linearbase.js b/src/scales/scale.linearbase.js index 28cde52d6..33bc22517 100644 --- a/src/scales/scale.linearbase.js +++ b/src/scales/scale.linearbase.js @@ -1,9 +1,10 @@ 'use strict'; +var helpers = require('../helpers/index'); + module.exports = function(Chart) { - var helpers = Chart.helpers, - noop = helpers.noop; + var noop = helpers.noop; Chart.LinearScaleBase = Chart.Scale.extend({ handleTickRangeOptions: function() { diff --git a/src/scales/scale.logarithmic.js b/src/scales/scale.logarithmic.js index a343aaf73..a5c5fa918 100644 --- a/src/scales/scale.logarithmic.js +++ b/src/scales/scale.logarithmic.js @@ -1,8 +1,8 @@ 'use strict'; -module.exports = function(Chart) { +var helpers = require('../helpers/index'); - var helpers = Chart.helpers; +module.exports = function(Chart) { var defaultConfig = { position: 'left', diff --git a/src/scales/scale.radialLinear.js b/src/scales/scale.radialLinear.js index 90cb52cd0..93036c691 100644 --- a/src/scales/scale.radialLinear.js +++ b/src/scales/scale.radialLinear.js @@ -1,8 +1,9 @@ 'use strict'; +var helpers = require('../helpers/index'); + module.exports = function(Chart) { - var helpers = Chart.helpers; var globalDefaults = Chart.defaults.global; var defaultConfig = { diff --git a/src/scales/scale.time.js b/src/scales/scale.time.js index 14621ba7a..fab5ccd64 100644 --- a/src/scales/scale.time.js +++ b/src/scales/scale.time.js @@ -4,9 +4,10 @@ var moment = require('moment'); moment = typeof(moment) === 'function' ? moment : window.moment; +var helpers = require('../helpers/index'); + module.exports = function(Chart) { - var helpers = Chart.helpers; var timeHelpers = helpers.time; var defaultConfig = { From 8e643db09d33bb86c7af73d3d48c502338e49b73 Mon Sep 17 00:00:00 2001 From: Evert Timberg Date: Sat, 15 Jul 2017 16:39:27 -0400 Subject: [PATCH 057/112] Fix copy paste error in new docs with respect to settings for line and radar charts. (#4510) --- docs/charts/line.md | 6 +++--- docs/charts/radar.md | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/charts/line.md b/docs/charts/line.md index 499191130..9d0901be0 100644 --- a/docs/charts/line.md +++ b/docs/charts/line.md @@ -48,9 +48,9 @@ All point* properties can be specified as an array. If these are set to an array | `label` | `String` | The label for the dataset which appears in the legend and tooltips. | `xAxisID` | `String` | The ID of the x axis to plot this dataset on. If not specified, this defaults to the ID of the first found x axis | `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/Color[]` | The fill color under the line. See [Colors](../general/colors.md#colors) -| `borderColor` | `Color/Color[]` | The color of the line. See [Colors](../general/colors.md#colors) -| `borderWidth` | `Number/Number[]` | The width of the line in pixels. +| `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. | `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/charts/radar.md b/docs/charts/radar.md index 592bb94ef..ac908315c 100644 --- a/docs/charts/radar.md +++ b/docs/charts/radar.md @@ -69,9 +69,9 @@ All point* properties can be specified as an array. If these are set to an array | Name | Type | Description | ---- | ---- | ----------- | `label` | `String` | The label for the dataset which appears in the legend and tooltips. -| `backgroundColor` | `Color/Color[]` | The fill color under the line. See [Colors](../general/colors.md#colors) -| `borderColor` | `Color/Color[]` | The color of the line. See [Colors](../general/colors.md#colors) -| `borderWidth` | `Number/Number[]` | The width of the line in pixels. +| `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. | `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) From 1833614e1d8a82c6c111ce8d1d31eba6df22e7ee Mon Sep 17 00:00:00 2001 From: Simon Brunel Date: Sun, 16 Jul 2017 11:02:25 +0200 Subject: [PATCH 058/112] Make `Chart.platform` importable (#4509) --- src/chart.js | 3 +- src/core/core.controller.js | 2 +- src/platforms/platform.dom.js | 615 +++++++++++++++++----------------- src/platforms/platform.js | 105 +++--- 4 files changed, 364 insertions(+), 361 deletions(-) diff --git a/src/chart.js b/src/chart.js index 493568686..9580c2387 100644 --- a/src/chart.js +++ b/src/chart.js @@ -8,7 +8,8 @@ Chart.helpers = require('./helpers/index'); // @todo dispatch these helpers into appropriated helpers/helpers.* file and write unit tests! require('./core/core.helpers')(Chart); -require('./platforms/platform')(Chart); +Chart.platform = require('./platforms/platform'); + require('./core/core.element')(Chart); require('./core/core.plugin')(Chart); require('./core/core.animation')(Chart); diff --git a/src/core/core.controller.js b/src/core/core.controller.js index c8578a937..4a86d70d2 100644 --- a/src/core/core.controller.js +++ b/src/core/core.controller.js @@ -1,10 +1,10 @@ 'use strict'; var helpers = require('../helpers/index'); +var platform = require('../platforms/platform'); module.exports = function(Chart) { var plugins = Chart.plugins; - var platform = Chart.platform; // Create a dictionary of chart types, to allow for extension of existing types Chart.types = {}; diff --git a/src/platforms/platform.dom.js b/src/platforms/platform.dom.js index f8e328954..d0895ae96 100644 --- a/src/platforms/platform.dom.js +++ b/src/platforms/platform.dom.js @@ -1,342 +1,347 @@ +/** + * Chart.Platform implementation for targeting a web browser + */ + 'use strict'; var helpers = require('../helpers/index'); -// Chart.Platform implementation for targeting a web browser -module.exports = function() { +/** + * DOM event types -> Chart.js event types. + * Note: only events with different types are mapped. + * @see https://developer.mozilla.org/en-US/docs/Web/Events + */ - // DOM event types -> Chart.js event types. - // Note: only events with different types are mapped. - // https://developer.mozilla.org/en-US/docs/Web/Events - var eventTypeMap = { - // Touch events - touchstart: 'mousedown', - touchmove: 'mousemove', - touchend: 'mouseup', +var eventTypeMap = { + // Touch events + touchstart: 'mousedown', + touchmove: 'mousemove', + touchend: 'mouseup', - // Pointer events - pointerenter: 'mouseenter', - pointerdown: 'mousedown', - pointermove: 'mousemove', - pointerup: 'mouseup', - pointerleave: 'mouseout', - pointerout: 'mouseout' + // Pointer events + pointerenter: 'mouseenter', + pointerdown: 'mousedown', + pointermove: 'mousemove', + pointerup: 'mouseup', + pointerleave: 'mouseout', + pointerout: 'mouseout' +}; + +/** + * The "used" size is the final value of a dimension property after all calculations have + * been performed. This method uses the computed style of `element` but returns undefined + * if the computed style is not expressed in pixels. That can happen in some cases where + * `element` has a size relative to its parent and this last one is not yet displayed, + * for example because of `display: none` on a parent node. + * @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value + * @returns {Number} Size in pixels or undefined if unknown. + */ +function readUsedSize(element, property) { + var value = helpers.getStyle(element, property); + var matches = value && value.match(/^(\d+)(\.\d+)?px$/); + return matches? Number(matches[1]) : undefined; +} + +/** + * Initializes the canvas style and render size without modifying the canvas display size, + * since responsiveness is handled by the controller.resize() method. The config is used + * to determine the aspect ratio to apply in case no explicit height has been specified. + */ +function initCanvas(canvas, config) { + var style = canvas.style; + + // NOTE(SB) canvas.getAttribute('width') !== canvas.width: in the first case it + // returns null or '' if no explicit value has been set to the canvas attribute. + var renderHeight = canvas.getAttribute('height'); + var renderWidth = canvas.getAttribute('width'); + + // Chart.js modifies some canvas values that we want to restore on destroy + canvas._chartjs = { + initial: { + height: renderHeight, + width: renderWidth, + style: { + display: style.display, + height: style.height, + width: style.width + } + } }; - /** - * The "used" size is the final value of a dimension property after all calculations have - * been performed. This method uses the computed style of `element` but returns undefined - * if the computed style is not expressed in pixels. That can happen in some cases where - * `element` has a size relative to its parent and this last one is not yet displayed, - * for example because of `display: none` on a parent node. - * @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value - * @returns {Number} Size in pixels or undefined if unknown. - */ - function readUsedSize(element, property) { - var value = helpers.getStyle(element, property); - var matches = value && value.match(/^(\d+)(\.\d+)?px$/); - return matches? Number(matches[1]) : undefined; + // Force canvas to display as block to avoid extra space caused by inline + // elements, which would interfere with the responsive resize process. + // https://github.com/chartjs/Chart.js/issues/2538 + style.display = style.display || 'block'; + + if (renderWidth === null || renderWidth === '') { + var displayWidth = readUsedSize(canvas, 'width'); + if (displayWidth !== undefined) { + canvas.width = displayWidth; + } } - /** - * Initializes the canvas style and render size without modifying the canvas display size, - * since responsiveness is handled by the controller.resize() method. The config is used - * to determine the aspect ratio to apply in case no explicit height has been specified. - */ - function initCanvas(canvas, config) { - var style = canvas.style; - - // NOTE(SB) canvas.getAttribute('width') !== canvas.width: in the first case it - // returns null or '' if no explicit value has been set to the canvas attribute. - var renderHeight = canvas.getAttribute('height'); - var renderWidth = canvas.getAttribute('width'); - - // Chart.js modifies some canvas values that we want to restore on destroy - canvas._chartjs = { - initial: { - height: renderHeight, - width: renderWidth, - style: { - display: style.display, - height: style.height, - width: style.width - } - } - }; - - // Force canvas to display as block to avoid extra space caused by inline - // elements, which would interfere with the responsive resize process. - // https://github.com/chartjs/Chart.js/issues/2538 - style.display = style.display || 'block'; - - if (renderWidth === null || renderWidth === '') { - var displayWidth = readUsedSize(canvas, 'width'); + if (renderHeight === null || renderHeight === '') { + if (canvas.style.height === '') { + // If no explicit render height and style height, let's apply the aspect ratio, + // which one can be specified by the user but also by charts as default option + // (i.e. options.aspectRatio). If not specified, use canvas aspect ratio of 2. + canvas.height = canvas.width / (config.options.aspectRatio || 2); + } else { + var displayHeight = readUsedSize(canvas, 'height'); if (displayWidth !== undefined) { - canvas.width = displayWidth; + canvas.height = displayHeight; } } - - if (renderHeight === null || renderHeight === '') { - if (canvas.style.height === '') { - // If no explicit render height and style height, let's apply the aspect ratio, - // which one can be specified by the user but also by charts as default option - // (i.e. options.aspectRatio). If not specified, use canvas aspect ratio of 2. - canvas.height = canvas.width / (config.options.aspectRatio || 2); - } else { - var displayHeight = readUsedSize(canvas, 'height'); - if (displayWidth !== undefined) { - canvas.height = displayHeight; - } - } - } - - return canvas; } - /** - * Detects support for options object argument in addEventListener. - * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support - * @private - */ - var supportsEventListenerOptions = (function() { - var supports = false; - try { - var options = Object.defineProperty({}, 'passive', { - get: function() { - supports = true; + return canvas; +} + +/** + * Detects support for options object argument in addEventListener. + * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support + * @private + */ +var supportsEventListenerOptions = (function() { + var supports = false; + try { + var options = Object.defineProperty({}, 'passive', { + get: function() { + supports = true; + } + }); + window.addEventListener('e', null, options); + } catch (e) { + // continue regardless of error + } + return supports; +}()); + +// Default passive to true as expected by Chrome for 'touchstart' and 'touchend' events. +// https://github.com/chartjs/Chart.js/issues/4287 +var eventListenerOptions = supportsEventListenerOptions? {passive: true} : false; + +function addEventListener(node, type, listener) { + node.addEventListener(type, listener, eventListenerOptions); +} + +function removeEventListener(node, type, listener) { + node.removeEventListener(type, listener, eventListenerOptions); +} + +function createEvent(type, chart, x, y, nativeEvent) { + return { + type: type, + chart: chart, + native: nativeEvent || null, + x: x !== undefined? x : null, + y: y !== undefined? y : null, + }; +} + +function fromNativeEvent(event, chart) { + var type = eventTypeMap[event.type] || event.type; + var pos = helpers.getRelativePosition(event, chart); + return createEvent(type, chart, pos.x, pos.y, event); +} + +function createResizer(handler) { + var iframe = document.createElement('iframe'); + iframe.className = 'chartjs-hidden-iframe'; + iframe.style.cssText = + 'display:block;'+ + 'overflow:hidden;'+ + 'border:0;'+ + 'margin:0;'+ + 'top:0;'+ + 'left:0;'+ + 'bottom:0;'+ + 'right:0;'+ + 'height:100%;'+ + 'width:100%;'+ + 'position:absolute;'+ + 'pointer-events:none;'+ + 'z-index:-1;'; + + // Prevent the iframe to gain focus on tab. + // https://github.com/chartjs/Chart.js/issues/3090 + iframe.tabIndex = -1; + + // Prevent iframe from gaining focus on ItemMode keyboard navigation + // Accessibility bug fix + iframe.setAttribute('aria-hidden', 'true'); + + // If the iframe is re-attached to the DOM, the resize listener is removed because the + // content is reloaded, so make sure to install the handler after the iframe is loaded. + // https://github.com/chartjs/Chart.js/issues/3521 + addEventListener(iframe, 'load', function() { + addEventListener(iframe.contentWindow || iframe, 'resize', handler); + + // The iframe size might have changed while loading, which can also + // happen if the size has been changed while detached from the DOM. + handler(); + }); + + return iframe; +} + +function addResizeListener(node, listener, chart) { + var stub = node._chartjs = { + ticking: false + }; + + // Throttle the callback notification until the next animation frame. + var notify = function() { + if (!stub.ticking) { + stub.ticking = true; + helpers.requestAnimFrame.call(window, function() { + if (stub.resizer) { + stub.ticking = false; + return listener(createEvent('resize', chart)); } }); - window.addEventListener('e', null, options); - } catch (e) { - // continue regardless of error } - return supports; - }()); + }; - // Default passive to true as expected by Chrome for 'touchstart' and 'touchend' events. - // https://github.com/chartjs/Chart.js/issues/4287 - var eventListenerOptions = supportsEventListenerOptions? {passive: true} : false; + // Let's keep track of this added iframe and thus avoid DOM query when removing it. + stub.resizer = createResizer(notify); - function addEventListener(node, type, listener) { - node.addEventListener(type, listener, eventListenerOptions); + node.insertBefore(stub.resizer, node.firstChild); +} + +function removeResizeListener(node) { + if (!node || !node._chartjs) { + return; } - function removeEventListener(node, type, listener) { - node.removeEventListener(type, listener, eventListenerOptions); + var resizer = node._chartjs.resizer; + if (resizer) { + resizer.parentNode.removeChild(resizer); + node._chartjs.resizer = null; } - function createEvent(type, chart, x, y, nativeEvent) { - return { - type: type, - chart: chart, - native: nativeEvent || null, - x: x !== undefined? x : null, - y: y !== undefined? y : null, - }; - } + delete node._chartjs; +} - function fromNativeEvent(event, chart) { - var type = eventTypeMap[event.type] || event.type; - var pos = helpers.getRelativePosition(event, chart); - return createEvent(type, chart, pos.x, pos.y, event); - } +module.exports = { + acquireContext: function(item, config) { + if (typeof item === 'string') { + item = document.getElementById(item); + } else if (item.length) { + // Support for array based queries (such as jQuery) + item = item[0]; + } - function createResizer(handler) { - var iframe = document.createElement('iframe'); - iframe.className = 'chartjs-hidden-iframe'; - iframe.style.cssText = - 'display:block;'+ - 'overflow:hidden;'+ - 'border:0;'+ - 'margin:0;'+ - 'top:0;'+ - 'left:0;'+ - 'bottom:0;'+ - 'right:0;'+ - 'height:100%;'+ - 'width:100%;'+ - 'position:absolute;'+ - 'pointer-events:none;'+ - 'z-index:-1;'; + if (item && item.canvas) { + // Support for any object associated to a canvas (including a context2d) + item = item.canvas; + } - // Prevent the iframe to gain focus on tab. - // https://github.com/chartjs/Chart.js/issues/3090 - iframe.tabIndex = -1; + // To prevent canvas fingerprinting, some add-ons undefine the getContext + // method, for example: https://github.com/kkapsner/CanvasBlocker + // https://github.com/chartjs/Chart.js/issues/2807 + var context = item && item.getContext && item.getContext('2d'); - // Prevent iframe from gaining focus on ItemMode keyboard navigation - // Accessibility bug fix - iframe.setAttribute('aria-hidden', 'true'); + // `instanceof HTMLCanvasElement/CanvasRenderingContext2D` fails when the item is + // inside an iframe or when running in a protected environment. We could guess the + // types from their toString() value but let's keep things flexible and assume it's + // a sufficient condition if the item has a context2D which has item as `canvas`. + // https://github.com/chartjs/Chart.js/issues/3887 + // https://github.com/chartjs/Chart.js/issues/4102 + // https://github.com/chartjs/Chart.js/issues/4152 + if (context && context.canvas === item) { + initCanvas(item, config); + return context; + } - // If the iframe is re-attached to the DOM, the resize listener is removed because the - // content is reloaded, so make sure to install the handler after the iframe is loaded. - // https://github.com/chartjs/Chart.js/issues/3521 - addEventListener(iframe, 'load', function() { - addEventListener(iframe.contentWindow || iframe, 'resize', handler); + return null; + }, - // The iframe size might have changed while loading, which can also - // happen if the size has been changed while detached from the DOM. - handler(); - }); - - return iframe; - } - - function addResizeListener(node, listener, chart) { - var stub = node._chartjs = { - ticking: false - }; - - // Throttle the callback notification until the next animation frame. - var notify = function() { - if (!stub.ticking) { - stub.ticking = true; - helpers.requestAnimFrame.call(window, function() { - if (stub.resizer) { - stub.ticking = false; - return listener(createEvent('resize', chart)); - } - }); - } - }; - - // Let's keep track of this added iframe and thus avoid DOM query when removing it. - stub.resizer = createResizer(notify); - - node.insertBefore(stub.resizer, node.firstChild); - } - - function removeResizeListener(node) { - if (!node || !node._chartjs) { + releaseContext: function(context) { + var canvas = context.canvas; + if (!canvas._chartjs) { return; } - var resizer = node._chartjs.resizer; - if (resizer) { - resizer.parentNode.removeChild(resizer); - node._chartjs.resizer = null; + var initial = canvas._chartjs.initial; + ['height', 'width'].forEach(function(prop) { + var value = initial[prop]; + if (helpers.isNullOrUndef(value)) { + canvas.removeAttribute(prop); + } else { + canvas.setAttribute(prop, value); + } + }); + + helpers.each(initial.style || {}, function(value, key) { + canvas.style[key] = value; + }); + + // The canvas render size might have been changed (and thus the state stack discarded), + // we can't use save() and restore() to restore the initial state. So make sure that at + // least the canvas context is reset to the default state by setting the canvas width. + // https://www.w3.org/TR/2011/WD-html5-20110525/the-canvas-element.html + canvas.width = canvas.width; + + delete canvas._chartjs; + }, + + addEventListener: function(chart, type, listener) { + var canvas = chart.canvas; + if (type === 'resize') { + // Note: the resize event is not supported on all browsers. + addResizeListener(canvas.parentNode, listener, chart); + return; } - delete node._chartjs; + var stub = listener._chartjs || (listener._chartjs = {}); + var proxies = stub.proxies || (stub.proxies = {}); + var proxy = proxies[chart.id + '_' + type] = function(event) { + listener(fromNativeEvent(event, chart)); + }; + + addEventListener(canvas, type, proxy); + }, + + removeEventListener: function(chart, type, listener) { + var canvas = chart.canvas; + if (type === 'resize') { + // Note: the resize event is not supported on all browsers. + removeResizeListener(canvas.parentNode, listener); + return; + } + + var stub = listener._chartjs || {}; + var proxies = stub.proxies || {}; + var proxy = proxies[chart.id + '_' + type]; + if (!proxy) { + return; + } + + removeEventListener(canvas, type, proxy); } - - /** - * Provided for backward compatibility, use EventTarget.addEventListener instead. - * EventTarget.addEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+ - * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener - * @function Chart.helpers.addEvent - * @deprecated since version 2.7.0 - * @todo remove at version 3 - * @private - */ - helpers.addEvent = addEventListener; - - /** - * Provided for backward compatibility, use EventTarget.removeEventListener instead. - * EventTarget.removeEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+ - * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener - * @function Chart.helpers.removeEvent - * @deprecated since version 2.7.0 - * @todo remove at version 3 - * @private - */ - helpers.removeEvent = removeEventListener; - - return { - acquireContext: function(item, config) { - if (typeof item === 'string') { - item = document.getElementById(item); - } else if (item.length) { - // Support for array based queries (such as jQuery) - item = item[0]; - } - - if (item && item.canvas) { - // Support for any object associated to a canvas (including a context2d) - item = item.canvas; - } - - // To prevent canvas fingerprinting, some add-ons undefine the getContext - // method, for example: https://github.com/kkapsner/CanvasBlocker - // https://github.com/chartjs/Chart.js/issues/2807 - var context = item && item.getContext && item.getContext('2d'); - - // `instanceof HTMLCanvasElement/CanvasRenderingContext2D` fails when the item is - // inside an iframe or when running in a protected environment. We could guess the - // types from their toString() value but let's keep things flexible and assume it's - // a sufficient condition if the item has a context2D which has item as `canvas`. - // https://github.com/chartjs/Chart.js/issues/3887 - // https://github.com/chartjs/Chart.js/issues/4102 - // https://github.com/chartjs/Chart.js/issues/4152 - if (context && context.canvas === item) { - initCanvas(item, config); - return context; - } - - return null; - }, - - releaseContext: function(context) { - var canvas = context.canvas; - if (!canvas._chartjs) { - return; - } - - var initial = canvas._chartjs.initial; - ['height', 'width'].forEach(function(prop) { - var value = initial[prop]; - if (helpers.isNullOrUndef(value)) { - canvas.removeAttribute(prop); - } else { - canvas.setAttribute(prop, value); - } - }); - - helpers.each(initial.style || {}, function(value, key) { - canvas.style[key] = value; - }); - - // The canvas render size might have been changed (and thus the state stack discarded), - // we can't use save() and restore() to restore the initial state. So make sure that at - // least the canvas context is reset to the default state by setting the canvas width. - // https://www.w3.org/TR/2011/WD-html5-20110525/the-canvas-element.html - canvas.width = canvas.width; - - delete canvas._chartjs; - }, - - addEventListener: function(chart, type, listener) { - var canvas = chart.canvas; - if (type === 'resize') { - // Note: the resize event is not supported on all browsers. - addResizeListener(canvas.parentNode, listener, chart); - return; - } - - var stub = listener._chartjs || (listener._chartjs = {}); - var proxies = stub.proxies || (stub.proxies = {}); - var proxy = proxies[chart.id + '_' + type] = function(event) { - listener(fromNativeEvent(event, chart)); - }; - - addEventListener(canvas, type, proxy); - }, - - removeEventListener: function(chart, type, listener) { - var canvas = chart.canvas; - if (type === 'resize') { - // Note: the resize event is not supported on all browsers. - removeResizeListener(canvas.parentNode, listener); - return; - } - - var stub = listener._chartjs || {}; - var proxies = stub.proxies || {}; - var proxy = proxies[chart.id + '_' + type]; - if (!proxy) { - return; - } - - removeEventListener(canvas, type, proxy); - } - }; }; + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use EventTarget.addEventListener instead. + * EventTarget.addEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+ + * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener + * @function Chart.helpers.addEvent + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers.addEvent = addEventListener; + +/** + * Provided for backward compatibility, use EventTarget.removeEventListener instead. + * EventTarget.removeEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+ + * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener + * @function Chart.helpers.removeEvent + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers.removeEvent = removeEventListener; diff --git a/src/platforms/platform.js b/src/platforms/platform.js index 3464d5ff5..199e9548d 100644 --- a/src/platforms/platform.js +++ b/src/platforms/platform.js @@ -6,66 +6,63 @@ var helpers = require('../helpers/index'); // @TODO Make possible to select another platform at build time. var implementation = require('./platform.dom'); -module.exports = function(Chart) { +/** + * @namespace Chart.platform + * @see https://chartjs.gitbooks.io/proposals/content/Platform.html + * @since 2.4.0 + */ +module.exports = helpers.extend({ /** - * @namespace Chart.platform - * @see https://chartjs.gitbooks.io/proposals/content/Platform.html - * @since 2.4.0 + * Called at chart construction time, returns a context2d instance implementing + * the [W3C Canvas 2D Context API standard]{@link https://www.w3.org/TR/2dcontext/}. + * @param {*} item - The native item from which to acquire context (platform specific) + * @param {Object} options - The chart options + * @returns {CanvasRenderingContext2D} context2d instance */ - Chart.platform = { - /** - * Called at chart construction time, returns a context2d instance implementing - * the [W3C Canvas 2D Context API standard]{@link https://www.w3.org/TR/2dcontext/}. - * @param {*} item - The native item from which to acquire context (platform specific) - * @param {Object} options - The chart options - * @returns {CanvasRenderingContext2D} context2d instance - */ - acquireContext: function() {}, - - /** - * Called at chart destruction time, releases any resources associated to the context - * previously returned by the acquireContext() method. - * @param {CanvasRenderingContext2D} context - The context2d instance - * @returns {Boolean} true if the method succeeded, else false - */ - releaseContext: function() {}, - - /** - * Registers the specified listener on the given chart. - * @param {Chart} chart - Chart from which to listen for event - * @param {String} type - The ({@link IEvent}) type to listen for - * @param {Function} listener - Receives a notification (an object that implements - * the {@link IEvent} interface) when an event of the specified type occurs. - */ - addEventListener: function() {}, - - /** - * Removes the specified listener previously registered with addEventListener. - * @param {Chart} chart -Chart from which to remove the listener - * @param {String} type - The ({@link IEvent}) type to remove - * @param {Function} listener - The listener function to remove from the event target. - */ - removeEventListener: function() {} - }; + acquireContext: function() {}, /** - * @interface IPlatform - * Allows abstracting platform dependencies away from the chart - * @borrows Chart.platform.acquireContext as acquireContext - * @borrows Chart.platform.releaseContext as releaseContext - * @borrows Chart.platform.addEventListener as addEventListener - * @borrows Chart.platform.removeEventListener as removeEventListener + * Called at chart destruction time, releases any resources associated to the context + * previously returned by the acquireContext() method. + * @param {CanvasRenderingContext2D} context - The context2d instance + * @returns {Boolean} true if the method succeeded, else false */ + releaseContext: function() {}, /** - * @interface IEvent - * @prop {String} type - The event type name, possible values are: - * 'contextmenu', 'mouseenter', 'mousedown', 'mousemove', 'mouseup', 'mouseout', - * 'click', 'dblclick', 'keydown', 'keypress', 'keyup' and 'resize' - * @prop {*} native - The original native event (null for emulated events, e.g. 'resize') - * @prop {Number} x - The mouse x position, relative to the canvas (null for incompatible events) - * @prop {Number} y - The mouse y position, relative to the canvas (null for incompatible events) + * Registers the specified listener on the given chart. + * @param {Chart} chart - Chart from which to listen for event + * @param {String} type - The ({@link IEvent}) type to listen for + * @param {Function} listener - Receives a notification (an object that implements + * the {@link IEvent} interface) when an event of the specified type occurs. */ + addEventListener: function() {}, - helpers.extend(Chart.platform, implementation(Chart)); -}; + /** + * Removes the specified listener previously registered with addEventListener. + * @param {Chart} chart -Chart from which to remove the listener + * @param {String} type - The ({@link IEvent}) type to remove + * @param {Function} listener - The listener function to remove from the event target. + */ + removeEventListener: function() {} + +}, implementation); + +/** + * @interface IPlatform + * Allows abstracting platform dependencies away from the chart + * @borrows Chart.platform.acquireContext as acquireContext + * @borrows Chart.platform.releaseContext as releaseContext + * @borrows Chart.platform.addEventListener as addEventListener + * @borrows Chart.platform.removeEventListener as removeEventListener + */ + +/** + * @interface IEvent + * @prop {String} type - The event type name, possible values are: + * 'contextmenu', 'mouseenter', 'mousedown', 'mousemove', 'mouseup', 'mouseout', + * 'click', 'dblclick', 'keydown', 'keypress', 'keyup' and 'resize' + * @prop {*} native - The original native event (null for emulated events, e.g. 'resize') + * @prop {Number} x - The mouse x position, relative to the canvas (null for incompatible events) + * @prop {Number} y - The mouse y position, relative to the canvas (null for incompatible events) + */ From 889ecd560bba46a81a29ca29d02f0691aaadc8d2 Mon Sep 17 00:00:00 2001 From: Simon Brunel Date: Sun, 16 Jul 2017 19:38:19 +0200 Subject: [PATCH 059/112] Make `Chart.defaults/Ticks/Interaction` importable (#4512) Default options can now be accessed by importing `core/core.defaults`. The returned object acts as a singleton and is populated when importing classes that expose their own default values (meaning that importing only `code.defaults` results in an empty object). Also make `Chart.Ticks` and `Chart.Interaction` importable since existing defaults rely on these values. Add the `defaults._set` method that make easier declaring new defaults by merging given values with existing ones for a specific scope (`global`, `scale`, `bar`, etc). --- src/chart.js | 6 +- src/charts/Chart.Scatter.js | 40 -- src/controllers/controller.bar.js | 155 +++---- src/controllers/controller.bubble.js | 62 +-- src/controllers/controller.doughnut.js | 230 +++++----- src/controllers/controller.line.js | 41 +- src/controllers/controller.polarArea.js | 196 ++++---- src/controllers/controller.radar.js | 23 +- src/controllers/controller.scatter.js | 42 ++ src/core/core.animation.js | 11 +- src/core/core.controller.js | 16 +- src/core/core.defaults.js | 12 + src/core/core.helpers.js | 8 +- src/core/core.interaction.js | 567 ++++++++++++------------ src/core/core.js | 87 ++-- src/core/core.plugin.js | 10 +- src/core/core.scale.js | 106 ++--- src/core/core.scaleService.js | 13 +- src/core/core.ticks.js | 367 ++++++++------- src/core/core.tooltip.js | 29 +- src/elements/element.arc.js | 19 +- src/elements/element.line.js | 35 +- src/elements/element.point.js | 36 +- src/elements/element.rectangle.js | 22 +- src/plugins/plugin.filler.js | 26 +- src/plugins/plugin.legend.js | 37 +- src/plugins/plugin.title.js | 32 +- src/scales/scale.linear.js | 6 +- src/scales/scale.linearbase.js | 3 +- src/scales/scale.logarithmic.js | 5 +- src/scales/scale.radialLinear.js | 6 +- src/scales/scale.time.js | 3 +- 32 files changed, 1154 insertions(+), 1097 deletions(-) create mode 100644 src/controllers/controller.scatter.js create mode 100644 src/core/core.defaults.js diff --git a/src/chart.js b/src/chart.js index 9580c2387..146cfbaa8 100644 --- a/src/chart.js +++ b/src/chart.js @@ -8,7 +8,10 @@ Chart.helpers = require('./helpers/index'); // @todo dispatch these helpers into appropriated helpers/helpers.* file and write unit tests! require('./core/core.helpers')(Chart); +Chart.defaults = require('./core/core.defaults'); +Chart.Interaction = require('./core/core.interaction'); Chart.platform = require('./platforms/platform'); +Chart.Ticks = require('./core/core.ticks'); require('./core/core.element')(Chart); require('./core/core.plugin')(Chart); @@ -17,9 +20,7 @@ require('./core/core.controller')(Chart); require('./core/core.datasetController')(Chart); require('./core/core.layoutService')(Chart); require('./core/core.scaleService')(Chart); -require('./core/core.ticks')(Chart); require('./core/core.scale')(Chart); -require('./core/core.interaction')(Chart); require('./core/core.tooltip')(Chart); require('./elements/element.arc')(Chart); @@ -42,6 +43,7 @@ require('./controllers/controller.doughnut')(Chart); require('./controllers/controller.line')(Chart); require('./controllers/controller.polarArea')(Chart); require('./controllers/controller.radar')(Chart); +require('./controllers/controller.scatter')(Chart); require('./charts/Chart.Bar')(Chart); require('./charts/Chart.Bubble')(Chart); diff --git a/src/charts/Chart.Scatter.js b/src/charts/Chart.Scatter.js index 7b7e67d43..9006e5712 100644 --- a/src/charts/Chart.Scatter.js +++ b/src/charts/Chart.Scatter.js @@ -1,48 +1,8 @@ 'use strict'; module.exports = function(Chart) { - - var defaultConfig = { - hover: { - mode: 'single' - }, - - scales: { - xAxes: [{ - type: 'linear', // scatter should not use a category axis - position: 'bottom', - id: 'x-axis-1' // need an ID so datasets can reference the scale - }], - yAxes: [{ - type: 'linear', - position: 'left', - id: 'y-axis-1' - }] - }, - showLines: false, - - tooltips: { - callbacks: { - title: function() { - // Title doesn't make sense for scatter since we format the data as a point - return ''; - }, - label: function(tooltipItem) { - return '(' + tooltipItem.xLabel + ', ' + tooltipItem.yLabel + ')'; - } - } - } - }; - - // Register the default config for this type - Chart.defaults.scatter = defaultConfig; - - // Scatter charts use line controllers - Chart.controllers.scatter = Chart.controllers.line; - Chart.Scatter = function(context, config) { config.type = 'scatter'; return new Chart(context, config); }; - }; diff --git a/src/controllers/controller.bar.js b/src/controllers/controller.bar.js index 93d285323..97a077e66 100644 --- a/src/controllers/controller.bar.js +++ b/src/controllers/controller.bar.js @@ -1,32 +1,91 @@ 'use strict'; +var defaults = require('../core/core.defaults'); var helpers = require('../helpers/index'); -module.exports = function(Chart) { +defaults._set('bar', { + hover: { + mode: 'label' + }, - Chart.defaults.bar = { - hover: { - mode: 'label' - }, + scales: { + xAxes: [{ + type: 'category', - scales: { - xAxes: [{ - type: 'category', + // Specific to Bar Controller + categoryPercentage: 0.8, + barPercentage: 0.9, - // Specific to Bar Controller - categoryPercentage: 0.8, - barPercentage: 0.9, + // grid line settings + gridLines: { + offsetGridLines: true + } + }], - // grid line settings - gridLines: { - offsetGridLines: true - } - }], - yAxes: [{ - type: 'linear' - }] + yAxes: [{ + type: 'linear' + }] + } +}); + +defaults._set('horizontalBar', { + hover: { + mode: 'label' + }, + + scales: { + xAxes: [{ + type: 'linear', + position: 'bottom' + }], + + yAxes: [{ + position: 'left', + type: 'category', + + // Specific to Horizontal Bar Controller + categoryPercentage: 0.8, + barPercentage: 0.9, + + // grid line settings + gridLines: { + offsetGridLines: true + } + }] + }, + + elements: { + rectangle: { + borderSkipped: 'left' } - }; + }, + + tooltips: { + callbacks: { + title: function(item, data) { + // Pick first xLabel for now + var title = ''; + + if (item.length > 0) { + if (item[0].yLabel) { + title = item[0].yLabel; + } else if (data.labels.length > 0 && item[0].index < data.labels.length) { + title = data.labels[item[0].index]; + } + } + + return title; + }, + + label: function(item, data) { + var datasetLabel = data.datasets[item.datasetIndex].label || ''; + return datasetLabel + ': ' + item.xLabel; + } + } + } +}); + +module.exports = function(Chart) { Chart.controllers.bar = Chart.DatasetController.extend({ @@ -309,62 +368,6 @@ module.exports = function(Chart) { } }); - - // including horizontalBar in the bar file, instead of a file of its own - // it extends bar (like pie extends doughnut) - Chart.defaults.horizontalBar = { - hover: { - mode: 'label' - }, - - scales: { - xAxes: [{ - type: 'linear', - position: 'bottom' - }], - yAxes: [{ - position: 'left', - type: 'category', - - // Specific to Horizontal Bar Controller - categoryPercentage: 0.8, - barPercentage: 0.9, - - // grid line settings - gridLines: { - offsetGridLines: true - } - }] - }, - elements: { - rectangle: { - borderSkipped: 'left' - } - }, - tooltips: { - callbacks: { - title: function(tooltipItems, data) { - // Pick first xLabel for now - var title = ''; - - if (tooltipItems.length > 0) { - if (tooltipItems[0].yLabel) { - title = tooltipItems[0].yLabel; - } else if (data.labels.length > 0 && tooltipItems[0].index < data.labels.length) { - title = data.labels[tooltipItems[0].index]; - } - } - - return title; - }, - label: function(tooltipItem, data) { - var datasetLabel = data.datasets[tooltipItem.datasetIndex].label || ''; - return datasetLabel + ': ' + tooltipItem.xLabel; - } - } - } - }; - Chart.controllers.horizontalBar = Chart.controllers.bar.extend({ /** * @private diff --git a/src/controllers/controller.bubble.js b/src/controllers/controller.bubble.js index b91b15f12..6d410114e 100644 --- a/src/controllers/controller.bubble.js +++ b/src/controllers/controller.bubble.js @@ -1,41 +1,43 @@ 'use strict'; +var defaults = require('../core/core.defaults'); var helpers = require('../helpers/index'); -module.exports = function(Chart) { +defaults._set('bubble', { + hover: { + mode: 'single' + }, - Chart.defaults.bubble = { - hover: { - mode: 'single' - }, + scales: { + xAxes: [{ + type: 'linear', // bubble should probably use a linear scale by default + position: 'bottom', + id: 'x-axis-0' // need an ID so datasets can reference the scale + }], + yAxes: [{ + type: 'linear', + position: 'left', + id: 'y-axis-0' + }] + }, - scales: { - xAxes: [{ - type: 'linear', // bubble should probably use a linear scale by default - position: 'bottom', - id: 'x-axis-0' // need an ID so datasets can reference the scale - }], - yAxes: [{ - type: 'linear', - position: 'left', - id: 'y-axis-0' - }] - }, - - tooltips: { - callbacks: { - title: function() { - // Title doesn't make sense for scatter since we format the data as a point - return ''; - }, - label: function(tooltipItem, data) { - var datasetLabel = data.datasets[tooltipItem.datasetIndex].label || ''; - var dataPoint = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index]; - return datasetLabel + ': (' + tooltipItem.xLabel + ', ' + tooltipItem.yLabel + ', ' + dataPoint.r + ')'; - } + tooltips: { + callbacks: { + title: function() { + // Title doesn't make sense for scatter since we format the data as a point + return ''; + }, + label: function(item, data) { + var datasetLabel = data.datasets[item.datasetIndex].label || ''; + var dataPoint = data.datasets[item.datasetIndex].data[item.index]; + return datasetLabel + ': (' + item.xLabel + ', ' + item.yLabel + ', ' + dataPoint.r + ')'; } } - }; + } +}); + + +module.exports = function(Chart) { Chart.controllers.bubble = Chart.DatasetController.extend({ diff --git a/src/controllers/controller.doughnut.js b/src/controllers/controller.doughnut.js index c87c59bbc..ffbf22a66 100644 --- a/src/controllers/controller.doughnut.js +++ b/src/controllers/controller.doughnut.js @@ -1,130 +1,128 @@ 'use strict'; +var defaults = require('../core/core.defaults'); var helpers = require('../helpers/index'); -module.exports = function(Chart) { +defaults._set('doughnut', { + animation: { + // Boolean - Whether we animate the rotation of the Doughnut + animateRotate: true, + // Boolean - Whether we animate scaling the Doughnut from the centre + animateScale: false + }, + hover: { + mode: 'single' + }, + legendCallback: function(chart) { + var text = []; + text.push('
    '); - var defaults = Chart.defaults; + var data = chart.data; + var datasets = data.datasets; + var labels = data.labels; - defaults.doughnut = { - animation: { - // Boolean - Whether we animate the rotation of the Doughnut - animateRotate: true, - // Boolean - Whether we animate scaling the Doughnut from the centre - animateScale: false - }, - hover: { - mode: 'single' - }, - legendCallback: function(chart) { - var text = []; - text.push('
      '); - - var data = chart.data; - var datasets = data.datasets; - var labels = data.labels; - - if (datasets.length) { - for (var i = 0; i < datasets[0].data.length; ++i) { - text.push('
    • '); - if (labels[i]) { - text.push(labels[i]); - } - text.push('
    • '); - } - } - - text.push('
    '); - return text.join(''); - }, - legend: { - labels: { - generateLabels: function(chart) { - var data = chart.data; - if (data.labels.length && data.datasets.length) { - return data.labels.map(function(label, i) { - var meta = chart.getDatasetMeta(0); - var ds = data.datasets[0]; - var arc = meta.data[i]; - var custom = arc && arc.custom || {}; - var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault; - var arcOpts = chart.options.elements.arc; - var fill = custom.backgroundColor ? custom.backgroundColor : valueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor); - var stroke = custom.borderColor ? custom.borderColor : valueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor); - var bw = custom.borderWidth ? custom.borderWidth : valueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth); - - return { - text: label, - fillStyle: fill, - strokeStyle: stroke, - lineWidth: bw, - hidden: isNaN(ds.data[i]) || meta.data[i].hidden, - - // Extra data used for toggling the correct item - index: i - }; - }); - } - return []; - } - }, - - onClick: function(e, legendItem) { - var index = legendItem.index; - var chart = this.chart; - var i, ilen, meta; - - for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { - meta = chart.getDatasetMeta(i); - // toggle visibility of index if exists - if (meta.data[index]) { - meta.data[index].hidden = !meta.data[index].hidden; - } - } - - chart.update(); - } - }, - - // The percentage of the chart that we cut out of the middle. - cutoutPercentage: 50, - - // The rotation of the chart, where the first data arc begins. - rotation: Math.PI * -0.5, - - // The total circumference of the chart. - circumference: Math.PI * 2.0, - - // Need to override these to give a nice default - tooltips: { - callbacks: { - title: function() { - return ''; - }, - label: function(tooltipItem, data) { - var dataLabel = data.labels[tooltipItem.index]; - var value = ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index]; - - if (helpers.isArray(dataLabel)) { - // show value on first line of multiline label - // need to clone because we are changing the value - dataLabel = dataLabel.slice(); - dataLabel[0] += value; - } else { - dataLabel += value; - } - - return dataLabel; + if (datasets.length) { + for (var i = 0; i < datasets[0].data.length; ++i) { + text.push('
  • '); + if (labels[i]) { + text.push(labels[i]); } + text.push('
  • '); } } - }; - defaults.pie = helpers.clone(defaults.doughnut); - helpers.extend(defaults.pie, { - cutoutPercentage: 0 - }); + text.push('
'); + return text.join(''); + }, + legend: { + labels: { + generateLabels: function(chart) { + var data = chart.data; + if (data.labels.length && data.datasets.length) { + return data.labels.map(function(label, i) { + var meta = chart.getDatasetMeta(0); + var ds = data.datasets[0]; + var arc = meta.data[i]; + var custom = arc && arc.custom || {}; + var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault; + var arcOpts = chart.options.elements.arc; + var fill = custom.backgroundColor ? custom.backgroundColor : valueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor); + var stroke = custom.borderColor ? custom.borderColor : valueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor); + var bw = custom.borderWidth ? custom.borderWidth : valueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth); + return { + text: label, + fillStyle: fill, + strokeStyle: stroke, + lineWidth: bw, + hidden: isNaN(ds.data[i]) || meta.data[i].hidden, + + // Extra data used for toggling the correct item + index: i + }; + }); + } + return []; + } + }, + + onClick: function(e, legendItem) { + var index = legendItem.index; + var chart = this.chart; + var i, ilen, meta; + + for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { + meta = chart.getDatasetMeta(i); + // toggle visibility of index if exists + if (meta.data[index]) { + meta.data[index].hidden = !meta.data[index].hidden; + } + } + + chart.update(); + } + }, + + // The percentage of the chart that we cut out of the middle. + cutoutPercentage: 50, + + // The rotation of the chart, where the first data arc begins. + rotation: Math.PI * -0.5, + + // The total circumference of the chart. + circumference: Math.PI * 2.0, + + // Need to override these to give a nice default + tooltips: { + callbacks: { + title: function() { + return ''; + }, + label: function(tooltipItem, data) { + var dataLabel = data.labels[tooltipItem.index]; + var value = ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index]; + + if (helpers.isArray(dataLabel)) { + // show value on first line of multiline label + // need to clone because we are changing the value + dataLabel = dataLabel.slice(); + dataLabel[0] += value; + } else { + dataLabel += value; + } + + return dataLabel; + } + } + } +}); + +defaults._set('pie', helpers.clone(defaults.doughnut)); +defaults._set('pie', { + cutoutPercentage: 0 +}); + +module.exports = function(Chart) { Chart.controllers.doughnut = Chart.controllers.pie = Chart.DatasetController.extend({ diff --git a/src/controllers/controller.line.js b/src/controllers/controller.line.js index c31e73f23..d841ada64 100644 --- a/src/controllers/controller.line.js +++ b/src/controllers/controller.line.js @@ -1,29 +1,30 @@ 'use strict'; +var defaults = require('../core/core.defaults'); var helpers = require('../helpers/index'); +defaults._set('line', { + showLines: true, + spanGaps: false, + + hover: { + mode: 'label' + }, + + scales: { + xAxes: [{ + type: 'category', + id: 'x-axis-0' + }], + yAxes: [{ + type: 'linear', + id: 'y-axis-0' + }] + } +}); + module.exports = function(Chart) { - Chart.defaults.line = { - showLines: true, - spanGaps: false, - - hover: { - mode: 'label' - }, - - scales: { - xAxes: [{ - type: 'category', - id: 'x-axis-0' - }], - yAxes: [{ - type: 'linear', - id: 'y-axis-0' - }] - } - }; - function lineEnabled(dataset, options) { return helpers.valueOrDefault(dataset.showLine, options.showLines); } diff --git a/src/controllers/controller.polarArea.js b/src/controllers/controller.polarArea.js index bf2cd6b41..c0ac2993e 100644 --- a/src/controllers/controller.polarArea.js +++ b/src/controllers/controller.polarArea.js @@ -1,113 +1,113 @@ 'use strict'; +var defaults = require('../core/core.defaults'); var helpers = require('../helpers/index'); -module.exports = function(Chart) { - - Chart.defaults.polarArea = { - - scale: { - type: 'radialLinear', - angleLines: { - display: false - }, - gridLines: { - circular: true - }, - pointLabels: { - display: false - }, - ticks: { - beginAtZero: true - } +defaults._set('polarArea', { + scale: { + type: 'radialLinear', + angleLines: { + display: false }, - - // Boolean - Whether to animate the rotation of the chart - animation: { - animateRotate: true, - animateScale: true + gridLines: { + circular: true }, - - startAngle: -0.5 * Math.PI, - legendCallback: function(chart) { - var text = []; - text.push('
    '); - - var data = chart.data; - var datasets = data.datasets; - var labels = data.labels; - - if (datasets.length) { - for (var i = 0; i < datasets[0].data.length; ++i) { - text.push('
  • '); - if (labels[i]) { - text.push(labels[i]); - } - text.push('
  • '); - } - } - - text.push('
'); - return text.join(''); + pointLabels: { + display: false }, - legend: { - labels: { - generateLabels: function(chart) { - var data = chart.data; - if (data.labels.length && data.datasets.length) { - return data.labels.map(function(label, i) { - var meta = chart.getDatasetMeta(0); - var ds = data.datasets[0]; - var arc = meta.data[i]; - var custom = arc.custom || {}; - var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault; - var arcOpts = chart.options.elements.arc; - var fill = custom.backgroundColor ? custom.backgroundColor : valueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor); - var stroke = custom.borderColor ? custom.borderColor : valueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor); - var bw = custom.borderWidth ? custom.borderWidth : valueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth); + ticks: { + beginAtZero: true + } + }, - return { - text: label, - fillStyle: fill, - strokeStyle: stroke, - lineWidth: bw, - hidden: isNaN(ds.data[i]) || meta.data[i].hidden, + // Boolean - Whether to animate the rotation of the chart + animation: { + animateRotate: true, + animateScale: true + }, - // Extra data used for toggling the correct item - index: i - }; - }); - } - return []; - } - }, - - onClick: function(e, legendItem) { - var index = legendItem.index; - var chart = this.chart; - var i, ilen, meta; - - for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { - meta = chart.getDatasetMeta(i); - meta.data[index].hidden = !meta.data[index].hidden; - } - - chart.update(); - } - }, - - // Need to override these to give a nice default - tooltips: { - callbacks: { - title: function() { - return ''; - }, - label: function(tooltipItem, data) { - return data.labels[tooltipItem.index] + ': ' + tooltipItem.yLabel; + startAngle: -0.5 * Math.PI, + legendCallback: function(chart) { + var text = []; + text.push('
    '); + + var data = chart.data; + var datasets = data.datasets; + var labels = data.labels; + + if (datasets.length) { + for (var i = 0; i < datasets[0].data.length; ++i) { + text.push('
  • '); + if (labels[i]) { + text.push(labels[i]); } + text.push('
  • '); } } - }; + + text.push('
'); + return text.join(''); + }, + legend: { + labels: { + generateLabels: function(chart) { + var data = chart.data; + if (data.labels.length && data.datasets.length) { + return data.labels.map(function(label, i) { + var meta = chart.getDatasetMeta(0); + var ds = data.datasets[0]; + var arc = meta.data[i]; + var custom = arc.custom || {}; + var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault; + var arcOpts = chart.options.elements.arc; + var fill = custom.backgroundColor ? custom.backgroundColor : valueAtIndexOrDefault(ds.backgroundColor, i, arcOpts.backgroundColor); + var stroke = custom.borderColor ? custom.borderColor : valueAtIndexOrDefault(ds.borderColor, i, arcOpts.borderColor); + var bw = custom.borderWidth ? custom.borderWidth : valueAtIndexOrDefault(ds.borderWidth, i, arcOpts.borderWidth); + + return { + text: label, + fillStyle: fill, + strokeStyle: stroke, + lineWidth: bw, + hidden: isNaN(ds.data[i]) || meta.data[i].hidden, + + // Extra data used for toggling the correct item + index: i + }; + }); + } + return []; + } + }, + + onClick: function(e, legendItem) { + var index = legendItem.index; + var chart = this.chart; + var i, ilen, meta; + + for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { + meta = chart.getDatasetMeta(i); + meta.data[index].hidden = !meta.data[index].hidden; + } + + chart.update(); + } + }, + + // Need to override these to give a nice default + tooltips: { + callbacks: { + title: function() { + return ''; + }, + label: function(item, data) { + return data.labels[item.index] + ': ' + item.yLabel; + } + } + } +}); + +module.exports = function(Chart) { Chart.controllers.polarArea = Chart.DatasetController.extend({ diff --git a/src/controllers/controller.radar.js b/src/controllers/controller.radar.js index 24c83b463..b3bccdca9 100644 --- a/src/controllers/controller.radar.js +++ b/src/controllers/controller.radar.js @@ -1,19 +1,20 @@ 'use strict'; +var defaults = require('../core/core.defaults'); var helpers = require('../helpers/index'); -module.exports = function(Chart) { - - Chart.defaults.radar = { - scale: { - type: 'radialLinear' - }, - elements: { - line: { - tension: 0 // no bezier in radar - } +defaults._set('radar', { + scale: { + type: 'radialLinear' + }, + elements: { + line: { + tension: 0 // no bezier in radar } - }; + } +}); + +module.exports = function(Chart) { Chart.controllers.radar = Chart.DatasetController.extend({ diff --git a/src/controllers/controller.scatter.js b/src/controllers/controller.scatter.js new file mode 100644 index 000000000..b2e2cf1f7 --- /dev/null +++ b/src/controllers/controller.scatter.js @@ -0,0 +1,42 @@ +'use strict'; + +var defaults = require('../core/core.defaults'); + +defaults._set('scatter', { + hover: { + mode: 'single' + }, + + scales: { + xAxes: [{ + id: 'x-axis-1', // need an ID so datasets can reference the scale + type: 'linear', // scatter should not use a category axis + position: 'bottom' + }], + yAxes: [{ + id: 'y-axis-1', + type: 'linear', + position: 'left' + }] + }, + + showLines: false, + + tooltips: { + callbacks: { + title: function() { + return ''; // doesn't make sense for scatter since data are formatted as a point + }, + label: function(item) { + return '(' + item.xLabel + ', ' + item.yLabel + ')'; + } + } + } +}); + +module.exports = function(Chart) { + + // Scatter charts use line controllers + Chart.controllers.scatter = Chart.controllers.line; + +}; diff --git a/src/core/core.animation.js b/src/core/core.animation.js index 40208916a..bbd44cde1 100644 --- a/src/core/core.animation.js +++ b/src/core/core.animation.js @@ -1,16 +1,19 @@ /* global window: false */ 'use strict'; +var defaults = require('./core.defaults'); var helpers = require('../helpers/index'); -module.exports = function(Chart) { - - Chart.defaults.global.animation = { +defaults._set('global', { + animation: { duration: 1000, easing: 'easeOutQuart', onProgress: helpers.noop, onComplete: helpers.noop - }; + } +}); + +module.exports = function(Chart) { Chart.Animation = Chart.Element.extend({ chart: null, // the animation associated chart instance diff --git a/src/core/core.controller.js b/src/core/core.controller.js index 4a86d70d2..2033c5425 100644 --- a/src/core/core.controller.js +++ b/src/core/core.controller.js @@ -1,6 +1,8 @@ 'use strict'; +var defaults = require('./core.defaults'); var helpers = require('../helpers/index'); +var Interaction = require('./core.interaction'); var platform = require('../platforms/platform'); module.exports = function(Chart) { @@ -29,8 +31,8 @@ module.exports = function(Chart) { data.labels = data.labels || []; config.options = helpers.configMerge( - Chart.defaults.global, - Chart.defaults[config.type], + defaults.global, + defaults[config.type], config.options || {}); return config; @@ -601,19 +603,19 @@ module.exports = function(Chart) { // 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) { - return Chart.Interaction.modes.single(this, e); + return Interaction.modes.single(this, e); }, getElementsAtEvent: function(e) { - return Chart.Interaction.modes.label(this, e, {intersect: true}); + return Interaction.modes.label(this, e, {intersect: true}); }, getElementsAtXAxis: function(e) { - return Chart.Interaction.modes['x-axis'](this, e, {intersect: true}); + return Interaction.modes['x-axis'](this, e, {intersect: true}); }, getElementsAtEventForMode: function(e, mode, options) { - var method = Chart.Interaction.modes[mode]; + var method = Interaction.modes[mode]; if (typeof method === 'function') { return method(this, e, options); } @@ -622,7 +624,7 @@ module.exports = function(Chart) { }, getDatasetAtEvent: function(e) { - return Chart.Interaction.modes.dataset(this, e, {intersect: true}); + return Interaction.modes.dataset(this, e, {intersect: true}); }, getDatasetMeta: function(datasetIndex) { diff --git a/src/core/core.defaults.js b/src/core/core.defaults.js new file mode 100644 index 000000000..29bb040d4 --- /dev/null +++ b/src/core/core.defaults.js @@ -0,0 +1,12 @@ +'use strict'; + +var helpers = require('../helpers/index'); + +module.exports = { + /** + * @private + */ + _set: function(scope, values) { + return helpers.merge(this[scope] || (this[scope] = {}), values); + } +}; diff --git a/src/core/core.helpers.js b/src/core/core.helpers.js index 8896e232a..90e116d2c 100644 --- a/src/core/core.helpers.js +++ b/src/core/core.helpers.js @@ -3,6 +3,7 @@ 'use strict'; var color = require('chartjs-color'); +var defaults = require('./core.defaults'); var helpers = require('../helpers/index'); module.exports = function(Chart) { @@ -43,7 +44,7 @@ module.exports = function(Chart) { merger: function(key, target, source, options) { if (key === 'xAxes' || key === 'yAxes') { var slen = source[key].length; - var i, type, scale, defaults; + var i, type, scale; if (!target[key]) { target[key] = []; @@ -52,7 +53,6 @@ module.exports = function(Chart) { for (i = 0; i < slen; ++i) { scale = source[key][i]; type = helpers.valueOrDefault(scale.type, key === 'xAxes'? 'category' : 'linear'); - defaults = Chart.scaleService.getScaleDefaults(type); if (i >= target[key].length) { target[key].push({}); @@ -61,7 +61,7 @@ module.exports = function(Chart) { if (!target[key][i].type || (scale.type && scale.type !== target[key][i].type)) { // new/untyped scale or type changed: let's apply the new defaults // then merge source scale to correctly overwrite the defaults. - helpers.merge(target[key][i], [defaults, scale]); + helpers.merge(target[key][i], [Chart.scaleService.getScaleDefaults(type), scale]); } else { // scales type are the same helpers.merge(target[key][i], scale); @@ -612,7 +612,7 @@ module.exports = function(Chart) { function(value) { /* global CanvasGradient */ if (value instanceof CanvasGradient) { - value = Chart.defaults.global.defaultColor; + value = defaults.global.defaultColor; } return color(value); diff --git a/src/core/core.interaction.js b/src/core/core.interaction.js index 6e35bd950..793468d43 100644 --- a/src/core/core.interaction.js +++ b/src/core/core.interaction.js @@ -2,316 +2,313 @@ var helpers = require('../helpers/index'); -module.exports = function(Chart) { - - /** - * Helper function to get relative position for an event - * @param {Event|IEvent} event - The event to get the position for - * @param {Chart} chart - The chart - * @returns {Point} the event position - */ - function getRelativePosition(e, chart) { - if (e.native) { - return { - x: e.x, - y: e.y - }; - } - - return helpers.getRelativePosition(e, chart); +/** + * Helper function to get relative position for an event + * @param {Event|IEvent} event - The event to get the position for + * @param {Chart} chart - The chart + * @returns {Point} the event position + */ +function getRelativePosition(e, chart) { + if (e.native) { + return { + x: e.x, + y: e.y + }; } - /** - * Helper function to traverse all of the visible elements in the chart - * @param chart {chart} the chart - * @param handler {Function} the callback to execute for each visible item - */ - function parseVisibleItems(chart, handler) { - var datasets = chart.data.datasets; - var meta, i, j, ilen, jlen; + return helpers.getRelativePosition(e, chart); +} - for (i = 0, ilen = datasets.length; i < ilen; ++i) { - if (!chart.isDatasetVisible(i)) { - continue; - } +/** + * Helper function to traverse all of the visible elements in the chart + * @param chart {chart} the chart + * @param handler {Function} the callback to execute for each visible item + */ +function parseVisibleItems(chart, handler) { + var datasets = chart.data.datasets; + var meta, i, j, ilen, jlen; - meta = chart.getDatasetMeta(i); - for (j = 0, jlen = meta.data.length; j < jlen; ++j) { - var element = meta.data[j]; - if (!element._view.skip) { - handler(element); - } + for (i = 0, ilen = datasets.length; i < ilen; ++i) { + if (!chart.isDatasetVisible(i)) { + continue; + } + + meta = chart.getDatasetMeta(i); + for (j = 0, jlen = meta.data.length; j < jlen; ++j) { + var element = meta.data[j]; + if (!element._view.skip) { + handler(element); } } } +} - /** - * Helper function to get the items that intersect the event position - * @param items {ChartElement[]} elements to filter - * @param position {Point} the point to be nearest to - * @return {ChartElement[]} the nearest items - */ - function getIntersectItems(chart, position) { - var elements = []; +/** + * Helper function to get the items that intersect the event position + * @param items {ChartElement[]} elements to filter + * @param position {Point} the point to be nearest to + * @return {ChartElement[]} the nearest items + */ +function getIntersectItems(chart, position) { + var elements = []; - parseVisibleItems(chart, function(element) { - if (element.inRange(position.x, position.y)) { + parseVisibleItems(chart, function(element) { + if (element.inRange(position.x, position.y)) { + elements.push(element); + } + }); + + return elements; +} + +/** + * Helper function to get the items nearest to the event position considering all visible items in teh chart + * @param chart {Chart} the chart to look at elements from + * @param position {Point} the point to be nearest to + * @param intersect {Boolean} if true, only consider items that intersect the position + * @param distanceMetric {Function} Optional function to provide the distance between + * @return {ChartElement[]} the nearest items + */ +function getNearestItems(chart, position, intersect, distanceMetric) { + var minDistance = Number.POSITIVE_INFINITY; + var nearestItems = []; + + if (!distanceMetric) { + distanceMetric = helpers.distanceBetweenPoints; + } + + parseVisibleItems(chart, function(element) { + if (intersect && !element.inRange(position.x, position.y)) { + return; + } + + var center = element.getCenterPoint(); + var distance = distanceMetric(position, center); + + if (distance < minDistance) { + nearestItems = [element]; + minDistance = distance; + } else if (distance === minDistance) { + // Can have multiple items at the same distance in which case we sort by size + nearestItems.push(element); + } + }); + + return nearestItems; +} + +function indexMode(chart, e, options) { + var position = getRelativePosition(e, chart); + var distanceMetric = function(pt1, pt2) { + return Math.abs(pt1.x - pt2.x); + }; + var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric); + var elements = []; + + if (!items.length) { + return []; + } + + chart.data.datasets.forEach(function(dataset, datasetIndex) { + if (chart.isDatasetVisible(datasetIndex)) { + var meta = chart.getDatasetMeta(datasetIndex), + element = meta.data[items[0]._index]; + + // don't count items that are skipped (null data) + if (element && !element._view.skip) { elements.push(element); } - }); - - return elements; - } - - /** - * Helper function to get the items nearest to the event position considering all visible items in teh chart - * @param chart {Chart} the chart to look at elements from - * @param position {Point} the point to be nearest to - * @param intersect {Boolean} if true, only consider items that intersect the position - * @param distanceMetric {Function} Optional function to provide the distance between - * @return {ChartElement[]} the nearest items - */ - function getNearestItems(chart, position, intersect, distanceMetric) { - var minDistance = Number.POSITIVE_INFINITY; - var nearestItems = []; - - if (!distanceMetric) { - distanceMetric = helpers.distanceBetweenPoints; } + }); - parseVisibleItems(chart, function(element) { - if (intersect && !element.inRange(position.x, position.y)) { - return; - } + return elements; +} - var center = element.getCenterPoint(); - var distance = distanceMetric(position, center); +/** + * @interface IInteractionOptions + */ +/** + * If true, only consider items that intersect the point + * @name IInterfaceOptions#boolean + * @type Boolean + */ - if (distance < minDistance) { - nearestItems = [element]; - minDistance = distance; - } else if (distance === minDistance) { - // Can have multiple items at the same distance in which case we sort by size - nearestItems.push(element); - } - }); +/** + * Contains interaction related functions + * @namespace Chart.Interaction + */ +module.exports = { + // Helper function for different modes + modes: { + single: function(chart, e) { + var position = getRelativePosition(e, chart); + var elements = []; - return nearestItems; - } - - function indexMode(chart, e, options) { - var position = getRelativePosition(e, chart); - var distanceMetric = function(pt1, pt2) { - return Math.abs(pt1.x - pt2.x); - }; - var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric); - var elements = []; - - if (!items.length) { - return []; - } - - chart.data.datasets.forEach(function(dataset, datasetIndex) { - if (chart.isDatasetVisible(datasetIndex)) { - var meta = chart.getDatasetMeta(datasetIndex), - element = meta.data[items[0]._index]; - - // don't count items that are skipped (null data) - if (element && !element._view.skip) { + parseVisibleItems(chart, function(element) { + if (element.inRange(position.x, position.y)) { elements.push(element); + return elements; } + }); + + return elements.slice(0, 1); + }, + + /** + * @function Chart.Interaction.modes.label + * @deprecated since version 2.4.0 + * @todo remove at version 3 + * @private + */ + label: indexMode, + + /** + * Returns items at the same index. If the options.intersect parameter is true, we only return items if we intersect something + * If the options.intersect mode is false, we find the nearest item and return the items at the same index as that item + * @function Chart.Interaction.modes.index + * @since v2.4.0 + * @param chart {chart} the chart we are returning items from + * @param e {Event} the event we are find things at + * @param options {IInteractionOptions} options to use during interaction + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + index: indexMode, + + /** + * Returns items in the same dataset. If the options.intersect parameter is true, we only return items if we intersect something + * If the options.intersect is false, we find the nearest item and return the items in that dataset + * @function Chart.Interaction.modes.dataset + * @param chart {chart} the chart we are returning items from + * @param e {Event} the event we are find things at + * @param options {IInteractionOptions} options to use during interaction + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + dataset: function(chart, e, options) { + var position = getRelativePosition(e, chart); + var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false); + + if (items.length > 0) { + items = chart.getDatasetMeta(items[0]._datasetIndex).data; } - }); - return elements; - } + return items; + }, - /** - * @interface IInteractionOptions - */ - /** - * If true, only consider items that intersect the point - * @name IInterfaceOptions#boolean - * @type Boolean - */ + /** + * @function Chart.Interaction.modes.x-axis + * @deprecated since version 2.4.0. Use index mode and intersect == true + * @todo remove at version 3 + * @private + */ + 'x-axis': function(chart, e) { + return indexMode(chart, e, true); + }, - /** - * Contains interaction related functions - * @namespace Chart.Interaction - */ - Chart.Interaction = { - // Helper function for different modes - modes: { - single: function(chart, e) { - var position = getRelativePosition(e, chart); - var elements = []; + /** + * Point mode returns all elements that hit test based on the event position + * of the event + * @function Chart.Interaction.modes.intersect + * @param chart {chart} the chart we are returning items from + * @param e {Event} the event we are find things at + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + point: function(chart, e) { + var position = getRelativePosition(e, chart); + return getIntersectItems(chart, position); + }, - parseVisibleItems(chart, function(element) { - if (element.inRange(position.x, position.y)) { - elements.push(element); - return elements; + /** + * nearest mode returns the element closest to the point + * @function Chart.Interaction.modes.intersect + * @param chart {chart} the chart we are returning items from + * @param e {Event} the event we are find things at + * @param options {IInteractionOptions} options to use + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + nearest: function(chart, e, options) { + var position = getRelativePosition(e, chart); + var nearestItems = getNearestItems(chart, position, options.intersect); + + // We have multiple items at the same distance from the event. Now sort by smallest + if (nearestItems.length > 1) { + nearestItems.sort(function(a, b) { + var sizeA = a.getArea(); + var sizeB = b.getArea(); + var ret = sizeA - sizeB; + + if (ret === 0) { + // if equal sort by dataset index + ret = a._datasetIndex - b._datasetIndex; } + + return ret; }); - - return elements.slice(0, 1); - }, - - /** - * @function Chart.Interaction.modes.label - * @deprecated since version 2.4.0 - * @todo remove at version 3 - * @private - */ - label: indexMode, - - /** - * Returns items at the same index. If the options.intersect parameter is true, we only return items if we intersect something - * If the options.intersect mode is false, we find the nearest item and return the items at the same index as that item - * @function Chart.Interaction.modes.index - * @since v2.4.0 - * @param chart {chart} the chart we are returning items from - * @param e {Event} the event we are find things at - * @param options {IInteractionOptions} options to use during interaction - * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned - */ - index: indexMode, - - /** - * Returns items in the same dataset. If the options.intersect parameter is true, we only return items if we intersect something - * If the options.intersect is false, we find the nearest item and return the items in that dataset - * @function Chart.Interaction.modes.dataset - * @param chart {chart} the chart we are returning items from - * @param e {Event} the event we are find things at - * @param options {IInteractionOptions} options to use during interaction - * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned - */ - dataset: function(chart, e, options) { - var position = getRelativePosition(e, chart); - var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false); - - if (items.length > 0) { - items = chart.getDatasetMeta(items[0]._datasetIndex).data; - } - - return items; - }, - - /** - * @function Chart.Interaction.modes.x-axis - * @deprecated since version 2.4.0. Use index mode and intersect == true - * @todo remove at version 3 - * @private - */ - 'x-axis': function(chart, e) { - return indexMode(chart, e, true); - }, - - /** - * Point mode returns all elements that hit test based on the event position - * of the event - * @function Chart.Interaction.modes.intersect - * @param chart {chart} the chart we are returning items from - * @param e {Event} the event we are find things at - * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned - */ - point: function(chart, e) { - var position = getRelativePosition(e, chart); - return getIntersectItems(chart, position); - }, - - /** - * nearest mode returns the element closest to the point - * @function Chart.Interaction.modes.intersect - * @param chart {chart} the chart we are returning items from - * @param e {Event} the event we are find things at - * @param options {IInteractionOptions} options to use - * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned - */ - nearest: function(chart, e, options) { - var position = getRelativePosition(e, chart); - var nearestItems = getNearestItems(chart, position, options.intersect); - - // We have multiple items at the same distance from the event. Now sort by smallest - if (nearestItems.length > 1) { - nearestItems.sort(function(a, b) { - var sizeA = a.getArea(); - var sizeB = b.getArea(); - var ret = sizeA - sizeB; - - if (ret === 0) { - // if equal sort by dataset index - ret = a._datasetIndex - b._datasetIndex; - } - - return ret; - }); - } - - // Return only 1 item - return nearestItems.slice(0, 1); - }, - - /** - * x mode returns the elements that hit-test at the current x coordinate - * @function Chart.Interaction.modes.x - * @param chart {chart} the chart we are returning items from - * @param e {Event} the event we are find things at - * @param options {IInteractionOptions} options to use - * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned - */ - x: function(chart, e, options) { - var position = getRelativePosition(e, chart); - var items = []; - var intersectsItem = false; - - parseVisibleItems(chart, function(element) { - if (element.inXRange(position.x)) { - items.push(element); - } - - if (element.inRange(position.x, position.y)) { - intersectsItem = true; - } - }); - - // If we want to trigger on an intersect and we don't have any items - // that intersect the position, return nothing - if (options.intersect && !intersectsItem) { - items = []; - } - return items; - }, - - /** - * y mode returns the elements that hit-test at the current y coordinate - * @function Chart.Interaction.modes.y - * @param chart {chart} the chart we are returning items from - * @param e {Event} the event we are find things at - * @param options {IInteractionOptions} options to use - * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned - */ - y: function(chart, e, options) { - var position = getRelativePosition(e, chart); - var items = []; - var intersectsItem = false; - - parseVisibleItems(chart, function(element) { - if (element.inYRange(position.y)) { - items.push(element); - } - - if (element.inRange(position.x, position.y)) { - intersectsItem = true; - } - }); - - // If we want to trigger on an intersect and we don't have any items - // that intersect the position, return nothing - if (options.intersect && !intersectsItem) { - items = []; - } - return items; } + + // Return only 1 item + return nearestItems.slice(0, 1); + }, + + /** + * x mode returns the elements that hit-test at the current x coordinate + * @function Chart.Interaction.modes.x + * @param chart {chart} the chart we are returning items from + * @param e {Event} the event we are find things at + * @param options {IInteractionOptions} options to use + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + x: function(chart, e, options) { + var position = getRelativePosition(e, chart); + var items = []; + var intersectsItem = false; + + parseVisibleItems(chart, function(element) { + if (element.inXRange(position.x)) { + items.push(element); + } + + if (element.inRange(position.x, position.y)) { + intersectsItem = true; + } + }); + + // If we want to trigger on an intersect and we don't have any items + // that intersect the position, return nothing + if (options.intersect && !intersectsItem) { + items = []; + } + return items; + }, + + /** + * y mode returns the elements that hit-test at the current y coordinate + * @function Chart.Interaction.modes.y + * @param chart {chart} the chart we are returning items from + * @param e {Event} the event we are find things at + * @param options {IInteractionOptions} options to use + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + y: function(chart, e, options) { + var position = getRelativePosition(e, chart); + var items = []; + var intersectsItem = false; + + parseVisibleItems(chart, function(element) { + if (element.inYRange(position.y)) { + items.push(element); + } + + if (element.inRange(position.x, position.y)) { + intersectsItem = true; + } + }); + + // If we want to trigger on an intersect and we don't have any items + // that intersect the position, return nothing + if (options.intersect && !intersectsItem) { + items = []; + } + return items; } - }; + } }; diff --git a/src/core/core.js b/src/core/core.js index afb51c71d..906b897c6 100644 --- a/src/core/core.js +++ b/src/core/core.js @@ -1,5 +1,40 @@ 'use strict'; +var defaults = require('./core.defaults'); + +defaults._set('global', { + responsive: true, + responsiveAnimationDuration: 0, + maintainAspectRatio: true, + events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'], + hover: { + onHover: null, + mode: 'nearest', + intersect: true, + animationDuration: 400 + }, + onClick: null, + defaultColor: 'rgba(0,0,0,0.1)', + defaultFontColor: '#666', + defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", + defaultFontSize: 12, + defaultFontStyle: 'normal', + showLines: true, + + // Element defaults defined in element extensions + elements: {}, + + // Layout options such as padding + layout: { + padding: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + } +}); + module.exports = function() { // Occupy the global variable of Chart, and create a simple base class @@ -8,58 +43,6 @@ module.exports = function() { return this; }; - // Globally expose the defaults to allow for user updating/changing - Chart.defaults = { - global: { - responsive: true, - responsiveAnimationDuration: 0, - maintainAspectRatio: true, - events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'], - hover: { - onHover: null, - mode: 'nearest', - intersect: true, - animationDuration: 400 - }, - onClick: null, - defaultColor: 'rgba(0,0,0,0.1)', - defaultFontColor: '#666', - defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", - defaultFontSize: 12, - defaultFontStyle: 'normal', - showLines: true, - - // Element defaults defined in element extensions - elements: {}, - - // Layout options such as padding - layout: { - padding: { - top: 0, - right: 0, - bottom: 0, - left: 0 - } - }, - - // Legend callback string - legendCallback: function(chart) { - var text = []; - text.push('
    '); - for (var i = 0; i < chart.data.datasets.length; i++) { - text.push('
  • '); - if (chart.data.datasets[i].label) { - text.push(chart.data.datasets[i].label); - } - text.push('
  • '); - } - text.push('
'); - - return text.join(''); - } - } - }; - Chart.Chart = Chart; return Chart; diff --git a/src/core/core.plugin.js b/src/core/core.plugin.js index 927611580..2c472ce0d 100644 --- a/src/core/core.plugin.js +++ b/src/core/core.plugin.js @@ -1,10 +1,13 @@ 'use strict'; +var defaults = require('./core.defaults'); var helpers = require('../helpers/index'); -module.exports = function(Chart) { +defaults._set('global', { + plugins: {} +}); - Chart.defaults.global.plugins = {}; +module.exports = function(Chart) { /** * The plugin service singleton @@ -128,7 +131,6 @@ module.exports = function(Chart) { var plugins = []; var descriptors = []; var config = (chart && chart.config) || {}; - var defaults = Chart.defaults.global.plugins; var options = (config.options && config.options.plugins) || {}; this._plugins.concat(config.plugins || []).forEach(function(plugin) { @@ -144,7 +146,7 @@ module.exports = function(Chart) { } if (opts === true) { - opts = helpers.clone(defaults[id]); + opts = helpers.clone(defaults.global.plugins[id]); } plugins.push(plugin); diff --git a/src/core/core.scale.js b/src/core/core.scale.js index 38d1d10ac..9626c3f0e 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -1,59 +1,61 @@ 'use strict'; +var defaults = require('./core.defaults'); var helpers = require('../helpers/index'); +var Ticks = require('./core.ticks'); + +defaults._set('scale', { + display: true, + position: 'left', + + // grid line settings + gridLines: { + display: true, + color: 'rgba(0, 0, 0, 0.1)', + lineWidth: 1, + drawBorder: true, + drawOnChartArea: true, + drawTicks: true, + tickMarkLength: 10, + zeroLineWidth: 1, + zeroLineColor: 'rgba(0,0,0,0.25)', + zeroLineBorderDash: [], + zeroLineBorderDashOffset: 0.0, + offsetGridLines: false, + borderDash: [], + borderDashOffset: 0.0 + }, + + // scale label + scaleLabel: { + // actual label + labelString: '', + + // display property + display: false, + }, + + // label settings + ticks: { + beginAtZero: false, + minRotation: 0, + maxRotation: 50, + mirror: false, + padding: 0, + reverse: false, + display: true, + autoSkip: true, + autoSkipPadding: 0, + labelOffset: 0, + // We pass through arrays to be rendered as multiline labels, we convert Others to strings here. + callback: Ticks.formatters.values, + minor: {}, + major: {} + } +}); module.exports = function(Chart) { - Chart.defaults.scale = { - display: true, - position: 'left', - - // grid line settings - gridLines: { - display: true, - color: 'rgba(0, 0, 0, 0.1)', - lineWidth: 1, - drawBorder: true, - drawOnChartArea: true, - drawTicks: true, - tickMarkLength: 10, - zeroLineWidth: 1, - zeroLineColor: 'rgba(0,0,0,0.25)', - zeroLineBorderDash: [], - zeroLineBorderDashOffset: 0.0, - offsetGridLines: false, - borderDash: [], - borderDashOffset: 0.0 - }, - - // scale label - scaleLabel: { - // actual label - labelString: '', - - // display property - display: false, - }, - - // label settings - ticks: { - beginAtZero: false, - minRotation: 0, - maxRotation: 50, - mirror: false, - padding: 0, - reverse: false, - display: true, - autoSkip: true, - autoSkipPadding: 0, - labelOffset: 0, - // We pass through arrays to be rendered as multiline labels, we convert Others to strings here. - callback: Chart.Ticks.formatters.values, - minor: {}, - major: {} - } - }; - function computeTextSize(context, tick, font) { return helpers.isArray(tick) ? helpers.longestText(context, font, tick) : @@ -62,7 +64,7 @@ module.exports = function(Chart) { function parseFontOptions(options) { var valueOrDefault = helpers.valueOrDefault; - var globalDefaults = Chart.defaults.global; + var globalDefaults = defaults.global; var size = valueOrDefault(options.fontSize, globalDefaults.defaultFontSize); var style = valueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle); var family = valueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily); @@ -511,7 +513,7 @@ module.exports = function(Chart) { } var context = me.ctx; - var globalDefaults = Chart.defaults.global; + var globalDefaults = defaults.global; var optionTicks = options.ticks.minor; var optionMajorTicks = options.ticks.major || optionTicks; var gridLines = options.gridLines; diff --git a/src/core/core.scaleService.js b/src/core/core.scaleService.js index e903b701b..23cabe610 100644 --- a/src/core/core.scaleService.js +++ b/src/core/core.scaleService.js @@ -1,5 +1,6 @@ 'use strict'; +var defaults = require('./core.defaults'); var helpers = require('../helpers/index'); module.exports = function(Chart) { @@ -13,21 +14,21 @@ module.exports = function(Chart) { // Scale config defaults defaults: {}, - registerScaleType: function(type, scaleConstructor, defaults) { + registerScaleType: function(type, scaleConstructor, scaleDefaults) { this.constructors[type] = scaleConstructor; - this.defaults[type] = helpers.clone(defaults); + this.defaults[type] = helpers.clone(scaleDefaults); }, getScaleConstructor: function(type) { return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined; }, getScaleDefaults: function(type) { // Return the scale defaults merged with the global settings so that we always use the latest ones - return this.defaults.hasOwnProperty(type) ? helpers.merge({}, [Chart.defaults.scale, this.defaults[type]]) : {}; + return this.defaults.hasOwnProperty(type) ? helpers.merge({}, [defaults.scale, this.defaults[type]]) : {}; }, updateScaleDefaults: function(type, additions) { - var defaults = this.defaults; - if (defaults.hasOwnProperty(type)) { - defaults[type] = helpers.extend(defaults[type], additions); + var me = this; + if (me.defaults.hasOwnProperty(type)) { + me.defaults[type] = helpers.extend(me.defaults[type], additions); } }, addScalesToLayout: function(chart) { diff --git a/src/core/core.ticks.js b/src/core/core.ticks.js index a3b721164..11f44142c 100644 --- a/src/core/core.ticks.js +++ b/src/core/core.ticks.js @@ -2,209 +2,206 @@ var helpers = require('../helpers/index'); -module.exports = function(Chart) { - +/** + * Namespace to hold static tick generation functions + * @namespace Chart.Ticks + */ +module.exports = { /** - * Namespace to hold static tick generation functions - * @namespace Chart.Ticks + * Namespace to hold generators for different types of ticks + * @namespace Chart.Ticks.generators */ - Chart.Ticks = { + generators: { /** - * Namespace to hold generators for different types of ticks - * @namespace Chart.Ticks.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 */ - 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} 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. + /** + * 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} 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 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 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 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); - // 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} 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; - var 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; - }, - - time: helpers.time.generateTicks + return ticks; }, /** - * Namespace to hold formatters for different types of ticks - * @namespace Chart.Ticks.formatters + * 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} array of tick values */ - formatters: { - /** - * Formatter for value labels - * @method Chart.Ticks.formatters.values - * @param value the value to display - * @return {String|Array} the label to display - */ - values: function(value) { - return helpers.isArray(value) ? value : '' + value; - }, + logarithmic: function(generationOptions, dataRange) { + var ticks = []; + var valueOrDefault = helpers.valueOrDefault; - /** - * Formatter for linear numeric ticks - * @method Chart.Ticks.formatters.linear - * @param tickValue {Number} the value to be formatted - * @param index {Number} the position of the tickValue parameter in the ticks array - * @param ticks {Array} the list of ticks being converted - * @return {String} string representation of the tickValue parameter - */ - linear: function(tickValue, index, ticks) { - // If we have lots of ticks, don't use the ones - var delta = ticks.length > 3 ? ticks[2] - ticks[1] : ticks[1] - ticks[0]; + // 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)))); - // If we have a number like 2.5 as the delta, figure out how many decimal places we need - if (Math.abs(delta) > 1) { - if (tickValue !== Math.floor(tickValue)) { - // not an integer - delta = tickValue - Math.floor(tickValue); - } - } + var endExp = Math.floor(helpers.log10(dataRange.max)); + var endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp)); + var exp; + var significand; - var logDelta = helpers.log10(Math.abs(delta)); - var tickString = ''; + if (tickVal === 0) { + exp = Math.floor(helpers.log10(dataRange.minNotZero)); + significand = Math.floor(dataRange.minNotZero / Math.pow(10, exp)); - if (tickValue !== 0) { - var numDecimal = -1 * Math.floor(logDelta); - numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places - tickString = tickValue.toFixed(numDecimal); - } else { - tickString = '0'; // never show decimal places for 0 - } - - return tickString; - }, - - logarithmic: function(tickValue, index, ticks) { - var remain = tickValue / (Math.pow(10, Math.floor(helpers.log10(tickValue)))); - - if (tickValue === 0) { - return '0'; - } else if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === ticks.length - 1) { - return tickValue.toExponential(); - } - return ''; + 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; + }, + + time: helpers.time.generateTicks + }, + + /** + * Namespace to hold formatters for different types of ticks + * @namespace Chart.Ticks.formatters + */ + formatters: { + /** + * Formatter for value labels + * @method Chart.Ticks.formatters.values + * @param value the value to display + * @return {String|Array} the label to display + */ + values: function(value) { + return helpers.isArray(value) ? value : '' + value; + }, + + /** + * Formatter for linear numeric ticks + * @method Chart.Ticks.formatters.linear + * @param tickValue {Number} the value to be formatted + * @param index {Number} the position of the tickValue parameter in the ticks array + * @param ticks {Array} the list of ticks being converted + * @return {String} string representation of the tickValue parameter + */ + linear: function(tickValue, index, ticks) { + // If we have lots of ticks, don't use the ones + var delta = ticks.length > 3 ? ticks[2] - ticks[1] : ticks[1] - ticks[0]; + + // If we have a number like 2.5 as the delta, figure out how many decimal places we need + if (Math.abs(delta) > 1) { + if (tickValue !== Math.floor(tickValue)) { + // not an integer + delta = tickValue - Math.floor(tickValue); + } + } + + var logDelta = helpers.log10(Math.abs(delta)); + var tickString = ''; + + if (tickValue !== 0) { + var numDecimal = -1 * Math.floor(logDelta); + numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places + tickString = tickValue.toFixed(numDecimal); + } else { + tickString = '0'; // never show decimal places for 0 + } + + return tickString; + }, + + logarithmic: function(tickValue, index, ticks) { + var remain = tickValue / (Math.pow(10, Math.floor(helpers.log10(tickValue)))); + + if (tickValue === 0) { + return '0'; + } else if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === ticks.length - 1) { + return tickValue.toExponential(); + } + return ''; } - }; + } }; diff --git a/src/core/core.tooltip.js b/src/core/core.tooltip.js index 679432051..a1ded8e3b 100644 --- a/src/core/core.tooltip.js +++ b/src/core/core.tooltip.js @@ -1,18 +1,10 @@ 'use strict'; +var defaults = require('./core.defaults'); var helpers = require('../helpers/index'); -module.exports = function(Chart) { - - /** - * Helper method to merge the opacity into a color - */ - function mergeOpacity(colorString, opacity) { - var color = helpers.color(colorString); - return color.alpha(opacity * color.alpha()).rgbaString(); - } - - Chart.defaults.global.tooltips = { +defaults._set('global', { + tooltips: { enabled: true, custom: null, mode: 'nearest', @@ -100,7 +92,18 @@ module.exports = function(Chart) { footer: helpers.noop, afterFooter: helpers.noop } - }; + } +}); + +module.exports = function(Chart) { + + /** + * Helper method to merge the opacity into a color + */ + function mergeOpacity(colorString, opacity) { + var color = helpers.color(colorString); + return color.alpha(opacity * color.alpha()).rgbaString(); + } // Helper to push or concat based on if the 2nd parameter is an array or not function pushOrConcat(base, toPush) { @@ -140,7 +143,7 @@ module.exports = function(Chart) { * @param tooltipOpts {Object} the tooltip options */ function getBaseModel(tooltipOpts) { - var globalDefaults = Chart.defaults.global; + var globalDefaults = defaults.global; var valueOrDefault = helpers.valueOrDefault; return { diff --git a/src/elements/element.arc.js b/src/elements/element.arc.js index 8604dfaea..101302cf8 100644 --- a/src/elements/element.arc.js +++ b/src/elements/element.arc.js @@ -1,17 +1,20 @@ 'use strict'; +var defaults = require('../core/core.defaults'); var helpers = require('../helpers/index'); +defaults._set('global', { + elements: { + arc: { + backgroundColor: defaults.global.defaultColor, + borderColor: '#fff', + borderWidth: 2 + } + } +}); + module.exports = function(Chart) { - var globalOpts = Chart.defaults.global; - - globalOpts.elements.arc = { - backgroundColor: globalOpts.defaultColor, - borderColor: '#fff', - borderWidth: 2 - }; - Chart.elements.Arc = Chart.Element.extend({ inLabelRange: function(mouseX) { var vm = this._view; diff --git a/src/elements/element.line.js b/src/elements/element.line.js index 5a21fafb2..f2bfcf67d 100644 --- a/src/elements/element.line.js +++ b/src/elements/element.line.js @@ -1,24 +1,29 @@ 'use strict'; +var defaults = require('../core/core.defaults'); var helpers = require('../helpers/index'); +var globalDefaults = defaults.global; + +defaults._set('global', { + elements: { + line: { + tension: 0.4, + backgroundColor: globalDefaults.defaultColor, + borderWidth: 3, + borderColor: globalDefaults.defaultColor, + borderCapStyle: 'butt', + borderDash: [], + borderDashOffset: 0.0, + borderJoinStyle: 'miter', + capBezierPoints: true, + fill: true, // do we fill in the area between the line and its base axis + } + } +}); + module.exports = function(Chart) { - var globalDefaults = Chart.defaults.global; - - Chart.defaults.global.elements.line = { - tension: 0.4, - backgroundColor: globalDefaults.defaultColor, - borderWidth: 3, - borderColor: globalDefaults.defaultColor, - borderCapStyle: 'butt', - borderDash: [], - borderDashOffset: 0.0, - borderJoinStyle: 'miter', - capBezierPoints: true, - fill: true, // do we fill in the area between the line and its base axis - }; - Chart.elements.Line = Chart.Element.extend({ draw: function() { var me = this; diff --git a/src/elements/element.point.js b/src/elements/element.point.js index c09329b17..0b5d12d12 100644 --- a/src/elements/element.point.js +++ b/src/elements/element.point.js @@ -1,24 +1,28 @@ 'use strict'; +var defaults = require('../core/core.defaults'); var helpers = require('../helpers/index'); +var defaultColor = defaults.global.defaultColor; + +defaults._set('global', { + elements: { + point: { + radius: 3, + pointStyle: 'circle', + backgroundColor: defaultColor, + borderColor: defaultColor, + borderWidth: 1, + // Hover + hitRadius: 1, + hoverRadius: 4, + hoverBorderWidth: 1 + } + } +}); + module.exports = function(Chart) { - var globalOpts = Chart.defaults.global, - defaultColor = globalOpts.defaultColor; - - globalOpts.elements.point = { - radius: 3, - pointStyle: 'circle', - backgroundColor: defaultColor, - borderWidth: 1, - borderColor: defaultColor, - // Hover - hitRadius: 1, - hoverRadius: 4, - hoverBorderWidth: 1 - }; - function xRange(mouseX) { var vm = this._view; return vm ? (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false; @@ -74,7 +78,7 @@ module.exports = function(Chart) { } ctx.strokeStyle = vm.borderColor || defaultColor; - ctx.lineWidth = helpers.valueOrDefault(vm.borderWidth, globalOpts.elements.point.borderWidth); + ctx.lineWidth = helpers.valueOrDefault(vm.borderWidth, defaults.global.elements.point.borderWidth); ctx.fillStyle = vm.backgroundColor || defaultColor; // Cliping for Points. diff --git a/src/elements/element.rectangle.js b/src/elements/element.rectangle.js index c3b819761..00f017e66 100644 --- a/src/elements/element.rectangle.js +++ b/src/elements/element.rectangle.js @@ -1,16 +1,20 @@ 'use strict'; +var defaults = require('../core/core.defaults'); + +defaults._set('global', { + elements: { + rectangle: { + backgroundColor: defaults.global.defaultColor, + borderColor: defaults.global.defaultColor, + borderSkipped: 'bottom', + borderWidth: 0 + } + } +}); + module.exports = function(Chart) { - var globalOpts = Chart.defaults.global; - - globalOpts.elements.rectangle = { - backgroundColor: globalOpts.defaultColor, - borderWidth: 0, - borderColor: globalOpts.defaultColor, - borderSkipped: 'bottom' - }; - function isVertical(bar) { return bar._view.width !== undefined; } diff --git a/src/plugins/plugin.filler.js b/src/plugins/plugin.filler.js index 4f49064f0..51aed7669 100644 --- a/src/plugins/plugin.filler.js +++ b/src/plugins/plugin.filler.js @@ -1,18 +1,24 @@ +/** + * Plugin based on discussion from the following Chart.js issues: + * @see https://github.com/chartjs/Chart.js/issues/2380#issuecomment-279961569 + * @see https://github.com/chartjs/Chart.js/issues/2440#issuecomment-256461897 + */ + 'use strict'; +var defaults = require('../core/core.defaults'); var helpers = require('../helpers/index'); -module.exports = function(Chart) { - /** - * Plugin based on discussion from the following Chart.js issues: - * @see https://github.com/chartjs/Chart.js/issues/2380#issuecomment-279961569 - * @see https://github.com/chartjs/Chart.js/issues/2440#issuecomment-256461897 - */ - Chart.defaults.global.plugins.filler = { - propagate: true - }; +defaults._set('global', { + plugins: { + filler: { + propagate: true + } + } +}); + +module.exports = function(Chart) { - var defaults = Chart.defaults; var mappers = { dataset: function(source) { var index = source.fill; diff --git a/src/plugins/plugin.legend.js b/src/plugins/plugin.legend.js index 3eb0f57df..1a32c0798 100644 --- a/src/plugins/plugin.legend.js +++ b/src/plugins/plugin.legend.js @@ -1,13 +1,10 @@ 'use strict'; +var defaults = require('../core/core.defaults'); var helpers = require('../helpers/index'); -module.exports = function(Chart) { - - var layout = Chart.layoutService; - var noop = helpers.noop; - - Chart.defaults.global.legend = { +defaults._set('global', { + legend: { display: true, position: 'top', fullWidth: true, @@ -64,7 +61,27 @@ module.exports = function(Chart) { }, this) : []; } } - }; + }, + + legendCallback: function(chart) { + var text = []; + text.push('
    '); + for (var i = 0; i < chart.data.datasets.length; i++) { + text.push('
  • '); + if (chart.data.datasets[i].label) { + text.push(chart.data.datasets[i].label); + } + text.push('
  • '); + } + text.push('
'); + return text.join(''); + } +}); + +module.exports = function(Chart) { + + var layout = Chart.layoutService; + var noop = helpers.noop; /** * Helper function to get the box width based on the usePointStyle option @@ -192,7 +209,7 @@ module.exports = function(Chart) { var ctx = me.ctx; - var globalDefault = Chart.defaults.global, + var globalDefault = defaults.global, valueOrDefault = helpers.valueOrDefault, fontSize = valueOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize), fontStyle = valueOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle), @@ -304,7 +321,7 @@ module.exports = function(Chart) { var me = this; var opts = me.options; var labelOpts = opts.labels; - var globalDefault = Chart.defaults.global, + var globalDefault = defaults.global, lineDefault = globalDefault.elements.line, legendWidth = me.width, lineWidths = me.lineWidths; @@ -525,7 +542,7 @@ module.exports = function(Chart) { var legend = chart.legend; if (legendOpts) { - helpers.mergeIf(legendOpts, Chart.defaults.global.legend); + helpers.mergeIf(legendOpts, defaults.global.legend); if (legend) { layout.configure(chart, legend, legendOpts); diff --git a/src/plugins/plugin.title.js b/src/plugins/plugin.title.js index 42ad1b0d3..30f8813d9 100644 --- a/src/plugins/plugin.title.js +++ b/src/plugins/plugin.title.js @@ -1,24 +1,25 @@ 'use strict'; +var defaults = require('../core/core.defaults'); var helpers = require('../helpers/index'); +defaults._set('global', { + title: { + display: false, + fontStyle: 'bold', + fullWidth: true, + padding: 10, + position: 'top', + text: '', + weight: 2000 // by default greater than legend (1000) to be above + } +}); + module.exports = function(Chart) { var layout = Chart.layoutService; var noop = helpers.noop; - Chart.defaults.global.title = { - display: false, - position: 'top', - fullWidth: true, - weight: 2000, // by default greater than legend (1000) to be above - fontStyle: 'bold', - padding: 10, - - // actual title - text: '' - }; - Chart.Title = Chart.Element.extend({ initialize: function(config) { var me = this; @@ -109,9 +110,8 @@ module.exports = function(Chart) { var me = this, valueOrDefault = helpers.valueOrDefault, opts = me.options, - globalDefaults = Chart.defaults.global, display = opts.display, - fontSize = valueOrDefault(opts.fontSize, globalDefaults.defaultFontSize), + fontSize = valueOrDefault(opts.fontSize, defaults.global.defaultFontSize), minSize = me.minSize, lineCount = helpers.isArray(opts.text) ? opts.text.length : 1, lineHeight = valueOrDefault(opts.lineHeight, fontSize), @@ -143,7 +143,7 @@ module.exports = function(Chart) { ctx = me.ctx, valueOrDefault = helpers.valueOrDefault, opts = me.options, - globalDefaults = Chart.defaults.global; + globalDefaults = defaults.global; if (opts.display) { var fontSize = valueOrDefault(opts.fontSize, globalDefaults.defaultFontSize), @@ -225,7 +225,7 @@ module.exports = function(Chart) { var titleBlock = chart.titleBlock; if (titleOpts) { - helpers.mergeIf(titleOpts, Chart.defaults.global.title); + helpers.mergeIf(titleOpts, defaults.global.title); if (titleBlock) { layout.configure(chart, titleBlock, titleOpts); diff --git a/src/scales/scale.linear.js b/src/scales/scale.linear.js index 34223c283..94ebd12a5 100644 --- a/src/scales/scale.linear.js +++ b/src/scales/scale.linear.js @@ -1,13 +1,15 @@ 'use strict'; +var defaults = require('../core/core.defaults'); var helpers = require('../helpers/index'); +var Ticks = require('../core/core.ticks'); module.exports = function(Chart) { var defaultConfig = { position: 'left', ticks: { - callback: Chart.Ticks.formatters.linear + callback: Ticks.formatters.linear } }; @@ -139,7 +141,7 @@ module.exports = function(Chart) { maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.width / 50)); } else { // The factor of 2 used to scale the font size has been experimentally determined. - var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, Chart.defaults.global.defaultFontSize); + var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, defaults.global.defaultFontSize); maxTicks = Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(me.height / (2 * tickFontSize))); } diff --git a/src/scales/scale.linearbase.js b/src/scales/scale.linearbase.js index 33bc22517..cf75b3166 100644 --- a/src/scales/scale.linearbase.js +++ b/src/scales/scale.linearbase.js @@ -1,6 +1,7 @@ 'use strict'; var helpers = require('../helpers/index'); +var Ticks = require('../core/core.ticks'); module.exports = function(Chart) { @@ -94,7 +95,7 @@ module.exports = function(Chart) { max: tickOpts.max, stepSize: helpers.valueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize) }; - var ticks = me.ticks = Chart.Ticks.generators.linear(numericGeneratorOptions, me); + var ticks = me.ticks = Ticks.generators.linear(numericGeneratorOptions, me); me.handleDirectionalChanges(); diff --git a/src/scales/scale.logarithmic.js b/src/scales/scale.logarithmic.js index a5c5fa918..a6a0606ad 100644 --- a/src/scales/scale.logarithmic.js +++ b/src/scales/scale.logarithmic.js @@ -1,6 +1,7 @@ 'use strict'; var helpers = require('../helpers/index'); +var Ticks = require('../core/core.ticks'); module.exports = function(Chart) { @@ -9,7 +10,7 @@ module.exports = function(Chart) { // label settings ticks: { - callback: Chart.Ticks.formatters.logarithmic + callback: Ticks.formatters.logarithmic } }; @@ -142,7 +143,7 @@ module.exports = function(Chart) { min: tickOpts.min, max: tickOpts.max }; - var ticks = me.ticks = Chart.Ticks.generators.logarithmic(generationOptions, me); + var ticks = me.ticks = Ticks.generators.logarithmic(generationOptions, me); if (!me.isHorizontal()) { // We are in a vertical orientation. The top value is the highest. So reverse the array diff --git a/src/scales/scale.radialLinear.js b/src/scales/scale.radialLinear.js index 93036c691..f1194fd82 100644 --- a/src/scales/scale.radialLinear.js +++ b/src/scales/scale.radialLinear.js @@ -1,10 +1,12 @@ 'use strict'; +var defaults = require('../core/core.defaults'); var helpers = require('../helpers/index'); +var Ticks = require('../core/core.ticks'); module.exports = function(Chart) { - var globalDefaults = Chart.defaults.global; + var globalDefaults = defaults.global; var defaultConfig = { display: true, @@ -37,7 +39,7 @@ module.exports = function(Chart) { // Number - The backdrop padding to the side of the label in pixels backdropPaddingX: 2, - callback: Chart.Ticks.formatters.linear + callback: Ticks.formatters.linear }, pointLabels: { diff --git a/src/scales/scale.time.js b/src/scales/scale.time.js index fab5ccd64..41d022a79 100644 --- a/src/scales/scale.time.js +++ b/src/scales/scale.time.js @@ -4,6 +4,7 @@ var moment = require('moment'); moment = typeof(moment) === 'function' ? moment : window.moment; +var defaults = require('../core/core.defaults'); var helpers = require('../helpers/index'); module.exports = function(Chart) { @@ -274,7 +275,7 @@ module.exports = function(Chart) { var tickLabelWidth = me.ctx.measureText(label).width; var cosRotation = Math.cos(helpers.toRadians(ticks.maxRotation)); var sinRotation = Math.sin(helpers.toRadians(ticks.maxRotation)); - var tickFontSize = helpers.valueOrDefault(ticks.fontSize, Chart.defaults.global.defaultFontSize); + var tickFontSize = helpers.valueOrDefault(ticks.fontSize, defaults.global.defaultFontSize); return (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation); }, getLabelCapacity: function(exampleTime) { From a0fc1c9019503e873405fe7f8e81b1e1092feb79 Mon Sep 17 00:00:00 2001 From: Marceau Dewilde Date: Wed, 19 Jul 2017 00:26:02 +0200 Subject: [PATCH 060/112] Add link to Java integration (#4527) --- docs/notes/extensions.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/notes/extensions.md b/docs/notes/extensions.md index 43991102e..311058857 100644 --- a/docs/notes/extensions.md +++ b/docs/notes/extensions.md @@ -45,3 +45,6 @@ In addition, many plugins can be found on the [npm registry](https://www.npmjs.c ### Vue.js - vue-chartjs + +### Java + - Chart.java From 3bb31ca592cae7d93678444d308ac8ee3668e171 Mon Sep 17 00:00:00 2001 From: andig Date: Wed, 19 Jul 2017 12:41:17 +0200 Subject: [PATCH 061/112] Allow category labels definition at scale level (#4506) --- docs/axes/cartesian/category.md | 35 +++++++++++++++++++++++++++++- src/scales/scale.category.js | 2 +- test/specs/scale.category.tests.js | 24 +++++++++++++++++++- 3 files changed, 58 insertions(+), 3 deletions(-) diff --git a/docs/axes/cartesian/category.md b/docs/axes/cartesian/category.md index 41bfd74d7..38c306712 100644 --- a/docs/axes/cartesian/category.md +++ b/docs/axes/cartesian/category.md @@ -1,6 +1,38 @@ # Category Cartesian Axis -The category scale will be familiar to those who have used v1.0. Labels are drawn from one of the label arrays included in the chart data. If only `data.labels` is defined, this will be used. If `data.xLabels` is defined and the axis is horizontal, this will be used. Similarly, if `data.yLabels` is defined and the axis is vertical, this property will be used. Using both `xLabels` and `yLabels` together can create a chart that uses strings for both the X and Y axes. +If global configuration is used, labels are drawn from one of the label arrays included in the chart data. If only `data.labels` is defined, this will be used. If `data.xLabels` is defined and the axis is horizontal, this will be used. Similarly, if `data.yLabels` is defined and the axis is vertical, this property will be used. Using both `xLabels` and `yLabels` together can create a chart that uses strings for both the X and Y axes. + +Specifying any of the settings above defines the x axis as `type: category` if not defined otherwise. For more fine-grained control of category labels it is also possible to add `labels` as part of the category axis definition. Doing so does not apply the global defaults. + +## Category Axis Definition + +Globally: + +```javascript +let chart = new Chart(ctx, { + type: ... + data: { + labels: ['January', 'February', 'March', 'April', 'May', 'June'], + datasets: ... + }, +}); +``` +As part of axis definition: + +```javascript +let chart = new Chart(ctx, { + type: ... + data: ... + options: { + scales: { + xAxes: [{ + type: 'category', + labels: ['January', 'February', 'March', 'April', 'May', 'June'], + }] + } + } +}); +``` ## Tick Configuration Options @@ -8,6 +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. | `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/src/scales/scale.category.js b/src/scales/scale.category.js index 6b1532c17..d5c21e788 100644 --- a/src/scales/scale.category.js +++ b/src/scales/scale.category.js @@ -15,7 +15,7 @@ module.exports = function(Chart) { */ getLabels: function() { var data = this.chart.data; - return (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels; + return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels; }, determineDataLimits: function() { diff --git a/test/specs/scale.category.tests.js b/test/specs/scale.category.tests.js index d63fc59bf..31c0a4e6f 100644 --- a/test/specs/scale.category.tests.js +++ b/test/specs/scale.category.tests.js @@ -108,7 +108,7 @@ describe('Category scale tests', function() { expect(scale.ticks).toEqual(mockData.xLabels); }); - it('Should generate ticks from the data xLabels', function() { + it('Should generate ticks from the data yLabels', function() { var scaleID = 'myScale'; var mockData = { @@ -136,6 +136,28 @@ describe('Category scale tests', function() { expect(scale.ticks).toEqual(mockData.yLabels); }); + it('Should generate ticks from the axis labels', function() { + var labels = ['tick1', 'tick2', 'tick3', 'tick4', 'tick5']; + var chart = window.acquireChart({ + type: 'line', + data: { + data: [10, 5, 0, 25, 78] + }, + options: { + scales: { + xAxes: [{ + id: 'x', + type: 'category', + labels: labels + }] + } + } + }); + + var scale = chart.scales.x; + expect(scale.ticks).toEqual(labels); + }); + it ('should get the correct label for the index', function() { var scaleID = 'myScale'; From 5ad1b4ade7d78eced9c98c4beb678861a41039b7 Mon Sep 17 00:00:00 2001 From: Simon Brunel Date: Wed, 19 Jul 2017 21:04:15 +0200 Subject: [PATCH 062/112] New time scale `ticks.mode/.source` options (#4507) `ticks.source` (`'auto'`(default)|`'labels'`): `auto` generates "optimal" ticks based on min, max and a few more options (current `time` implementation`). `labels` generates ticks from the user given `data.labels` values (two additional trailing and leading ticks can be added if min and max are provided). `ticks.mode` (`'linear'`(default)|`series`): `series` displays ticks at the same distance from each other, whatever the time value they represent, while `linear` displays them linearly in time: the distance between each tick represent the amount of time between their time values. --- src/helpers/helpers.time.js | 4 +- src/scales/scale.time.js | 476 +++++++++++++++++++++------------ test/specs/scale.time.tests.js | 253 +++++++++++++++++- 3 files changed, 559 insertions(+), 174 deletions(-) diff --git a/src/helpers/helpers.time.js b/src/helpers/helpers.time.js index f01dfaecd..b667df54c 100644 --- a/src/helpers/helpers.time.js +++ b/src/helpers/helpers.time.js @@ -3,6 +3,8 @@ var moment = require('moment'); moment = typeof(moment) === 'function' ? moment : window.moment; +var helpers = require('./helpers.core'); + var interval = { millisecond: { size: 1, @@ -53,7 +55,7 @@ function generateTicksNiceRange(options, dataRange, niceRange) { var ticks = []; if (options.maxTicks) { var stepSize = options.stepSize; - var startTick = options.min !== undefined ? options.min : niceRange.min; + var startTick = helpers.isNullOrUndef(options.min)? niceRange.min : options.min; var majorUnit = options.majorUnit; var majorUnitStart = majorUnit ? moment(startTick).add(1, majorUnit).startOf(majorUnit) : startTick; var startRange = majorUnitStart.valueOf() - startTick; diff --git a/src/scales/scale.time.js b/src/scales/scale.time.js index 41d022a79..93e49887b 100644 --- a/src/scales/scale.time.js +++ b/src/scales/scale.time.js @@ -7,10 +7,132 @@ moment = typeof(moment) === 'function' ? moment : window.moment; var defaults = require('../core/core.defaults'); var helpers = require('../helpers/index'); +function sorter(a, b) { + return a - b; +} + +/** + * Returns an array of {time, pos} objects used to interpolate a specific `time` or position + * (`pos`) on the scale, by searching entries before and after the requested value. `pos` is + * a decimal between 0 and 1: 0 being the start of the scale (left or top) and 1 the other + * extremity (left + width or top + height). Note that it would be more optimized to directly + * store pre-computed pixels, but the scale dimensions are not guaranteed at the time we need + * to create the lookup table. The table ALWAYS contains at least two items: min and max. + * + * @param {Number[]} timestamps - timestamps sorted from lowest to highest. + * @param {Boolean} linear - If true, timestamps will be spread linearly along the min/max + * range, so basically, the table will contains only two items: {min, 0} and {max, 1}. If + * false, timestamps will be positioned at the same distance from each other. In this case, + * only timestamps that break the time linearity are registered, meaning that in the best + * case, all timestamps are linear, the table contains only min and max. + */ +function buildLookupTable(timestamps, min, max, linear) { + if (linear || !timestamps.length) { + return [ + {time: min, pos: 0}, + {time: max, pos: 1} + ]; + } + + var table = []; + var items = timestamps.slice(0); + var i, ilen, prev, curr, next; + + if (min < timestamps[0]) { + items.unshift(min); + } + if (max > timestamps[timestamps.length - 1]) { + items.push(max); + } + + for (i = 0, ilen = items.length; i= 0 && lo <= hi) { + mid = (lo + hi) >> 1; + i0 = table[mid - 1] || null; + i1 = table[mid]; + + if (!i0) { + // given value is outside table (before first item) + return {lo: null, hi: i1}; + } else if (i1[key] < value) { + lo = mid + 1; + } else if (i0[key] > value) { + hi = mid - 1; + } else { + return {lo: i0, hi: i1}; + } + } + + // given value is outside table (after last item) + return {lo: i1, hi: null}; +} + +/** + * Linearly interpolates the given source `value` using the table items `skey` values and + * returns the associated `tkey` value. For example, interpolate(table, 'time', 42, 'pos') + * returns the position for a timestamp equal to 42. If value is out of bounds, values at + * index [0, 1] or [n - 1, n] are used for the interpolation. + */ +function interpolate(table, skey, sval, tkey) { + var range = lookup(table, skey, sval); + + // Note: the lookup table ALWAYS contains at least 2 items (min and max) + var prev = !range.lo ? table[0] : !range.hi ? table[table.length - 2] : range.lo; + var next = !range.lo ? table[1] : !range.hi ? table[table.length - 1] : range.hi; + + var span = next[skey] - prev[skey]; + var ratio = span ? (sval - prev[skey]) / span : 0; + var offset = (next[tkey] - prev[tkey]) * ratio; + + return prev[tkey] + offset; +} + +function parse(input, scale) { + if (helpers.isNullOrUndef(input)) { + return null; + } + + var round = scale.options.time.round; + var value = scale.getRightValue(input); + var time = value.isValid ? value : helpers.time.parseTime(scale, value); + if (!time || !time.isValid()) { + return null; + } + + if (round) { + time.startOf(round); + } + + return time.valueOf(); +} + module.exports = function(Chart) { var timeHelpers = helpers.time; + // Integer constants are from the ES6 spec. + var MIN_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991; + var MAX_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; + var defaultConfig = { position: 'bottom', @@ -37,7 +159,9 @@ module.exports = function(Chart) { }, }, ticks: { - autoSkip: false + autoSkip: false, + mode: 'linear', // 'linear|series' + source: 'auto' // 'auto|labels' } }; @@ -51,246 +175,254 @@ module.exports = function(Chart) { Chart.Scale.prototype.initialize.call(this); }, + determineDataLimits: function() { var me = this; - var timeOpts = me.options.time; + var chart = me.chart; + var options = me.options; + var datasets = chart.data.datasets || []; + var min = MAX_INTEGER; + var max = MIN_INTEGER; + var timestamps = []; + var labels = []; + var i, j, ilen, jlen, data, timestamp; - // We store the data range as unix millisecond timestamps so dataMin and dataMax will always be integers. - // Integer constants are from the ES6 spec. - var dataMin = Number.MAX_SAFE_INTEGER || 9007199254740991; - var dataMax = Number.MIN_SAFE_INTEGER || -9007199254740991; + // Convert labels to timestamps + for (i = 0, ilen = chart.data.labels.length; i < ilen; ++i) { + timestamp = parse(chart.data.labels[i], me); + min = Math.min(min, timestamp); + max = Math.max(max, timestamp); + labels.push(timestamp); + } - var chartData = me.chart.data; - var parsedData = { - labels: [], - datasets: [] - }; + // Convert data to timestamps + for (i = 0, ilen = datasets.length; i < ilen; ++i) { + if (chart.isDatasetVisible(i)) { + data = datasets[i].data; - var timestamp; + // Let's consider that all data have the same format. + if (helpers.isObject(data[0])) { + timestamps[i] = []; - helpers.each(chartData.labels, function(label, labelIndex) { - var labelMoment = timeHelpers.parseTime(me, label); - - if (labelMoment.isValid()) { - // We need to round the time - if (timeOpts.round) { - labelMoment.startOf(timeOpts.round); - } - - timestamp = labelMoment.valueOf(); - dataMin = Math.min(timestamp, dataMin); - dataMax = Math.max(timestamp, dataMax); - - // Store this value for later - parsedData.labels[labelIndex] = timestamp; - } - }); - - helpers.each(chartData.datasets, function(dataset, datasetIndex) { - var timestamps = []; - - if (typeof dataset.data[0] === 'object' && dataset.data[0] !== null && me.chart.isDatasetVisible(datasetIndex)) { - // We have potential point data, so we need to parse this - helpers.each(dataset.data, function(value, dataIndex) { - var dataMoment = timeHelpers.parseTime(me, me.getRightValue(value)); - - if (dataMoment.isValid()) { - if (timeOpts.round) { - dataMoment.startOf(timeOpts.round); - } - - timestamp = dataMoment.valueOf(); - dataMin = Math.min(timestamp, dataMin); - dataMax = Math.max(timestamp, dataMax); - timestamps[dataIndex] = timestamp; + for (j = 0, jlen = data.length; j < jlen; ++j) { + timestamp = parse(data[j], me); + min = Math.min(min, timestamp); + max = Math.max(max, timestamp); + timestamps[i][j] = timestamp; } - }); + } else { + timestamps[i] = labels.slice(0); + } } else { - // We have no x coordinates, so use the ones from the labels - timestamps = parsedData.labels.slice(); + timestamps[i] = []; } + } - parsedData.datasets[datasetIndex] = timestamps; - }); + // Enforce limits with user min/max options + min = parse(options.time.min, me) || min; + max = parse(options.time.max, me) || max; - me.dataMin = dataMin; - me.dataMax = dataMax; - me._parsedData = parsedData; + // 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; + + me._model = { + datasets: timestamps, + horizontal: me.isHorizontal(), + labels: labels.sort(sorter), // Sort labels **after** data have been converted + min: Math.min(min, max), // Make sure that max is **strictly** higher ... + max: Math.max(min + 1, max), // ... than min (required by the lookup table) + offset: null, + size: null, + table: [] + }; }, + buildTicks: function() { var me = this; + var model = me._model; + var min = model.min; + var max = model.max; var timeOpts = me.options.time; - - var minTimestamp; - var maxTimestamp; - var dataMin = me.dataMin; - var dataMax = me.dataMax; - - if (timeOpts.min) { - var minMoment = timeHelpers.parseTime(me, timeOpts.min); - if (timeOpts.round) { - minMoment.startOf(timeOpts.round); - } - minTimestamp = minMoment.valueOf(); - } - - if (timeOpts.max) { - maxTimestamp = timeHelpers.parseTime(me, timeOpts.max).valueOf(); - } - - var maxTicks = me.getLabelCapacity(minTimestamp || dataMin); - - var unit = timeOpts.unit || timeHelpers.determineUnit(timeOpts.minUnit, minTimestamp || dataMin, maxTimestamp || dataMax, maxTicks); + var ticksOpts = me.options.ticks; + var formats = timeOpts.displayFormats; + var capacity = me.getLabelCapacity(min); + var unit = timeOpts.unit || timeHelpers.determineUnit(timeOpts.minUnit, min, max, capacity); var majorUnit = timeHelpers.determineMajorUnit(unit); + var ticks = []; + var i, ilen, timestamp, stepSize; - me.displayFormat = timeOpts.displayFormats[unit]; - me.majorDisplayFormat = timeOpts.displayFormats[majorUnit]; + if (ticksOpts.source === 'labels') { + for (i = 0, ilen = model.labels.length; i < ilen; ++i) { + timestamp = model.labels[i]; + if (timestamp >= min && timestamp <= max) { + ticks.push(timestamp); + } + } + } else { + stepSize = helpers.valueOrDefault(timeOpts.stepSize, timeOpts.unitStepSize) + || timeHelpers.determineStepSize(min, max, unit, capacity); + + ticks = timeHelpers.generateTicks({ + maxTicks: capacity, + min: parse(timeOpts.min, me), + max: parse(timeOpts.max, me), + stepSize: stepSize, + majorUnit: majorUnit, + unit: unit, + timeOpts: timeOpts + }, { + min: min, + max: max + }); + + // Recompute min/max, the ticks generation might have changed them (BUG?) + min = ticks.length ? ticks[0] : min; + max = ticks.length ? ticks[ticks.length - 1] : max; + } + + me.ticks = ticks; + me.min = min; + me.max = max; me.unit = unit; me.majorUnit = majorUnit; + me.displayFormat = formats[unit]; + me.majorDisplayFormat = formats[majorUnit]; - var optionStepSize = helpers.valueOrDefault(timeOpts.stepSize, timeOpts.unitStepSize); - var stepSize = optionStepSize || timeHelpers.determineStepSize(minTimestamp || dataMin, maxTimestamp || dataMax, unit, maxTicks); - me.ticks = timeHelpers.generateTicks({ - maxTicks: maxTicks, - min: minTimestamp, - max: maxTimestamp, - stepSize: stepSize, - majorUnit: majorUnit, - unit: unit, - timeOpts: timeOpts - }, { - min: dataMin, - max: dataMax - }); - - // At this point, we need to update our max and min given the tick values since we have expanded the - // range of the scale - me.max = helpers.max(me.ticks); - me.min = helpers.min(me.ticks); + model.table = buildLookupTable(ticks, min, max, ticksOpts.mode === 'linear'); }, - // Get tooltip label + getLabelForIndex: function(index, datasetIndex) { var me = this; - var label = me.chart.data.labels && index < me.chart.data.labels.length ? me.chart.data.labels[index] : ''; - var value = me.chart.data.datasets[datasetIndex].data[index]; + var data = me.chart.data; + var timeOpts = me.options.time; + var label = data.labels && index < data.labels.length ? data.labels[index] : ''; + var value = data.datasets[datasetIndex].data[index]; - if (value !== null && typeof value === 'object') { + if (helpers.isObject(value)) { label = me.getRightValue(value); } - - // Format nicely - if (me.options.time.tooltipFormat) { - label = timeHelpers.parseTime(me, label).format(me.options.time.tooltipFormat); + if (timeOpts.tooltipFormat) { + label = timeHelpers.parseTime(me, label).format(timeOpts.tooltipFormat); } return label; }, - // Function to format an individual tick mark + + /** + * Function to format an individual tick mark + * @private + */ tickFormatFunction: function(tick, index, ticks) { - var formattedTick; - var tickClone = tick.clone(); - var tickTimestamp = tick.valueOf(); - var major = false; - var tickOpts; - if (this.majorUnit && this.majorDisplayFormat && tickTimestamp === tickClone.startOf(this.majorUnit).valueOf()) { - // format as major unit - formattedTick = tick.format(this.majorDisplayFormat); - tickOpts = this.options.ticks.major; - major = true; - } else { - // format as minor (base) unit - formattedTick = tick.format(this.displayFormat); - tickOpts = this.options.ticks.minor; + var me = this; + var options = me.options; + var time = tick.valueOf(); + var majorUnit = me.majorUnit; + var majorFormat = me.majorDisplayFormat; + var majorTime = tick.clone().startOf(me.majorUnit).valueOf(); + var major = majorUnit && majorFormat && time === majorTime; + var formattedTick = tick.format(major? majorFormat : me.displayFormat); + var tickOpts = major? options.ticks.major : options.ticks.minor; + var formatter = helpers.valueOrDefault(tickOpts.callback, tickOpts.userCallback); + + if (formatter) { + formattedTick = formatter(formattedTick, index, ticks); } - var callback = helpers.valueOrDefault(tickOpts.callback, tickOpts.userCallback); - - if (callback) { - return { - value: callback(formattedTick, index, ticks), - major: major - }; - } return { value: formattedTick, - major: major + major: major, + time: time, }; }, + convertTicksToLabels: function() { - var me = this; - me.ticksAsTimestamps = me.ticks; - me.ticks = me.ticks.map(function(tick) { - return moment(tick); - }).map(me.tickFormatFunction, me); - }, - getPixelForOffset: function(offset) { - var me = this; - var epochWidth = me.max - me.min; - var decimal = epochWidth ? (offset - me.min) / epochWidth : 0; + var ticks = this.ticks; + var i, ilen; - if (me.isHorizontal()) { - var valueOffset = (me.width * decimal); - return me.left + Math.round(valueOffset); + for (i = 0, ilen = ticks.length; i < ilen; ++i) { + ticks[i] = this.tickFormatFunction(moment(ticks[i])); } - - var heightOffset = (me.height * decimal); - return me.top + Math.round(heightOffset); }, + + /** + * @private + */ + getPixelForOffset: function(time) { + var me = this; + var model = me._model; + var size = model.horizontal ? me.width : me.height; + var start = model.horizontal ? me.left : me.top; + var pos = interpolate(model.table, 'time', time, 'pos'); + + return start + size * pos; + }, + getPixelForValue: function(value, index, datasetIndex) { var me = this; - var offset = null; + var time = null; + if (index !== undefined && datasetIndex !== undefined) { - offset = me._parsedData.datasets[datasetIndex][index]; + time = me._model.datasets[datasetIndex][index]; } - if (offset === null) { - if (!value || !value.isValid) { - // not already a moment object - value = timeHelpers.parseTime(me, me.getRightValue(value)); - } - - if (value && value.isValid && value.isValid()) { - offset = value.valueOf(); - } + if (time === null) { + time = parse(value, me); } - if (offset !== null) { - return me.getPixelForOffset(offset); + if (time !== null) { + return me.getPixelForOffset(time); } }, + getPixelForTick: function(index) { - return this.getPixelForOffset(this.ticksAsTimestamps[index]); + return index >= 0 && index < this.ticks.length ? + this.getPixelForOffset(this.ticks[index].time) : + null; }, + getValueForPixel: function(pixel) { var me = this; - var innerDimension = me.isHorizontal() ? me.width : me.height; - var offset = (pixel - (me.isHorizontal() ? me.left : me.top)) / innerDimension; - return moment(me.min + (offset * (me.max - me.min))); + var model = me._model; + var size = model.horizontal ? me.width : me.height; + var start = model.horizontal ? me.left : me.top; + var pos = size ? (pixel - start) / size : 0; + var time = interpolate(model.table, 'pos', pos, 'time'); + + return moment(time); }, - // Crude approximation of what the label width might be + + /** + * Crude approximation of what the label width might be + * @private + */ getLabelWidth: function(label) { var me = this; - var ticks = me.options.ticks; - + var ticksOpts = me.options.ticks; var tickLabelWidth = me.ctx.measureText(label).width; - var cosRotation = Math.cos(helpers.toRadians(ticks.maxRotation)); - var sinRotation = Math.sin(helpers.toRadians(ticks.maxRotation)); - var tickFontSize = helpers.valueOrDefault(ticks.fontSize, defaults.global.defaultFontSize); + var angle = helpers.toRadians(ticksOpts.maxRotation); + var cosRotation = Math.cos(angle); + var sinRotation = Math.sin(angle); + var tickFontSize = helpers.valueOrDefault(ticksOpts.fontSize, defaults.global.defaultFontSize); + return (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation); }, + + /** + * @private + */ getLabelCapacity: function(exampleTime) { var me = this; me.displayFormat = me.options.time.displayFormats.millisecond; // Pick the longest format for guestimation + var exampleLabel = me.tickFormatFunction(moment(exampleTime), 0, []).value; var tickLabelWidth = me.getLabelWidth(exampleLabel); - var innerWidth = me.isHorizontal() ? me.width : me.height; - var labelCapacity = innerWidth / tickLabelWidth; - return labelCapacity; + return innerWidth / tickLabelWidth; } }); - Chart.scaleService.registerScaleType('time', TimeScale, defaultConfig); + Chart.scaleService.registerScaleType('time', TimeScale, defaultConfig); }; diff --git a/test/specs/scale.time.tests.js b/test/specs/scale.time.tests.js index 2b8c50ed9..ff02bc8df 100755 --- a/test/specs/scale.time.tests.js +++ b/test/specs/scale.time.tests.js @@ -23,6 +23,12 @@ describe('Time scale tests', function() { }); } + function fetchTickPositions(scale) { + return scale.ticks.map(function(tick, index) { + return scale.getPixelForTick(index); + }); + } + beforeEach(function() { // Need a time matcher for getValueFromPixel jasmine.addMatchers({ @@ -83,6 +89,8 @@ describe('Time scale tests', function() { minRotation: 0, maxRotation: 50, mirror: false, + mode: 'linear', + source: 'auto', padding: 0, reverse: false, display: true, @@ -417,7 +425,8 @@ describe('Time scale tests', function() { var xScale = chart.scales.xScale0; xScale.update(800, 200); - var step = xScale.ticksAsTimestamps[1] - xScale.ticksAsTimestamps[0]; + + var step = xScale.ticks[1].time - xScale.ticks[0].time; var stepsAmount = Math.floor((xScale.max - xScale.min) / step); it('should be bounded by nearest step year starts', function() { @@ -534,4 +543,246 @@ describe('Time scale tests', function() { expect(chart.scales['y-axis-0'].maxWidth).toEqual(0); expect(chart.width).toEqual(0); }); + + describe('when ticks.source', function() { + describe('is "labels"', function() { + beforeEach(function() { + this.chart = window.acquireChart({ + type: 'line', + data: { + labels: ['2017', '2019', '2020', '2025', '2042'], + datasets: [{data: [0, 1, 2, 3, 4, 5]}] + }, + options: { + scales: { + xAxes: [{ + id: 'x', + type: 'time', + time: { + parser: 'YYYY' + }, + ticks: { + source: 'labels' + } + }] + } + } + }); + }); + + it ('should generate ticks from "data.labels"', function() { + var scale = this.chart.scales.x; + + expect(scale.min).toEqual(+moment('2017', 'YYYY')); + expect(scale.max).toEqual(+moment('2042', 'YYYY')); + expect(getTicksValues(scale.ticks)).toEqual([ + '2017', '2019', '2020', '2025', '2042']); + }); + it ('should not add ticks for min and max if they extend the labels range', function() { + var chart = this.chart; + var scale = chart.scales.x; + var options = chart.options.scales.xAxes[0]; + + options.time.min = '2012'; + options.time.max = '2051'; + chart.update(); + + expect(scale.min).toEqual(+moment('2012', 'YYYY')); + expect(scale.max).toEqual(+moment('2051', 'YYYY')); + expect(getTicksValues(this.chart.scales.x.ticks)).toEqual([ + '2017', '2019', '2020', '2025', '2042']); + }); + it ('should remove ticks that are not inside the min and max time range', function() { + var chart = this.chart; + var scale = chart.scales.x; + var options = chart.options.scales.xAxes[0]; + + options.time.min = '2022'; + options.time.max = '2032'; + chart.update(); + + expect(scale.min).toEqual(+moment('2022', 'YYYY')); + expect(scale.max).toEqual(+moment('2032', 'YYYY')); + expect(getTicksValues(this.chart.scales.x.ticks)).toEqual([ + '2025']); + }); + it ('should not duplicate ticks if min and max are the labels limits', function() { + var chart = this.chart; + var scale = chart.scales.x; + var options = chart.options.scales.xAxes[0]; + + options.time.min = '2017'; + options.time.max = '2042'; + chart.update(); + + expect(scale.min).toEqual(+moment('2017', 'YYYY')); + expect(scale.max).toEqual(+moment('2042', 'YYYY')); + expect(getTicksValues(this.chart.scales.x.ticks)).toEqual([ + '2017', '2019', '2020', '2025', '2042']); + }); + it ('should correctly handle empty `data.labels`', function() { + var chart = this.chart; + var scale = chart.scales.x; + + chart.data.labels = []; + chart.update(); + + expect(scale.min).toEqual(+moment().startOf('day')); + expect(scale.max).toEqual(+moment().endOf('day') + 1); + expect(getTicksValues(this.chart.scales.x.ticks)).toEqual([]); + }); + }); + }); + + describe('when ticks.mode', function() { + describe('is "series"', function() { + beforeEach(function() { + this.chart = window.acquireChart({ + type: 'line', + data: { + labels: ['2017', '2019', '2020', '2025', '2042'], + datasets: [{data: [0, 1, 2, 3, 4, 5]}] + }, + options: { + scales: { + xAxes: [{ + id: 'x', + type: 'time', + time: { + parser: 'YYYY' + }, + ticks: { + mode: 'series', + source: 'labels' + } + }], + yAxes: [{ + display: false + }] + } + } + }); + }); + + it ('should space ticks out with the same gap, whatever their time values', function() { + var scale = this.chart.scales.x; + var start = scale.left; + var slice = scale.width / 4; + var pixels = fetchTickPositions(scale); + + expect(pixels[0]).toBeCloseToPixel(start); + expect(pixels[1]).toBeCloseToPixel(start + slice); + expect(pixels[2]).toBeCloseToPixel(start + slice * 2); + expect(pixels[3]).toBeCloseToPixel(start + slice * 3); + expect(pixels[4]).toBeCloseToPixel(start + slice * 4); + }); + it ('should add a step before if scale.min is before the first tick', function() { + var chart = this.chart; + var scale = chart.scales.x; + var options = chart.options.scales.xAxes[0]; + + options.time.min = '2012'; + chart.update(); + + var start = scale.left; + var slice = scale.width / 5; + var pixels = fetchTickPositions(scale); + + expect(pixels[0]).toBeCloseToPixel(start + slice); + expect(pixels[4]).toBeCloseToPixel(start + slice * 5); + }); + it ('should add a step after if scale.max is after the last tick', function() { + var chart = this.chart; + var scale = chart.scales.x; + var options = chart.options.scales.xAxes[0]; + + options.time.max = '2050'; + chart.update(); + + var start = scale.left; + var slice = scale.width / 5; + var pixels = fetchTickPositions(scale); + + expect(pixels[0]).toBeCloseToPixel(start); + expect(pixels[4]).toBeCloseToPixel(start + slice * 4); + }); + it ('should add steps before and after if scale.min/max are outside the labels range', function() { + var chart = this.chart; + var scale = chart.scales.x; + var options = chart.options.scales.xAxes[0]; + + options.time.min = '2012'; + options.time.max = '2050'; + chart.update(); + + var start = scale.left; + var slice = scale.width / 6; + var pixels = fetchTickPositions(scale); + + expect(pixels[0]).toBeCloseToPixel(start + slice); + expect(pixels[4]).toBeCloseToPixel(start + slice * 5); + }); + }); + describe('is "linear"', function() { + beforeEach(function() { + this.chart = window.acquireChart({ + type: 'line', + data: { + labels: ['2017', '2019', '2020', '2025', '2042'], + datasets: [{data: [0, 1, 2, 3, 4, 5]}] + }, + options: { + scales: { + xAxes: [{ + id: 'x', + type: 'time', + time: { + parser: 'YYYY' + }, + ticks: { + mode: 'linear', + source: 'labels' + } + }], + yAxes: [{ + display: false + }] + } + } + }); + }); + + it ('should space ticks out with a gap relative to their time values', function() { + var scale = this.chart.scales.x; + var start = scale.left; + var slice = scale.width / (2042 - 2017); + var pixels = fetchTickPositions(scale); + + expect(pixels[0]).toBeCloseToPixel(start); + expect(pixels[1]).toBeCloseToPixel(start + slice * (2019 - 2017)); + expect(pixels[2]).toBeCloseToPixel(start + slice * (2020 - 2017)); + expect(pixels[3]).toBeCloseToPixel(start + slice * (2025 - 2017)); + expect(pixels[4]).toBeCloseToPixel(start + slice * (2042 - 2017)); + }); + it ('should take in account scale min and max if outside the ticks range', function() { + var chart = this.chart; + var scale = chart.scales.x; + var options = chart.options.scales.xAxes[0]; + + options.time.min = '2012'; + options.time.max = '2050'; + chart.update(); + + var start = scale.left; + var slice = scale.width / (2050 - 2012); + var pixels = fetchTickPositions(scale); + + expect(pixels[0]).toBeCloseToPixel(start + slice * (2017 - 2012)); + expect(pixels[1]).toBeCloseToPixel(start + slice * (2019 - 2012)); + expect(pixels[2]).toBeCloseToPixel(start + slice * (2020 - 2012)); + expect(pixels[3]).toBeCloseToPixel(start + slice * (2025 - 2012)); + expect(pixels[4]).toBeCloseToPixel(start + slice * (2042 - 2012)); + }); + }); + }); }); From 090196c07c601cdaf5a91ff0ba731cc46945c101 Mon Sep 17 00:00:00 2001 From: Simon Brunel Date: Thu, 20 Jul 2017 19:20:54 +0200 Subject: [PATCH 063/112] Add support for line height CSS values (#4531) The title plugin and scale title now accept lineHeight specified using unitless value (1.4), length ('1.4em' or '12px'), percentage ('200%') or keyword ('normal' === 1.2). The line height parsing has been refactored under the 'Chart.helpers.options' namespace. Also fix incorrect text positioning in the title plugin. https://developer.mozilla.org/en-US/docs/Web/CSS/line-height --- docs/axes/labelling.md | 2 +- docs/configuration/title.md | 2 +- src/core/core.scale.js | 16 +++++++++--- src/helpers/helpers.options.js | 35 ++++++++++++++++++++++++++ src/helpers/index.js | 1 + src/plugins/plugin.title.js | 10 +++++--- test/specs/core.helpers.tests.js | 6 +++-- test/specs/helpers.options.tests.js | 27 ++++++++++++++++++++ test/specs/plugin.title.tests.js | 15 +++++------ test/specs/scale.category.tests.js | 3 ++- test/specs/scale.linear.tests.js | 14 ++++++----- test/specs/scale.logarithmic.tests.js | 3 ++- test/specs/scale.radialLinear.tests.js | 3 ++- test/specs/scale.time.tests.js | 3 ++- 14 files changed, 111 insertions(+), 29 deletions(-) create mode 100644 src/helpers/helpers.options.js create mode 100644 test/specs/helpers.options.tests.js diff --git a/docs/axes/labelling.md b/docs/axes/labelling.md index 5e4a6a76b..22fc26004 100644 --- a/docs/axes/labelling.md +++ b/docs/axes/labelling.md @@ -10,7 +10,7 @@ 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` | `` | Height of an individual line of text. If not defined, the font size is used. +| `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. diff --git a/docs/configuration/title.md b/docs/configuration/title.md index 3952f2ca6..b2c04cd8f 100644 --- a/docs/configuration/title.md +++ b/docs/configuration/title.md @@ -14,7 +14,7 @@ The title configuration is passed into the `options.title` namespace. The global | `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` | `undefined` | Height of line of text. If not specified, the `fontSize` is used. +| `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 diff --git a/src/core/core.scale.js b/src/core/core.scale.js index 9626c3f0e..942e4fff6 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -28,11 +28,13 @@ defaults._set('scale', { // scale label scaleLabel: { + // display property + display: false, + // actual label labelString: '', - // display property - display: false, + lineHeight: 1.2 }, // label settings @@ -77,6 +79,12 @@ module.exports = function(Chart) { }; } + function parseLineHeight(options) { + return helpers.options.toLineHeight( + helpers.valueOrDefault(options.lineHeight, 1.2), + helpers.valueOrDefault(options.fontSize, defaults.global.defaultFontSize)); + } + Chart.Scale = Chart.Element.extend({ /** * Get the padding needed for the scale @@ -310,8 +318,8 @@ module.exports = function(Chart) { var isHorizontal = me.isHorizontal(); var tickFont = parseFontOptions(tickOpts); - var scaleLabelLineHeight = helpers.valueOrDefault(scaleLabelOpts.lineHeight, parseFontOptions(scaleLabelOpts).size * 1.5); var tickMarkLength = opts.gridLines.tickMarkLength; + var scaleLabelLineHeight = parseLineHeight(scaleLabelOpts); // Width if (isHorizontal) { @@ -738,7 +746,7 @@ module.exports = function(Chart) { var scaleLabelX; var scaleLabelY; var rotation = 0; - var halfLineHeight = helpers.valueOrDefault(scaleLabel.lineHeight, scaleLabelFont.size) / 2; + var halfLineHeight = parseLineHeight(scaleLabel) / 2; if (isHorizontal) { scaleLabelX = me.left + ((me.right - me.left) / 2); // midpoint of the width diff --git a/src/helpers/helpers.options.js b/src/helpers/helpers.options.js new file mode 100644 index 000000000..1aab9bb96 --- /dev/null +++ b/src/helpers/helpers.options.js @@ -0,0 +1,35 @@ +'use strict'; + +/** + * @namespace Chart.helpers.options + */ +module.exports = { + /** + * Converts the given line height `value` in pixels for a specific font `size`. + * @param {Number|String} value - The lineHeight to parse (eg. 1.6, '14px', '75%', '1.6em'). + * @param {Number} size - The font size (in pixels) used to resolve relative `value`. + * @returns {Number} The effective line height in pixels (size * 1.2 if value is invalid). + * @see https://developer.mozilla.org/en-US/docs/Web/CSS/line-height + * @since 2.7.0 + */ + toLineHeight: function(value, size) { + var matches = (''+value).match(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/); + if (!matches || matches[1] === 'normal') { + return size * 1.2; + } + + value = parseFloat(matches[2]); + + switch (matches[3]) { + case 'px': + return value; + case '%': + value /= 100; + break; + default: + break; + } + + return size * value; + } +}; diff --git a/src/helpers/index.js b/src/helpers/index.js index a21c99aca..632b77207 100644 --- a/src/helpers/index.js +++ b/src/helpers/index.js @@ -3,4 +3,5 @@ module.exports = require('./helpers.core'); module.exports.easing = require('./helpers.easing'); module.exports.canvas = require('./helpers.canvas'); +module.exports.options = require('./helpers.options'); module.exports.time = require('./helpers.time'); diff --git a/src/plugins/plugin.title.js b/src/plugins/plugin.title.js index 30f8813d9..259863b62 100644 --- a/src/plugins/plugin.title.js +++ b/src/plugins/plugin.title.js @@ -8,6 +8,7 @@ defaults._set('global', { display: false, fontStyle: 'bold', fullWidth: true, + lineHeight: 1.2, padding: 10, position: 'top', text: '', @@ -114,7 +115,7 @@ module.exports = function(Chart) { fontSize = valueOrDefault(opts.fontSize, defaults.global.defaultFontSize), minSize = me.minSize, lineCount = helpers.isArray(opts.text) ? opts.text.length : 1, - lineHeight = valueOrDefault(opts.lineHeight, fontSize), + lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize), textSize = display ? (lineCount * lineHeight) + (opts.padding * 2) : 0; if (me.isHorizontal()) { @@ -150,7 +151,8 @@ module.exports = function(Chart) { fontStyle = valueOrDefault(opts.fontStyle, globalDefaults.defaultFontStyle), fontFamily = valueOrDefault(opts.fontFamily, globalDefaults.defaultFontFamily), titleFont = helpers.fontString(fontSize, fontStyle, fontFamily), - lineHeight = valueOrDefault(opts.lineHeight, fontSize), + lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize), + offset = lineHeight/2 + opts.padding, rotation = 0, titleX, titleY, @@ -166,10 +168,10 @@ module.exports = function(Chart) { // Horizontal if (me.isHorizontal()) { titleX = left + ((right - left) / 2); // midpoint of the width - titleY = top + ((bottom - top) / 2); // midpoint of the height + titleY = top + offset; maxWidth = right - left; } else { - titleX = opts.position === 'left' ? left + (fontSize / 2) : right - (fontSize / 2); + 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); diff --git a/test/specs/core.helpers.tests.js b/test/specs/core.helpers.tests.js index d0182cc6c..e6f626439 100644 --- a/test/specs/core.helpers.tests.js +++ b/test/specs/core.helpers.tests.js @@ -128,8 +128,9 @@ describe('Core helper tests', function() { }, position: 'right', scaleLabel: { - labelString: '', display: false, + labelString: '', + lineHeight: 1.2 }, ticks: { beginAtZero: false, @@ -168,8 +169,9 @@ describe('Core helper tests', function() { }, position: 'left', scaleLabel: { - labelString: '', display: false, + labelString: '', + lineHeight: 1.2 }, ticks: { beginAtZero: false, diff --git a/test/specs/helpers.options.tests.js b/test/specs/helpers.options.tests.js new file mode 100644 index 000000000..1b01159dc --- /dev/null +++ b/test/specs/helpers.options.tests.js @@ -0,0 +1,27 @@ +'use strict'; + +describe('Chart.helpers.options', function() { + var options = Chart.helpers.options; + + describe('toLineHeight', function() { + it ('should support keyword values', function() { + expect(options.toLineHeight('normal', 16)).toBe(16 * 1.2); + }); + it ('should support unitless values', function() { + expect(options.toLineHeight(1.4, 16)).toBe(16 * 1.4); + expect(options.toLineHeight('1.4', 16)).toBe(16 * 1.4); + }); + it ('should support length values', function() { + expect(options.toLineHeight('42px', 16)).toBe(42); + expect(options.toLineHeight('1.4em', 16)).toBe(16 * 1.4); + }); + it ('should support percentage values', function() { + expect(options.toLineHeight('140%', 16)).toBe(16 * 1.4); + }); + it ('should fallback to default (1.2) for invalid values', function() { + expect(options.toLineHeight(null, 16)).toBe(16 * 1.2); + expect(options.toLineHeight(undefined, 16)).toBe(16 * 1.2); + expect(options.toLineHeight('foobar', 16)).toBe(16 * 1.2); + }); + }); +}); diff --git a/test/specs/plugin.title.tests.js b/test/specs/plugin.title.tests.js index 3c73b55d4..edeb32a8b 100644 --- a/test/specs/plugin.title.tests.js +++ b/test/specs/plugin.title.tests.js @@ -13,6 +13,7 @@ describe('Title block tests', function() { fullWidth: true, weight: 2000, fontStyle: 'bold', + lineHeight: 1.2, padding: 10, text: '' }); @@ -43,7 +44,7 @@ describe('Title block tests', function() { expect(minSize).toEqual({ width: 400, - height: 32 + height: 34.4 }); }); @@ -72,7 +73,7 @@ describe('Title block tests', function() { minSize = title.update(200, 400); expect(minSize).toEqual({ - width: 32, + width: 34.4, height: 400 }); }); @@ -84,7 +85,7 @@ describe('Title block tests', function() { options.text = ['line1', 'line2']; options.position = 'left'; options.display = true; - options.lineHeight = 15; + options.lineHeight = 1.5; var title = new Chart.Title({ chart: chart, @@ -94,7 +95,7 @@ describe('Title block tests', function() { var minSize = title.update(200, 400); expect(minSize).toEqual({ - width: 50, + width: 56, height: 400 }); }); @@ -135,7 +136,7 @@ describe('Title block tests', function() { args: [] }, { name: 'translate', - args: [300, 66] + args: [300, 67.2] }, { name: 'rotate', args: [0] @@ -185,7 +186,7 @@ describe('Title block tests', function() { args: [] }, { name: 'translate', - args: [106, 250] + args: [117.2, 250] }, { name: 'rotate', args: [-0.5 * Math.PI] @@ -218,7 +219,7 @@ describe('Title block tests', function() { args: [] }, { name: 'translate', - args: [126, 250] + args: [117.2, 250] }, { name: 'rotate', args: [0.5 * Math.PI] diff --git a/test/specs/scale.category.tests.js b/test/specs/scale.category.tests.js index 31c0a4e6f..c11f75196 100644 --- a/test/specs/scale.category.tests.js +++ b/test/specs/scale.category.tests.js @@ -30,8 +30,9 @@ describe('Category scale tests', function() { }, position: 'bottom', scaleLabel: { + display: false, labelString: '', - display: false + lineHeight: 1.2 }, ticks: { beginAtZero: false, diff --git a/test/specs/scale.linear.tests.js b/test/specs/scale.linear.tests.js index 5b37a0c11..de653268d 100644 --- a/test/specs/scale.linear.tests.js +++ b/test/specs/scale.linear.tests.js @@ -28,8 +28,9 @@ describe('Linear Scale', function() { }, position: 'left', scaleLabel: { - labelString: '', display: false, + labelString: '', + lineHeight: 1.2 }, ticks: { beginAtZero: false, @@ -772,15 +773,15 @@ describe('Linear Scale', function() { expect(xScale.paddingBottom).toBeCloseToPixel(0); expect(xScale.paddingLeft).toBeCloseToPixel(0); expect(xScale.paddingRight).toBeCloseToPixel(0); - expect(xScale.width).toBeCloseToPixel(450); - expect(xScale.height).toBeCloseToPixel(46); + expect(xScale.width).toBeCloseToPixel(454); + expect(xScale.height).toBeCloseToPixel(42); expect(yScale.paddingTop).toBeCloseToPixel(0); expect(yScale.paddingBottom).toBeCloseToPixel(0); expect(yScale.paddingLeft).toBeCloseToPixel(0); expect(yScale.paddingRight).toBeCloseToPixel(0); - expect(yScale.width).toBeCloseToPixel(48); - expect(yScale.height).toBeCloseToPixel(434); + expect(yScale.width).toBeCloseToPixel(44); + expect(yScale.height).toBeCloseToPixel(438); }); it('should fit correctly when display is turned off', function() { @@ -820,7 +821,8 @@ describe('Linear Scale', function() { drawBorder: false }, scaleLabel: { - display: false + display: false, + lineHeight: 1.2 }, ticks: { display: false, diff --git a/test/specs/scale.logarithmic.tests.js b/test/specs/scale.logarithmic.tests.js index 95287aa79..16ab3d780 100644 --- a/test/specs/scale.logarithmic.tests.js +++ b/test/specs/scale.logarithmic.tests.js @@ -27,8 +27,9 @@ describe('Logarithmic Scale tests', function() { }, position: 'left', scaleLabel: { - labelString: '', display: false, + labelString: '', + lineHeight: 1.2 }, ticks: { beginAtZero: false, diff --git a/test/specs/scale.radialLinear.tests.js b/test/specs/scale.radialLinear.tests.js index a8ac68ff1..6164249d6 100644 --- a/test/specs/scale.radialLinear.tests.js +++ b/test/specs/scale.radialLinear.tests.js @@ -40,8 +40,9 @@ describe('Test the radial linear scale', function() { }, position: 'chartArea', scaleLabel: { - labelString: '', display: false, + labelString: '', + lineHeight: 1.2 }, ticks: { backdropColor: 'rgba(255,255,255,0.75)', diff --git a/test/specs/scale.time.tests.js b/test/specs/scale.time.tests.js index ff02bc8df..c5ca74fa9 100755 --- a/test/specs/scale.time.tests.js +++ b/test/specs/scale.time.tests.js @@ -81,8 +81,9 @@ describe('Time scale tests', function() { }, position: 'bottom', scaleLabel: { + display: false, labelString: '', - display: false + lineHeight: 1.2 }, ticks: { beginAtZero: false, From 94099c10f7568d5b17542d6d13727e2b69f7c0b6 Mon Sep 17 00:00:00 2001 From: Ben McCann Date: Thu, 20 Jul 2017 23:21:29 -0700 Subject: [PATCH 064/112] Remove duplicate npm install (#4542) --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3c50e3415..40e5b442b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,9 +7,6 @@ before_install: - "export DISPLAY=:99.0" - "sh -e /etc/init.d/xvfb start" -before_script: - - npm install - script: - gulp build - gulp test --coverage From 586b8c12fc546aabcac341b3a949b3891019bd36 Mon Sep 17 00:00:00 2001 From: Simon Brunel Date: Fri, 21 Jul 2017 08:40:01 +0200 Subject: [PATCH 065/112] Make `Chart.Element/elements.*` importable (#4540) --- src/chart.js | 8 +- src/controllers/controller.bar.js | 15 +- src/controllers/controller.bubble.js | 3 +- src/controllers/controller.doughnut.js | 11 +- src/controllers/controller.line.js | 5 +- src/controllers/controller.polarArea.js | 3 +- src/controllers/controller.radar.js | 5 +- src/core/core.animation.js | 3 +- src/core/core.element.js | 203 +++++++------ src/core/core.plugin.js | 3 +- src/core/core.scale.js | 3 +- src/core/core.tooltip.js | 3 +- src/elements/element.arc.js | 185 ++++++------ src/elements/element.line.js | 108 ++++--- src/elements/element.point.js | 147 ++++----- src/elements/element.rectangle.js | 377 ++++++++++++------------ src/elements/index.js | 7 + src/plugins/plugin.filler.js | 5 +- src/plugins/plugin.legend.js | 3 +- src/plugins/plugin.title.js | 3 +- 20 files changed, 560 insertions(+), 540 deletions(-) create mode 100644 src/elements/index.js diff --git a/src/chart.js b/src/chart.js index 146cfbaa8..56fc8f24c 100644 --- a/src/chart.js +++ b/src/chart.js @@ -9,11 +9,12 @@ Chart.helpers = require('./helpers/index'); require('./core/core.helpers')(Chart); Chart.defaults = require('./core/core.defaults'); +Chart.Element = require('./core/core.element'); +Chart.elements = require('./elements/index'); Chart.Interaction = require('./core/core.interaction'); Chart.platform = require('./platforms/platform'); Chart.Ticks = require('./core/core.ticks'); -require('./core/core.element')(Chart); require('./core/core.plugin')(Chart); require('./core/core.animation')(Chart); require('./core/core.controller')(Chart); @@ -23,11 +24,6 @@ require('./core/core.scaleService')(Chart); require('./core/core.scale')(Chart); require('./core/core.tooltip')(Chart); -require('./elements/element.arc')(Chart); -require('./elements/element.line')(Chart); -require('./elements/element.point')(Chart); -require('./elements/element.rectangle')(Chart); - require('./scales/scale.linearbase')(Chart); require('./scales/scale.category')(Chart); require('./scales/scale.linear')(Chart); diff --git a/src/controllers/controller.bar.js b/src/controllers/controller.bar.js index 97a077e66..637d589d1 100644 --- a/src/controllers/controller.bar.js +++ b/src/controllers/controller.bar.js @@ -1,6 +1,7 @@ 'use strict'; var defaults = require('../core/core.defaults'); +var elements = require('../elements/index'); var helpers = require('../helpers/index'); defaults._set('bar', { @@ -89,7 +90,7 @@ module.exports = function(Chart) { Chart.controllers.bar = Chart.DatasetController.extend({ - dataElementType: Chart.elements.Rectangle, + dataElementType: elements.Rectangle, initialize: function() { var me = this; @@ -104,13 +105,13 @@ module.exports = function(Chart) { update: function(reset) { var me = this; - var elements = me.getMeta().data; + var rects = me.getMeta().data; var i, ilen; me._ruler = me.getRuler(); - for (i = 0, ilen = elements.length; i < ilen; ++i) { - me.updateElement(elements[i], i, reset); + for (i = 0, ilen = rects.length; i < ilen; ++i) { + me.updateElement(rects[i], i, reset); } }, @@ -326,9 +327,9 @@ module.exports = function(Chart) { draw: function() { var me = this; var chart = me.chart; - var elements = me.getMeta().data; + var rects = me.getMeta().data; var dataset = me.getDataset(); - var ilen = elements.length; + var ilen = rects.length; var i = 0; var d; @@ -337,7 +338,7 @@ module.exports = function(Chart) { for (; i max ? borderWidth : max; max = hoverWidth > max ? hoverWidth : max; diff --git a/src/controllers/controller.line.js b/src/controllers/controller.line.js index d841ada64..dd7d01d60 100644 --- a/src/controllers/controller.line.js +++ b/src/controllers/controller.line.js @@ -1,6 +1,7 @@ 'use strict'; var defaults = require('../core/core.defaults'); +var elements = require('../elements/index'); var helpers = require('../helpers/index'); defaults._set('line', { @@ -31,9 +32,9 @@ module.exports = function(Chart) { Chart.controllers.line = Chart.DatasetController.extend({ - datasetElementType: Chart.elements.Line, + datasetElementType: elements.Line, - dataElementType: Chart.elements.Point, + dataElementType: elements.Point, update: function(reset) { var me = this; diff --git a/src/controllers/controller.polarArea.js b/src/controllers/controller.polarArea.js index c0ac2993e..e59aedfb9 100644 --- a/src/controllers/controller.polarArea.js +++ b/src/controllers/controller.polarArea.js @@ -1,6 +1,7 @@ 'use strict'; var defaults = require('../core/core.defaults'); +var elements = require('../elements/index'); var helpers = require('../helpers/index'); defaults._set('polarArea', { @@ -111,7 +112,7 @@ module.exports = function(Chart) { Chart.controllers.polarArea = Chart.DatasetController.extend({ - dataElementType: Chart.elements.Arc, + dataElementType: elements.Arc, linkScales: helpers.noop, diff --git a/src/controllers/controller.radar.js b/src/controllers/controller.radar.js index b3bccdca9..5de4e4ede 100644 --- a/src/controllers/controller.radar.js +++ b/src/controllers/controller.radar.js @@ -1,6 +1,7 @@ 'use strict'; var defaults = require('../core/core.defaults'); +var elements = require('../elements/index'); var helpers = require('../helpers/index'); defaults._set('radar', { @@ -18,9 +19,9 @@ module.exports = function(Chart) { Chart.controllers.radar = Chart.DatasetController.extend({ - datasetElementType: Chart.elements.Line, + datasetElementType: elements.Line, - dataElementType: Chart.elements.Point, + dataElementType: elements.Point, linkScales: helpers.noop, diff --git a/src/core/core.animation.js b/src/core/core.animation.js index bbd44cde1..17d8cb554 100644 --- a/src/core/core.animation.js +++ b/src/core/core.animation.js @@ -2,6 +2,7 @@ 'use strict'; var defaults = require('./core.defaults'); +var Element = require('./core.element'); var helpers = require('../helpers/index'); defaults._set('global', { @@ -15,7 +16,7 @@ defaults._set('global', { module.exports = function(Chart) { - Chart.Animation = Chart.Element.extend({ + Chart.Animation = Element.extend({ chart: null, // the animation associated chart instance currentStep: 0, // the current animation step numSteps: 60, // default number of steps diff --git a/src/core/core.element.js b/src/core/core.element.js index ba508fddc..016bf5d9e 100644 --- a/src/core/core.element.js +++ b/src/core/core.element.js @@ -3,116 +3,113 @@ var color = require('chartjs-color'); var helpers = require('../helpers/index'); -module.exports = function(Chart) { +function interpolate(start, view, model, ease) { + var keys = Object.keys(model); + var i, ilen, key, actual, origin, target, type, c0, c1; - function interpolate(start, view, model, ease) { - var keys = Object.keys(model); - var i, ilen, key, actual, origin, target, type, c0, c1; + for (i=0, ilen=keys.length; i No Transition - if (!model || ease === 1) { - me._view = model; - me._start = null; - return me; - } - - if (!view) { - view = me._view = {}; - } - - if (!start) { - start = me._start = {}; - } - - interpolate(start, view, model, ease); - - return me; - }, - - tooltipPosition: function() { - return { - x: this._model.x, - y: this._model.y - }; - }, - - hasValue: function() { - return helpers.isNumber(this._model.x) && helpers.isNumber(this._model.y); + if (actual === target || key[0] === '_') { + continue; } - }); - Chart.Element.extend = helpers.inherits; + if (!start.hasOwnProperty(key)) { + start[key] = actual; + } + + origin = start[key]; + + type = typeof(target); + + if (type === typeof(origin)) { + if (type === 'string') { + c0 = color(origin); + if (c0.valid) { + c1 = color(target); + if (c1.valid) { + view[key] = c1.mix(c0, ease).rgbString(); + continue; + } + } + } else if (type === 'number' && isFinite(origin) && isFinite(target)) { + view[key] = origin + (target - origin) * ease; + continue; + } + } + + view[key] = target; + } +} + +var Element = function(configuration) { + helpers.extend(this, configuration); + this.initialize.apply(this, arguments); }; + +helpers.extend(Element.prototype, { + + initialize: function() { + this.hidden = false; + }, + + pivot: function() { + var me = this; + if (!me._view) { + me._view = helpers.clone(me._model); + } + me._start = {}; + return me; + }, + + transition: function(ease) { + var me = this; + var model = me._model; + var start = me._start; + var view = me._view; + + // No animation -> No Transition + if (!model || ease === 1) { + me._view = model; + me._start = null; + return me; + } + + if (!view) { + view = me._view = {}; + } + + if (!start) { + start = me._start = {}; + } + + interpolate(start, view, model, ease); + + return me; + }, + + tooltipPosition: function() { + return { + x: this._model.x, + y: this._model.y + }; + }, + + hasValue: function() { + return helpers.isNumber(this._model.x) && helpers.isNumber(this._model.y); + } +}); + +Element.extend = helpers.inherits; + +module.exports = Element; diff --git a/src/core/core.plugin.js b/src/core/core.plugin.js index 2c472ce0d..484b694f9 100644 --- a/src/core/core.plugin.js +++ b/src/core/core.plugin.js @@ -1,6 +1,7 @@ 'use strict'; var defaults = require('./core.defaults'); +var Element = require('./core.element'); var helpers = require('../helpers/index'); defaults._set('global', { @@ -369,5 +370,5 @@ module.exports = function(Chart) { * @todo remove at version 3 * @private */ - Chart.PluginBase = Chart.Element.extend({}); + Chart.PluginBase = Element.extend({}); }; diff --git a/src/core/core.scale.js b/src/core/core.scale.js index 942e4fff6..0fcc95c47 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -1,6 +1,7 @@ 'use strict'; var defaults = require('./core.defaults'); +var Element = require('./core.element'); var helpers = require('../helpers/index'); var Ticks = require('./core.ticks'); @@ -85,7 +86,7 @@ module.exports = function(Chart) { helpers.valueOrDefault(options.fontSize, defaults.global.defaultFontSize)); } - Chart.Scale = Chart.Element.extend({ + Chart.Scale = Element.extend({ /** * Get the padding needed for the scale * @method getPadding diff --git a/src/core/core.tooltip.js b/src/core/core.tooltip.js index a1ded8e3b..896ddb677 100644 --- a/src/core/core.tooltip.js +++ b/src/core/core.tooltip.js @@ -1,6 +1,7 @@ 'use strict'; var defaults = require('./core.defaults'); +var Element = require('./core.element'); var helpers = require('../helpers/index'); defaults._set('global', { @@ -380,7 +381,7 @@ module.exports = function(Chart) { }; } - Chart.Tooltip = Chart.Element.extend({ + Chart.Tooltip = Element.extend({ initialize: function() { this._model = getBaseModel(this._options); }, diff --git a/src/elements/element.arc.js b/src/elements/element.arc.js index 101302cf8..787ab5c88 100644 --- a/src/elements/element.arc.js +++ b/src/elements/element.arc.js @@ -1,6 +1,7 @@ 'use strict'; var defaults = require('../core/core.defaults'); +var Element = require('../core/core.element'); var helpers = require('../helpers/index'); defaults._set('global', { @@ -13,96 +14,98 @@ defaults._set('global', { } }); -module.exports = function(Chart) { +module.exports = Element.extend({ + inLabelRange: function(mouseX) { + var vm = this._view; - Chart.elements.Arc = Chart.Element.extend({ - inLabelRange: function(mouseX) { - var vm = this._view; - - if (vm) { - return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2)); - } - return false; - }, - inRange: function(chartX, chartY) { - var vm = this._view; - - if (vm) { - var pointRelativePosition = helpers.getAngleFromPoint(vm, { - x: chartX, - y: chartY - }), - angle = pointRelativePosition.angle, - distance = pointRelativePosition.distance; - - // Sanitise angle range - var startAngle = vm.startAngle; - var endAngle = vm.endAngle; - while (endAngle < startAngle) { - endAngle += 2.0 * Math.PI; - } - while (angle > endAngle) { - angle -= 2.0 * Math.PI; - } - while (angle < startAngle) { - angle += 2.0 * Math.PI; - } - - // Check if within the range of the open/close angle - var betweenAngles = (angle >= startAngle && angle <= endAngle), - withinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius); - - return (betweenAngles && withinRadius); - } - return false; - }, - getCenterPoint: function() { - var vm = this._view; - var halfAngle = (vm.startAngle + vm.endAngle) / 2; - var halfRadius = (vm.innerRadius + vm.outerRadius) / 2; - return { - x: vm.x + Math.cos(halfAngle) * halfRadius, - y: vm.y + Math.sin(halfAngle) * halfRadius - }; - }, - getArea: function() { - var vm = this._view; - return Math.PI * ((vm.endAngle - vm.startAngle) / (2 * Math.PI)) * (Math.pow(vm.outerRadius, 2) - Math.pow(vm.innerRadius, 2)); - }, - tooltipPosition: function() { - var vm = this._view; - - var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2), - rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius; - return { - x: vm.x + (Math.cos(centreAngle) * rangeFromCentre), - y: vm.y + (Math.sin(centreAngle) * rangeFromCentre) - }; - }, - draw: function() { - - var ctx = this._chart.ctx, - vm = this._view, - sA = vm.startAngle, - eA = vm.endAngle; - - ctx.beginPath(); - - ctx.arc(vm.x, vm.y, vm.outerRadius, sA, eA); - ctx.arc(vm.x, vm.y, vm.innerRadius, eA, sA, true); - - ctx.closePath(); - ctx.strokeStyle = vm.borderColor; - ctx.lineWidth = vm.borderWidth; - - ctx.fillStyle = vm.backgroundColor; - - ctx.fill(); - ctx.lineJoin = 'bevel'; - - if (vm.borderWidth) { - ctx.stroke(); - } + if (vm) { + return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2)); } - }); -}; + return false; + }, + + inRange: function(chartX, chartY) { + var vm = this._view; + + if (vm) { + var pointRelativePosition = helpers.getAngleFromPoint(vm, { + x: chartX, + y: chartY + }), + angle = pointRelativePosition.angle, + distance = pointRelativePosition.distance; + + // Sanitise angle range + var startAngle = vm.startAngle; + var endAngle = vm.endAngle; + while (endAngle < startAngle) { + endAngle += 2.0 * Math.PI; + } + while (angle > endAngle) { + angle -= 2.0 * Math.PI; + } + while (angle < startAngle) { + angle += 2.0 * Math.PI; + } + + // Check if within the range of the open/close angle + var betweenAngles = (angle >= startAngle && angle <= endAngle), + withinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius); + + return (betweenAngles && withinRadius); + } + return false; + }, + + getCenterPoint: function() { + var vm = this._view; + var halfAngle = (vm.startAngle + vm.endAngle) / 2; + var halfRadius = (vm.innerRadius + vm.outerRadius) / 2; + return { + x: vm.x + Math.cos(halfAngle) * halfRadius, + y: vm.y + Math.sin(halfAngle) * halfRadius + }; + }, + + getArea: function() { + var vm = this._view; + return Math.PI * ((vm.endAngle - vm.startAngle) / (2 * Math.PI)) * (Math.pow(vm.outerRadius, 2) - Math.pow(vm.innerRadius, 2)); + }, + + tooltipPosition: function() { + var vm = this._view; + + var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2), + rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius; + return { + x: vm.x + (Math.cos(centreAngle) * rangeFromCentre), + y: vm.y + (Math.sin(centreAngle) * rangeFromCentre) + }; + }, + + draw: function() { + + var ctx = this._chart.ctx, + vm = this._view, + sA = vm.startAngle, + eA = vm.endAngle; + + ctx.beginPath(); + + ctx.arc(vm.x, vm.y, vm.outerRadius, sA, eA); + ctx.arc(vm.x, vm.y, vm.innerRadius, eA, sA, true); + + ctx.closePath(); + ctx.strokeStyle = vm.borderColor; + ctx.lineWidth = vm.borderWidth; + + ctx.fillStyle = vm.backgroundColor; + + ctx.fill(); + ctx.lineJoin = 'bevel'; + + if (vm.borderWidth) { + ctx.stroke(); + } + } +}); diff --git a/src/elements/element.line.js b/src/elements/element.line.js index f2bfcf67d..1500d353c 100644 --- a/src/elements/element.line.js +++ b/src/elements/element.line.js @@ -1,6 +1,7 @@ 'use strict'; var defaults = require('../core/core.defaults'); +var Element = require('../core/core.element'); var helpers = require('../helpers/index'); var globalDefaults = defaults.global; @@ -22,72 +23,69 @@ defaults._set('global', { } }); -module.exports = function(Chart) { +module.exports = Element.extend({ + draw: function() { + var me = this; + var vm = me._view; + var ctx = me._chart.ctx; + var spanGaps = vm.spanGaps; + var points = me._children.slice(); // clone array + var globalOptionLineElements = globalDefaults.elements.line; + var lastDrawnIndex = -1; + var index, current, previous, currentVM; - Chart.elements.Line = Chart.Element.extend({ - draw: function() { - var me = this; - var vm = me._view; - var ctx = me._chart.ctx; - var spanGaps = vm.spanGaps; - var points = me._children.slice(); // clone array - var globalOptionLineElements = globalDefaults.elements.line; - var lastDrawnIndex = -1; - var index, current, previous, currentVM; + // If we are looping, adding the first point again + if (me._loop && points.length) { + points.push(points[0]); + } - // If we are looping, adding the first point again - if (me._loop && points.length) { - points.push(points[0]); - } + ctx.save(); - ctx.save(); + // Stroke Line Options + ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle; - // Stroke Line Options - ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle; + // IE 9 and 10 do not support line dash + if (ctx.setLineDash) { + ctx.setLineDash(vm.borderDash || globalOptionLineElements.borderDash); + } - // IE 9 and 10 do not support line dash - if (ctx.setLineDash) { - ctx.setLineDash(vm.borderDash || globalOptionLineElements.borderDash); - } + ctx.lineDashOffset = vm.borderDashOffset || globalOptionLineElements.borderDashOffset; + ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle; + ctx.lineWidth = vm.borderWidth || globalOptionLineElements.borderWidth; + ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor; - ctx.lineDashOffset = vm.borderDashOffset || globalOptionLineElements.borderDashOffset; - ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle; - ctx.lineWidth = vm.borderWidth || globalOptionLineElements.borderWidth; - ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor; + // Stroke Line + ctx.beginPath(); + lastDrawnIndex = -1; - // Stroke Line - ctx.beginPath(); - lastDrawnIndex = -1; + for (index = 0; index < points.length; ++index) { + current = points[index]; + previous = helpers.previousItem(points, index); + currentVM = current._view; - for (index = 0; index < points.length; ++index) { - current = points[index]; - previous = helpers.previousItem(points, index); - currentVM = current._view; + // First point moves to it's starting position no matter what + if (index === 0) { + if (!currentVM.skip) { + ctx.moveTo(currentVM.x, currentVM.y); + lastDrawnIndex = index; + } + } else { + previous = lastDrawnIndex === -1 ? previous : points[lastDrawnIndex]; - // First point moves to it's starting position no matter what - if (index === 0) { - if (!currentVM.skip) { + if (!currentVM.skip) { + if ((lastDrawnIndex !== (index - 1) && !spanGaps) || lastDrawnIndex === -1) { + // There was a gap and this is the first point after the gap ctx.moveTo(currentVM.x, currentVM.y); - lastDrawnIndex = index; - } - } else { - previous = lastDrawnIndex === -1 ? previous : points[lastDrawnIndex]; - - if (!currentVM.skip) { - if ((lastDrawnIndex !== (index - 1) && !spanGaps) || lastDrawnIndex === -1) { - // There was a gap and this is the first point after the gap - ctx.moveTo(currentVM.x, currentVM.y); - } else { - // Line to next point - helpers.canvas.lineTo(ctx, previous._view, current._view); - } - lastDrawnIndex = index; + } else { + // Line to next point + helpers.canvas.lineTo(ctx, previous._view, current._view); } + lastDrawnIndex = index; } } - - ctx.stroke(); - ctx.restore(); } - }); -}; + + ctx.stroke(); + ctx.restore(); + } +}); diff --git a/src/elements/element.point.js b/src/elements/element.point.js index 0b5d12d12..919c648ec 100644 --- a/src/elements/element.point.js +++ b/src/elements/element.point.js @@ -1,6 +1,7 @@ 'use strict'; var defaults = require('../core/core.defaults'); +var Element = require('../core/core.element'); var helpers = require('../helpers/index'); var defaultColor = defaults.global.defaultColor; @@ -21,85 +22,85 @@ defaults._set('global', { } }); -module.exports = function(Chart) { +function xRange(mouseX) { + var vm = this._view; + return vm ? (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false; +} - function xRange(mouseX) { +function yRange(mouseY) { + var vm = this._view; + return vm ? (Math.pow(mouseY - vm.y, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false; +} + +module.exports = Element.extend({ + inRange: function(mouseX, mouseY) { var vm = this._view; - return vm ? (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false; - } + return vm ? ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(vm.hitRadius + vm.radius, 2)) : false; + }, - function yRange(mouseY) { + inLabelRange: xRange, + inXRange: xRange, + inYRange: yRange, + + getCenterPoint: function() { var vm = this._view; - return vm ? (Math.pow(mouseY - vm.y, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false; - } + return { + x: vm.x, + y: vm.y + }; + }, - Chart.elements.Point = Chart.Element.extend({ - inRange: function(mouseX, mouseY) { - var vm = this._view; - return vm ? ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(vm.hitRadius + vm.radius, 2)) : false; - }, + getArea: function() { + return Math.PI * Math.pow(this._view.radius, 2); + }, - inLabelRange: xRange, - inXRange: xRange, - inYRange: yRange, + tooltipPosition: function() { + var vm = this._view; + return { + x: vm.x, + y: vm.y, + padding: vm.radius + vm.borderWidth + }; + }, - getCenterPoint: function() { - var vm = this._view; - return { - x: vm.x, - y: vm.y - }; - }, - getArea: function() { - return Math.PI * Math.pow(this._view.radius, 2); - }, - tooltipPosition: function() { - var vm = this._view; - return { - x: vm.x, - y: vm.y, - padding: vm.radius + vm.borderWidth - }; - }, - draw: function(chartArea) { - var vm = this._view; - var model = this._model; - var ctx = this._chart.ctx; - var pointStyle = vm.pointStyle; - var radius = vm.radius; - var x = vm.x; - var y = vm.y; - var color = helpers.color; - var errMargin = 1.01; // 1.01 is margin for Accumulated error. (Especially Edge, IE.) - var ratio = 0; + draw: function(chartArea) { + var vm = this._view; + var model = this._model; + var ctx = this._chart.ctx; + var pointStyle = vm.pointStyle; + var radius = vm.radius; + var x = vm.x; + var y = vm.y; + var color = helpers.color; + var errMargin = 1.01; // 1.01 is margin for Accumulated error. (Especially Edge, IE.) + var ratio = 0; - if (vm.skip) { - return; - } - - ctx.strokeStyle = vm.borderColor || defaultColor; - ctx.lineWidth = helpers.valueOrDefault(vm.borderWidth, defaults.global.elements.point.borderWidth); - ctx.fillStyle = vm.backgroundColor || defaultColor; - - // Cliping for Points. - // going out from inner charArea? - if ((chartArea !== undefined) && ((model.x < chartArea.left) || (chartArea.right*errMargin < model.x) || (model.y < chartArea.top) || (chartArea.bottom*errMargin < model.y))) { - // Point fade out - if (model.x < chartArea.left) { - ratio = (x - model.x) / (chartArea.left - model.x); - } else if (chartArea.right*errMargin < model.x) { - ratio = (model.x - x) / (model.x - chartArea.right); - } else if (model.y < chartArea.top) { - ratio = (y - model.y) / (chartArea.top - model.y); - } else if (chartArea.bottom*errMargin < model.y) { - ratio = (model.y - y) / (model.y - chartArea.bottom); - } - ratio = Math.round(ratio*100) / 100; - ctx.strokeStyle = color(ctx.strokeStyle).alpha(ratio).rgbString(); - ctx.fillStyle = color(ctx.fillStyle).alpha(ratio).rgbString(); - } - - helpers.canvas.drawPoint(ctx, pointStyle, radius, x, y); + if (vm.skip) { + return; } - }); -}; + + ctx.strokeStyle = vm.borderColor || defaultColor; + ctx.lineWidth = helpers.valueOrDefault(vm.borderWidth, defaults.global.elements.point.borderWidth); + ctx.fillStyle = vm.backgroundColor || defaultColor; + + // Cliping for Points. + // going out from inner charArea? + if ((chartArea !== undefined) && ((model.x < chartArea.left) || (chartArea.right*errMargin < model.x) || (model.y < chartArea.top) || (chartArea.bottom*errMargin < model.y))) { + // Point fade out + if (model.x < chartArea.left) { + ratio = (x - model.x) / (chartArea.left - model.x); + } else if (chartArea.right*errMargin < model.x) { + ratio = (model.x - x) / (model.x - chartArea.right); + } else if (model.y < chartArea.top) { + ratio = (y - model.y) / (chartArea.top - model.y); + } else if (chartArea.bottom*errMargin < model.y) { + ratio = (model.y - y) / (model.y - chartArea.bottom); + } + ratio = Math.round(ratio*100) / 100; + ctx.strokeStyle = color(ctx.strokeStyle).alpha(ratio).rgbString(); + ctx.fillStyle = color(ctx.fillStyle).alpha(ratio).rgbString(); + } + + helpers.canvas.drawPoint(ctx, pointStyle, radius, x, y); + } +}); diff --git a/src/elements/element.rectangle.js b/src/elements/element.rectangle.js index 00f017e66..c4322063f 100644 --- a/src/elements/element.rectangle.js +++ b/src/elements/element.rectangle.js @@ -1,6 +1,7 @@ 'use strict'; var defaults = require('../core/core.defaults'); +var Element = require('../core/core.element'); defaults._set('global', { elements: { @@ -13,200 +14,204 @@ defaults._set('global', { } }); -module.exports = function(Chart) { +function isVertical(bar) { + return bar._view.width !== undefined; +} - function isVertical(bar) { - return bar._view.width !== undefined; +/** + * Helper function to get the bounds of the bar regardless of the orientation + * @param bar {Chart.Element.Rectangle} the bar + * @return {Bounds} bounds of the bar + * @private + */ +function getBarBounds(bar) { + var vm = bar._view; + var x1, x2, y1, y2; + + if (isVertical(bar)) { + // vertical + var halfWidth = vm.width / 2; + x1 = vm.x - halfWidth; + x2 = vm.x + halfWidth; + y1 = Math.min(vm.y, vm.base); + y2 = Math.max(vm.y, vm.base); + } else { + // horizontal bar + var halfHeight = vm.height / 2; + x1 = Math.min(vm.x, vm.base); + x2 = Math.max(vm.x, vm.base); + y1 = vm.y - halfHeight; + y2 = vm.y + halfHeight; } - /** - * Helper function to get the bounds of the bar regardless of the orientation - * @private - * @param bar {Chart.Element.Rectangle} the bar - * @return {Bounds} bounds of the bar - */ - function getBarBounds(bar) { - var vm = bar._view; - var x1, x2, y1, y2; + return { + left: x1, + top: y1, + right: x2, + bottom: y2 + }; +} - if (isVertical(bar)) { - // vertical - var halfWidth = vm.width / 2; - x1 = vm.x - halfWidth; - x2 = vm.x + halfWidth; - y1 = Math.min(vm.y, vm.base); - y2 = Math.max(vm.y, vm.base); +module.exports = Element.extend({ + draw: function() { + var ctx = this._chart.ctx; + var vm = this._view; + var left, right, top, bottom, signX, signY, borderSkipped; + var borderWidth = vm.borderWidth; + + if (!vm.horizontal) { + // bar + left = vm.x - vm.width / 2; + right = vm.x + vm.width / 2; + top = vm.y; + bottom = vm.base; + signX = 1; + signY = bottom > top? 1: -1; + borderSkipped = vm.borderSkipped || 'bottom'; } else { // horizontal bar - var halfHeight = vm.height / 2; - x1 = Math.min(vm.x, vm.base); - x2 = Math.max(vm.x, vm.base); - y1 = vm.y - halfHeight; - y2 = vm.y + halfHeight; + left = vm.base; + right = vm.x; + top = vm.y - vm.height / 2; + bottom = vm.y + vm.height / 2; + signX = right > left? 1: -1; + signY = 1; + borderSkipped = vm.borderSkipped || 'left'; } + // Canvas doesn't allow us to stroke inside the width so we can + // adjust the sizes to fit if we're setting a stroke on the line + if (borderWidth) { + // borderWidth shold be less than bar width and bar height. + var barSize = Math.min(Math.abs(left - right), Math.abs(top - bottom)); + borderWidth = borderWidth > barSize? barSize: borderWidth; + var halfStroke = borderWidth / 2; + // Adjust borderWidth when bar top position is near vm.base(zero). + var borderLeft = left + (borderSkipped !== 'left'? halfStroke * signX: 0); + var borderRight = right + (borderSkipped !== 'right'? -halfStroke * signX: 0); + var borderTop = top + (borderSkipped !== 'top'? halfStroke * signY: 0); + var borderBottom = bottom + (borderSkipped !== 'bottom'? -halfStroke * signY: 0); + // not become a vertical line? + if (borderLeft !== borderRight) { + top = borderTop; + bottom = borderBottom; + } + // not become a horizontal line? + if (borderTop !== borderBottom) { + left = borderLeft; + right = borderRight; + } + } + + ctx.beginPath(); + ctx.fillStyle = vm.backgroundColor; + ctx.strokeStyle = vm.borderColor; + ctx.lineWidth = borderWidth; + + // Corner points, from bottom-left to bottom-right clockwise + // | 1 2 | + // | 0 3 | + var corners = [ + [left, bottom], + [left, top], + [right, top], + [right, bottom] + ]; + + // Find first (starting) corner with fallback to 'bottom' + var borders = ['bottom', 'left', 'top', 'right']; + var startCorner = borders.indexOf(borderSkipped, 0); + if (startCorner === -1) { + startCorner = 0; + } + + function cornerAt(index) { + return corners[(startCorner + index) % 4]; + } + + // Draw rectangle from 'startCorner' + var corner = cornerAt(0); + ctx.moveTo(corner[0], corner[1]); + + for (var i = 1; i < 4; i++) { + corner = cornerAt(i); + ctx.lineTo(corner[0], corner[1]); + } + + ctx.fill(); + if (borderWidth) { + ctx.stroke(); + } + }, + + height: function() { + var vm = this._view; + return vm.base - vm.y; + }, + + inRange: function(mouseX, mouseY) { + var inRange = false; + + if (this._view) { + var bounds = getBarBounds(this); + inRange = mouseX >= bounds.left && mouseX <= bounds.right && mouseY >= bounds.top && mouseY <= bounds.bottom; + } + + return inRange; + }, + + inLabelRange: function(mouseX, mouseY) { + var me = this; + if (!me._view) { + return false; + } + + var inRange = false; + var bounds = getBarBounds(me); + + if (isVertical(me)) { + inRange = mouseX >= bounds.left && mouseX <= bounds.right; + } else { + inRange = mouseY >= bounds.top && mouseY <= bounds.bottom; + } + + return inRange; + }, + + inXRange: function(mouseX) { + var bounds = getBarBounds(this); + return mouseX >= bounds.left && mouseX <= bounds.right; + }, + + inYRange: function(mouseY) { + var bounds = getBarBounds(this); + return mouseY >= bounds.top && mouseY <= bounds.bottom; + }, + + getCenterPoint: function() { + var vm = this._view; + var x, y; + if (isVertical(this)) { + x = vm.x; + y = (vm.y + vm.base) / 2; + } else { + x = (vm.x + vm.base) / 2; + y = vm.y; + } + + return {x: x, y: y}; + }, + + getArea: function() { + var vm = this._view; + return vm.width * Math.abs(vm.y - vm.base); + }, + + tooltipPosition: function() { + var vm = this._view; return { - left: x1, - top: y1, - right: x2, - bottom: y2 + x: vm.x, + y: vm.y }; } - - Chart.elements.Rectangle = Chart.Element.extend({ - draw: function() { - var ctx = this._chart.ctx; - var vm = this._view; - var left, right, top, bottom, signX, signY, borderSkipped; - var borderWidth = vm.borderWidth; - - if (!vm.horizontal) { - // bar - left = vm.x - vm.width / 2; - right = vm.x + vm.width / 2; - top = vm.y; - bottom = vm.base; - signX = 1; - signY = bottom > top? 1: -1; - borderSkipped = vm.borderSkipped || 'bottom'; - } else { - // horizontal bar - left = vm.base; - right = vm.x; - top = vm.y - vm.height / 2; - bottom = vm.y + vm.height / 2; - signX = right > left? 1: -1; - signY = 1; - borderSkipped = vm.borderSkipped || 'left'; - } - - // Canvas doesn't allow us to stroke inside the width so we can - // adjust the sizes to fit if we're setting a stroke on the line - if (borderWidth) { - // borderWidth shold be less than bar width and bar height. - var barSize = Math.min(Math.abs(left - right), Math.abs(top - bottom)); - borderWidth = borderWidth > barSize? barSize: borderWidth; - var halfStroke = borderWidth / 2; - // Adjust borderWidth when bar top position is near vm.base(zero). - var borderLeft = left + (borderSkipped !== 'left'? halfStroke * signX: 0); - var borderRight = right + (borderSkipped !== 'right'? -halfStroke * signX: 0); - var borderTop = top + (borderSkipped !== 'top'? halfStroke * signY: 0); - var borderBottom = bottom + (borderSkipped !== 'bottom'? -halfStroke * signY: 0); - // not become a vertical line? - if (borderLeft !== borderRight) { - top = borderTop; - bottom = borderBottom; - } - // not become a horizontal line? - if (borderTop !== borderBottom) { - left = borderLeft; - right = borderRight; - } - } - - ctx.beginPath(); - ctx.fillStyle = vm.backgroundColor; - ctx.strokeStyle = vm.borderColor; - ctx.lineWidth = borderWidth; - - // Corner points, from bottom-left to bottom-right clockwise - // | 1 2 | - // | 0 3 | - var corners = [ - [left, bottom], - [left, top], - [right, top], - [right, bottom] - ]; - - // Find first (starting) corner with fallback to 'bottom' - var borders = ['bottom', 'left', 'top', 'right']; - var startCorner = borders.indexOf(borderSkipped, 0); - if (startCorner === -1) { - startCorner = 0; - } - - function cornerAt(index) { - return corners[(startCorner + index) % 4]; - } - - // Draw rectangle from 'startCorner' - var corner = cornerAt(0); - ctx.moveTo(corner[0], corner[1]); - - for (var i = 1; i < 4; i++) { - corner = cornerAt(i); - ctx.lineTo(corner[0], corner[1]); - } - - ctx.fill(); - if (borderWidth) { - ctx.stroke(); - } - }, - height: function() { - var vm = this._view; - return vm.base - vm.y; - }, - inRange: function(mouseX, mouseY) { - var inRange = false; - - if (this._view) { - var bounds = getBarBounds(this); - inRange = mouseX >= bounds.left && mouseX <= bounds.right && mouseY >= bounds.top && mouseY <= bounds.bottom; - } - - return inRange; - }, - inLabelRange: function(mouseX, mouseY) { - var me = this; - if (!me._view) { - return false; - } - - var inRange = false; - var bounds = getBarBounds(me); - - if (isVertical(me)) { - inRange = mouseX >= bounds.left && mouseX <= bounds.right; - } else { - inRange = mouseY >= bounds.top && mouseY <= bounds.bottom; - } - - return inRange; - }, - inXRange: function(mouseX) { - var bounds = getBarBounds(this); - return mouseX >= bounds.left && mouseX <= bounds.right; - }, - inYRange: function(mouseY) { - var bounds = getBarBounds(this); - return mouseY >= bounds.top && mouseY <= bounds.bottom; - }, - getCenterPoint: function() { - var vm = this._view; - var x, y; - if (isVertical(this)) { - x = vm.x; - y = (vm.y + vm.base) / 2; - } else { - x = (vm.x + vm.base) / 2; - y = vm.y; - } - - return {x: x, y: y}; - }, - getArea: function() { - var vm = this._view; - return vm.width * Math.abs(vm.y - vm.base); - }, - tooltipPosition: function() { - var vm = this._view; - return { - x: vm.x, - y: vm.y - }; - } - }); - -}; +}); diff --git a/src/elements/index.js b/src/elements/index.js new file mode 100644 index 000000000..175c9660e --- /dev/null +++ b/src/elements/index.js @@ -0,0 +1,7 @@ +'use strict'; + +module.exports = {}; +module.exports.Arc = require('./element.arc'); +module.exports.Line = require('./element.line'); +module.exports.Point = require('./element.point'); +module.exports.Rectangle = require('./element.rectangle'); diff --git a/src/plugins/plugin.filler.js b/src/plugins/plugin.filler.js index 51aed7669..b42a570f6 100644 --- a/src/plugins/plugin.filler.js +++ b/src/plugins/plugin.filler.js @@ -7,6 +7,7 @@ 'use strict'; var defaults = require('../core/core.defaults'); +var elements = require('../elements/index'); var helpers = require('../helpers/index'); defaults._set('global', { @@ -17,7 +18,7 @@ defaults._set('global', { } }); -module.exports = function(Chart) { +module.exports = function() { var mappers = { dataset: function(source) { @@ -272,7 +273,7 @@ module.exports = function(Chart) { el = meta.dataset; source = null; - if (el && el._model && el instanceof Chart.elements.Line) { + if (el && el._model && el instanceof elements.Line) { source = { visible: chart.isDatasetVisible(i), fill: decodeFill(el, i, count), diff --git a/src/plugins/plugin.legend.js b/src/plugins/plugin.legend.js index 1a32c0798..aafe521d1 100644 --- a/src/plugins/plugin.legend.js +++ b/src/plugins/plugin.legend.js @@ -1,6 +1,7 @@ 'use strict'; var defaults = require('../core/core.defaults'); +var Element = require('../core/core.element'); var helpers = require('../helpers/index'); defaults._set('global', { @@ -95,7 +96,7 @@ module.exports = function(Chart) { labelOpts.boxWidth; } - Chart.Legend = Chart.Element.extend({ + Chart.Legend = Element.extend({ initialize: function(config) { helpers.extend(this, config); diff --git a/src/plugins/plugin.title.js b/src/plugins/plugin.title.js index 259863b62..765c30f87 100644 --- a/src/plugins/plugin.title.js +++ b/src/plugins/plugin.title.js @@ -1,6 +1,7 @@ 'use strict'; var defaults = require('../core/core.defaults'); +var Element = require('../core/core.element'); var helpers = require('../helpers/index'); defaults._set('global', { @@ -21,7 +22,7 @@ module.exports = function(Chart) { var layout = Chart.layoutService; var noop = helpers.noop; - Chart.Title = Chart.Element.extend({ + Chart.Title = Element.extend({ initialize: function(config) { var me = this; helpers.extend(me, config); From 7b6388883a31cdd3c488eff893fcc33eb322c4c7 Mon Sep 17 00:00:00 2001 From: Simon Brunel Date: Fri, 21 Jul 2017 15:03:03 +0200 Subject: [PATCH 066/112] Refactor padding parsing under helpers.options (#4544) New Chart.helpers.options.toPadding helpers that converts a number or object into a padding {top, right, bottom, left, height, width} object. --- src/core/core.layoutService.js | 26 +++++--------------- src/helpers/helpers.options.js | 33 ++++++++++++++++++++++++- test/specs/helpers.options.tests.js | 37 +++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 21 deletions(-) diff --git a/src/core/core.layoutService.js b/src/core/core.layoutService.js index 65adac88f..98907de27 100644 --- a/src/core/core.layoutService.js +++ b/src/core/core.layoutService.js @@ -113,26 +113,12 @@ module.exports = function(Chart) { return; } - var layoutOptions = chart.options.layout; - var padding = layoutOptions ? layoutOptions.padding : null; - - var leftPadding = 0; - var rightPadding = 0; - var topPadding = 0; - var bottomPadding = 0; - - if (!isNaN(padding)) { - // options.layout.padding is a number. assign to all - leftPadding = padding; - rightPadding = padding; - topPadding = padding; - bottomPadding = padding; - } else { - leftPadding = padding.left || 0; - rightPadding = padding.right || 0; - topPadding = padding.top || 0; - bottomPadding = padding.bottom || 0; - } + var layoutOptions = chart.options.layout || {}; + var padding = helpers.options.toPadding(layoutOptions.padding); + var leftPadding = padding.left; + var rightPadding = padding.right; + var topPadding = padding.top; + var bottomPadding = padding.bottom; var leftBoxes = filterByPosition(chart.boxes, 'left'); var rightBoxes = filterByPosition(chart.boxes, 'right'); diff --git a/src/helpers/helpers.options.js b/src/helpers/helpers.options.js index 1aab9bb96..15e14c16a 100644 --- a/src/helpers/helpers.options.js +++ b/src/helpers/helpers.options.js @@ -1,5 +1,7 @@ 'use strict'; +var helpers = require('./helpers.core'); + /** * @namespace Chart.helpers.options */ @@ -18,7 +20,7 @@ module.exports = { return size * 1.2; } - value = parseFloat(matches[2]); + value = +matches[2]; switch (matches[3]) { case 'px': @@ -31,5 +33,34 @@ module.exports = { } return size * value; + }, + + /** + * Converts the given value into a padding object with pre-computed width/height. + * @param {Number|Object} value - If a number, set the value to all TRBL component, + * else, if and object, use defined properties and sets undefined ones to 0. + * @returns {Object} The padding values (top, right, bottom, left, width, height) + * @since 2.7.0 + */ + toPadding: function(value) { + var t, r, b, l; + + if (helpers.isObject(value)) { + t = +value.top || 0; + r = +value.right || 0; + b = +value.bottom || 0; + l = +value.left || 0; + } else { + t = r = b = l = +value || 0; + } + + return { + top: t, + right: r, + bottom: b, + left: l, + height: t + b, + width: l + r + }; } }; diff --git a/test/specs/helpers.options.tests.js b/test/specs/helpers.options.tests.js index 1b01159dc..46e522286 100644 --- a/test/specs/helpers.options.tests.js +++ b/test/specs/helpers.options.tests.js @@ -24,4 +24,41 @@ describe('Chart.helpers.options', function() { expect(options.toLineHeight('foobar', 16)).toBe(16 * 1.2); }); }); + + describe('toPadding', function() { + it ('should support number values', function() { + expect(options.toPadding(4)).toEqual( + {top: 4, right: 4, bottom: 4, left: 4, height: 8, width: 8}); + expect(options.toPadding(4.5)).toEqual( + {top: 4.5, right: 4.5, bottom: 4.5, left: 4.5, height: 9, width: 9}); + }); + it ('should support string values', function() { + expect(options.toPadding('4')).toEqual( + {top: 4, right: 4, bottom: 4, left: 4, height: 8, width: 8}); + expect(options.toPadding('4.5')).toEqual( + {top: 4.5, right: 4.5, bottom: 4.5, left: 4.5, height: 9, width: 9}); + }); + it ('should support object values', function() { + expect(options.toPadding({top: 1, right: 2, bottom: 3, left: 4})).toEqual( + {top: 1, right: 2, bottom: 3, left: 4, height: 4, width: 6}); + expect(options.toPadding({top: 1.5, right: 2.5, bottom: 3.5, left: 4.5})).toEqual( + {top: 1.5, right: 2.5, bottom: 3.5, left: 4.5, height: 5, width: 7}); + expect(options.toPadding({top: '1', right: '2', bottom: '3', left: '4'})).toEqual( + {top: 1, right: 2, bottom: 3, left: 4, height: 4, width: 6}); + }); + it ('should fallback to 0 for invalid values', function() { + expect(options.toPadding({top: 'foo', right: 'foo', bottom: 'foo', left: 'foo'})).toEqual( + {top: 0, right: 0, bottom: 0, left: 0, height: 0, width: 0}); + expect(options.toPadding({top: null, right: null, bottom: null, left: null})).toEqual( + {top: 0, right: 0, bottom: 0, left: 0, height: 0, width: 0}); + expect(options.toPadding({})).toEqual( + {top: 0, right: 0, bottom: 0, left: 0, height: 0, width: 0}); + expect(options.toPadding('foo')).toEqual( + {top: 0, right: 0, bottom: 0, left: 0, height: 0, width: 0}); + expect(options.toPadding(null)).toEqual( + {top: 0, right: 0, bottom: 0, left: 0, height: 0, width: 0}); + expect(options.toPadding(undefined)).toEqual( + {top: 0, right: 0, bottom: 0, left: 0, height: 0, width: 0}); + }); + }); }); From 48d76b20fe055e604541bd4a5561940c9e36038c Mon Sep 17 00:00:00 2001 From: Ben McCann Date: Fri, 21 Jul 2017 23:33:22 -0700 Subject: [PATCH 067/112] Allow specifying the time axis via t attribute (#4533) For time series charts it may make more sense to specify the horizontal axis using the variable `t`. This change will make it much easier to use the time scale with the financial chart, which takes in the data points `{t, o, h, l, c}`. --- docs/axes/cartesian/time.md | 29 +++++++++++++++----- src/core/core.scale.js | 11 +++++--- src/scales/scale.time.js | 10 +++++++ test/specs/scale.time.tests.js | 48 ++++++++++++++++++++++++++++++++++ 4 files changed, 87 insertions(+), 11 deletions(-) diff --git a/docs/axes/cartesian/time.md b/docs/axes/cartesian/time.md index dc4a2bb42..6e107d515 100644 --- a/docs/axes/cartesian/time.md +++ b/docs/axes/cartesian/time.md @@ -2,6 +2,25 @@ The time scale is used to display times and dates. When building its ticks, it will automatically calculate the most comfortable unit base on the size of the scale. +## Data Sets + +### Input Data + +The x-axis data points may additionally be specified via the `t` attribute when using the time scale. + + data: [{ + x: new Date(), + y: 1 + }, { + t: new Date(), + y: 10 + }] + + +### Date Formats + +When providing data for the time scale, Chart.js supports all of the formats that Moment.js accepts. See [Moment.js docs](http://momentjs.com/docs/#/parsing/) for details. + ## Configuration Options The following options are provided by the time scale. They are all located in the `time` sub options. These options extend the [common tick configuration](README.md#tick-configuration). @@ -19,11 +38,7 @@ The following options are provided by the time scale. They are all located in th | `stepSize` | `Number` | `1` | The number of units between grid lines. | `minUnit` | `String` | `'millisecond'` | The minimum display format to be used for a time unit. -## Date Formats - -When providing data for the time scale, Chart.js supports all of the formats that Moment.js accepts. See [Moment.js docs](http://momentjs.com/docs/#/parsing/) for details. - -## Time Units +### Time Units The following time measurements are supported. The names can be passed as strings to the `time.unit` config option to force a certain unit. @@ -55,7 +70,7 @@ var chart = new Chart(ctx, { }) ``` -## Display Formats +### Display Formats The following display formats are used to configure how different time units are formed into strings for the axis tick marks. See [moment.js](http://momentjs.com/docs/#/displaying/format/) for the allowable format strings. Name | Default | Example @@ -91,7 +106,7 @@ var chart = new Chart(ctx, { }) ``` -## Parser +### 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 is a function, it must return a moment.js object given the appropriate data value. diff --git a/src/core/core.scale.js b/src/core/core.scale.js index 0fcc95c47..27a0f0596 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -442,11 +442,14 @@ module.exports = function(Chart) { return NaN; } // If it is in fact an object, dive in one more level - if (typeof(rawValue) === 'object') { - if ((rawValue instanceof Date) || (rawValue.isValid)) { - return rawValue; + if (rawValue) { + if (this.isHorizontal()) { + if (rawValue.x !== undefined) { + return this.getRightValue(rawValue.x); + } + } else if (rawValue.y !== undefined) { + return this.getRightValue(rawValue.y); } - return this.getRightValue(this.isHorizontal() ? rawValue.x : rawValue.y); } // Value is good, return it diff --git a/src/scales/scale.time.js b/src/scales/scale.time.js index 93e49887b..8b3dc9743 100644 --- a/src/scales/scale.time.js +++ b/src/scales/scale.time.js @@ -176,6 +176,16 @@ module.exports = function(Chart) { Chart.Scale.prototype.initialize.call(this); }, + /** + * Allows data to be referenced via 't' attribute + */ + getRightValue: function(rawValue) { + if (rawValue && rawValue.t !== undefined) { + rawValue = rawValue.t; + } + return Chart.Scale.prototype.getRightValue.call(this, rawValue); + }, + determineDataLimits: function() { var me = this; var chart = me.chart; diff --git a/test/specs/scale.time.tests.js b/test/specs/scale.time.tests.js index c5ca74fa9..057521ab4 100755 --- a/test/specs/scale.time.tests.js +++ b/test/specs/scale.time.tests.js @@ -205,6 +205,54 @@ describe('Time scale tests', function() { expect(ticks).toEqual(['Jan 2015', 'Jan 2', 'Jan 3', 'Jan 4', 'Jan 5', 'Jan 6', 'Jan 7', 'Jan 8', 'Jan 9', 'Jan 10', 'Jan 11']); }); + + it('should accept data as ty points', function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + xAxisID: 'tScale0', + data: [{ + t: newDateFromRef(0), + y: 1 + }, { + t: newDateFromRef(1), + y: 10 + }, { + t: newDateFromRef(2), + y: 0 + }, { + t: newDateFromRef(4), + y: 5 + }, { + t: newDateFromRef(6), + y: 77 + }, { + t: newDateFromRef(7), + y: 9 + }, { + t: newDateFromRef(9), + y: 5 + }] + }], + }, + options: { + scales: { + xAxes: [{ + id: 'tScale0', + type: 'time', + position: 'bottom' + }], + } + } + }); + + var tScale = chart.scales.tScale0; + tScale.update(800, 200); + var ticks = getTicksValues(tScale.ticks); + + expect(ticks).toEqual(['Jan 2015', 'Jan 2', 'Jan 3', 'Jan 4', 'Jan 5', 'Jan 6', 'Jan 7', 'Jan 8', 'Jan 9', 'Jan 10', 'Jan 11']); + }); }); it('should allow custom time parsers', function() { From f6b6956a3aeb341fb3987a8ddb6c76fb9a37999c Mon Sep 17 00:00:00 2001 From: Ben McCann Date: Sat, 22 Jul 2017 00:19:06 -0700 Subject: [PATCH 068/112] Fix ESLint errors (#4485) --- src/chart.js | 6 +++--- src/controllers/controller.doughnut.js | 2 +- src/core/core.controller.js | 6 +++--- src/core/core.scale.js | 6 +++--- src/scales/scale.logarithmic.js | 2 +- src/scales/scale.radialLinear.js | 6 +++--- test/jasmine.utils.js | 2 +- test/specs/controller.polarArea.tests.js | 4 ++-- test/specs/scale.category.tests.js | 2 +- test/specs/scale.logarithmic.tests.js | 24 ++++++++++++------------ 10 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/chart.js b/src/chart.js index 56fc8f24c..9b50728a2 100644 --- a/src/chart.js +++ b/src/chart.js @@ -53,9 +53,9 @@ require('./charts/Chart.Scatter')(Chart); var plugins = []; plugins.push( - require('./plugins/plugin.filler')(Chart), - require('./plugins/plugin.legend')(Chart), - require('./plugins/plugin.title')(Chart) + require('./plugins/plugin.filler')(Chart), + require('./plugins/plugin.legend')(Chart), + require('./plugins/plugin.title')(Chart) ); Chart.plugins.register(plugins); diff --git a/src/controllers/controller.doughnut.js b/src/controllers/controller.doughnut.js index 8c472fe30..500c83c7c 100644 --- a/src/controllers/controller.doughnut.js +++ b/src/controllers/controller.doughnut.js @@ -168,7 +168,7 @@ module.exports = function(Chart) { var endAngle = startAngle + circumference; var start = {x: Math.cos(startAngle), y: Math.sin(startAngle)}; var end = {x: Math.cos(endAngle), y: Math.sin(endAngle)}; - var contains0 = (startAngle <= 0 && 0 <= endAngle) || (startAngle <= Math.PI * 2.0 && Math.PI * 2.0 <= endAngle); + var contains0 = (startAngle <= 0 && endAngle >= 0) || (startAngle <= Math.PI * 2.0 && Math.PI * 2.0 <= endAngle); var contains90 = (startAngle <= Math.PI * 0.5 && Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 2.5 && Math.PI * 2.5 <= endAngle); var contains180 = (startAngle <= -Math.PI && -Math.PI <= endAngle) || (startAngle <= Math.PI && Math.PI <= endAngle); var contains270 = (startAngle <= -Math.PI * 0.5 && -Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 1.5 && Math.PI * 1.5 <= endAngle); diff --git a/src/core/core.controller.js b/src/core/core.controller.js index 2033c5425..54de51d77 100644 --- a/src/core/core.controller.js +++ b/src/core/core.controller.js @@ -95,7 +95,7 @@ module.exports = function(Chart) { * @private */ me.chart = me; - me.controller = me; // chart.chart.controller #inception + me.controller = me; // chart.chart.controller #inception // Add the chart instance to the global namespace Chart.instances[me.id] = me; @@ -172,7 +172,7 @@ module.exports = function(Chart) { // the canvas render width and height will be casted to integers so make sure that // the canvas display style uses the same integer values to avoid blurring effect. - // Set to 0 instead of canvas.size because the size defaults to 300x150 if the element is collased + // Set to 0 instead of canvas.size because the size defaults to 300x150 if the element is collased var newWidth = Math.max(0, Math.floor(helpers.getMaximumWidth(canvas))); var newHeight = Math.max(0, Math.floor(aspectRatio ? newWidth / aspectRatio : helpers.getMaximumHeight(canvas))); @@ -709,7 +709,7 @@ module.exports = function(Chart) { var me = this; me.tooltip = new Chart.Tooltip({ _chart: me, - _chartInstance: me, // deprecated, backward compatibility + _chartInstance: me, // deprecated, backward compatibility _data: me.data, _options: me.options.tooltips }, me); diff --git a/src/core/core.scale.js b/src/core/core.scale.js index 27a0f0596..18c04bd99 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -509,9 +509,9 @@ module.exports = function(Chart) { var min = me.min; var max = me.max; - return me.beginAtZero ? 0: - min < 0 && max < 0? max : - min > 0 && max > 0? min : + return me.beginAtZero ? 0 : + min < 0 && max < 0 ? max : + min > 0 && max > 0 ? min : 0; }, diff --git a/src/scales/scale.logarithmic.js b/src/scales/scale.logarithmic.js index a6a0606ad..3d2b81c4e 100644 --- a/src/scales/scale.logarithmic.js +++ b/src/scales/scale.logarithmic.js @@ -235,7 +235,7 @@ module.exports = function(Chart) { if (me.isHorizontal()) { innerDimension = me.width; value = me.start * Math.pow(10, (pixel - me.left) * range / innerDimension); - } else { // todo: if start === 0 + } else { // todo: if start === 0 innerDimension = me.height; value = Math.pow(10, (me.bottom - pixel) * range / innerDimension) / me.start; } diff --git a/src/scales/scale.radialLinear.js b/src/scales/scale.radialLinear.js index f1194fd82..7b5f5a537 100644 --- a/src/scales/scale.radialLinear.js +++ b/src/scales/scale.radialLinear.js @@ -460,9 +460,9 @@ module.exports = function(Chart) { var max = me.max; return me.getPointPositionForValue(0, - me.beginAtZero? 0: - min < 0 && max < 0? max : - min > 0 && max > 0? min : + me.beginAtZero ? 0 : + min < 0 && max < 0 ? max : + min > 0 && max > 0 ? min : 0); }, diff --git a/test/jasmine.utils.js b/test/jasmine.utils.js index f5ead49ec..8d27485bf 100644 --- a/test/jasmine.utils.js +++ b/test/jasmine.utils.js @@ -98,7 +98,7 @@ function injectCSS(css) { var head = document.getElementsByTagName('head')[0]; var style = document.createElement('style'); style.setAttribute('type', 'text/css'); - if (style.styleSheet) { // IE + if (style.styleSheet) { // IE style.styleSheet.cssText = css; } else { style.appendChild(document.createTextNode(css)); diff --git a/test/specs/controller.polarArea.tests.js b/test/specs/controller.polarArea.tests.js index de7d4acfa..636473cab 100644 --- a/test/specs/controller.polarArea.tests.js +++ b/test/specs/controller.polarArea.tests.js @@ -5,8 +5,8 @@ describe('Polar area controller tests', function() { type: 'polarArea', data: { datasets: [ - {data: []}, - {data: []} + {data: []}, + {data: []} ], labels: [] } diff --git a/test/specs/scale.category.tests.js b/test/specs/scale.category.tests.js index c11f75196..e7774bf44 100644 --- a/test/specs/scale.category.tests.js +++ b/test/specs/scale.category.tests.js @@ -42,7 +42,7 @@ describe('Category scale tests', function() { padding: 0, reverse: false, display: true, - callback: defaultConfig.ticks.callback, // make this nicer, then check explicitly below + callback: defaultConfig.ticks.callback, // make this nicer, then check explicitly below autoSkip: true, autoSkipPadding: 0, labelOffset: 0, diff --git a/test/specs/scale.logarithmic.tests.js b/test/specs/scale.logarithmic.tests.js index 16ab3d780..863f74772 100644 --- a/test/specs/scale.logarithmic.tests.js +++ b/test/specs/scale.logarithmic.tests.js @@ -718,9 +718,9 @@ describe('Logarithmic Scale tests', function() { }); var xScale = chart.scales.xScale; - expect(xScale.getPixelForValue(80, 0, 0)).toBeCloseToPixel(495); // right - paddingRight + expect(xScale.getPixelForValue(80, 0, 0)).toBeCloseToPixel(495); // right - paddingRight expect(xScale.getPixelForValue(1, 0, 0)).toBeCloseToPixel(37); // left + paddingLeft - expect(xScale.getPixelForValue(10, 0, 0)).toBeCloseToPixel(278); // halfway + expect(xScale.getPixelForValue(10, 0, 0)).toBeCloseToPixel(278); // halfway expect(xScale.getPixelForValue(0, 0, 0)).toBeCloseToPixel(37); // 0 is invalid, put it on the left. expect(xScale.getValueForPixel(495)).toBeCloseToPixel(80); @@ -728,10 +728,10 @@ describe('Logarithmic Scale tests', function() { expect(xScale.getValueForPixel(278)).toBeCloseTo(10, 1e-4); var yScale = chart.scales.yScale; - expect(yScale.getPixelForValue(80, 0, 0)).toBeCloseToPixel(32); // top + paddingTop + 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(yScale.getPixelForValue(10, 0, 0)).toBeCloseToPixel(246); // halfway + expect(yScale.getPixelForValue(0, 0, 0)).toBeCloseToPixel(484); // 0 is invalid. force it on bottom expect(yScale.getValueForPixel(32)).toBeCloseTo(80, 1e-4); expect(yScale.getValueForPixel(484)).toBeCloseTo(1, 1e-4); @@ -762,20 +762,20 @@ describe('Logarithmic Scale tests', function() { }); 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(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); - chart.options.scales.yAxes[0].ticks.reverse = true; // Reverse mode + chart.options.scales.yAxes[0].ticks.reverse = true; // Reverse mode chart.update(); - 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(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); From 4c763bff44345ff5bbf2acc97cbce8918fa5b055 Mon Sep 17 00:00:00 2001 From: Simon Brunel Date: Sat, 22 Jul 2017 14:13:09 +0200 Subject: [PATCH 069/112] Enforce spaces around infix/unary words operators (#4547) Enable ESLint `space-infix-ops` and `space-unary-ops` (for words only) rules. Also added `samples` to the linting task to match Code Climate expectations. http://eslint.org/docs/rules/space-infix-ops http://eslint.org/docs/rules/space-unary-ops --- .eslintrc | 4 ++-- gulpfile.js | 10 ++++---- samples/charts/area/analyser.js | 2 +- samples/utils.js | 16 ++++++------- src/controllers/controller.bar.js | 18 +++++++------- src/controllers/controller.line.js | 6 ++--- src/core/core.animation.js | 2 +- src/core/core.controller.js | 14 +++++------ src/core/core.datasetController.js | 10 ++++---- src/core/core.element.js | 6 ++--- src/core/core.helpers.js | 22 ++++++++--------- src/core/core.layoutService.js | 4 ++-- src/core/core.plugin.js | 2 +- src/core/core.scale.js | 14 +++++------ src/elements/element.point.js | 8 +++---- src/elements/element.rectangle.js | 14 +++++------ src/helpers/helpers.canvas.js | 12 +++++----- src/helpers/helpers.core.js | 16 ++++++------- src/helpers/helpers.options.js | 2 +- src/helpers/helpers.time.js | 4 ++-- src/platforms/platform.dom.js | 32 ++++++++++++------------- src/plugins/plugin.filler.js | 32 ++++++++++++------------- src/plugins/plugin.legend.js | 2 +- src/plugins/plugin.title.js | 2 +- src/scales/scale.logarithmic.js | 4 ++-- src/scales/scale.radialLinear.js | 2 +- src/scales/scale.time.js | 8 +++---- test/jasmine.matchers.js | 10 ++++---- test/jasmine.utils.js | 4 ++-- test/specs/controller.bubble.tests.js | 2 +- test/specs/core.controller.tests.js | 2 +- test/specs/global.deprecations.tests.js | 4 ++-- test/specs/helpers.core.tests.js | 2 +- test/specs/helpers.easing.tests.js | 4 ++-- test/specs/platform.dom.tests.js | 2 +- 35 files changed, 149 insertions(+), 149 deletions(-) diff --git a/.eslintrc b/.eslintrc index fdf1951dd..aa4cdbb3b 100644 --- a/.eslintrc +++ b/.eslintrc @@ -197,8 +197,8 @@ rules: space-before-blocks: [2, always] space-before-function-paren: [2, never] space-in-parens: [2, never] - space-infix-ops: 0 - space-unary-ops: 0 + space-infix-ops: 2 + space-unary-ops: [2, {words: true, nonwords: false}] spaced-comment: [2, always] unicode-bom: 0 wrap-regex: 2 diff --git a/gulpfile.js b/gulpfile.js index ff3cce5b6..2ae49d77d 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -23,7 +23,6 @@ var package = require('./package.json'); var srcDir = './src/'; var outDir = './dist/'; -var testDir = './test/'; var header = "/*!\n" + " * Chart.js\n" + @@ -128,8 +127,9 @@ function packageTask() { function lintTask() { var files = [ - srcDir + '**/*.js', - testDir + '**/*.js' + 'samples/**/*.js', + 'src/**/*.js', + 'test/**/*.js' ]; // NOTE(SB) codeclimate has 'complexity' and 'max-statements' eslint rules way too strict @@ -174,8 +174,8 @@ function startTest() { './test/jasmine.index.js', './src/**/*.js', ].concat( - argv.inputs? - argv.inputs.split(';'): + argv.inputs ? + argv.inputs.split(';') : ['./test/specs/**/*.js'] ); } diff --git a/samples/charts/area/analyser.js b/samples/charts/area/analyser.js index f9d001af5..e4ed8e90b 100644 --- a/samples/charts/area/analyser.js +++ b/samples/charts/area/analyser.js @@ -20,7 +20,7 @@ return; } - for (i=0, ilen=datasets.length; i= 0; --i) { + for (var i = (me.data.datasets || []).length - 1; i >= 0; --i) { if (me.isDatasetVisible(i)) { me.drawDataset(i, easingValue); } @@ -652,7 +652,7 @@ module.exports = function(Chart) { getVisibleDatasetCount: function() { var count = 0; - for (var i = 0, ilen = this.data.datasets.length; i