From dfbb57468f8cb4f670d99f26050fb499c7068294 Mon Sep 17 00:00:00 2001 From: Niladri Dutta Date: Wed, 17 Oct 2018 02:42:51 +0530 Subject: [PATCH 001/137] Add 'parser' instead of deprecated 'format' in samples (#5769) --- samples/scales/time/line.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/scales/time/line.html b/samples/scales/time/line.html index 70a6978af18..77c81b7aa1b 100644 --- a/samples/scales/time/line.html +++ b/samples/scales/time/line.html @@ -106,7 +106,7 @@ xAxes: [{ type: 'time', time: { - format: timeFormat, + parser: timeFormat, // round: 'day' tooltipFormat: 'll HH:mm' }, From f815dd51968a0bec9305e1cf14d738d6f23c93aa Mon Sep 17 00:00:00 2001 From: Evert Timberg Date: Thu, 18 Oct 2018 15:28:49 -0400 Subject: [PATCH 002/137] Ensure that when the time axis accesses `data.labels` it actually exists (#5750) --- 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 2496a487c3e..405d8ff8c80 100644 --- a/src/scales/scale.time.js +++ b/src/scales/scale.time.js @@ -534,10 +534,11 @@ module.exports = function() { var datasets = []; var labels = []; var i, j, ilen, jlen, data, timestamp; + var dataLabels = chart.data.labels || []; // Convert labels to timestamps - for (i = 0, ilen = chart.data.labels.length; i < ilen; ++i) { - labels.push(parse(chart.data.labels[i], me)); + for (i = 0, ilen = dataLabels.length; i < ilen; ++i) { + labels.push(parse(dataLabels[i], me)); } // Convert data to timestamps From 5816770e459284847be10d1d0c00e5a4ee2b8434 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20Dub=C3=A9?= Date: Thu, 18 Oct 2018 16:28:56 -0400 Subject: [PATCH 003/137] Introduce the 'minBarLength' bar option (#5741) --- docs/charts/bar.md | 1 + src/controllers/controller.bar.js | 11 +++++++ test/specs/scale.linear.tests.js | 48 +++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+) diff --git a/docs/charts/bar.md b/docs/charts/bar.md index e4d146b2c67..eaaad0b58ce 100644 --- a/docs/charts/bar.md +++ b/docs/charts/bar.md @@ -97,6 +97,7 @@ The bar chart defines the following configuration options. These options are mer | `categoryPercentage` | `Number` | `0.8` | Percent (0-1) of the available width each category should be within the sample width. [more...](#barpercentage-vs-categorypercentage) | `barThickness` | `Number/String` | | Manually set width of each bar in pixels. If set to `'flex'`, it computes "optimal" sample widths that globally arrange bars side by side. If not set (default), bars are equally sized based on the smallest interval. [more...](#barthickness) | `maxBarThickness` | `Number` | | Set this to ensure that bars are not sized thicker than this. +| `minBarLength` | `Number` | | Set this to ensure that bars have a minimum length in pixels. | `gridLines.offsetGridLines` | `Boolean` | `true` | If true, the bars for a particular data point fall between the grid lines. The grid line will move to the left by one half of the tick interval. If false, the grid line will go right down the middle of the bars. [more...](#offsetgridlines) ### barThickness diff --git a/src/controllers/controller.bar.js b/src/controllers/controller.bar.js index cadae3fdb6c..74e6d2289f0 100644 --- a/src/controllers/controller.bar.js +++ b/src/controllers/controller.bar.js @@ -382,8 +382,10 @@ module.exports = function(Chart) { var chart = me.chart; var meta = me.getMeta(); var scale = me.getValueScale(); + var isHorizontal = scale.isHorizontal(); var datasets = chart.data.datasets; var value = scale.getRightValue(datasets[datasetIndex].data[index]); + var minBarLength = scale.options.minBarLength; var stacked = scale.options.stacked; var stack = meta.stack; var start = 0; @@ -410,6 +412,15 @@ module.exports = function(Chart) { head = scale.getPixelForValue(start + value); size = (head - base) / 2; + if (minBarLength !== undefined && Math.abs(size) < minBarLength) { + size = minBarLength; + if (value >= 0 && !isHorizontal || value < 0 && isHorizontal) { + head = base - minBarLength; + } else { + head = base + minBarLength; + } + } + return { size: size, base: base, diff --git a/test/specs/scale.linear.tests.js b/test/specs/scale.linear.tests.js index 380feaac09d..959a76db01e 100644 --- a/test/specs/scale.linear.tests.js +++ b/test/specs/scale.linear.tests.js @@ -1046,4 +1046,52 @@ describe('Linear Scale', function() { expect(chart.scales['x-axis-0'].max).toEqual(0); }); + + it('minBarLength settings should be used on Y axis on bar chart', function() { + var minBarLength = 4; + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + data: [0.05, -0.05, 10, 15, 20, 25, 30, 35] + }] + }, + options: { + scales: { + yAxes: [{ + minBarLength: minBarLength + }] + } + } + }); + + var data = chart.getDatasetMeta(0).data; + + expect(data[0]._model.base - minBarLength).toEqual(data[0]._model.y); + expect(data[1]._model.base + minBarLength).toEqual(data[1]._model.y); + }); + + it('minBarLength settings should be used on X axis on horizontalBar chart', function() { + var minBarLength = 4; + var chart = window.acquireChart({ + type: 'horizontalBar', + data: { + datasets: [{ + data: [0.05, -0.05, 10, 15, 20, 25, 30, 35] + }] + }, + options: { + scales: { + xAxes: [{ + minBarLength: minBarLength + }] + } + } + }); + + var data = chart.getDatasetMeta(0).data; + + expect(data[0]._model.base + minBarLength).toEqual(data[0]._model.x); + expect(data[1]._model.base - minBarLength).toEqual(data[1]._model.x); + }); }); From 2dbf1cd1af69f494f971c3603063e4265fb10e88 Mon Sep 17 00:00:00 2001 From: Simon Brunel Date: Sat, 20 Oct 2018 11:38:48 +0200 Subject: [PATCH 004/137] Add support for *.js test fixture config (#5777) JSON doesn't support functions which are needed to create scriptable options, so implement a very basic method to load a JavaScript file exporting the config in `module.exports`. Also rename test sources (remove the `jasmine.` prefix), cleanup `karma.conf.js` and add an example .js fixture config (bubble radius option). --- gulpfile.js | 20 +---- karma.conf.js | 13 ++- test/{jasmine.context.js => context.js} | 0 test/fixture.js | 85 ++++++++++++++++++ .../controller.bubble/radius-scriptable.js | 45 ++++++++++ .../controller.bubble/radius-scriptable.png | Bin 0 -> 3989 bytes test/{jasmine.index.js => index.js} | 11 ++- test/{jasmine.matchers.js => matchers.js} | 2 +- test/specs/controller.bar.tests.js | 2 +- test/specs/controller.bubble.tests.js | 2 +- test/specs/controller.line.tests.js | 2 +- test/specs/controller.polarArea.tests.js | 2 +- test/specs/controller.radar.tests.js | 2 +- test/specs/core.scale.tests.js | 2 +- test/specs/element.point.tests.js | 2 +- test/specs/plugin.filler.tests.js | 2 +- test/{jasmine.utils.js => utils.js} | 57 +----------- 17 files changed, 162 insertions(+), 87 deletions(-) rename test/{jasmine.context.js => context.js} (100%) create mode 100644 test/fixture.js create mode 100644 test/fixtures/controller.bubble/radius-scriptable.js create mode 100644 test/fixtures/controller.bubble/radius-scriptable.png rename test/{jasmine.index.js => index.js} (88%) rename test/{jasmine.matchers.js => matchers.js} (99%) rename test/{jasmine.utils.js => utils.js} (72%) diff --git a/gulpfile.js b/gulpfile.js index f6795fa7f56..daba620f0f5 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -198,27 +198,15 @@ function docsTask(done) { }); } -function startTest() { - return [ - {pattern: './test/fixtures/**/*.json', included: false}, - {pattern: './test/fixtures/**/*.png', included: false}, - './node_modules/moment/min/moment.min.js', - './test/jasmine.index.js', - './src/**/*.js', - ].concat( - argv.inputs ? - argv.inputs.split(';') : - ['./test/specs/**/*.js'] - ); -} - function unittestTask(done) { new karma.Server({ configFile: path.join(__dirname, 'karma.conf.js'), singleRun: !argv.watch, - files: startTest(), args: { - coverage: !!argv.coverage + coverage: !!argv.coverage, + inputs: argv.inputs + ? argv.inputs.split(';') + : ['./test/specs/**/*.js'] } }, // https://github.com/karma-runner/gulp-karma/issues/18 diff --git a/karma.conf.js b/karma.conf.js index 5bb73ef03a0..4b3a301f178 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -25,9 +25,18 @@ module.exports = function(karma) { } }, + files: [ + {pattern: 'test/fixtures/**/*.js', included: false}, + {pattern: 'test/fixtures/**/*.json', included: false}, + {pattern: 'test/fixtures/**/*.png', included: false}, + 'node_modules/moment/min/moment.min.js', + 'test/index.js', + 'src/**/*.js' + ].concat(args.inputs), + preprocessors: { - './test/jasmine.index.js': ['browserify'], - './src/**/*.js': ['browserify'] + 'test/index.js': ['browserify'], + 'src/**/*.js': ['browserify'] }, browserify: { diff --git a/test/jasmine.context.js b/test/context.js similarity index 100% rename from test/jasmine.context.js rename to test/context.js diff --git a/test/fixture.js b/test/fixture.js new file mode 100644 index 00000000000..5652c13e944 --- /dev/null +++ b/test/fixture.js @@ -0,0 +1,85 @@ +/* global __karma__ */ + +'use strict'; + +var utils = require('./utils'); + +function readFile(url, callback) { + var request = new XMLHttpRequest(); + request.onreadystatechange = function() { + if (request.readyState === 4) { + return callback(request.responseText); + } + }; + + request.open('GET', url, false); + request.send(null); +} + +function loadConfig(url, callback) { + var regex = /\.(json|js)$/i; + var matches = url.match(regex); + var type = matches ? matches[1] : 'json'; + var cfg = null; + + readFile(url, function(content) { + switch (type) { + case 'js': + // eslint-disable-next-line + cfg = new Function('"use strict"; var module = {};' + content + '; return module.exports;')(); + break; + case 'json': + cfg = JSON.parse(content); + break; + default: + } + + callback(cfg); + }); +} + +function specFromFixture(description, inputs) { + var input = inputs.js || inputs.json; + it(input, function(done) { + loadConfig(input, function(json) { + var chart = utils.acquireChart(json.config, json.options); + if (!inputs.png) { + fail('Missing PNG comparison file for ' + inputs.json); + done(); + } + + utils.readImageData(inputs.png, function(expected) { + expect(chart).toEqualImageData(expected, json); + utils.releaseChart(chart); + done(); + }); + }); + }); +} + +function specsFromFixtures(path) { + var regex = new RegExp('(^/base/test/fixtures/' + path + '.+)\\.(png|json|js)'); + var inputs = {}; + + Object.keys(__karma__.files || {}).forEach(function(file) { + var matches = file.match(regex); + var name = matches && matches[1]; + var type = matches && matches[2]; + + if (name && type) { + inputs[name] = inputs[name] || {}; + inputs[name][type] = file; + } + }); + + return function() { + Object.keys(inputs).forEach(function(key) { + specFromFixture(key, inputs[key]); + }); + }; +} + +module.exports = { + specs: specsFromFixtures +}; + diff --git a/test/fixtures/controller.bubble/radius-scriptable.js b/test/fixtures/controller.bubble/radius-scriptable.js new file mode 100644 index 00000000000..593316b3c73 --- /dev/null +++ b/test/fixtures/controller.bubble/radius-scriptable.js @@ -0,0 +1,45 @@ +module.exports = { + config: { + type: 'bubble', + data: { + datasets: [{ + data: [ + {x: 0, y: 0}, + {x: 1, y: 0}, + {x: 2, y: 0}, + {x: 3, y: 0}, + {x: 4, y: 0}, + {x: 5, y: 0} + ], + radius: function(ctx) { + return ctx.dataset.data[ctx.dataIndex].x * 4; + } + }] + }, + options: { + legend: false, + title: false, + scales: { + xAxes: [{display: false}], + yAxes: [{display: false}] + }, + elements: { + point: { + backgroundColor: '#444' + } + }, + layout: { + padding: { + left: 24, + right: 24 + } + } + } + }, + options: { + canvas: { + height: 128, + width: 256 + } + } +}; diff --git a/test/fixtures/controller.bubble/radius-scriptable.png b/test/fixtures/controller.bubble/radius-scriptable.png new file mode 100644 index 0000000000000000000000000000000000000000..546466f7192830b3be443775f1dadba6d202e158 GIT binary patch literal 3989 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 O8!$1nG^o*YOZpGX%prFG literal 0 HcmV?d00001 diff --git a/test/jasmine.index.js b/test/index.js similarity index 88% rename from test/jasmine.index.js rename to test/index.js index c1706130f2d..b3fa6cf3b60 100644 --- a/test/jasmine.index.js +++ b/test/index.js @@ -1,6 +1,9 @@ -var Context = require('./jasmine.context'); -var matchers = require('./jasmine.matchers'); -var utils = require('./jasmine.utils'); +'use strict'; + +var fixture = require('./fixture'); +var Context = require('./context'); +var matchers = require('./matchers'); +var utils = require('./utils'); (function() { @@ -42,7 +45,7 @@ var utils = require('./jasmine.utils'); 'position: absolute' + '}'); - jasmine.specsFromFixtures = utils.specsFromFixtures; + jasmine.fixture = fixture; jasmine.triggerMouseEvent = utils.triggerMouseEvent; beforeEach(function() { diff --git a/test/jasmine.matchers.js b/test/matchers.js similarity index 99% rename from test/jasmine.matchers.js rename to test/matchers.js index 88f5de8fe7e..92a6f970427 100644 --- a/test/jasmine.matchers.js +++ b/test/matchers.js @@ -1,7 +1,7 @@ 'use strict'; var pixelmatch = require('pixelmatch'); -var utils = require('./jasmine.utils'); +var utils = require('./utils'); function toPercent(value) { return Math.round(value * 10000) / 100; diff --git a/test/specs/controller.bar.tests.js b/test/specs/controller.bar.tests.js index 7957b6ab5db..0e843e1435f 100644 --- a/test/specs/controller.bar.tests.js +++ b/test/specs/controller.bar.tests.js @@ -1,5 +1,5 @@ describe('Chart.controllers.bar', function() { - describe('auto', jasmine.specsFromFixtures('controller.bar')); + describe('auto', jasmine.fixture.specs('controller.bar')); it('should be constructed', function() { var chart = window.acquireChart({ diff --git a/test/specs/controller.bubble.tests.js b/test/specs/controller.bubble.tests.js index 2597b9ae641..a5f5b89a5de 100644 --- a/test/specs/controller.bubble.tests.js +++ b/test/specs/controller.bubble.tests.js @@ -1,5 +1,5 @@ describe('Chart.controllers.bubble', function() { - describe('auto', jasmine.specsFromFixtures('controller.bubble')); + describe('auto', jasmine.fixture.specs('controller.bubble')); it('should be constructed', function() { var chart = window.acquireChart({ diff --git a/test/specs/controller.line.tests.js b/test/specs/controller.line.tests.js index ed8f4ec5c80..25a9ed6b0f5 100644 --- a/test/specs/controller.line.tests.js +++ b/test/specs/controller.line.tests.js @@ -1,5 +1,5 @@ describe('Chart.controllers.line', function() { - describe('auto', jasmine.specsFromFixtures('controller.line')); + describe('auto', jasmine.fixture.specs('controller.line')); it('should be constructed', function() { var chart = window.acquireChart({ diff --git a/test/specs/controller.polarArea.tests.js b/test/specs/controller.polarArea.tests.js index 11e0be0b7d3..b706f8376a6 100644 --- a/test/specs/controller.polarArea.tests.js +++ b/test/specs/controller.polarArea.tests.js @@ -1,4 +1,4 @@ -describe('auto', jasmine.specsFromFixtures('controller.polarArea')); +describe('auto', jasmine.fixture.specs('controller.polarArea')); describe('Chart.controllers.polarArea', function() { it('should be constructed', function() { diff --git a/test/specs/controller.radar.tests.js b/test/specs/controller.radar.tests.js index 2bc0a2e0d24..7e85e9e7eac 100644 --- a/test/specs/controller.radar.tests.js +++ b/test/specs/controller.radar.tests.js @@ -1,5 +1,5 @@ describe('Chart.controllers.radar', function() { - describe('auto', jasmine.specsFromFixtures('controller.radar')); + describe('auto', jasmine.fixture.specs('controller.radar')); it('Should be constructed', function() { var chart = window.acquireChart({ diff --git a/test/specs/core.scale.tests.js b/test/specs/core.scale.tests.js index dc2da042aca..2981c35c9b4 100644 --- a/test/specs/core.scale.tests.js +++ b/test/specs/core.scale.tests.js @@ -1,5 +1,5 @@ describe('Core.scale', function() { - describe('auto', jasmine.specsFromFixtures('core.scale')); + describe('auto', jasmine.fixture.specs('core.scale')); it('should provide default scale label options', function() { expect(Chart.defaults.scale.scaleLabel).toEqual({ diff --git a/test/specs/element.point.tests.js b/test/specs/element.point.tests.js index 8d1227003b9..0f3a03e319d 100644 --- a/test/specs/element.point.tests.js +++ b/test/specs/element.point.tests.js @@ -1,5 +1,5 @@ describe('Chart.elements.Point', function() { - describe('auto', jasmine.specsFromFixtures('element.point')); + describe('auto', jasmine.fixture.specs('element.point')); it ('Should be constructed', function() { var point = new Chart.elements.Point({ diff --git a/test/specs/plugin.filler.tests.js b/test/specs/plugin.filler.tests.js index 117f680360e..94979375832 100644 --- a/test/specs/plugin.filler.tests.js +++ b/test/specs/plugin.filler.tests.js @@ -7,7 +7,7 @@ describe('Plugin.filler', function() { }); } - describe('auto', jasmine.specsFromFixtures('plugin.filler')); + describe('auto', jasmine.fixture.specs('plugin.filler')); describe('dataset.fill', function() { it('should support boundaries', function() { diff --git a/test/jasmine.utils.js b/test/utils.js similarity index 72% rename from test/jasmine.utils.js rename to test/utils.js index 2c2006a2c07..80430d4d5bc 100644 --- a/test/jasmine.utils.js +++ b/test/utils.js @@ -1,18 +1,3 @@ -/* global __karma__ */ - -function loadJSON(url, callback) { - var request = new XMLHttpRequest(); - request.onreadystatechange = function() { - if (request.readyState === 4) { - return callback(JSON.parse(request.responseText)); - } - }; - - request.overrideMimeType('application/json'); - request.open('GET', url, true); - request.send(null); -} - function createCanvas(w, h) { var canvas = document.createElement('canvas'); canvas.width = w; @@ -112,46 +97,6 @@ function injectCSS(css) { head.appendChild(style); } -function specFromFixture(description, inputs) { - it(inputs.json, function(done) { - loadJSON(inputs.json, function(json) { - var chart = acquireChart(json.config, json.options); - if (!inputs.png) { - fail('Missing PNG comparison file for ' + inputs.json); - done(); - } - - readImageData(inputs.png, function(expected) { - expect(chart).toEqualImageData(expected, json); - releaseChart(chart); - done(); - }); - }); - }); -} - -function specsFromFixtures(path) { - var regex = new RegExp('(^/base/test/fixtures/' + path + '.+)\\.(png|json)'); - var inputs = {}; - - Object.keys(__karma__.files || {}).forEach(function(file) { - var matches = file.match(regex); - var name = matches && matches[1]; - var type = matches && matches[2]; - - if (name && type) { - inputs[name] = inputs[name] || {}; - inputs[name][type] = file; - } - }); - - return function() { - Object.keys(inputs).forEach(function(key) { - specFromFixture(key, inputs[key]); - }); - }; -} - function waitForResize(chart, callback) { var override = chart.resize; chart.resize = function() { @@ -180,7 +125,7 @@ module.exports = { createCanvas: createCanvas, acquireChart: acquireChart, releaseChart: releaseChart, - specsFromFixtures: specsFromFixtures, + readImageData: readImageData, triggerMouseEvent: triggerMouseEvent, waitForResize: waitForResize }; From 81b4b87666e2bdb6d7cc58d678002358adb72aba Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Sun, 21 Oct 2018 12:56:57 -0700 Subject: [PATCH 005/137] Radar code cleanup (#5624) --- src/controllers/controller.radar.js | 49 +++++++++++++++++------------ 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/src/controllers/controller.radar.js b/src/controllers/controller.radar.js index 89717157a37..dc2b86c617b 100644 --- a/src/controllers/controller.radar.js +++ b/src/controllers/controller.radar.js @@ -29,11 +29,12 @@ module.exports = function(Chart) { var me = this; var meta = me.getMeta(); var line = meta.dataset; - var points = meta.data; + var points = meta.data || []; var custom = line.custom || {}; var dataset = me.getDataset(); var lineElementOptions = me.chart.options.elements.line; var scale = me.chart.scale; + var i, ilen; // Compatibility: If the properties are defined with only the old name, use those values if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) { @@ -65,12 +66,17 @@ module.exports = function(Chart) { meta.dataset.pivot(); // Update Points - helpers.each(points, function(point, index) { - me.updateElement(point, index, reset); - }, me); + for (i = 0, ilen = points.length; i < ilen; i++) { + me.updateElement(points[i], i, reset); + } // Update bezier control points me.updateBezierControlPoints(); + + // Now pivot the point for animation + for (i = 0, ilen = points.length; i < ilen; i++) { + points[i].pivot(); + } }, updateElement: function(point, index, reset) { var me = this; @@ -116,28 +122,31 @@ module.exports = function(Chart) { point._model.skip = custom.skip ? custom.skip : (isNaN(point._model.x) || isNaN(point._model.y)); }, updateBezierControlPoints: function() { - var chartArea = this.chart.chartArea; - var meta = this.getMeta(); + var me = this; + var meta = me.getMeta(); + var area = me.chart.chartArea; + var points = meta.data || []; + var i, ilen, model, controlPoints; - helpers.each(meta.data, function(point, index) { - var model = point._model; - var controlPoints = helpers.splineCurve( - helpers.previousItem(meta.data, index, true)._model, + function capControlPoint(pt, min, max) { + return Math.max(Math.min(pt, max), min); + } + + for (i = 0, ilen = points.length; i < ilen; i++) { + model = points[i]._model; + controlPoints = helpers.splineCurve( + helpers.previousItem(points, i, true)._model, model, - helpers.nextItem(meta.data, index, true)._model, + helpers.nextItem(points, i, true)._model, model.tension ); // Prevent the bezier going outside of the bounds of the graph - model.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, chartArea.right), chartArea.left); - model.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, chartArea.bottom), chartArea.top); - - model.controlPointNextX = Math.max(Math.min(controlPoints.next.x, chartArea.right), chartArea.left); - model.controlPointNextY = Math.max(Math.min(controlPoints.next.y, chartArea.bottom), chartArea.top); - - // Now pivot the point for animation - point.pivot(); - }); + model.controlPointPreviousX = capControlPoint(controlPoints.previous.x, area.left, area.right); + model.controlPointPreviousY = capControlPoint(controlPoints.previous.y, area.top, area.bottom); + model.controlPointNextX = capControlPoint(controlPoints.next.x, area.left, area.right); + model.controlPointNextY = capControlPoint(controlPoints.next.y, area.top, area.bottom); + } }, setHoverStyle: function(point) { From b3b5c7de4f8780be009619d1732a1915c81f0ea9 Mon Sep 17 00:00:00 2001 From: Evert Timberg Date: Mon, 22 Oct 2018 03:52:05 -0400 Subject: [PATCH 006/137] Ensure that when we check typeof x == 'number' we also check instanceof Number (#5752) --- src/core/core.element.js | 2 +- src/core/core.scale.js | 2 +- src/helpers/helpers.core.js | 9 +++++++++ src/plugins/plugin.filler.js | 2 +- test/specs/helpers.core.tests.js | 18 ++++++++++++++++++ 5 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/core/core.element.js b/src/core/core.element.js index 2ca60692b97..665f20c4302 100644 --- a/src/core/core.element.js +++ b/src/core/core.element.js @@ -42,7 +42,7 @@ function interpolate(start, view, model, ease) { continue; } } - } else if (type === 'number' && isFinite(origin) && isFinite(target)) { + } else if (helpers.isFinite(origin) && helpers.isFinite(target)) { view[key] = origin + (target - origin) * ease; continue; } diff --git a/src/core/core.scale.js b/src/core/core.scale.js index 78ede6985f6..363815e63b6 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -519,7 +519,7 @@ module.exports = Element.extend({ return NaN; } // isNaN(object) returns true, so make sure NaN is checking for a number; Discard Infinite values - if (typeof rawValue === 'number' && !isFinite(rawValue)) { + if ((typeof rawValue === 'number' || rawValue instanceof Number) && !isFinite(rawValue)) { return NaN; } // If it is in fact an object, dive in one more level diff --git a/src/helpers/helpers.core.js b/src/helpers/helpers.core.js index 2a4b0098ccf..c22dff651b9 100644 --- a/src/helpers/helpers.core.js +++ b/src/helpers/helpers.core.js @@ -51,6 +51,15 @@ var helpers = { return value !== null && Object.prototype.toString.call(value) === '[object Object]'; }, + /** + * Returns true if `value` is a finite number, else returns false + * @param {*} value - The value to test. + * @returns {Boolean} + */ + isFinite: function(value) { + return (typeof value === 'number' || value instanceof Number) && isFinite(value); + }, + /** * Returns `value` if defined, else returns `defaultValue`. * @param {*} value - The value to return if defined. diff --git a/src/plugins/plugin.filler.js b/src/plugins/plugin.filler.js index eb8dad4c3b0..e89bb0d8785 100644 --- a/src/plugins/plugin.filler.js +++ b/src/plugins/plugin.filler.js @@ -128,7 +128,7 @@ function computeBoundary(source) { return target; } - if (typeof target === 'number' && isFinite(target)) { + if (helpers.isFinite(target)) { horizontal = scale.isHorizontal(); return { x: horizontal ? target : null, diff --git a/test/specs/helpers.core.tests.js b/test/specs/helpers.core.tests.js index 80b0640b2cc..e3993de635a 100644 --- a/test/specs/helpers.core.tests.js +++ b/test/specs/helpers.core.tests.js @@ -56,6 +56,24 @@ describe('Chart.helpers.core', function() { }); }); + describe('isFinite', function() { + it('should return true if value is a finite number', function() { + expect(helpers.isFinite(0)).toBeTruthy(); + // eslint-disable-next-line no-new-wrappers + expect(helpers.isFinite(new Number(10))).toBeTruthy(); + }); + + it('should return false if the value is infinite', function() { + expect(helpers.isFinite(Number.POSITIVE_INFINITY)).toBeFalsy(); + expect(helpers.isFinite(Number.NEGATIVE_INFINITY)).toBeFalsy(); + }); + + it('should return false if the value is not a number', function() { + expect(helpers.isFinite('a')).toBeFalsy(); + expect(helpers.isFinite({})).toBeFalsy(); + }); + }); + describe('isNullOrUndef', function() { it('should return true if value is null/undefined', function() { expect(helpers.isNullOrUndef(null)).toBeTruthy(); From cb14217c88c0e307d2ed129449d7d5d04b7d90ea Mon Sep 17 00:00:00 2001 From: Akihiko Kusanagi Date: Sat, 27 Oct 2018 23:55:11 +0800 Subject: [PATCH 007/137] Add error margin for detecting if a point or line is in the chartArea (#5790) --- src/core/core.scale.js | 6 ++++-- src/elements/element.point.js | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/core/core.scale.js b/src/core/core.scale.js index 363815e63b6..c267cb4adbf 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -710,6 +710,8 @@ module.exports = Element.extend({ var yTickStart = options.position === 'bottom' ? me.top + axisWidth : me.bottom - tl - axisWidth; var yTickEnd = options.position === 'bottom' ? me.top + axisWidth + tl : me.bottom + axisWidth; + var epsilon = 0.0000001; // 0.0000001 is margin in pixels for Accumulated error. + helpers.each(ticks, function(tick, index) { // autoskipper skipped this tick (#4635) if (helpers.isNullOrUndef(tick.label)) { @@ -753,7 +755,7 @@ module.exports = Element.extend({ } var xLineValue = getLineValue(me, index, gridLines.offsetGridLines && ticks.length > 1); - if (xLineValue < me.left) { + if (xLineValue < me.left - epsilon) { lineColor = 'rgba(0,0,0,0)'; } xLineValue += helpers.aliasPixel(lineWidth); @@ -780,7 +782,7 @@ module.exports = Element.extend({ labelX = isLeft ? me.right - labelXOffset : me.left + labelXOffset; var yLineValue = getLineValue(me, index, gridLines.offsetGridLines && ticks.length > 1); - if (yLineValue < me.top) { + if (yLineValue < me.top - epsilon) { lineColor = 'rgba(0,0,0,0)'; } yLineValue += helpers.aliasPixel(lineWidth); diff --git a/src/elements/element.point.js b/src/elements/element.point.js index 2bcdc88f0f8..56eb5796617 100644 --- a/src/elements/element.point.js +++ b/src/elements/element.point.js @@ -72,14 +72,14 @@ module.exports = Element.extend({ var radius = vm.radius; var x = vm.x; var y = vm.y; - var errMargin = 1.01; // 1.01 is margin for Accumulated error. (Especially Edge, IE.) + var epsilon = 0.0000001; // 0.0000001 is margin in pixels for Accumulated error. if (vm.skip) { return; } // Clipping for Points. - if (chartArea === undefined || (model.x >= chartArea.left && chartArea.right * errMargin >= model.x && model.y >= chartArea.top && chartArea.bottom * errMargin >= model.y)) { + if (chartArea === undefined || (model.x > chartArea.left - epsilon && chartArea.right + epsilon > model.x && model.y > chartArea.top - epsilon && chartArea.bottom + epsilon > model.y)) { ctx.strokeStyle = vm.borderColor || defaultColor; ctx.lineWidth = helpers.valueOrDefault(vm.borderWidth, defaults.global.elements.point.borderWidth); ctx.fillStyle = vm.backgroundColor || defaultColor; From 820d28907bbaaddb4f6ce980f519bf2da3073053 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hendrik=20R=C3=B6hm?= Date: Thu, 1 Nov 2018 16:26:20 +0100 Subject: [PATCH 008/137] Remove dead and broken code from gulpfile (#5794) In commit c216c0af76, the task unittestWatch was removed. However, the watch task links to it, when executed with the test flag. As watching the unittest is possible with `gulp unittest --watch`, this code is not needed anymore and thus removed. --- gulpfile.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index daba620f0f5..49fd66ade16 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -233,9 +233,6 @@ function moduleSizesTask() { } function watchTask() { - if (util.env.test) { - return gulp.watch('./src/**', ['build', 'unittest', 'unittestWatch']); - } return gulp.watch('./src/**', ['build']); } From 6bea6aba7b8160a8c89b6fe24a2a6f175edf7a48 Mon Sep 17 00:00:00 2001 From: Jordan Ephron Date: Thu, 1 Nov 2018 11:28:12 -0400 Subject: [PATCH 009/137] Document padding option for ticks configuration (#5795) --- docs/axes/styling.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/axes/styling.md b/docs/axes/styling.md index 12a170f3e54..0e45efcd62e 100644 --- a/docs/axes/styling.md +++ b/docs/axes/styling.md @@ -38,6 +38,7 @@ The tick configuration is nested under the scale configuration in the `ticks` ke | `reverse` | `Boolean` | `false` | Reverses order of tick labels. | `minor` | `object` | `{}` | Minor ticks configuration. Omitted options are inherited from options above. | `major` | `object` | `{}` | Major ticks configuration. Omitted options are inherited from options above. +| `padding` | `Number` | `0` | Sets the offset of the tick labels from the axis ## Minor Tick Configuration The minorTick configuration is nested under the ticks configuration in the `minor` key. It defines options for the minor tick marks that are generated by the axis. Omitted options are inherited from `ticks` configuration. From f74699a591111a3478667923cfa0256e7086184f Mon Sep 17 00:00:00 2001 From: Akihiko Kusanagi Date: Fri, 2 Nov 2018 16:44:10 +0900 Subject: [PATCH 010/137] Fix offsetGridLine behavior with a single data point (#5609) --- src/core/core.scale.js | 12 ++- test/specs/core.scale.tests.js | 133 +++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+), 4 deletions(-) diff --git a/src/core/core.scale.js b/src/core/core.scale.js index c267cb4adbf..f08c68fe918 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -76,11 +76,15 @@ function labelsFromTicks(ticks) { return labels; } -function getLineValue(scale, index, offsetGridLines) { +function getPixelForGridLine(scale, index, offsetGridLines) { var lineValue = scale.getPixelForTick(index); if (offsetGridLines) { - if (index === 0) { + if (scale.getTicks().length === 1) { + lineValue -= scale.isHorizontal() ? + Math.max(lineValue - scale.left, scale.right - lineValue) : + Math.max(lineValue - scale.top, scale.bottom - lineValue); + } else if (index === 0) { lineValue -= (scale.getPixelForTick(1) - lineValue) / 2; } else { lineValue -= (lineValue - scale.getPixelForTick(index - 1)) / 2; @@ -754,7 +758,7 @@ module.exports = Element.extend({ labelY = me.bottom - labelYOffset; } - var xLineValue = getLineValue(me, index, gridLines.offsetGridLines && ticks.length > 1); + var xLineValue = getPixelForGridLine(me, index, gridLines.offsetGridLines); if (xLineValue < me.left - epsilon) { lineColor = 'rgba(0,0,0,0)'; } @@ -781,7 +785,7 @@ module.exports = Element.extend({ labelX = isLeft ? me.right - labelXOffset : me.left + labelXOffset; - var yLineValue = getLineValue(me, index, gridLines.offsetGridLines && ticks.length > 1); + var yLineValue = getPixelForGridLine(me, index, gridLines.offsetGridLines); if (yLineValue < me.top - epsilon) { lineColor = 'rgba(0,0,0,0)'; } diff --git a/test/specs/core.scale.tests.js b/test/specs/core.scale.tests.js index 2981c35c9b4..573c132ae07 100644 --- a/test/specs/core.scale.tests.js +++ b/test/specs/core.scale.tests.js @@ -19,4 +19,137 @@ describe('Core.scale', function() { } }); }); + + var gridLineTests = [{ + labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5'], + offsetGridLines: false, + offset: false, + expected: [0.5, 128.5, 256.5, 384.5, 512.5] + }, { + labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5'], + offsetGridLines: false, + offset: true, + expected: [51.5, 154.5, 256.5, 358.5, 461.5] + }, { + labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5'], + offsetGridLines: true, + offset: false, + expected: [-63.5, 64.5, 192.5, 320.5, 448.5] + }, { + labels: ['tick1', 'tick2', 'tick3', 'tick4', 'tick5'], + offsetGridLines: true, + offset: true, + expected: [0, 103, 205.5, 307.5, 410] + }, { + labels: ['tick1'], + offsetGridLines: false, + offset: false, + expected: [0.5] + }, { + labels: ['tick1'], + offsetGridLines: false, + offset: true, + expected: [256.5] + }, { + labels: ['tick1'], + offsetGridLines: true, + offset: false, + expected: [-511.5] + }, { + labels: ['tick1'], + offsetGridLines: true, + offset: true, + expected: [0.5] + }]; + + gridLineTests.forEach(function(test) { + it('should get the correct pixels for ' + test.labels.length + ' gridLine(s) for the horizontal scale when offsetGridLines is ' + test.offsetGridLines + ' and offset is ' + test.offset, function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [] + }], + labels: test.labels + }, + options: { + scales: { + xAxes: [{ + id: 'xScale0', + gridLines: { + offsetGridLines: test.offsetGridLines, + drawTicks: false + }, + ticks: { + display: false + }, + offset: test.offset + }], + yAxes: [{ + display: false + }] + }, + legend: { + display: false + } + } + }); + + var xScale = chart.scales.xScale0; + xScale.ctx = window.createMockContext(); + chart.draw(); + + expect(xScale.ctx.getCalls().filter(function(x) { + return x.name === 'moveTo' && x.args[1] === 0; + }).map(function(x) { + return x.args[0]; + })).toEqual(test.expected); + }); + }); + + gridLineTests.forEach(function(test) { + it('should get the correct pixels for ' + test.labels.length + ' gridLine(s) for the vertical scale when offsetGridLines is ' + test.offsetGridLines + ' and offset is ' + test.offset, function() { + var chart = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + data: [] + }], + labels: test.labels + }, + options: { + scales: { + xAxes: [{ + display: false + }], + yAxes: [{ + type: 'category', + id: 'yScale0', + gridLines: { + offsetGridLines: test.offsetGridLines, + drawTicks: false + }, + ticks: { + display: false + }, + offset: test.offset + }] + }, + legend: { + display: false + } + } + }); + + var yScale = chart.scales.yScale0; + yScale.ctx = window.createMockContext(); + chart.draw(); + + expect(yScale.ctx.getCalls().filter(function(x) { + return x.name === 'moveTo' && x.args[0] === 0; + }).map(function(x) { + return x.args[1]; + })).toEqual(test.expected); + }); + }); }); From f40abe924425f03a592248373603c1ef169a84bd Mon Sep 17 00:00:00 2001 From: Bart Deslagmulder Date: Fri, 2 Nov 2018 08:46:06 +0100 Subject: [PATCH 011/137] Consistent use of punctuation and quick review in docs (#5796) --- docs/README.md | 2 +- docs/axes/README.md | 8 +++--- docs/axes/cartesian/README.md | 2 +- docs/axes/cartesian/time.md | 10 +++---- docs/axes/labelling.md | 4 +-- docs/axes/radial/linear.md | 12 ++++---- docs/axes/styling.md | 12 ++++---- docs/charts/README.md | 2 +- docs/charts/area.md | 2 +- docs/charts/bar.md | 8 +++--- docs/charts/bubble.md | 22 +++++++-------- docs/charts/doughnut.md | 8 +++--- docs/charts/line.md | 24 ++++++++-------- docs/charts/polar.md | 4 +-- docs/charts/radar.md | 14 ++++----- docs/configuration/README.md | 1 - docs/configuration/animations.md | 2 +- docs/configuration/elements.md | 14 ++++----- docs/configuration/legend.md | 16 +++++------ docs/configuration/title.md | 10 +++---- docs/configuration/tooltip.md | 44 ++++++++++++++--------------- docs/developers/api.md | 2 +- docs/developers/charts.md | 2 +- docs/general/colors.md | 2 +- docs/general/interactions/README.md | 2 +- docs/general/interactions/events.md | 4 +-- docs/general/interactions/modes.md | 4 +-- 27 files changed, 117 insertions(+), 120 deletions(-) diff --git a/docs/README.md b/docs/README.md index 24ee8d49982..7cf993d3c9f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -10,7 +10,7 @@ You can download the latest version of Chart.js from the [GitHub releases](https It's easy to get started with Chart.js. All that's required is the script included in your page along with a single `` node to render the chart. -In this example, we create a bar chart for a single dataset and render that in our page. You can see all the ways to use Chart.js in the [usage documentation](./getting-started/usage.md) +In this example, we create a bar chart for a single dataset and render that in our page. You can see all the ways to use Chart.js in the [usage documentation](./getting-started/usage.md). ```html + + + +
+
+
+ + + +
+
+ + + + diff --git a/samples/utils.js b/samples/utils.js index 50bb81c0c1b..71ac5e4e122 100644 --- a/samples/utils.js +++ b/samples/utils.js @@ -11,7 +11,7 @@ window.chartColors = { }; (function(global) { - var Months = [ + var MONTHS = [ 'January', 'February', 'March', @@ -106,7 +106,7 @@ window.chartColors = { var i, value; for (i = 0; i < count; ++i) { - value = Months[Math.ceil(i) % 12]; + value = MONTHS[Math.ceil(i) % 12]; values.push(value.substring(0, section)); } diff --git a/src/controllers/controller.bar.js b/src/controllers/controller.bar.js index 74e6d2289f0..36f7c7e6e37 100644 --- a/src/controllers/controller.bar.js +++ b/src/controllers/controller.bar.js @@ -213,27 +213,24 @@ module.exports = function(Chart) { updateElement: function(rectangle, index, reset) { var me = this; - var chart = me.chart; var meta = me.getMeta(); var dataset = me.getDataset(); - var custom = rectangle.custom || {}; - var rectangleOptions = chart.options.elements.rectangle; + var options = me._resolveElementOptions(rectangle, index); rectangle._xScale = me.getScaleForId(meta.xAxisID); rectangle._yScale = me.getScaleForId(meta.yAxisID); rectangle._datasetIndex = me.index; rectangle._index = index; - rectangle._model = { + backgroundColor: options.backgroundColor, + borderColor: options.borderColor, + borderSkipped: options.borderSkipped, + borderWidth: options.borderWidth, datasetLabel: dataset.label, - label: chart.data.labels[index], - borderSkipped: custom.borderSkipped ? custom.borderSkipped : rectangleOptions.borderSkipped, - backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.backgroundColor, index, rectangleOptions.backgroundColor), - borderColor: custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.borderColor, index, rectangleOptions.borderColor), - borderWidth: custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.borderWidth, index, rectangleOptions.borderWidth) + label: me.chart.data.labels[index] }; - me.updateElementGeometry(rectangle, index, reset); + me._updateElementGeometry(rectangle, index, reset); rectangle.pivot(); }, @@ -241,7 +238,7 @@ module.exports = function(Chart) { /** * @private */ - updateElementGeometry: function(rectangle, index, reset) { + _updateElementGeometry: function(rectangle, index, reset) { var me = this; var model = rectangle._model; var vscale = me.getValueScale(); @@ -472,6 +469,47 @@ module.exports = function(Chart) { helpers.canvas.unclipArea(chart.ctx); }, + + /** + * @private + */ + _resolveElementOptions: function(rectangle, index) { + var me = this; + var chart = me.chart; + var datasets = chart.data.datasets; + var dataset = datasets[me.index]; + var custom = rectangle.custom || {}; + var options = chart.options.elements.rectangle; + var resolve = helpers.options.resolve; + var values = {}; + var i, ilen, key; + + // Scriptable options + var context = { + chart: chart, + dataIndex: index, + dataset: dataset, + datasetIndex: me.index + }; + + var keys = [ + 'backgroundColor', + 'borderColor', + 'borderSkipped', + 'borderWidth' + ]; + + for (i = 0, ilen = keys.length; i < ilen; ++i) { + key = keys[i]; + values[key] = resolve([ + custom[key], + dataset[key], + options[key] + ], context, index); + } + + return values; + } }); Chart.controllers.horizontalBar = Chart.controllers.bar.extend({ diff --git a/src/controllers/controller.bubble.js b/src/controllers/controller.bubble.js index f14e512300f..ed1e2045c71 100644 --- a/src/controllers/controller.bubble.js +++ b/src/controllers/controller.bubble.js @@ -103,12 +103,14 @@ module.exports = function(Chart) { setHoverStyle: function(point) { var model = point._model; var options = point._options; + point.$previousStyle = { backgroundColor: model.backgroundColor, borderColor: model.borderColor, borderWidth: model.borderWidth, radius: model.radius }; + model.backgroundColor = helpers.valueOrDefault(options.hoverBackgroundColor, helpers.getHoverColor(options.backgroundColor)); model.borderColor = helpers.valueOrDefault(options.hoverBorderColor, helpers.getHoverColor(options.borderColor)); model.borderWidth = helpers.valueOrDefault(options.hoverBorderWidth, options.borderWidth); @@ -167,6 +169,7 @@ module.exports = function(Chart) { dataset.radius, options.radius ], context, index); + return values; } }); diff --git a/test/fixtures/controller.bar/backgroundColor/indexable.js b/test/fixtures/controller.bar/backgroundColor/indexable.js new file mode 100644 index 00000000000..781f81b878f --- /dev/null +++ b/test/fixtures/controller.bar/backgroundColor/indexable.js @@ -0,0 +1,52 @@ +module.exports = { + config: { + type: 'bar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + backgroundColor: [ + '#ff0000', + '#00ff00', + '#0000ff', + '#ffff00', + '#ff00ff', + '#000000' + ] + }, + { + // option in element (fallback) + data: [0, 5, 10, null, -10, -5], + } + ] + }, + options: { + legend: false, + title: false, + elements: { + rectangle: { + backgroundColor: [ + '#ff88ff', + '#888888', + '#ff8800', + '#00ff88', + '#8800ff', + '#ffff88' + ] + } + }, + scales: { + xAxes: [{display: false}], + yAxes: [{display: false}] + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/controller.bar/backgroundColor/indexable.png b/test/fixtures/controller.bar/backgroundColor/indexable.png new file mode 100644 index 0000000000000000000000000000000000000000..eecc4ac6df20de91e23ae5df2518d246c2eed8dd GIT binary patch literal 4491 zcmeH}Ye*DP7>2(&9%FV(ns!Mmx}0*&FiE?ZQqaztmTP8aRz_u#r6xgz8RfP*8(okh z7elfw+N29<=o(5lyew2Eh?$6OHxV)t+%+p>T-0?tQ~&zakBEYqKWEOI!+b9v-}~|u zB_*!)Q_fHVfS*1tCK&+HR|eP#ba`rio&_)^Ss$ZIIhr=`R(F18Bp1`s?<1c2h;wJ_ z`Ytcl7&3gKyH1$GFKpNzS<+qn(0y&OtyPPz20vCZij zc~DR|*~@a|bFe932CBu_9t4?Hb-Pv*4T0#I=6e_mkUelUSK$gGAU+D~5x4Nb3U7nH zFbG#WRq!&sOaKlZZ+X)`ms4>tH#K8BLOj$RX@7`C`oYkB@ql3=0c;;AaRYJ+Cr5qFiAVKYt^sfc-VhY|fF5Bg#Q%o@Xpi?CZ-KFdR1;8?>ud6%-ancxjNN*6MF@ zWs3+{B12&j!=>7#405mhG#1DUb}PnwZX~@H=dwcFZHqqr3eBFh3YsoO9yKU7FIAjV zbI`VMDj;&V5Rvkn`KCh%T|0IY@t_emNI9YjPvl{lErn*(lW~P`vhoscRsa^T@I_1Teqbi}~oT1k_8#48{A(V&u-qGxLAB zDg0Bshah}}b2t9P9;0K}nJ;Bh))&L^osQ97>)_p%@|^sgjsu;8gYEB!-T6&vbDEl- zb!(Y}!5^B`geMd^s@DPbr-UN~4@YVeB<|VU`pu?8q&Fvo;*YG;w$uxH2m zAev$GBuMeEDuEW>5-))1oWw?#+P)(5p@>onKW8PLO0A1egC*u$Dq1Iyf)JlsTmS11 z0)$C7Dlp5nu(XExb!Zq&-?~YXLFtGUV()zLB|iHk5lbZ4+OxcMBH!GVf-oCOkqf1F zgdAgJ333F{O+%gTT_+h)T%#03zbGr10)45ov%?x8yGIyO pa8b!Wj^9s|kL9+= 8 ? '#ff0000' + : value > 0 ? '#00ff00' + : value > -8 ? '#0000ff' + : '#ff00ff'; + } + }, + { + // option in element (fallback) + data: [0, 5, 10, null, -10, -5] + } + ] + }, + options: { + legend: false, + title: false, + elements: { + rectangle: { + backgroundColor: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 8 ? '#ff00ff' + : value > 0 ? '#0000ff' + : value > -8 ? '#ff0000' + : '#00ff00'; + } + } + }, + scales: { + xAxes: [{display: false}], + yAxes: [ + { + display: false, + ticks: { + beginAtZero: true + } + } + ] + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/controller.bar/backgroundColor/scriptable.png b/test/fixtures/controller.bar/backgroundColor/scriptable.png new file mode 100644 index 0000000000000000000000000000000000000000..e78839af1d8b9a268db6eaeda414131687b80dc4 GIT binary patch literal 4553 zcmeHK`%@EF6u$Se!3~xgf(%$J1W?clg0z6PNEQnMS{?!dGv$#{M$v*&C<+G1#!)+p z3KW$B3Rr8cFF@2HPq%6j1PWC&$TN-<+6GJzc_xJ3^)Kl3H)oPxcIWJud%ivA`}W*> zG$_Ew(BK0D0Dz&NuU9Yt2;V|L>f_6AIlu1&z$nMh%QG}JdbBI*WbdOJhqpWf;>v?V zvLms2!@pM4W+rX7`}kSf?ZvUz$rqXV*)0;{eg1SqS-GMn^K|B8*~^;D`LxNYiKxu% zhXYe8ONa<Pd%IdEv9=byLQyxCt{;6oMz+~BjtE5jZu~#=G^^4ltTlvTO(ZIRt)0qf91xH zuo8f<6@@oWu$Z2u7fh`xs%g-b?isCgVF^Unnn?#-yV8mO`l0qHP)`Q!j?r#~CH(|q}WKT3fxC`M`;u~GmC z=8@Pri)ltkJ~;ZmCIM04J$JFNfcX+f99$*4!rAou>iw18A~y&tdN$Uz`yh-;|Ax{! zOFa=1byV7ELNUm;w|c-=beIMa=i?e(SxkKL@#sU$5XOvi(&}X_fz;7$nq4TFLomI4 zmNP9JT)@{Ab1M*pkVm;x+F?eNa@M_osmHYIC;v>4@OS_*LOvpjjd`Gb-Ass^5st`h zFa6`f?c@+t1}9o{kuBRQWwSl9TS(G*RiCMo>&rK z-k0R122j9#crdWfixo-H*(00De{unY*d#VOll*s+lva-@6^fd&n2X`lH5M|ZM5wM+TgYAtm1EV1 zuu@85a=F9~+^C^q4r4i2z8PF=xL5vg!;`0uJ1rm*vrjCrew%trK+~PVP4X2Gc0^vn z|13}&gC*f1+@)9@g^I-h36dp9@N)M@(hFFL$>(tQVqXP<*hv57iKl1~R~CXB+&agl zk6*n^oW$B}OECa2r&$o2vQdbsRSi8Vzt!R$mh zW6>$RPf+Bu2t<(f?2r~UTg^@JAn%~d8355{cK$~s`69uCID5P|+rUniLjf(OH1FCC zg5*V5vjgh8&tG$N09_q)dU)r2!G{&GF58c|H)?`~r?BD8-yTamd|*dEJ!~v+3TC&v zlP?z1lltx@K$^??|JI@yaLY|TLy*T8<1a>O#xyFehwXNh=tp)3J$m01j2fnTKxD&i z-{UMBK-^wtoXay~&n}c&#i7J{#ep>A%YK2nzjXxN=Bji=Y-G=99Uker)VhM}WcGi} aHP3l|6XsuizT6(*pPzStSJ?*9!G8gzNe6iV literal 0 HcmV?d00001 diff --git a/test/fixtures/controller.bar/backgroundColor/value.js b/test/fixtures/controller.bar/backgroundColor/value.js new file mode 100644 index 00000000000..d82d60d5dfc --- /dev/null +++ b/test/fixtures/controller.bar/backgroundColor/value.js @@ -0,0 +1,38 @@ +module.exports = { + config: { + type: 'bar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + backgroundColor: '#ff0000' + }, + { + // option in element (fallback) + data: [0, 5, 10, null, -10, -5], + } + ] + }, + options: { + legend: false, + title: false, + elements: { + rectangle: { + backgroundColor: '#00ff00' + } + }, + scales: { + xAxes: [{display: false}], + yAxes: [{display: false}] + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/controller.bar/backgroundColor/value.png b/test/fixtures/controller.bar/backgroundColor/value.png new file mode 100644 index 0000000000000000000000000000000000000000..4885e1086070373c7f29779af35b114929f4e866 GIT binary patch literal 4911 zcmeI0`&ScZ8pq$6o!Chzm=I{%y47%LNTCX@<)X`FtWcl^$QDAVivgBXS%E^V?g{~9 z(iWtm3$BM2NjAw@yHEp5vg-vYL?hr0CDa83m159V1Vv)GBou^wC-@Kaw>^9E)699# z^L^j%e4fjBlP`qv(awz582|w1*qF!<06^g@0`OJvajDvr1%T^ZY~f;;`G>&j+j95$D`SJp_q?#t9Vn*qw>+NLJ#wb^)7;32nj4^{w|cuUxt3@G(c?N-A#fwb z>reY{OFu!Pde-2HTtYwg92xVo)614GZj@JIZe(S7NvBu@m22jP_j?C;Xn=q( zyPSzp6gFOe6orSTLJBsrU6Mc%m92U3j=TusE6Q5#&Todcbg8*%hm5NbTKccF(GMvd zx1}VHC;gH{rMKGKCm|joSd77$-@p$I^l5NmQ5oHqJdopB;lu~eSOxM}%2MsHPp_wC z7j)j!XRx%r1!0!uO!={GM2Pv(y%KrX5v*3@(jMXLA=u~ch}f*7JY z&yP0xLZx%j)8D$*GKGNW7WQDHokHzLGoIL5SPP-Wghb~<5j7myQ)e_lo#9mJkHKQ9 z&^Da)k4pax78)|(wU^RW-wh9DhioU1kQ~X#pCKMTIN*)p{SaNg6&eNVycIAg7$FUG*iWho+LS}H2^Pr(L`ya94aP*hmw z-=|2O-@z>vm^~qezh~UDh}@yFJk0tAT>wvFA5uMg)lT8YZ)7ZVHLQiT;auGyq~S)6 zTn>pdOQ3G#_JyAmRImAQ?tbN|-%21u^@v6#w05p%ClZOg|~kQ;ZN`LU(AD0q@#Bhy0HV^8n9T=vo1_T z8CQL)7^yV&K`sq(T?@tV5I#s-Umd}d!URfh>ho<{O4+Ge_zT8^QS_+Zg2Q$SyIwQk z@`JDzR%;xHU2dPxF$xiF* zR{PaY*EfdEZAQ>6*rXR*>5cRHKWxc=eesKPbwGXumdAU;11HJ`ic>cJEqLeTzk2D+ zkVFE(O>rqb2i^M$`^*Vl0~nqQM^VuI=C$ja9(EoYZy(RDDv2kJ$00~*UPpF#Q>{s{ zs?@WYN36`T{j-03;YeKcWG`x;WS{P3Cr@Ww+Bfo!jlCxjLD#^NI&!Wt+G)knw|6qt zY_7DPM43v(3*#5n6^(S}5Fc{s+8MUvg`KVjGjY7YNfFtO&k5Yg?&QteNFdKZ zG6tL)84;8_;cee6oXHJ%o7*JI5RGOz%sI$8YR=IRzi5oT!JJB>OPC+ zSX=zu$r)@ofi%|Ge+o#7Y1}Zx3h!Y5pe~?abJCEQ1mklIPlXpKJ{Z3w2LEA8BM?*! sFHa0Y_RJx7phK%2W%EDNGRvjz9!SOfmWDI%k1!A$6(8CBZu+PH1-Vs`O#lD@ literal 0 HcmV?d00001 diff --git a/test/fixtures/controller.bar/borderColor/indexable.js b/test/fixtures/controller.bar/borderColor/indexable.js new file mode 100644 index 00000000000..de39b134ae8 --- /dev/null +++ b/test/fixtures/controller.bar/borderColor/indexable.js @@ -0,0 +1,54 @@ +module.exports = { + config: { + type: 'bar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + borderColor: [ + '#ff0000', + '#00ff00', + '#0000ff', + '#ffff00', + '#ff00ff', + '#000000' + ] + }, + { + // option in element (fallback) + data: [0, 5, 10, null, -10, -5], + } + ] + }, + options: { + legend: false, + title: false, + elements: { + rectangle: { + backgroundColor: 'transparent', + borderColor: [ + '#ff88ff', + '#888888', + '#ff8800', + '#00ff88', + '#8800ff', + '#ffff88' + ], + borderWidth: 8 + } + }, + scales: { + xAxes: [{display: false}], + yAxes: [{display: false}] + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/controller.bar/borderColor/indexable.png b/test/fixtures/controller.bar/borderColor/indexable.png new file mode 100644 index 0000000000000000000000000000000000000000..1a3a45cdd214a70f46635a7a0329e11cec036056 GIT binary patch literal 4654 zcmeHLeNasec0r^a$qo~M9+O}TY08zbk*b{;`1yhCkKyJ zlOzjnbYC@kgkRfsmYx1#6X8@p$lNci0=DQQQjfR(x~XHSPfDRX5~M zos*bN7F)f|G5Jhyb-d218$^-?!i30o0c@=Pr{LpjE1zmgix<0E!#HIxtk_N+P6m6-MvyB7~HB8H2KoN z6lRlsg`K)EyW=kJ%2pw`(qEO|VZ@;4rY}v|BF-TQr5%1|pGJ#9!afjv%=r*R=$X#X zyLiajb~~JZ)|X~NKXULmw*+rwMO?SjE+G42P;CYYkliHN3GFikb*x_#dW-GXQum(+aZw^)=tXgvheyl9a2$7OOG z4|C>f`Pqv!qgQ~V;x%}j&{J!Q63bW-=x@l=uEL`4+l;N`t^fw?%1#!%`~xIm_EkrvOpt`m;Q)iQYy* zTXwORoN-yir9?^W*ei$PHGjb;4q?OhxFdu?UONknbAlq+}t8*C~M?)Y}L_ zdf$_u+Dn2W)wW@asli$T2pEZw`TiIpE&VbUkcVY7V~&Kd;)D+r0@Cgch|7yeieeM| z5%n3rF9FrTUnmrU+HQPkt&FRX?WYK&&^e1_QH@>-;OcD@O2C_I@sWjYC+La@#5{1g zPHF%RC7RK~V7;xdvA2}x2|?SZ3xb?@J!Wx&2;m^tE7$OyB34G5y+0~Z|oX`laN z>@zN42F#%F^F%@HLQ?c|#_U%z=w}!e2{coymWvc8$NN$@{Fqu?IDNvLH}9fCTinxX z%q`Az%$3N{+Zf+4VKHVkO$Im|(x`_kw;o#87QTAQdfAfOJ6oVONbXGj20b#@kMZSN z%`<~Ny<|FBmZEpWvZyTKt{|0SwK)MGB`gb7Yj_iXq_(31EO}mGBkyoz^1}?MW!XVcT0pM)r8tgiVfTOqD8kA+{?7ppvsW@DeeW1Up^1YU0x(3 z&CqRlQG{mLo#Q8(-e!R`47bJR%BAD3;@My31PED 8 ? '#ff0000' + : value > 0 ? '#00ff00' + : value > -8 ? '#0000ff' + : '#ff00ff'; + } + }, + { + // option in element (fallback) + data: [0, 5, 10, null, -10, -5] + } + ] + }, + options: { + legend: false, + title: false, + elements: { + rectangle: { + backgroundColor: 'transparent', + borderColor: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return value > 8 ? '#ff00ff' + : value > 0 ? '#0000ff' + : value > -8 ? '#ff0000' + : '#00ff00'; + }, + borderWidth: 8 + } + }, + scales: { + xAxes: [{display: false}], + yAxes: [ + { + display: false, + ticks: { + beginAtZero: true + } + } + ] + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/controller.bar/borderColor/scriptable.png b/test/fixtures/controller.bar/borderColor/scriptable.png new file mode 100644 index 0000000000000000000000000000000000000000..c24a2beab8e8fc50d8b19977fe473c530059e70c GIT binary patch literal 4614 zcmeHKT~t$77C!fKgEt`dLad-7Tos{~j-k|6sSqv}3oH<*)iM>{}oD)Ond9FF9-9 zefH0{zkSX=AICdCaz!GW{Ypj`fMMh3iF*jki)X|CygqUseI zRONkV*LDsn*(Do&MFj-GTpx;!Lw1@{|C#GN>m{&2h%N1O)Tc(UemyM2Yn8v90GC;WmR-YC4A!xw+fA zjaWp%L{yf4yotPx^y+UC-D_YXho$!?ITW^{n4-tevmm?sX3STi$bWIvofMasTq!UZ zrxQ#rrU0JQ6Eyt_9=C)7ajjZzU@KJOE}KsaLvftmlUe=^vXjv8sD(TvqJTE_y0sa) zAozH@j59oqVSdB4K6yK!d+)J(Sza~AS)hLFcJdWxXj#?e)OA=##hBmP__WO}Byq(M$Y*w_Itf((RY zE6p^$>w`ejdA&PvmS~67o_;Z#3lcz()R{4oKl7&r1b;d$OP3{rHPVgA_u`+jpg`?D z@-pH`pDag=Ya!PfmOqiCJ?BU->+bCr&VYqYV?yZ;=w7(Vyh1p(k_MBp&z8y%{QF;f zTfAA-2p5WwMC0p-HHq;jmEL}F4CrjJMk+F)WL>~P=?NagaUIz1`96%5V6>LNFZo88 zTBD&uBY~vD`7{@tD20sZ37EcAj6h2N>{8BFd=OUK=FrTSB9O)!eUw{t?a)$t!=~}? zgaGqPpKSH#V345)7ae{Pab)z37FRA6?$Ugct!#MCkrZZ;$jyreFN>|MsVS9(WpP(4 zW%AiTYogU7#cIrL7O#W^wECKa#6HNI_S_{vYH3&l5_kS2*S6(9>JcAL)ttI0ZaMGi z&FI!c+olSbJUQtD@b&Nzn}Q@QcmD{M`r0hPutOxwRdvT6so1amS%m_XZ=l5e!ZAt} zu$hI3)giRM3AS>k?7 zoXP@{zW!Qs_Jitgr&HMKgldr8)e$42$Fs1bj;f5MIIjeRRRplJs|j*S#J;y2!a}=U z0OT$Am5}^b`cY3@qXoZVq==kJML5l59s`io2kc<6hz}S490NHY0<6S53W$&YCny|b z0fw(245Nzp6uHf`4h|o!ilITNc*~x1b72n$tEWrs3b)4XZ|a@Ntenu7G&EP%$!9v7 zswb{%6?2^(ZHAPfyu!!&WRKZ?kJ)3MG{XW;3lH!Ok7Tl*nHwXGjueny#C45zQ)}S> z_BlE*ix&1Mf!BdU?MZ^E@o`boiAvU_S#JGq8rb z`kv|q_xhSD>m;Z0)MBMv(`!)_hPBm)1?%RbQFB|-d!S~nLJ=k4Luw^oKv6Dg!kQbW zW=%_R9sPUHNZ7LpXaG^~4WF}7SUUA>;TneJ*F7I){Jo&y>*oSEzB9mm{qNv2P58QHgr>u z{N%`vEFaIqiMP=kD>f((i?Gi+|H@ ane&w1@b}ngboU4F7qKNev}$uw{(k_LjMa|- literal 0 HcmV?d00001 diff --git a/test/fixtures/controller.bar/borderColor/value.js b/test/fixtures/controller.bar/borderColor/value.js new file mode 100644 index 00000000000..4f328b59168 --- /dev/null +++ b/test/fixtures/controller.bar/borderColor/value.js @@ -0,0 +1,40 @@ +module.exports = { + config: { + type: 'bar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + borderColor: '#ff0000' + }, + { + // option in element (fallback) + data: [0, 5, 10, null, -10, -5], + } + ] + }, + options: { + legend: false, + title: false, + elements: { + rectangle: { + backgroundColor: 'transparent', + borderColor: '#00ff00', + borderWidth: 8 + } + }, + scales: { + xAxes: [{display: false}], + yAxes: [{display: false}] + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/controller.bar/borderColor/value.png b/test/fixtures/controller.bar/borderColor/value.png new file mode 100644 index 0000000000000000000000000000000000000000..6e170412c308114a5698bbfe92f61ae61d578a26 GIT binary patch literal 5144 zcmeHL`%@EF6h3?1)CCMKM${3N2(2hOl?sa1S{6YC0Ra_AMI z(L!a^QWOk9+a^d769Q#0;mu0t@`BsFaVy+-PL*hzD+$(eNS{OGUfQ(R&fd+XT1&> zvwpC4oZa@+{G7%G-r5b`Y2&;5E~Ne$`^H7?jDnk0Q9;czul#{mrQ!zV_1g!OQ56ZY zi$;|NB=s~{9;=;OI#X1_Lf=l+Ot^EfW;I&JA=%5P61xtX0Ft@{J_$FQJ(-3&_nj?s z_&`DCNM{7HF`~f6?_SUugjkS7OfaG7p-D72R9+-WAmqN>o~=~GL>dO{^ZExvuF(Wdg!@9DnHnb|}mRWp=xL6pK%mN=`1~To9GG_B3L$Gk~+f@X? zK;M3vPxENlyvBdJm9F&U8&|?7Y{TWWddSf!UFklLycVMqM4EIdpCE)j1vceZFyh%2t#r#;&OnaO zjoO|1HZH2~vLYit=EKu+MtlbQlkCP?ftOH>!xXbGbrYB339flSe|7DGJr_ z49;XKJ>iyEIq0Q*qwY{OpKt#tM7_VFy{uMMcx%;9+o#rzr|89ay0*<*ClvIQXTr(f z%ppy9n#bQ2P7qlIpCM)>IAE;j2$$K?u=E^=hA~AQFQmrhW)}B5f_qTnn3Lr73=jpH zV7$W&G+b$5Pp6F7ceFkQvtO|8CDuCfyZV|3?VJ0p5uU&&1G-@^e|QW@10xb#Sxg2$ z%+Sqo@i|`@^~8+TG)4q+nZVos==S)W)u*Mq5ov1Hrv!&db&@NV{+rDqXhwzQ4Q zRfkmHXut7_7v(@83(WS%OLDJrN70yvwLjvPWV6sHaICtLAo%2}<4%q1G%r@V5|fp6 z?~d?yXppL)lcX~6Kf&Qqy&+9QHw|m^e>Fnlt41`x&l^?lh!<=ykhpGuDO+1H;nhE(yjBN|AyxhIEp3;4o?1<_`r%GNQ@O5+)wjhIWhc{3T7DPTiRS@@eJWG`7q@PGAtMDv*} zfmBk*&83{0GA;w%Z5vL}e4lwjK}V9`x97LtUk`_3PAw!UdiEr{yxmo{l?{gU4Ba!V zz5n=VI0>gde%`iFzo&cqYE-H!BL}Kv@f-7HgMx?;LF3cfpyQzvj8G7d_jk@|Z~Il@ z^oT0i^2?-$ovH)M8O_b=84p6tovHMdMf>-9)eN-RTixb?)oML&c<}A=NH#PIW#65? z?jDDoc%~ZD#1vb4ia%nmIsV(Qw6T4tc+(T@Sh*~1k16KKw;ovd)=*kU#krq1Ve#i! zXpPa+qq;h+-h^c9rA0OjE7j_?UGIDs=n}L2M)VZD8gDy3A7TQeZ%FDGmfIxTTQeC- z=gQ*{xe{*+5wrK13ACB-`kX-49K1QrDtpCYdyN;L2T`gnL}S|!w&7%Wvr;#mQ0$6~ z!#3!?4I{jySH+IEpfQN(XdY;MX({fq51Fo(i4HCspDB}(0SR|xFE~s*pZ5U ziPOsw{mEE2`aZ=RW(CN~c>G3uLNsK-7Cj%1m;A$Xpe4LFECsY5er@2%$+q%L3j*2*V?RC{y^mcJ$Y+2u=AhD8PSpV${oqRdKUK=}TAj z4Q;;2jWda?wyPDT*ND^3f6P02Si9dl#5iXh(`+`aFuy{VV{7!+aB){xmnteM${^@D zTU}i(j){qJR3&%0iBb<`Q{3*Eq(M7#Q-+l+ncVADS#f)}JVWfnr~@!5`S7`!YX8Zf zD{jp+6TOTE+Lf5pWTJytY-JlQy5_~dP^3q5Xo&zw^A_HIy%Sf=Y}(j1;1OoRp*3rP zwr<;2-yNd@7L=+BENP7efnz^C-t05!jR2pOv8GFr%uzt2l$qt92+d!_CGPuXlk>+H zfwtV7!?gce-D~81a2oZaWgdSu)<&ZvJ$NDaeOcHc2m5t6l%lsfL} zbp0#>cfVeOQsSQWF462~rQG?o#drh}Tx7UTBXE>bFAho3%_1p8BpwfUpN)58lItKN zyv;cX|5hHl-6_@$ERFkNTyr3z)ar}FWiSTtq4_ z%}+^FM?wU5^@*;WMjVE3TYrYLmS)ggCVz7}0y*U`?@yDp^JtVUbh#hJ0WNj(EQ%W| zM?_iG&7$nXEK%rR^REYRLl|_G?@9|;W#0WkZ<8UjX%rH&Pg4)B+LiTXf2DpGynnau z!1qFyW^d!lc&FkSV9*9$+u&68=*mUfH&DVMu6#vwf*YaWwtB)VggK@U8x7g?TK!JylB3^z_%BOKfEVX_UL0O2qRQ`}}<<(3}w z=66Se`uNt59!+4m1~N&4d<8H2JqK$}u%2mxB5F=iS0QPMq-n=2P)4`=etSfH6WDG( zvp6t=;G%K0O(!f9KZD#+nex5PtU-kp(euc?d~j&RRi9=adsI60Min&?9!-xde*7&c z3$BZg2~ZwFM2GU+bq_Z8bz7!QN>#Uk=r&?nQ6K}+e{_Ao9mj+E^4}c?<1mfVBX`J` zNC=1!%0KSK8r^a!bSs(tC8#f~3{Lh3E+jp{Wz>t|!aE35NmeY8&&~Yl)-q25bL482 z#VYG)5M1PVeuD^4uSQu6AL6Qcl75PqlAgj|Lv% zK|7g094U(hJ_<0=Jo!}`wFiP_Z&OqE2_m?te(o86PTgs6$cOv1hP{ko^>(U2$vjBn zqG7%`1j2Fb%a^ODxm+4erNiAstg8)u&%RQ}gZgU4$!HgrOA}*7a?0Rk!_$^Ep-WgI z!MgBGJPu^~iKU(L1gk7-`0P|Hu;wB`-aFkpNd`@fOb!jyIDz+0JA5B0SelAgx#gU? zG%)CGOYH#&6&E$H=Q{FTA!&9teTVa`5O)2b`m&|W0+Y%^f0%J~3Dw5aM){5;fzvn*Z_uMTHYzeXt+)ABx!6!n&-|b$t zJb+Y&lpf!C`3D#1Y2zVt!9&lxQ`&PPgrX z8{u{#^t-kRxcHNTd@303VCv^Zf0t?aS3-xZnIuo)HxKjHX-$Z=KZl2txsZk*udZ<@ z_~lqx#wKT;6o=()xOpk6(U#)6Lg%7pA52IbF{c-wRs?t6KR=j?&OgqdgYQFmNuCJh z;jPPFxQRqI!sdJP#iF$3m+VI#K%-j#_gGRm_IfNVW7lD<492>{|4CO|Q?JNU<7sDS=Tn_dSIgJxdrN*uy2Xzv8(w+jS!0G=|Ek){VX$ctj0r$CRGWTo z)fcE?gB6aIpI~9U1Hd*{Gr+_5MnVg!f7;XfC}F`yuYyMu_b5!NxEC(Qj)Se+6w>uN z0qQ?~&h_$|o)WPoQ&l!NUKY*r_G}%@3&*iBL8F-Y2*SxQs(+kGF~;$dYeR$9%#_tN z0U9569{vf&yzrqMomb}1#_jAx3x7dcY6QM*Ujd3X(%B)6_L3 zJe2TYO>pOwE~!6y_1$asT(VGBuxTlJ-nsh?6RGhPssa!}23nVSN07=Z4*y$*3Q8S% zr)1czQl*ksR#xh@rCvhZZJ(QUTHlP`iOz@MQ4IiThm0$gi|+dMF2LO!4@W2PmeOak z80ncxs5hs*i4jX$0!s{y?tK}G?wNPU5$3a7$I++*_-F_%B{_CMEI8|ZV$JNqLwrWP zjK!R1ZFA6B>)#x~VJjAIUmhU2S&RX$#_>_<*97Pp*$a!(F&0=ZbMYmX36y@A`f3zr zC-sb}9t-$?dd)g)8DXx__N7*RzB(*0-V=YM?IjJ(qWP^o8VXsGkp{-)+; zY5zcbOtZnOimyAUWxvrR>kp*C$n^=(Jn!~h-P 8 ? 'left' + : value > 0 ? 'right' + : value > -8 ? 'top' + : 'bottom'; + } + }, + { + // option in element (fallback) + data: [0, 5, 10, null, -10, -5] + } + ] + }, + options: { + legend: false, + title: false, + elements: { + rectangle: { + backgroundColor: 'transparent', + borderColor: '#888', + borderSkipped: function(ctx) { + var index = ctx.dataIndex; + return index > 4 ? 'left' + : index > 3 ? 'right' + : index > 1 ? 'top' + : 'bottom'; + }, + borderWidth: 8 + } + }, + scales: { + xAxes: [{display: false}], + yAxes: [ + { + display: false, + ticks: { + beginAtZero: true + } + } + ] + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/controller.bar/borderSkipped/scriptable.png b/test/fixtures/controller.bar/borderSkipped/scriptable.png new file mode 100644 index 0000000000000000000000000000000000000000..54708387e1eab91a7a88fb121f6e7a1e12b51204 GIT binary patch literal 5681 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 literal 0 HcmV?d00001 diff --git a/test/fixtures/controller.bar/borderWidth/indexable.js b/test/fixtures/controller.bar/borderWidth/indexable.js new file mode 100644 index 00000000000..ddd544ff201 --- /dev/null +++ b/test/fixtures/controller.bar/borderWidth/indexable.js @@ -0,0 +1,54 @@ +module.exports = { + config: { + type: 'bar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + borderWidth: [ + 0, + 1, + 2, + 3, + 4, + 5 + ] + }, + { + // option in element (fallback) + data: [0, 5, 10, null, -10, -5], + } + ] + }, + options: { + legend: false, + title: false, + elements: { + rectangle: { + backgroundColor: 'transparent', + borderColor: '#888', + borderWidth: [ + 5, + 4, + 3, + 2, + 1, + 0 + ] + } + }, + scales: { + xAxes: [{display: false}], + yAxes: [{display: false}] + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/controller.bar/borderWidth/indexable.png b/test/fixtures/controller.bar/borderWidth/indexable.png new file mode 100644 index 0000000000000000000000000000000000000000..30011d3b722e474f7ae790f8a1c477f3679195b8 GIT binary patch literal 4969 zcmeHKX;4#H7QXNKkQW>@Aqvwd5CH?D0yeE6%JSMq(U#2~TtJqTc8~Nhtb(8MRvib+6CY!|>Nh$yJ+O2n`)nh-*u@1yb8)Ktx%shX-Gf0BAR=eyro zzH{D9_4W49*E7}w0O)Vq>i!J?DEx^4d_MeokXd&C0Q#D3?qB*P1P{D=l4h~{*|8nx zRs=jU{O69$z)Yi{Sb;yYCT&M}_X$J$ADpazr`H%-s2R1g?Rb3Z3t2|-ty=o&nu3om zCX*S`bj-V;ajYw9+~iGE3mJsKmjH|RRr&?9 z(vabl->XwHRTQ>~xjI!e55<~RGCpMZQApkG>wVO;R?#U{yzI-tSL<{==2}$vN;S{pNNbW)vhVN_}EhvXgYH7`&`8FLQv{H&aDl{X31=kQQS`LKiv zI0q!M5L0hG>d$0T0xtYcwAR&>!e0<_+LmqC^RlLo{$ za%g*UAh#Cx*7_h6g@0BMQ(1%sf;E4>bs`#KoG#v%mYkU4t9iLpTw4Pk5KM%?RQ0*ts< zp*llpwPb*ZePbSBFncJgq)KrIFZP1m!p#SJY{^;<1~8I61*f?nbZ!4ax1A7aVd9{p zK_Qh2mYgUpHzsX%T|X7^H|ZlCO!`Q-TUknnP@R=&BjG3qGU#|UG|FE9kTTf=Wj^i+ z{ph|y_GGj~gyy^_YcGnj_Kw8}=aERxmKYw$WB}WPqa|r%yOo{Z8JB~-K~aV)>gf9i z(o60tHsra$w7K{QyCUfT6?DDU)M60^PzdGf{0cqNt*I?G1Z#pwO%62GK|Wc_c$-W& z=F_cECB}!2=dFZD$6ZuQDSkN4)!W>>CylhZ;=jdl6H5*$$>+r+Hd7!}RNKRG2i_gn zltcaTYMBlcihQ4D2gZbcltSBVGFpa^CN$v_mbpPfU5z^05>R!z!LsLz;ktu;!Ay@WmAZl|7@m>5&Dr}`8MMZxyz%Wo28 z*Cwy8mf2Om9T`&fac-?$RmvGySJyK(Hj==uQV!iwabV4g4%Not{Xqo4tk@eaB)KeG zC`q20R0}=YAM))&rUK8&Gct^>0+G*j?1tOAj|;Bue;3yHoqv09aB%L>MtOCzHhR%e zM^SoFwrb!thIhj#>ZY_I?Wi;vv3^A$zQ4RT#{ytZIz>8!Rbzpf6ubf66s(tGik6M{~|wB7Zhf{%8q5Z_2+w)-+KuGAet*3jZ( zzejwhuNi=km-Ahe(6j|s)o#3AZg^v2J=IKm=b&(6QQZ`oJ0ENl^6z-A622;&> zzzc`^>}nYTim$?A1^O_~~{V*+hErQZv$GNvQT!Pu0i6CA1vIoQe z@<&*~9_B2mwc{!4e+0Z#s08!D_s>2>;E~vW5N(>9JAzo8l+>qA zs%yTyL?Wj(Q$dFBq4@}k-GOftvm>1P^ByV^b>Siq)yaeuD#Mq2Wg=?L+oEYmly0uu z3W6&wnR)lA+_*E@3KUre9R~=yo80124=O#?MIh^!-isJc9WN&rq~d@(_{pl1jbZ`h z#?B08Bbm9}F$y(wX3HNL(IJkYbykGn(v0;3BcPzH&jY-3^}|aNumv#ddWw%cvHrzVIn%ybAZasy@$c zT3M4gKDrq=Kht#78T^Oqvg{m4;Bd5Rw(IoA4<9}>TIiOqmbt-;UyQTqbW*ptZbAa@ ZVSYys`@d?L^aJp-ZL_y~=_c0EUjgZ(L^ literal 0 HcmV?d00001 diff --git a/test/fixtures/controller.bar/borderWidth/scriptable.js b/test/fixtures/controller.bar/borderWidth/scriptable.js new file mode 100644 index 00000000000..40128657749 --- /dev/null +++ b/test/fixtures/controller.bar/borderWidth/scriptable.js @@ -0,0 +1,52 @@ +module.exports = { + config: { + type: 'bar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + borderWidth: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return Math.abs(value); + } + }, + { + // option in element (fallback) + data: [0, 5, 10, null, -10, -5] + } + ] + }, + options: { + legend: false, + title: false, + elements: { + rectangle: { + backgroundColor: 'transparent', + borderColor: '#888', + borderWidth: function(ctx) { + return ctx.dataIndex * 2; + } + } + }, + scales: { + xAxes: [{display: false}], + yAxes: [ + { + display: false, + ticks: { + beginAtZero: true + } + } + ] + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/controller.bar/borderWidth/scriptable.png b/test/fixtures/controller.bar/borderWidth/scriptable.png new file mode 100644 index 0000000000000000000000000000000000000000..fd8b193460fdd37160b3b07774a99158645f5c36 GIT binary patch literal 5219 zcmeHLX;c$g7JgL}Qh`PbB4XPhB?t)Z75Jg24OpY5gi-B>0VKKC_D6*pjS;D-cJw0>g&;0H)|w!czhHH;{0A(ksByHhu~BL4_dZr8 z^DC%JRtO3PjEtxEIMeban0n3AJyDy{B|;v*L}KUUTFt|7*O>%h=fuA0EvZNhKi9Fk zKR0Ftit*tp2OSySg-S{yydslsXTSY3PIx<0;M#Is#lTIuHRrWh{I)+L+INviq1?7$ zLe|(l^jV;#d|)#6w%0UGStZ2qjExk?*q`>GJckAm0ya=DB1l4y0D9(iM({73ezZ`c zWnQz81Q)ehhHj%M1zrTDJ;yV=pk4SKGg&$yfSI9EWYKpVdO)k4UKtPo^>6O}xEFh+ zPk@8fkDiw-Aav=f;I@e|pCOsxvBP*ATGhmHX^GQJF$lFsP%(Y4W>DErRW#uMJY6j;b5lSOQJ zV6tdQzQtk)O4a3YD_JoFCCB%W-zoWY6!q8ga2jIa0I4a8>2YoGM$s1U!GT-fFu{gh z*3pzJ1s-Skh7n9c({JX@O{MPB;DCuxD;Hidnsr2+v7x03T2(za9&W~9Vt0ogEEVQp znB4sk_f{SagGqX_J4~e)Y2Y1bn%QB_c;fFOO)M5~qQF;khK?6lLmZFWtBhG5I8JMC z2v}&wAiK2k-CBI%A`;9pNhzyja!hHQ?Zv+EEeYD6sne9c@bvDB?r=8vYe9%K^2|mV z@ZU9Z!!1G{h(( znQ6#=Xx09H`dAPO6Y~}LrOVyn$*fkI?BX$O$I^Z?dX$jIryVXe(9Ym3XZ!Z^wBA+6O;9Ryim`Bz%1z#XO*n8klae%k=}{HK>r^BbRxVCnCYs*~nZ zwA@#C-LL?Z_nuH95rRQm@VT^~g<^R0RrvfYy=MAI57@X*u*jRd{CN~j-S};F3?&1s zfvHd19JqBRd{#A8T0i7~BiZQGMhMyzJX56jV@knEhb|AV;D4!6POYIRd8PG>$YJ|l zvSY%?5)8S8Ss*B;4V$N)eFi_N*mTBKF>t(7u>!-11+xfHEaDya1Ff!_)Lx12dKw6K zBS?y1=Z0ZT>Y2xXZ*=&}9Gh7U$ zDU@pUov^_6edmfN6sqQ^%qq^cz?d9`L-NIq&noz-OR6(37jySmgQYj~&0(=RLw($L zE)oeY4UsuSF;+4#tcr^@yc}3-J>D@Jk)F(328*ZL0wTtFFJF$f41G>LE7K{P$+c$l zeqArV>jvzE?Pl;$!52cso+zv z{}Bl%JOH|czc)w!^ilLj*l`z$Do2+o4&QI)_V#y&i^uzIGHsLrd^lzmeEz%~7NJc2 z`lxqkp{ME!(Js#2M00@w+9}- zXlPRURc%n)%u5|OuO;(s)`OBO9ffcyVp5rwUO8QR(5%z{SPPdDpdmsi&&TA?1fzG? noP%9i+$7IN3ljc^o+mVPwcQJIk8Y0!@Z-E;t7HB;|G)kVQn7p* literal 0 HcmV?d00001 diff --git a/test/fixtures/controller.bar/borderWidth/value.js b/test/fixtures/controller.bar/borderWidth/value.js new file mode 100644 index 00000000000..33cd40f780e --- /dev/null +++ b/test/fixtures/controller.bar/borderWidth/value.js @@ -0,0 +1,40 @@ +module.exports = { + config: { + type: 'bar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + borderWidth: 2 + }, + { + // option in element (fallback) + data: [0, 5, 10, null, -10, -5], + } + ] + }, + options: { + legend: false, + title: false, + elements: { + rectangle: { + backgroundColor: 'transparent', + borderColor: '#888', + borderWidth: 4 + } + }, + scales: { + xAxes: [{display: false}], + yAxes: [{display: false}] + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/controller.bar/borderWidth/value.png b/test/fixtures/controller.bar/borderWidth/value.png new file mode 100644 index 0000000000000000000000000000000000000000..3c81aff7f2dfb863751bc423a7c8db002e959871 GIT binary patch literal 5109 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 Date: Wed, 14 Nov 2018 11:08:40 +0100 Subject: [PATCH 017/137] Check pixel values using the pixel proximity matcher (#5833) --- test/specs/scale.logarithmic.tests.js | 30 +++++++++++++-------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/test/specs/scale.logarithmic.tests.js b/test/specs/scale.logarithmic.tests.js index 3d79e6cfc37..cb44f108d36 100644 --- a/test/specs/scale.logarithmic.tests.js +++ b/test/specs/scale.logarithmic.tests.js @@ -822,8 +822,8 @@ describe('Logarithmic Scale tests', function() { var start = chart.chartArea[chartStart]; var end = chart.chartArea[chartEnd]; - expect(scale.getPixelForValue(firstTick, 0, 0)).toBe(start); - expect(scale.getPixelForValue(lastTick, 0, 0)).toBe(end); + expect(scale.getPixelForValue(firstTick, 0, 0)).toBeCloseToPixel(start); + expect(scale.getPixelForValue(lastTick, 0, 0)).toBeCloseToPixel(end); expect(scale.getValueForPixel(start)).toBeCloseTo(firstTick, 4); expect(scale.getValueForPixel(end)).toBeCloseTo(lastTick, 4); @@ -835,8 +835,8 @@ describe('Logarithmic Scale tests', function() { start = chart.chartArea[chartEnd]; end = chart.chartArea[chartStart]; - expect(scale.getPixelForValue(firstTick, 0, 0)).toBe(start); - expect(scale.getPixelForValue(lastTick, 0, 0)).toBe(end); + expect(scale.getPixelForValue(firstTick, 0, 0)).toBeCloseToPixel(start); + expect(scale.getPixelForValue(lastTick, 0, 0)).toBeCloseToPixel(end); expect(scale.getValueForPixel(start)).toBeCloseTo(firstTick, 4); expect(scale.getValueForPixel(end)).toBeCloseTo(lastTick, 4); @@ -964,9 +964,9 @@ describe('Logarithmic Scale tests', function() { var start = chart.chartArea[axis.start]; var end = chart.chartArea[axis.end]; - expect(scale.getPixelForValue(firstTick, 0, 0)).toBe(start); - expect(scale.getPixelForValue(lastTick, 0, 0)).toBe(end); - expect(scale.getPixelForValue(0, 0, 0)).toBe(start); // 0 is invalid, put it at the start. + expect(scale.getPixelForValue(firstTick, 0, 0)).toBeCloseToPixel(start); + expect(scale.getPixelForValue(lastTick, 0, 0)).toBeCloseToPixel(end); + expect(scale.getPixelForValue(0, 0, 0)).toBeCloseToPixel(start); // 0 is invalid, put it at the start. expect(scale.getValueForPixel(start)).toBeCloseTo(firstTick, 4); expect(scale.getValueForPixel(end)).toBeCloseTo(lastTick, 4); @@ -978,8 +978,8 @@ describe('Logarithmic Scale tests', function() { start = chart.chartArea[axis.end]; end = chart.chartArea[axis.start]; - expect(scale.getPixelForValue(firstTick, 0, 0)).toBe(start); - expect(scale.getPixelForValue(lastTick, 0, 0)).toBe(end); + expect(scale.getPixelForValue(firstTick, 0, 0)).toBeCloseToPixel(start); + expect(scale.getPixelForValue(lastTick, 0, 0)).toBeCloseToPixel(end); expect(scale.getValueForPixel(start)).toBeCloseTo(firstTick, 4); expect(scale.getValueForPixel(end)).toBeCloseTo(lastTick, 4); @@ -1093,9 +1093,9 @@ describe('Logarithmic Scale tests', function() { var end = chart.chartArea[axis.end]; var sign = scale.isHorizontal() ? 1 : -1; - expect(scale.getPixelForValue(0, 0, 0)).toBe(start); - expect(scale.getPixelForValue(lastTick, 0, 0)).toBe(end); - expect(scale.getPixelForValue(firstTick, 0, 0)).toBe(start + sign * fontSize); + expect(scale.getPixelForValue(0, 0, 0)).toBeCloseToPixel(start); + expect(scale.getPixelForValue(lastTick, 0, 0)).toBeCloseToPixel(end); + expect(scale.getPixelForValue(firstTick, 0, 0)).toBeCloseToPixel(start + sign * fontSize); expect(scale.getValueForPixel(start)).toBeCloseTo(0, 4); expect(scale.getValueForPixel(end)).toBeCloseTo(lastTick, 4); @@ -1108,9 +1108,9 @@ describe('Logarithmic Scale tests', function() { start = chart.chartArea[axis.end]; end = chart.chartArea[axis.start]; - expect(scale.getPixelForValue(0, 0, 0)).toBe(start); - expect(scale.getPixelForValue(lastTick, 0, 0)).toBe(end); - expect(scale.getPixelForValue(firstTick, 0, 0)).toBe(start - sign * fontSize, 4); + expect(scale.getPixelForValue(0, 0, 0)).toBeCloseToPixel(start); + expect(scale.getPixelForValue(lastTick, 0, 0)).toBeCloseToPixel(end); + expect(scale.getPixelForValue(firstTick, 0, 0)).toBeCloseToPixel(start - sign * fontSize, 4); expect(scale.getValueForPixel(start)).toBeCloseTo(0, 4); expect(scale.getValueForPixel(end)).toBeCloseTo(lastTick, 4); From 3ea93a09f2047b727891f9e13dbd3fb7ebe44adf Mon Sep 17 00:00:00 2001 From: Jan Tagscherer <3198913+jtagscherer@users.noreply.github.com> Date: Wed, 14 Nov 2018 11:12:57 +0100 Subject: [PATCH 018/137] Add regression test for legend layout issue (#5776) --- test/specs/plugin.legend.tests.js | 37 +++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/test/specs/plugin.legend.tests.js b/test/specs/plugin.legend.tests.js index 924276587f0..b6bb1a89426 100644 --- a/test/specs/plugin.legend.tests.js +++ b/test/specs/plugin.legend.tests.js @@ -525,6 +525,43 @@ describe('Legend block tests', function() { }); }); + it('should not draw legend items outside of the chart bounds', function() { + var chart = window.acquireChart( + { + type: 'line', + data: { + datasets: [1, 2, 3].map(function(n) { + return { + label: 'dataset' + n, + data: [] + }; + }), + labels: [] + }, + options: { + legend: { + position: 'right' + } + } + }, + { + canvas: { + width: 512, + height: 105 + } + } + ); + + // Check some basic assertions about the test setup + expect(chart.width).toBe(512); + expect(chart.legend.legendHitBoxes.length).toBe(3); + + // Check whether any legend items reach outside the established bounds + chart.legend.legendHitBoxes.forEach(function(item) { + expect(item.left + item.width).toBeLessThanOrEqual(chart.width); + }); + }); + describe('config update', function() { it ('should update the options', function() { var chart = acquireChart({ From ecf64d361dc284acedd4b6a815905614ea79034c Mon Sep 17 00:00:00 2001 From: Thomas David Baker Date: Thu, 15 Nov 2018 06:41:02 -0800 Subject: [PATCH 019/137] Correct spelling mistake. (#5831) Use a simpler phrase for this heading. --- docs/general/fonts.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/general/fonts.md b/docs/general/fonts.md index 082456f3d95..f00c5aa5373 100644 --- a/docs/general/fonts.md +++ b/docs/general/fonts.md @@ -27,6 +27,6 @@ let chart = new Chart(ctx, { | `defaultFontSize` | `Number` | `12` | Default font size (in px) for text. Does not apply to radialLinear scale point labels. | `defaultFontStyle` | `String` | `'normal'` | Default font style. Does not apply to tooltip title or footer. Does not apply to chart title. -## Non-Existant Fonts +## Missing Fonts If a font is specified for a chart that does exist on the system, the browser will not apply the font when it is set. If you notice odd fonts appearing in your charts, check that the font you are applying exists on your system. See [issue 3318](https://github.com/chartjs/Chart.js/issues/3318) for more details. From 75aa44eef677b790768262638df0de223fd01a56 Mon Sep 17 00:00:00 2001 From: Simon Brunel Date: Sun, 18 Nov 2018 09:33:34 +0100 Subject: [PATCH 020/137] Upgrade dev dependencies to reduce vulnerabilities (#5840) --- gulpfile.js | 12 ++++---- package.json | 44 ++++++++++++++--------------- src/platforms/platform.dom.js | 2 ++ test/specs/core.controller.tests.js | 16 ++--------- test/specs/core.plugin.tests.js | 2 ++ 5 files changed, 34 insertions(+), 42 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index d8872df0e31..06229d6f89c 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -49,19 +49,19 @@ gulp.task('bower', bowerTask); gulp.task('build', buildTask); gulp.task('package', packageTask); gulp.task('watch', watchTask); -gulp.task('lint', ['lint-html', 'lint-js']); gulp.task('lint-html', lintHtmlTask); gulp.task('lint-js', lintJsTask); +gulp.task('lint', gulp.parallel('lint-html', 'lint-js')); gulp.task('docs', docsTask); -gulp.task('test', ['lint', 'unittest']); -gulp.task('size', ['library-size', 'module-sizes']); gulp.task('server', serverTask); gulp.task('unittest', unittestTask); +gulp.task('test', gulp.parallel('lint', 'unittest')); gulp.task('library-size', librarySizeTask); gulp.task('module-sizes', moduleSizesTask); +gulp.task('size', gulp.parallel('library-size', 'module-sizes')); gulp.task('_open', _openTask); -gulp.task('dev', ['server', 'default']); -gulp.task('default', ['build', 'watch']); +gulp.task('default', gulp.parallel('build', 'watch')); +gulp.task('dev', gulp.parallel('server', 'default')); /** * Generates the bower.json manifest file which will be pushed along release tags. @@ -233,7 +233,7 @@ function moduleSizesTask() { } function watchTask() { - return gulp.watch('./src/**', ['build']); + return gulp.watch('./src/**', gulp.parallel('build')); } function serverTask() { diff --git a/package.json b/package.json index 30e365f7467..b8c72f99afb 100644 --- a/package.json +++ b/package.json @@ -10,42 +10,42 @@ "url": "https://github.com/chartjs/Chart.js.git" }, "devDependencies": { - "browserify": "^14.5.0", + "browserify": "^16.2.3", "browserify-istanbul": "^3.0.1", "bundle-collapser": "^1.3.0", "child-process-promise": "^2.2.1", "coveralls": "^3.0.0", - "eslint": "^4.9.0", + "eslint": "^5.9.0", "eslint-config-chartjs": "^0.1.0", - "eslint-plugin-html": "^4.0.2", + "eslint-plugin-html": "^5.0.0", "gitbook-cli": "^2.3.2", - "gulp": "3.9.x", - "gulp-concat": "~2.6.x", - "gulp-connect": "~5.0.0", - "gulp-eslint": "^4.0.0", - "gulp-file": "^0.3.0", - "gulp-htmllint": "^0.0.15", - "gulp-insert": "~0.5.0", - "gulp-replace": "^0.6.1", - "gulp-size": "~2.1.0", + "gulp": "^4.0.0", + "gulp-concat": "^2.6.0", + "gulp-connect": "^5.6.1", + "gulp-eslint": "^5.0.0", + "gulp-file": "^0.4.0", + "gulp-htmllint": "^0.0.16", + "gulp-insert": "^0.5.0", + "gulp-replace": "^1.0.0", + "gulp-size": "^3.0.0", "gulp-streamify": "^1.0.2", - "gulp-uglify": "~3.0.x", - "gulp-util": "~3.0.x", - "gulp-zip": "~4.0.0", - "jasmine": "^2.8.0", - "jasmine-core": "^2.8.0", - "karma": "^1.7.1", + "gulp-uglify": "^3.0.0", + "gulp-util": "^3.0.0", + "gulp-zip": "^4.2.0", + "jasmine": "^3.3.0", + "jasmine-core": "^3.3.0", + "karma": "^3.1.1", "karma-browserify": "^5.1.1", "karma-chrome-launcher": "^2.2.0", "karma-coverage": "^1.1.1", "karma-firefox-launcher": "^1.0.1", - "karma-jasmine": "^1.1.0", - "karma-jasmine-html-reporter": "^0.2.2", + "karma-jasmine": "^2.0.0", + "karma-jasmine-html-reporter": "^1.4.0", "merge-stream": "^1.0.1", "pixelmatch": "^4.0.2", - "vinyl-source-stream": "^1.1.0", + "vinyl-source-stream": "^2.0.0", "watchify": "^3.9.0", - "yargs": "^9.0.1" + "yargs": "^12.0.2" }, "spm": { "main": "Chart.js" diff --git a/src/platforms/platform.dom.js b/src/platforms/platform.dom.js index a882d22a537..549ec1a1ca4 100644 --- a/src/platforms/platform.dom.js +++ b/src/platforms/platform.dom.js @@ -108,6 +108,7 @@ var supportsEventListenerOptions = (function() { var supports = false; try { var options = Object.defineProperty({}, 'passive', { + // eslint-disable-next-line getter-return get: function() { supports = true; } @@ -391,6 +392,7 @@ module.exports = { // 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 + // eslint-disable-next-line no-self-assign canvas.width = canvas.width; delete canvas[EXPANDO_KEY]; diff --git a/test/specs/core.controller.tests.js b/test/specs/core.controller.tests.js index 1ca699bc132..cf9eb30a22b 100644 --- a/test/specs/core.controller.tests.js +++ b/test/specs/core.controller.tests.js @@ -949,25 +949,13 @@ describe('Chart', function() { var meta = chart.getDatasetMeta(0); var point = meta.data[1]; - var node = chart.canvas; - var rect = node.getBoundingClientRect(); - - var evt = new MouseEvent('mousemove', { - view: window, - bubbles: true, - cancelable: true, - clientX: rect.left + point._model.x, - clientY: 0 - }); - - // Manually trigger rather than having an async test - node.dispatchEvent(evt); + jasmine.triggerMouseEvent(chart, 'mousemove', point); // Check and see if tooltip was displayed var tooltip = chart.tooltip; expect(chart.lastActive).toEqual([point]); - expect(tooltip._lastActive).toEqual([]); + expect(tooltip._lastActive).toEqual([point]); // Update and confirm tooltip is reset chart.update(); diff --git a/test/specs/core.plugin.tests.js b/test/specs/core.plugin.tests.js index 3a9e908a38d..977f607a7c9 100644 --- a/test/specs/core.plugin.tests.js +++ b/test/specs/core.plugin.tests.js @@ -323,6 +323,8 @@ describe('Chart.plugins', function() { expect(plugin.hook).toHaveBeenCalled(); expect(plugin.hook.calls.first().args[1]).toEqual({a: 42}); + + delete Chart.defaults.global.plugins.a; }); From ab06831f691d5a6520dd069b746d072dfc0c11a2 Mon Sep 17 00:00:00 2001 From: Kakhaber Date: Sun, 18 Nov 2018 12:36:21 +0400 Subject: [PATCH 021/137] Is node shadow root check improved (#5828) --- src/core/core.helpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/core.helpers.js b/src/core/core.helpers.js index d8175af50c5..4b0a18c1648 100644 --- a/src/core/core.helpers.js +++ b/src/core/core.helpers.js @@ -506,7 +506,7 @@ module.exports = function() { */ helpers._getParentNode = function(domNode) { var parent = domNode.parentNode; - if (parent && parent.host) { + if (parent && parent.toString() === '[object ShadowRoot]') { parent = parent.host; } return parent; From f6d9a39cb8e8661e1d6a733d4b77fb3f3238935a Mon Sep 17 00:00:00 2001 From: Evert Timberg Date: Sun, 18 Nov 2018 03:45:41 -0500 Subject: [PATCH 022/137] Fix axis line width when option is an array (#5751) When the axis lineWidth setting is set to an array, use the first item when determining the size of the axis area. --- src/core/core.scale.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/core.scale.js b/src/core/core.scale.js index f08c68fe918..28211b7f43a 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -708,7 +708,7 @@ module.exports = Element.extend({ var itemsToDraw = []; - var axisWidth = me.options.gridLines.lineWidth; + var axisWidth = helpers.valueAtIndexOrDefault(me.options.gridLines.lineWidth, 0); var xTickStart = options.position === 'right' ? me.left : me.right - axisWidth - tl; var xTickEnd = options.position === 'right' ? me.left + tl : me.right; var yTickStart = options.position === 'bottom' ? me.top + axisWidth : me.bottom - tl - axisWidth; From bc494e0a811f735ad58d70330023959ff58c0837 Mon Sep 17 00:00:00 2001 From: Akihiko Kusanagi Date: Wed, 21 Nov 2018 15:58:39 +0800 Subject: [PATCH 023/137] Use empty labels for tests so as not to be affected by the font width (#5842) --- test/specs/plugin.legend.tests.js | 75 ++++++++++++++++--------------- 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/test/specs/plugin.legend.tests.js b/test/specs/plugin.legend.tests.js index b6bb1a89426..7b7159dbb10 100644 --- a/test/specs/plugin.legend.tests.js +++ b/test/specs/plugin.legend.tests.js @@ -205,8 +205,8 @@ describe('Legend block tests', function() { expect(chart.legend.legendHitBoxes.length).toBe(3); [ - {h: 12, l: 107, t: 10, w: 93}, - {h: 12, l: 210, t: 10, w: 93}, + {h: 12, l: 106, t: 10, w: 93}, + {h: 12, l: 209, t: 10, w: 93}, {h: 12, l: 312, t: 10, w: 93} ].forEach(function(expected, i) { expect(chart.legend.legendHitBoxes[i].height).toBeCloseToPixel(expected.h); @@ -438,9 +438,9 @@ describe('Legend block tests', function() { var chart = window.acquireChart({ type: 'bar', data: { - datasets: [1, 2, 3, 4, 5].map(function(n) { + datasets: Array.apply(null, Array(9)).map(function() { return { - label: 'dataset' + n, + label: ' ', data: [] }; }), @@ -452,15 +452,18 @@ describe('Legend block tests', function() { expect(chart.legend.top).toBeCloseToPixel(0); expect(chart.legend.width).toBeCloseToPixel(512); expect(chart.legend.height).toBeCloseToPixel(54); - expect(chart.legend.legendHitBoxes.length).toBe(5); - expect(chart.legend.legendHitBoxes.length).toBe(5); + expect(chart.legend.legendHitBoxes.length).toBe(9); [ - {h: 12, l: 56, t: 10, w: 93}, - {h: 12, l: 158, t: 10, w: 93}, - {h: 12, l: 261, t: 10, w: 93}, - {h: 12, l: 364, t: 10, w: 93}, - {h: 12, l: 210, t: 32, w: 93} + {h: 12, l: 24, t: 10, w: 49}, + {h: 12, l: 83, t: 10, w: 49}, + {h: 12, l: 142, t: 10, w: 49}, + {h: 12, l: 202, t: 10, w: 49}, + {h: 12, l: 261, t: 10, w: 49}, + {h: 12, l: 320, t: 10, w: 49}, + {h: 12, l: 380, t: 10, w: 49}, + {h: 12, l: 439, t: 10, w: 49}, + {h: 12, l: 231, t: 32, w: 49} ].forEach(function(expected, i) { expect(chart.legend.legendHitBoxes[i].height).toBeCloseToPixel(expected.h); expect(chart.legend.legendHitBoxes[i].left).toBeCloseToPixel(expected.l); @@ -473,9 +476,9 @@ describe('Legend block tests', function() { var chart = window.acquireChart({ type: 'bar', data: { - datasets: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22].map(function(n) { + datasets: Array.apply(null, Array(22)).map(function() { return { - label: 'dataset' + n, + label: ' ', data: [] }; }), @@ -490,33 +493,33 @@ describe('Legend block tests', function() { expect(chart.legend.left).toBeCloseToPixel(0); expect(chart.legend.top).toBeCloseToPixel(6); - expect(chart.legend.width).toBeCloseToPixel(228.7); + expect(chart.legend.width).toBeCloseToPixel(128); expect(chart.legend.height).toBeCloseToPixel(478); expect(chart.legend.legendHitBoxes.length).toBe(22); [ - {h: 12, l: 10, t: 16, w: 93}, - {h: 12, l: 10, t: 38, w: 93}, - {h: 12, l: 10, t: 60, w: 93}, - {h: 12, l: 10, t: 82, w: 93}, - {h: 12, l: 10, t: 104, w: 93}, - {h: 12, l: 10, t: 126, w: 93}, - {h: 12, l: 10, t: 148, w: 93}, - {h: 12, l: 10, t: 170, w: 93}, - {h: 12, l: 10, t: 192, w: 93}, - {h: 12, l: 10, t: 214, w: 99}, - {h: 12, l: 10, t: 236, w: 99}, - {h: 12, l: 10, t: 258, w: 99}, - {h: 12, l: 10, t: 280, w: 99}, - {h: 12, l: 10, t: 302, w: 99}, - {h: 12, l: 10, t: 324, w: 99}, - {h: 12, l: 10, t: 346, w: 99}, - {h: 12, l: 10, t: 368, w: 99}, - {h: 12, l: 10, t: 390, w: 99}, - {h: 12, l: 10, t: 412, w: 99}, - {h: 12, l: 10, t: 434, w: 99}, - {h: 12, l: 10, t: 456, w: 99}, - {h: 12, l: 119, t: 16, w: 99} + {h: 12, l: 10, t: 16, w: 49}, + {h: 12, l: 10, t: 38, w: 49}, + {h: 12, l: 10, t: 60, w: 49}, + {h: 12, l: 10, t: 82, w: 49}, + {h: 12, l: 10, t: 104, w: 49}, + {h: 12, l: 10, t: 126, w: 49}, + {h: 12, l: 10, t: 148, w: 49}, + {h: 12, l: 10, t: 170, w: 49}, + {h: 12, l: 10, t: 192, w: 49}, + {h: 12, l: 10, t: 214, w: 49}, + {h: 12, l: 10, t: 236, w: 49}, + {h: 12, l: 10, t: 258, w: 49}, + {h: 12, l: 10, t: 280, w: 49}, + {h: 12, l: 10, t: 302, w: 49}, + {h: 12, l: 10, t: 324, w: 49}, + {h: 12, l: 10, t: 346, w: 49}, + {h: 12, l: 10, t: 368, w: 49}, + {h: 12, l: 10, t: 390, w: 49}, + {h: 12, l: 10, t: 412, w: 49}, + {h: 12, l: 10, t: 434, w: 49}, + {h: 12, l: 10, t: 456, w: 49}, + {h: 12, l: 69, t: 16, w: 49} ].forEach(function(expected, i) { expect(chart.legend.legendHitBoxes[i].height).toBeCloseToPixel(expected.h); expect(chart.legend.legendHitBoxes[i].left).toBeCloseToPixel(expected.l); From b68341d9b888f0289edc7879619bdac86958709b Mon Sep 17 00:00:00 2001 From: chtheis Date: Wed, 21 Nov 2018 09:35:49 +0100 Subject: [PATCH 024/137] Correct calculation of padding in percent (#5846) --- src/core/core.helpers.js | 2 +- test/specs/core.helpers.tests.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/core.helpers.js b/src/core/core.helpers.js index 4b0a18c1648..3b1eca5edb3 100644 --- a/src/core/core.helpers.js +++ b/src/core/core.helpers.js @@ -499,7 +499,7 @@ module.exports = function() { helpers._calculatePadding = function(container, padding, parentDimension) { padding = helpers.getStyle(container, padding); - return padding.indexOf('%') > -1 ? parentDimension / parseInt(padding, 10) : parseInt(padding, 10); + return padding.indexOf('%') > -1 ? parentDimension * parseInt(padding, 10) / 100 : parseInt(padding, 10); }; /** * @private diff --git a/test/specs/core.helpers.tests.js b/test/specs/core.helpers.tests.js index de6d0b41301..70f0981df0e 100644 --- a/test/specs/core.helpers.tests.js +++ b/test/specs/core.helpers.tests.js @@ -790,7 +790,7 @@ describe('Core helper tests', function() { div.style.height = '300px'; document.body.appendChild(div); - // Inner DIV to have 10% padding of parent + // Inner DIV to have 5% padding of parent var innerDiv = document.createElement('div'); div.appendChild(innerDiv); @@ -802,8 +802,8 @@ describe('Core helper tests', function() { expect(helpers.getMaximumWidth(canvas)).toBe(300); // test with percentage - innerDiv.style.padding = '10%'; - expect(helpers.getMaximumWidth(canvas)).toBe(240); + innerDiv.style.padding = '5%'; + expect(helpers.getMaximumWidth(canvas)).toBe(270); // test with pixels innerDiv.style.padding = '10px'; From 0351a88a631cc6b964af203d2ed92fe2e48f9d99 Mon Sep 17 00:00:00 2001 From: Akihiko Kusanagi Date: Mon, 26 Nov 2018 15:57:31 +0800 Subject: [PATCH 025/137] Add support for gridLines/angleLines borderDash for polarArea/radar charts (#5850) --- docs/axes/radial/linear.md | 2 + docs/axes/styling.md | 4 +- src/core/core.scale.js | 17 +++-- src/scales/scale.radialLinear.js | 64 +++++++++++------- .../scale.radialLinear/border-dash.json | 33 +++++++++ .../scale.radialLinear/border-dash.png | Bin 0 -> 31221 bytes .../circular-border-dash.json | 34 ++++++++++ .../circular-border-dash.png | Bin 0 -> 39138 bytes .../indexable-gridlines.json | 40 +++++++++++ .../indexable-gridlines.png | Bin 0 -> 54355 bytes test/specs/scale.radialLinear.tests.js | 6 +- 11 files changed, 166 insertions(+), 34 deletions(-) create mode 100644 test/fixtures/scale.radialLinear/border-dash.json create mode 100644 test/fixtures/scale.radialLinear/border-dash.png create mode 100644 test/fixtures/scale.radialLinear/circular-border-dash.json create mode 100644 test/fixtures/scale.radialLinear/circular-border-dash.png create mode 100644 test/fixtures/scale.radialLinear/indexable-gridlines.json create mode 100644 test/fixtures/scale.radialLinear/indexable-gridlines.png diff --git a/docs/axes/radial/linear.md b/docs/axes/radial/linear.md index d59b62251e8..d1edd18be54 100644 --- a/docs/axes/radial/linear.md +++ b/docs/axes/radial/linear.md @@ -95,6 +95,8 @@ The following options are used to configure angled lines that radiate from the c | `display` | `Boolean` | `true` | if true, angle lines are shown. | `color` | `Color` | `rgba(0, 0, 0, 0.1)` | Color of angled lines. | `lineWidth` | `Number` | `1` | Width of angled lines. +| `borderDash` | `Number[]` | `[]` | Length and spacing of dashes on angled lines. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash). +| `borderDashOffset` | `Number` | `0.0` | Offset for line dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset). ## Point Label Options diff --git a/docs/axes/styling.md b/docs/axes/styling.md index 80a7a2e66cb..7184bc96faf 100644 --- a/docs/axes/styling.md +++ b/docs/axes/styling.md @@ -12,7 +12,7 @@ The grid line configuration is nested under the scale configuration in the `grid | `circular` | `Boolean` | `false` | If true, gridlines are circular (on radar chart only). | `color` | `Color/Color[]` | `'rgba(0, 0, 0, 0.1)'` | The color of the grid lines. If specified as an array, the first color applies to the first grid line, the second to the second grid line and so on. | `borderDash` | `Number[]` | `[]` | Length and spacing of dashes on grid lines. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash). -| `borderDashOffset` | `Number` | `0` | Offset for line dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset). +| `borderDashOffset` | `Number` | `0.0` | Offset for line dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset). | `lineWidth` | `Number/Number[]` | `1` | Stroke width of grid lines. | `drawBorder` | `Boolean` | `true` | If true, draw border at the edge between the axis and the chart area. | `drawOnChartArea` | `Boolean` | `true` | If true, draw lines on the chart area inside the axis lines. This is useful when there are multiple axes and you need to control which grid lines are drawn. @@ -21,7 +21,7 @@ The grid line configuration is nested under the scale configuration in the `grid | `zeroLineWidth` | `Number` | `1` | Stroke width of the grid line for the first index (index 0). | `zeroLineColor` | Color | `'rgba(0, 0, 0, 0.25)'` | Stroke color of the grid line for the first index (index 0). | `zeroLineBorderDash` | `Number[]` | `[]` | Length and spacing of dashes of the grid line for the first index (index 0). See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash). -| `zeroLineBorderDashOffset` | `Number` | `0` | Offset for line dashes of the grid line for the first index (index 0). See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset). +| `zeroLineBorderDashOffset` | `Number` | `0.0` | Offset for line dashes of the grid line for the first index (index 0). See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset). | `offsetGridLines` | `Boolean` | `false` | If true, grid lines will be shifted to be between labels. This is set to `true` for a category scale in a bar chart by default. ## Tick Configuration diff --git a/src/core/core.scale.js b/src/core/core.scale.js index 28211b7f43a..e3a7979440c 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -728,13 +728,13 @@ module.exports = Element.extend({ // Draw the first index specially lineWidth = gridLines.zeroLineWidth; lineColor = gridLines.zeroLineColor; - borderDash = gridLines.zeroLineBorderDash; - borderDashOffset = gridLines.zeroLineBorderDashOffset; + borderDash = gridLines.zeroLineBorderDash || []; + borderDashOffset = gridLines.zeroLineBorderDashOffset || 0.0; } else { lineWidth = helpers.valueAtIndexOrDefault(gridLines.lineWidth, index); lineColor = helpers.valueAtIndexOrDefault(gridLines.color, index); - borderDash = helpers.valueOrDefault(gridLines.borderDash, globalDefaults.borderDash); - borderDashOffset = helpers.valueOrDefault(gridLines.borderDashOffset, globalDefaults.borderDashOffset); + borderDash = gridLines.borderDash || []; + borderDashOffset = gridLines.borderDashOffset || 0.0; } // Common properties @@ -825,10 +825,13 @@ module.exports = Element.extend({ // Draw all of the tick labels, tick marks, and grid lines at the correct places helpers.each(itemsToDraw, function(itemToDraw) { - if (gridLines.display) { + var glWidth = itemToDraw.glWidth; + var glColor = itemToDraw.glColor; + + if (gridLines.display && glWidth && glColor) { context.save(); - context.lineWidth = itemToDraw.glWidth; - context.strokeStyle = itemToDraw.glColor; + context.lineWidth = glWidth; + context.strokeStyle = glColor; if (context.setLineDash) { context.setLineDash(itemToDraw.glBorderDash); context.lineDashOffset = itemToDraw.glBorderDashOffset; diff --git a/src/scales/scale.radialLinear.js b/src/scales/scale.radialLinear.js index b580e1da75b..90ac22f0db7 100644 --- a/src/scales/scale.radialLinear.js +++ b/src/scales/scale.radialLinear.js @@ -19,7 +19,9 @@ module.exports = function(Chart) { angleLines: { display: true, color: 'rgba(0, 0, 0, 0.1)', - lineWidth: 1 + lineWidth: 1, + borderDash: [], + borderDashOffset: 0.0 }, gridLines: { @@ -240,26 +242,34 @@ module.exports = function(Chart) { var ctx = scale.ctx; var opts = scale.options; var angleLineOpts = opts.angleLines; + var gridLineOpts = opts.gridLines; var pointLabelOpts = opts.pointLabels; - - ctx.lineWidth = angleLineOpts.lineWidth; - ctx.strokeStyle = angleLineOpts.color; + var lineWidth = helpers.valueOrDefault(angleLineOpts.lineWidth, gridLineOpts.lineWidth); + var lineColor = helpers.valueOrDefault(angleLineOpts.color, gridLineOpts.color); + + ctx.save(); + ctx.lineWidth = lineWidth; + ctx.strokeStyle = lineColor; + if (ctx.setLineDash) { + ctx.setLineDash(helpers.valueOrDefault(angleLineOpts.borderDash, gridLineOpts.borderDash) || []); + ctx.lineDashOffset = helpers.valueOrDefault(angleLineOpts.borderDashOffset, gridLineOpts.borderDashOffset) || 0.0; + } var outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max); // Point Label Font var plFont = getPointLabelFontOptions(scale); + ctx.font = plFont.font; ctx.textBaseline = 'top'; for (var i = getValueCount(scale) - 1; i >= 0; i--) { - if (angleLineOpts.display) { + if (angleLineOpts.display && lineWidth && lineColor) { var outerPosition = scale.getPointPosition(i, outerDistance); ctx.beginPath(); ctx.moveTo(scale.xCenter, scale.yCenter); ctx.lineTo(outerPosition.x, outerPosition.y); ctx.stroke(); - ctx.closePath(); } if (pointLabelOpts.display) { @@ -268,7 +278,6 @@ module.exports = function(Chart) { // Keep this in loop since we may support array properties here var pointLabelFontColor = helpers.valueAtIndexOrDefault(pointLabelOpts.fontColor, i, globalDefaults.defaultFontColor); - ctx.font = plFont.font; ctx.fillStyle = pointLabelFontColor; var angleRadians = scale.getIndexAngle(i); @@ -278,39 +287,46 @@ module.exports = function(Chart) { fillText(ctx, scale.pointLabels[i] || '', pointLabelPosition, plFont.size); } } + ctx.restore(); } function drawRadiusLine(scale, gridLineOpts, radius, index) { var ctx = scale.ctx; - ctx.strokeStyle = helpers.valueAtIndexOrDefault(gridLineOpts.color, index - 1); - ctx.lineWidth = helpers.valueAtIndexOrDefault(gridLineOpts.lineWidth, index - 1); + var circular = gridLineOpts.circular; + var valueCount = getValueCount(scale); + var lineColor = helpers.valueAtIndexOrDefault(gridLineOpts.color, index - 1); + var lineWidth = helpers.valueAtIndexOrDefault(gridLineOpts.lineWidth, index - 1); + var pointPosition; - if (scale.options.gridLines.circular) { + if ((!circular && !valueCount) || !lineColor || !lineWidth) { + return; + } + + ctx.save(); + ctx.strokeStyle = lineColor; + ctx.lineWidth = lineWidth; + if (ctx.setLineDash) { + ctx.setLineDash(gridLineOpts.borderDash || []); + ctx.lineDashOffset = gridLineOpts.borderDashOffset || 0.0; + } + + ctx.beginPath(); + if (circular) { // Draw circular arcs between the points - ctx.beginPath(); ctx.arc(scale.xCenter, scale.yCenter, radius, 0, Math.PI * 2); - ctx.closePath(); - ctx.stroke(); } else { // Draw straight lines connecting each index - var valueCount = getValueCount(scale); - - if (valueCount === 0) { - return; - } - - ctx.beginPath(); - var pointPosition = scale.getPointPosition(0, radius); + pointPosition = scale.getPointPosition(0, radius); ctx.moveTo(pointPosition.x, pointPosition.y); for (var i = 1; i < valueCount; i++) { pointPosition = scale.getPointPosition(i, radius); ctx.lineTo(pointPosition.x, pointPosition.y); } - - ctx.closePath(); - ctx.stroke(); } + ctx.closePath(); + ctx.stroke(); + ctx.restore(); } function numberOrZero(param) { diff --git a/test/fixtures/scale.radialLinear/border-dash.json b/test/fixtures/scale.radialLinear/border-dash.json new file mode 100644 index 00000000000..5a28ffab087 --- /dev/null +++ b/test/fixtures/scale.radialLinear/border-dash.json @@ -0,0 +1,33 @@ +{ + "config": { + "type": "radar", + "data": { + "labels": ["A", "B", "C", "D", "E"] + }, + "options": { + "responsive": false, + "legend": false, + "title": false, + "scale": { + "gridLines": { + "color": "rgba(0, 0, 255, 0.5)", + "lineWidth": 1, + "borderDash": [4, 2], + "borderDashOffset": 2 + }, + "angleLines": { + "color": "rgba(0, 0, 255, 0.5)", + "lineWidth": 1, + "borderDash": [4, 2], + "borderDashOffset": 2 + }, + "pointLabels": { + "display": false + }, + "ticks": { + "display": false + } + } + } + } +} diff --git a/test/fixtures/scale.radialLinear/border-dash.png b/test/fixtures/scale.radialLinear/border-dash.png new file mode 100644 index 0000000000000000000000000000000000000000..eea0db5ff007aeccdbf4403974f9c10d0ce15747 GIT binary patch literal 31221 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 literal 0 HcmV?d00001 diff --git a/test/fixtures/scale.radialLinear/circular-border-dash.json b/test/fixtures/scale.radialLinear/circular-border-dash.json new file mode 100644 index 00000000000..b69250de975 --- /dev/null +++ b/test/fixtures/scale.radialLinear/circular-border-dash.json @@ -0,0 +1,34 @@ +{ + "config": { + "type": "radar", + "data": { + "labels": ["A", "B", "C", "D", "E"] + }, + "options": { + "responsive": false, + "legend": false, + "title": false, + "scale": { + "gridLines": { + "circular": true, + "color": "rgba(0, 0, 255, 0.5)", + "lineWidth": 1, + "borderDash": [4, 2], + "borderDashOffset": 2 + }, + "angleLines": { + "color": "rgba(0, 0, 255, 0.5)", + "lineWidth": 1, + "borderDash": [4, 2], + "borderDashOffset": 2 + }, + "pointLabels": { + "display": false + }, + "ticks": { + "display": false + } + } + } + } +} diff --git a/test/fixtures/scale.radialLinear/circular-border-dash.png b/test/fixtures/scale.radialLinear/circular-border-dash.png new file mode 100644 index 0000000000000000000000000000000000000000..fedb709e9a9cae3af99c8c70d02971f08c525452 GIT binary patch literal 39138 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

!ZqH3jW5J>(-5E9Y8wGTERg_>cC=ZY`!u& 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 literal 0 HcmV?d00001 diff --git a/test/specs/scale.radialLinear.tests.js b/test/specs/scale.radialLinear.tests.js index 707ce0b7c52..5390ef77d5f 100644 --- a/test/specs/scale.radialLinear.tests.js +++ b/test/specs/scale.radialLinear.tests.js @@ -1,5 +1,7 @@ // Tests for the radial linear scale used by the polar area and radar charts describe('Test the radial linear scale', function() { + describe('auto', jasmine.fixture.specs('scale.radialLinear')); + it('Should register the constructor with the scale service', function() { var Constructor = Chart.scaleService.getScaleConstructor('radialLinear'); expect(Constructor).not.toBe(undefined); @@ -12,7 +14,9 @@ describe('Test the radial linear scale', function() { angleLines: { display: true, color: 'rgba(0, 0, 0, 0.1)', - lineWidth: 1 + lineWidth: 1, + borderDash: [], + borderDashOffset: 0.0 }, animate: true, display: true, From 08447e9e19a7535b24b4c8888aaf24d013204ea1 Mon Sep 17 00:00:00 2001 From: Stef Louwers Date: Tue, 27 Nov 2018 15:14:41 +0100 Subject: [PATCH 026/137] Draw radial scale angle lines before tick labels (#5855) Moved drawing of radial lines before drawing the tick labels, such that the radial lines are not drawn on top of the tick labels and their backdrop. --- src/scales/scale.radialLinear.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/scales/scale.radialLinear.js b/src/scales/scale.radialLinear.js index 90ac22f0db7..ffd76dd43f4 100644 --- a/src/scales/scale.radialLinear.js +++ b/src/scales/scale.radialLinear.js @@ -497,6 +497,10 @@ module.exports = function(Chart) { var tickFontFamily = valueOrDefault(tickOpts.fontFamily, globalDefaults.defaultFontFamily); var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily); + if (opts.angleLines.display || opts.pointLabels.display) { + drawPointLabels(me); + } + helpers.each(me.ticks, function(label, index) { // Don't draw a centre value (if it is minimum) if (index > 0 || tickOpts.reverse) { @@ -534,10 +538,6 @@ module.exports = function(Chart) { } } }); - - if (opts.angleLines.display || opts.pointLabels.display) { - drawPointLabels(me); - } } } }); From f5437fe548e5ca4d9236f3724fae692c30913c84 Mon Sep 17 00:00:00 2001 From: Jukka Kurkela Date: Tue, 27 Nov 2018 17:26:41 +0200 Subject: [PATCH 027/137] Fix nearest interaction mode to return all items (#5857) Return all items that are at the nearest distance to the point and add unit tests for nearest + axis: 'x' and nearest + axis: 'y' --- docs/general/interactions/modes.md | 2 +- src/core/core.interaction.js | 21 +- test/specs/core.interaction.tests.js | 363 +++++++++++++++------------ 3 files changed, 208 insertions(+), 178 deletions(-) diff --git a/docs/general/interactions/modes.md b/docs/general/interactions/modes.md index 30c40616887..d6a8c261fba 100644 --- a/docs/general/interactions/modes.md +++ b/docs/general/interactions/modes.md @@ -20,7 +20,7 @@ var chart = new Chart(ctx, { ``` ## nearest -Gets the item that is nearest to the point. The nearest item is determined based on the distance to the center of the chart item (point, bar). If 2 or more items are at the same distance, the one with the smallest area is used. If `intersect` is true, this is only triggered when the mouse position intersects an item in the graph. This is very useful for combo charts where points are hidden behind bars. +Gets the items that are at the nearest distance to the point. The nearest item is determined based on the distance to the center of the chart item (point, bar). You can use the `axis` setting to define which directions are used in distance calculation. If `intersect` is true, this is only triggered when the mouse position intersects an item in the graph. This is very useful for combo charts where points are hidden behind bars. ```javascript var chart = new Chart(ctx, { diff --git a/src/core/core.interaction.js b/src/core/core.interaction.js index be85a080f76..9b99e53bb1b 100644 --- a/src/core/core.interaction.js +++ b/src/core/core.interaction.js @@ -243,26 +243,7 @@ module.exports = { var position = getRelativePosition(e, chart); options.axis = options.axis || 'xy'; var distanceMetric = getDistanceMetricForAxis(options.axis); - var nearestItems = getNearestItems(chart, position, options.intersect, distanceMetric); - - // 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); + return getNearestItems(chart, position, options.intersect, distanceMetric); }, /** diff --git a/test/specs/core.interaction.tests.js b/test/specs/core.interaction.tests.js index 49cd3bd193d..356d0de95b1 100644 --- a/test/specs/core.interaction.tests.js +++ b/test/specs/core.interaction.tests.js @@ -347,73 +347,145 @@ describe('Core.Interaction', function() { }); }); - it ('axis: xy should return the nearest item', function() { - var chart = this.chart; - var evt = { - type: 'click', - chart: chart, - native: true, // needed otherwise things its a DOM event - x: 0, - y: 0 - }; + describe('axis: xy', function() { + it ('should return the nearest item', function() { + var chart = this.chart; + var evt = { + type: 'click', + chart: chart, + native: true, // needed otherwise things its a DOM event + x: 0, + y: 0 + }; + + // Nearest to 0,0 (top left) will be first point of dataset 2 + var elements = Chart.Interaction.modes.nearest(chart, evt, {intersect: false}); + var meta = chart.getDatasetMeta(1); + expect(elements).toEqual([meta.data[0]]); + }); - // Nearest to 0,0 (top left) will be first point of dataset 2 - var elements = Chart.Interaction.modes.nearest(chart, evt, {intersect: false}); - var meta = chart.getDatasetMeta(1); - expect(elements).toEqual([meta.data[0]]); + it ('should return all items at the same nearest distance', function() { + var chart = this.chart; + var meta0 = chart.getDatasetMeta(0); + var meta1 = chart.getDatasetMeta(1); + + // Halfway between 2 mid points + var pt = { + x: meta0.data[1]._view.x, + y: (meta0.data[1]._view.y + meta1.data[1]._view.y) / 2 + }; + + var evt = { + type: 'click', + chart: chart, + native: true, // needed otherwise things its a DOM event + x: pt.x, + y: pt.y + }; + + // Both points are nearest + var elements = Chart.Interaction.modes.nearest(chart, evt, {intersect: false}); + expect(elements).toEqual([meta0.data[1], meta1.data[1]]); + }); }); - it ('should return the smallest item if more than 1 are at the same distance', function() { - var chart = this.chart; - var meta0 = chart.getDatasetMeta(0); - var meta1 = chart.getDatasetMeta(1); - - // Halfway between 2 mid points - var pt = { - x: meta0.data[1]._view.x, - y: (meta0.data[1]._view.y + meta1.data[1]._view.y) / 2 - }; - - var evt = { - type: 'click', - chart: chart, - native: true, // needed otherwise things its a DOM event - x: pt.x, - y: pt.y - }; + describe('axis: x', function() { + it ('should return all items at current x', function() { + var chart = this.chart; + var meta0 = chart.getDatasetMeta(0); + var meta1 = chart.getDatasetMeta(1); + + // At 'Point 2', 10 + var pt = { + x: meta0.data[1]._view.x, + y: meta0.data[0]._view.y + }; + + var evt = { + type: 'click', + chart: chart, + native: true, // needed otherwise things its a DOM event + x: pt.x, + y: pt.y + }; + + // Middle point from both series are nearest + var elements = Chart.Interaction.modes.nearest(chart, evt, {axis: 'x', intersect: false}); + expect(elements).toEqual([meta0.data[1], meta1.data[1]]); + }); - // Nearest to 0,0 (top left) will be first point of dataset 2 - var elements = Chart.Interaction.modes.nearest(chart, evt, {intersect: false}); - expect(elements).toEqual([meta0.data[1]]); + it ('should return all items at nearest x-distance', function() { + var chart = this.chart; + var meta0 = chart.getDatasetMeta(0); + var meta1 = chart.getDatasetMeta(1); + + // Haflway between 'Point 1' and 'Point 2', y=10 + var pt = { + x: (meta0.data[0]._view.x + meta0.data[1]._view.x) / 2, + y: meta0.data[0]._view.y + }; + + var evt = { + type: 'click', + chart: chart, + native: true, // needed otherwise things its a DOM event + x: pt.x, + y: pt.y + }; + + // Should return all (4) points from 'Point 1' and 'Point 2' + var elements = Chart.Interaction.modes.nearest(chart, evt, {axis: 'x', intersect: false}); + expect(elements).toEqual([meta0.data[0], meta0.data[1], meta1.data[0], meta1.data[1]]); + }); }); - it ('should return the lowest dataset index if size and area are the same', function() { - var chart = this.chart; - // Make equal sized points at index: 1 - chart.data.datasets[0].pointRadius[1] = 10; - chart.update(); - - // Trigger an event over top of the - var meta0 = chart.getDatasetMeta(0); - var meta1 = chart.getDatasetMeta(1); - - // Halfway between 2 mid points - var pt = { - x: meta0.data[1]._view.x, - y: (meta0.data[1]._view.y + meta1.data[1]._view.y) / 2 - }; - - var evt = { - type: 'click', - chart: chart, - native: true, // needed otherwise things its a DOM event - x: pt.x, - y: pt.y - }; + describe('axis: y', function() { + it ('should return item with value 30', function() { + var chart = this.chart; + var meta0 = chart.getDatasetMeta(0); + + // 'Point 1', y = 30 + var pt = { + x: meta0.data[0]._view.x, + y: meta0.data[2]._view.y + }; + + var evt = { + type: 'click', + chart: chart, + native: true, // needed otherwise things its a DOM event + x: pt.x, + y: pt.y + }; + + // Middle point from both series are nearest + var elements = Chart.Interaction.modes.nearest(chart, evt, {axis: 'y', intersect: false}); + expect(elements).toEqual([meta0.data[2]]); + }); - // Nearest to 0,0 (top left) will be first point of dataset 2 - var elements = Chart.Interaction.modes.nearest(chart, evt, {intersect: false}); - expect(elements).toEqual([meta0.data[1]]); + it ('should return all items at value 40', function() { + var chart = this.chart; + var meta0 = chart.getDatasetMeta(0); + var meta1 = chart.getDatasetMeta(1); + + // 'Point 1', y = 40 + var pt = { + x: meta0.data[0]._view.x, + y: meta0.data[1]._view.y + }; + + var evt = { + type: 'click', + chart: chart, + native: true, // needed otherwise things its a DOM event + x: pt.x, + y: pt.y + }; + + // Should return points with value 40 + var elements = Chart.Interaction.modes.nearest(chart, evt, {axis: 'y', intersect: false}); + expect(elements).toEqual([meta0.data[1], meta1.data[0], meta1.data[1], meta1.data[2]]); + }); }); }); @@ -438,117 +510,94 @@ describe('Core.Interaction', function() { }); }); - it ('should return the nearest item', function() { - var chart = this.chart; - var meta = chart.getDatasetMeta(1); - var point = meta.data[1]; - - var evt = { - type: 'click', - chart: chart, - native: true, // needed otherwise things its a DOM event - x: point._view.x + 15, - y: point._view.y - }; - - // Nothing intersects so find nothing - var elements = Chart.Interaction.modes.nearest(chart, evt, {intersect: true}); - expect(elements).toEqual([]); - - evt = { - type: 'click', - chart: chart, - native: true, - x: point._view.x, - y: point._view.y - }; - elements = Chart.Interaction.modes.nearest(chart, evt, {intersect: true}); - expect(elements).toEqual([point]); - }); - - it ('should return the nearest item even if 2 intersect', function() { - var chart = this.chart; - chart.data.datasets[0].pointRadius = [5, 30, 5]; - chart.data.datasets[0].data[1] = 39; + describe('axis=xy', function() { + it ('should return the nearest item', function() { + var chart = this.chart; + var meta = chart.getDatasetMeta(1); + var point = meta.data[1]; + + var evt = { + type: 'click', + chart: chart, + native: true, // needed otherwise things its a DOM event + x: point._view.x + 15, + y: point._view.y + }; + + // Nothing intersects so find nothing + var elements = Chart.Interaction.modes.nearest(chart, evt, {intersect: true}); + expect(elements).toEqual([]); + + evt = { + type: 'click', + chart: chart, + native: true, + x: point._view.x, + y: point._view.y + }; + elements = Chart.Interaction.modes.nearest(chart, evt, {intersect: true}); + expect(elements).toEqual([point]); + }); - chart.data.datasets[1].pointRadius = [10, 10, 10]; + it ('should return the nearest item even if 2 intersect', function() { + var chart = this.chart; + chart.data.datasets[0].pointRadius = [5, 30, 5]; + chart.data.datasets[0].data[1] = 39; - // Trigger an event over top of the - var meta0 = chart.getDatasetMeta(0); + chart.data.datasets[1].pointRadius = [10, 10, 10]; - // Halfway between 2 mid points - var pt = { - x: meta0.data[1]._view.x, - y: meta0.data[1]._view.y - }; + chart.update(); - var evt = { - type: 'click', - chart: chart, - native: true, // needed otherwise things its a DOM event - x: pt.x, - y: pt.y - }; + // Trigger an event over top of the + var meta0 = chart.getDatasetMeta(0); - var elements = Chart.Interaction.modes.nearest(chart, evt, {intersect: true}); - expect(elements).toEqual([meta0.data[1]]); - }); + // Halfway between 2 mid points + var pt = { + x: meta0.data[1]._view.x, + y: meta0.data[1]._view.y + }; - it ('should return the smallest item if more than 1 are at the same distance', function() { - var chart = this.chart; - chart.data.datasets[0].pointRadius = [5, 5, 5]; - chart.data.datasets[0].data[1] = 40; + var evt = { + type: 'click', + chart: chart, + native: true, // needed otherwise things its a DOM event + x: pt.x, + y: pt.y + }; - chart.data.datasets[1].pointRadius = [10, 10, 10]; + var elements = Chart.Interaction.modes.nearest(chart, evt, {intersect: true}); + expect(elements).toEqual([meta0.data[1]]); + }); - // Trigger an event over top of the - var meta0 = chart.getDatasetMeta(0); + it ('should return the all items if more than 1 are at the same distance', function() { + var chart = this.chart; + chart.data.datasets[0].pointRadius = [5, 5, 5]; + chart.data.datasets[0].data[1] = 40; - // Halfway between 2 mid points - var pt = { - x: meta0.data[1]._view.x, - y: meta0.data[1]._view.y - }; + chart.data.datasets[1].pointRadius = [10, 10, 10]; - var evt = { - type: 'click', - chart: chart, - native: true, // needed otherwise things its a DOM event - x: pt.x, - y: pt.y - }; + chart.update(); - var elements = Chart.Interaction.modes.nearest(chart, evt, {intersect: true}); - expect(elements).toEqual([meta0.data[1]]); - }); + var meta0 = chart.getDatasetMeta(0); + var meta1 = chart.getDatasetMeta(1); - it ('should return the item at the lowest dataset index if distance and area are the same', function() { - var chart = this.chart; - chart.data.datasets[0].pointRadius = [5, 10, 5]; - chart.data.datasets[0].data[1] = 40; + // Halfway between 2 mid points + var pt = { + x: meta0.data[1]._view.x, + y: meta0.data[1]._view.y + }; - chart.data.datasets[1].pointRadius = [10, 10, 10]; + var evt = { + type: 'click', + chart: chart, + native: true, // needed otherwise things its a DOM event + x: pt.x, + y: pt.y + }; - // Trigger an event over top of the - var meta0 = chart.getDatasetMeta(0); - - // Halfway between 2 mid points - var pt = { - x: meta0.data[1]._view.x, - y: meta0.data[1]._view.y - }; - - var evt = { - type: 'click', - chart: chart, - native: true, // needed otherwise things its a DOM event - x: pt.x, - y: pt.y - }; - - // Nearest to 0,0 (top left) will be first point of dataset 2 - var elements = Chart.Interaction.modes.nearest(chart, evt, {intersect: true}); - expect(elements).toEqual([meta0.data[1]]); + var elements = Chart.Interaction.modes.nearest(chart, evt, {intersect: true}); + expect(elements).toEqual([meta0.data[1], meta1.data[1]]); + }); }); }); }); From 241499d27f6b684a348eee3a7b1e615c031809b6 Mon Sep 17 00:00:00 2001 From: Akihiko Kusanagi Date: Wed, 28 Nov 2018 08:54:03 +0800 Subject: [PATCH 028/137] Make getHoverColor() return the original value if it is CanvasGradient (#5865) --- src/core/core.helpers.js | 2 +- test/specs/core.helpers.tests.js | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/core/core.helpers.js b/src/core/core.helpers.js index 3b1eca5edb3..ca8a7d20c38 100644 --- a/src/core/core.helpers.js +++ b/src/core/core.helpers.js @@ -647,7 +647,7 @@ module.exports = function() { helpers.getHoverColor = function(colorValue) { /* global CanvasPattern */ - return (colorValue instanceof CanvasPattern) ? + return (colorValue instanceof CanvasPattern || colorValue instanceof CanvasGradient) ? colorValue : helpers.color(colorValue).saturate(0.5).darken(0.1).rgbString(); }; diff --git a/test/specs/core.helpers.tests.js b/test/specs/core.helpers.tests.js index 70f0981df0e..796148aaf6c 100644 --- a/test/specs/core.helpers.tests.js +++ b/test/specs/core.helpers.tests.js @@ -848,6 +848,13 @@ describe('Core helper tests', function() { }; }); + it('should return a CanvasGradient when called with a CanvasGradient', function() { + var context = document.createElement('canvas').getContext('2d'); + var gradient = context.createLinearGradient(0, 1, 2, 3); + + expect(helpers.getHoverColor(gradient) instanceof CanvasGradient).toBe(true); + }); + it('should return a modified version of color when called with a color', function() { var originalColorRGB = 'rgb(70, 191, 189)'; From 1f2fa5c90c7a270874893919af1af9ae6fd5666b Mon Sep 17 00:00:00 2001 From: Akihiko Kusanagi Date: Wed, 28 Nov 2018 14:53:41 +0800 Subject: [PATCH 029/137] Adjust the size of rectRounded/rectRot point to fit pointRadius (#5858) - Calculate the vertices of the shapes so that they are inscribed in the circle that has the radius of `pointRadius` - Remove `translate()` and `rotate()` to fix the regression introduced by #5319 - Refactor `rectRounded` for better performance --- src/helpers/helpers.canvas.js | 166 ++++++++++-------- .../controller.bubble/point-style.png | Bin 6566 -> 11225 bytes test/fixtures/controller.line/point-style.png | Bin 6273 -> 6440 bytes .../fixtures/controller.radar/point-style.png | Bin 6986 -> 7070 bytes .../element.point/point-style-rect-rot.png | Bin 6520 -> 3174 bytes .../point-style-rect-rounded.png | Bin 8262 -> 4469 bytes test/fixtures/element.point/rotation.js | 56 ++++++ test/fixtures/element.point/rotation.png | Bin 0 -> 52523 bytes test/fixtures/helpers.canvas/rounded-rect.js | 39 ++++ test/fixtures/helpers.canvas/rounded-rect.png | Bin 0 -> 13050 bytes test/specs/element.point.tests.js | 14 +- test/specs/helpers.canvas.tests.js | 55 +++++- 12 files changed, 237 insertions(+), 93 deletions(-) create mode 100644 test/fixtures/element.point/rotation.js create mode 100644 test/fixtures/element.point/rotation.png create mode 100644 test/fixtures/helpers.canvas/rounded-rect.js create mode 100644 test/fixtures/helpers.canvas/rounded-rect.png diff --git a/src/helpers/helpers.canvas.js b/src/helpers/helpers.canvas.js index 60fb6e1a299..ea0c6f1cefe 100644 --- a/src/helpers/helpers.canvas.js +++ b/src/helpers/helpers.canvas.js @@ -2,6 +2,13 @@ var helpers = require('./helpers.core'); +var PI = Math.PI; +var RAD_PER_DEG = PI / 180; +var DOUBLE_PI = PI * 2; +var HALF_PI = PI / 2; +var QUARTER_PI = PI / 4; +var TWO_THIRDS_PI = PI * 2 / 3; + /** * @namespace Chart.helpers.canvas */ @@ -27,20 +34,28 @@ var exports = module.exports = { */ roundedRect: function(ctx, x, y, width, height, radius) { if (radius) { - // NOTE(SB) `epsilon` helps to prevent minor artifacts appearing - // on Chrome when `r` is exactly half the height or the width. - var epsilon = 0.0000001; - var r = Math.min(radius, (height / 2) - epsilon, (width / 2) - epsilon); - - ctx.moveTo(x + r, y); - ctx.lineTo(x + width - r, y); - ctx.arcTo(x + width, y, x + width, y + r, r); - ctx.lineTo(x + width, y + height - r); - ctx.arcTo(x + width, y + height, x + width - r, y + height, r); - ctx.lineTo(x + r, y + height); - ctx.arcTo(x, y + height, x, y + height - r, r); - ctx.lineTo(x, y + r); - ctx.arcTo(x, y, x + r, y, r); + var r = Math.min(radius, height / 2, width / 2); + var left = x + r; + var top = y + r; + var right = x + width - r; + var bottom = y + height - r; + + ctx.moveTo(x, top); + if (left < right && top < bottom) { + ctx.arc(left, top, r, -PI, -HALF_PI); + ctx.arc(right, top, r, -HALF_PI, 0); + ctx.arc(right, bottom, r, 0, HALF_PI); + ctx.arc(left, bottom, r, HALF_PI, PI); + } else if (left < right) { + ctx.moveTo(left, y); + ctx.arc(right, top, r, -HALF_PI, HALF_PI); + ctx.arc(left, top, r, HALF_PI, PI + HALF_PI); + } else if (top < bottom) { + ctx.arc(left, top, r, -PI, 0); + ctx.arc(left, bottom, r, 0, PI); + } else { + ctx.arc(left, top, r, -PI, PI); + } ctx.closePath(); ctx.moveTo(x, y); } else { @@ -49,8 +64,8 @@ var exports = module.exports = { }, drawPoint: function(ctx, style, radius, x, y, rotation) { - var type, edgeLength, xOffset, yOffset, height, size; - rotation = rotation || 0; + var type, xOffset, yOffset, size, cornerRadius; + var rad = (rotation || 0) * RAD_PER_DEG; if (style && typeof style === 'object') { type = style.toString(); @@ -64,88 +79,97 @@ var exports = module.exports = { return; } - ctx.save(); - ctx.translate(x, y); - ctx.rotate(rotation * Math.PI / 180); ctx.beginPath(); switch (style) { // Default includes circle default: - ctx.arc(0, 0, radius, 0, Math.PI * 2); + ctx.arc(x, y, radius, 0, DOUBLE_PI); ctx.closePath(); break; case 'triangle': - edgeLength = 3 * radius / Math.sqrt(3); - height = edgeLength * Math.sqrt(3) / 2; - ctx.moveTo(-edgeLength / 2, height / 3); - ctx.lineTo(edgeLength / 2, height / 3); - ctx.lineTo(0, -2 * height / 3); + ctx.moveTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); + rad += TWO_THIRDS_PI; + ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); + rad += TWO_THIRDS_PI; + ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); ctx.closePath(); break; - case 'rect': - size = 1 / Math.SQRT2 * radius; - ctx.rect(-size, -size, 2 * size, 2 * size); - break; case 'rectRounded': - var offset = radius / Math.SQRT2; - var leftX = -offset; - var topY = -offset; - var sideSize = Math.SQRT2 * radius; - - // NOTE(SB) the rounded rect implementation changed to use `arcTo` - // instead of `quadraticCurveTo` since it generates better results - // when rect is almost a circle. 0.425 (instead of 0.5) produces - // results visually closer to the previous impl. - this.roundedRect(ctx, leftX, topY, sideSize, sideSize, radius * 0.425); + // NOTE: the rounded rect implementation changed to use `arc` instead of + // `quadraticCurveTo` since it generates better results when rect is + // almost a circle. 0.516 (instead of 0.5) produces results with visually + // closer proportion to the previous impl and it is inscribed in the + // circle with `radius`. For more details, see the following PRs: + // https://github.com/chartjs/Chart.js/issues/5597 + // https://github.com/chartjs/Chart.js/issues/5858 + cornerRadius = radius * 0.516; + size = radius - cornerRadius; + xOffset = Math.cos(rad + QUARTER_PI) * size; + yOffset = Math.sin(rad + QUARTER_PI) * size; + ctx.arc(x - xOffset, y - yOffset, cornerRadius, rad - PI, rad - HALF_PI); + ctx.arc(x + yOffset, y - xOffset, cornerRadius, rad - HALF_PI, rad); + ctx.arc(x + xOffset, y + yOffset, cornerRadius, rad, rad + HALF_PI); + ctx.arc(x - yOffset, y + xOffset, cornerRadius, rad + HALF_PI, rad + PI); + ctx.closePath(); break; + case 'rect': + if (!rotation) { + size = Math.SQRT1_2 * radius; + ctx.rect(x - size, y - size, 2 * size, 2 * size); + break; + } + rad += QUARTER_PI; + /* falls through */ case 'rectRot': - size = 1 / Math.SQRT2 * radius; - ctx.moveTo(-size, 0); - ctx.lineTo(0, size); - ctx.lineTo(size, 0); - ctx.lineTo(0, -size); + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + yOffset, y - xOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.lineTo(x - yOffset, y + xOffset); ctx.closePath(); break; - case 'cross': - ctx.moveTo(0, radius); - ctx.lineTo(0, -radius); - ctx.moveTo(-radius, 0); - ctx.lineTo(radius, 0); - break; case 'crossRot': - xOffset = Math.cos(Math.PI / 4) * radius; - yOffset = Math.sin(Math.PI / 4) * radius; - ctx.moveTo(-xOffset, -yOffset); - ctx.lineTo(xOffset, yOffset); - ctx.moveTo(-xOffset, yOffset); - ctx.lineTo(xOffset, -yOffset); + rad += QUARTER_PI; + /* falls through */ + case 'cross': + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.moveTo(x + yOffset, y - xOffset); + ctx.lineTo(x - yOffset, y + xOffset); break; case 'star': - ctx.moveTo(0, radius); - ctx.lineTo(0, -radius); - ctx.moveTo(-radius, 0); - ctx.lineTo(radius, 0); - xOffset = Math.cos(Math.PI / 4) * radius; - yOffset = Math.sin(Math.PI / 4) * radius; - ctx.moveTo(-xOffset, -yOffset); - ctx.lineTo(xOffset, yOffset); - ctx.moveTo(-xOffset, yOffset); - ctx.lineTo(xOffset, -yOffset); + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.moveTo(x + yOffset, y - xOffset); + ctx.lineTo(x - yOffset, y + xOffset); + rad += QUARTER_PI; + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.moveTo(x + yOffset, y - xOffset); + ctx.lineTo(x - yOffset, y + xOffset); break; case 'line': - ctx.moveTo(-radius, 0); - ctx.lineTo(radius, 0); + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); break; case 'dash': - ctx.moveTo(0, 0); - ctx.lineTo(radius, 0); + ctx.moveTo(x, y); + ctx.lineTo(x + Math.cos(rad) * radius, y + Math.sin(rad) * radius); break; } ctx.fill(); ctx.stroke(); - ctx.restore(); }, clipArea: function(ctx, area) { diff --git a/test/fixtures/controller.bubble/point-style.png b/test/fixtures/controller.bubble/point-style.png index f1d3b8168c07d1e70eda4c04dc50325d25c0b39c..d949141d81d4f46dbb844b9a33e63d0b2f81a3fa 100644 GIT binary patch literal 11225 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 literal 6273 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 diff --git a/test/fixtures/controller.radar/point-style.png b/test/fixtures/controller.radar/point-style.png index 562cb620b054de95898cba7bbc3908b479bfe2da..3f73ff96f91a02142de3504a59531b8f954d59d2 100644 GIT binary patch literal 7070 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 P!tmG;N89qlzSsT(xdDrf diff --git a/test/fixtures/element.point/point-style-rect-rot.png b/test/fixtures/element.point/point-style-rect-rot.png index 09c0adac3d3e32580845000e7188e63fb60b7c95..a7c128855892682594576c778eeafaf043fd7f36 100644 GIT binary patch literal 3174 zcma)8X;f3!7Ctuw2?&}XXd*<#6m6|cB7>k3zy_g@!2zu}5TP1W1VluXNN$xP2niIZ zRZ&4-pHM}nDh32eYr-Ic@qvQMWTFD1kW>m$Lhd^!Z>_G?f6ZEV?S1zC_HfQU`+ms? z3Gy>DnQsCBF!Nu(ZX*CBsA7OJ0}5GAI}ZR;JO6dwo0BfS?+q4NKf#*RM^90|jj%Do zd`g`wr5-&XMza0~cRwb}eCDJX_YtYt+%|sh1|7FWWS&vFcxr}2=BV3f^^F%#o`g>8 zPOJ?+TUv1})Lo}jW$}Dqk-z`&Ei%VcdRk8x0A+iuhyfd;^nZA%blbL2CP6$V1;j_940Pld=Ss|iAp zpEn*+qL9R6$XQWE@0;bsw~9zQH?>UGVg)PVKT1`t)cb`;uY3$oe|e{-d~?5yXJMrg5 zBz-vbn+>@m3sA)S!56ewQQY7@+f;xbA%@XS{Vzmf}XBrmmLiGI|@+VFzoD6cam#O`6jb| z(;;y+N0KvjgjCA&M79mg{#hEaE3&zeLaw)R*ahm~w+k@%E{xd2Z-jWYvaoX%DXpj6 z3=g~LhK_EdzAJmM-}xYU+>-cNgbD|O&bQ-~A!nWcGgR|9DCyNuhCFrt=k+asDA`L2 zl{m3Sh}%q#fFc*CI%b5%RjP8F3_Y`{5p8n@7z*w~srX=M>t&=wgMnHUEe7oPOUUpq zFc3N-f*F(;XexDvU1b{rcvTR{9Y{8}W0QRyi>x`UW;pcU<~vKFxRDW_Mio$ObT*TL4>tMWUyBWZFeCh)DWQAx z#g}!qy@vP$pP7f`()RmI?DL1Kc8?t- zcUnBXFlxfyd-!BuE2YG##HEjce|h(_S2wfq3%+3=da)&$*}mxnthi({@NLyP$#y1Y zN51tv54bN|o5Ijd7ao3|`!ePr`Ibfd*~^9l?cb<`xZ8GC_suMP?x|-AXN>7-d7L#M zcJbXgMTuIKZ|7xY;jq@!MrW6*DeiImr9WR{g_Yms6dhndBhe|E8hi{_xPamX@=$`F0k*;8eHL+1XqB zc8_LK(7w9ZCzgD1FgIs%H7Pz#JWi7zrX;5%iX6e&Jz#kgIM)Q+%LXYE+IJTRd_n6U zHu!4|Rk9ilXSU5d^?4hsLbBMpknElvOj=!5yl7&)G{jV37Pm?_e_%Cy4y{S8Jk29} z49vvZlru4ASNx*wDXvAn_bgPa;d96?txCotsoLWQ$DrNd=!A*76w;sglEO4ext>TK$mHi#sFS3+FocR-DE^Taq=c@KNKd*8PQOj zSx$>VtDWX?Xw_z10|N}&jccLsy_UKYX7Mg2Iq2Y}uaswcfdsCXMll;OGd5_ke=t6O zK}tA$!IS|QTF0mQ@T;fPizW{O{E2Kb@n!big^#KUDy$h$BRFCuMx@5tW^509R1m*+x;S7p?KP59ivbVQMMt5}FXZ*e(@ zy-J=L>N%D(Q%Yzk;=-w6fOjpY6Ka}D3S+L8QV}l)R7dD(^|2))$s9lJ%1N^a2{J^$ zJGR#K%HC`kEy%a^MVK13Ld1qa2~Kbcj1 zHU}lD7(|J#)8yU=nDpzZMtoQj4LM;0u56F z6P5Xq*{?6cQ@IBNqpA#V%kYO z8H!ShktaaN2}AezZx=c$7g^;AXPGCy2n zFs=S*`QafQ3vBYrn7m;Hagt;0bZn>8>z639bt93XF>rjGN|T3b^GrCB5p_f1)J-hp z$2qK`R?SkwY*>oRV6lou;6j8JXwNFoQ!(-V%gax~tr}E_>S>E~$tI`;N$n^{0+$xZS~)kHth>sK}0+j^zS5+!1r&9#O(fXg;6YV16; zSlZLE@+QK2v={?R3Y<`)u((DFRRE$|QcKyonu%bQaMPWroYPjzPpL8d_CSfYl ziYR^|m;qKUOQHH`C+)Q`_jLN$X)2-2p2mti)xN~**?)bU09dXh_C8s^f%l2`9kTj= L6|}C*hkNpWYD$7~ literal 6520 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 diff --git a/test/fixtures/element.point/point-style-rect-rounded.png b/test/fixtures/element.point/point-style-rect-rounded.png index a58e9e62361a96fb187cc342c58d89c954faaaa4..8b58b44303ac578744a267231301427f461475b8 100644 GIT binary patch literal 4469 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 diff --git a/test/fixtures/element.point/rotation.js b/test/fixtures/element.point/rotation.js new file mode 100644 index 00000000000..b713f5d6bf4 --- /dev/null +++ b/test/fixtures/element.point/rotation.js @@ -0,0 +1,56 @@ +var gradient; + +var datasets = ['circle', 'cross', 'crossRot', 'dash', 'line', 'rect', 'rectRounded', 'rectRot', 'star', 'triangle'].map(function(style, y) { + return { + pointStyle: style, + data: Array.apply(null, Array(17)).map(function(v, x) { + return {x: x, y: 10 - y}; + }) + }; +}); + +var angles = Array.apply(null, Array(17)).map(function(v, i) { + return -180 + i * 22.5; +}); + +module.exports = { + config: { + type: 'bubble', + data: { + datasets: datasets + }, + options: { + responsive: false, + legend: false, + title: false, + elements: { + point: { + rotation: angles, + radius: 10, + backgroundColor: function(context) { + if (!gradient) { + gradient = context.chart.ctx.createLinearGradient(0, 0, 512, 256); + gradient.addColorStop(0, '#ff0000'); + gradient.addColorStop(1, '#0000ff'); + } + return gradient; + }, + borderColor: '#cccccc' + } + }, + layout: { + padding: 20 + }, + scales: { + xAxes: [{display: false}], + yAxes: [{display: false}] + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/element.point/rotation.png b/test/fixtures/element.point/rotation.png new file mode 100644 index 0000000000000000000000000000000000000000..579c712ecf621a087cf574b48e2c14be27a28aa7 GIT binary patch literal 52523 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�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 I-TKM@0=vZ0?*IS* literal 0 HcmV?d00001 diff --git a/test/specs/element.point.tests.js b/test/specs/element.point.tests.js index 0f3a03e319d..887250acd8d 100644 --- a/test/specs/element.point.tests.js +++ b/test/specs/element.point.tests.js @@ -124,21 +124,12 @@ describe('Chart.elements.Point', function() { }, { name: 'setFillStyle', args: ['rgba(0,0,0,0.1)'] - }, { - name: 'save', - args: [] - }, { - name: 'translate', - args: [10, 15] - }, { - name: 'rotate', - args: [0] }, { name: 'beginPath', args: [] }, { name: 'arc', - args: [0, 0, 2, 0, 2 * Math.PI] + args: [10, 15, 2, 0, 2 * Math.PI] }, { name: 'closePath', args: [], @@ -148,9 +139,6 @@ describe('Chart.elements.Point', function() { }, { name: 'stroke', args: [] - }, { - name: 'restore', - args: [] }]); }); diff --git a/test/specs/helpers.canvas.tests.js b/test/specs/helpers.canvas.tests.js index 1a342c1cb3b..ee42a414e6f 100644 --- a/test/specs/helpers.canvas.tests.js +++ b/test/specs/helpers.canvas.tests.js @@ -1,6 +1,8 @@ 'use strict'; describe('Chart.helpers.canvas', function() { + describe('auto', jasmine.fixture.specs('helpers.canvas')); + var helpers = Chart.helpers; describe('clear', function() { @@ -28,15 +30,50 @@ describe('Chart.helpers.canvas', function() { helpers.canvas.roundedRect(context, 10, 20, 30, 40, 5); expect(context.getCalls()).toEqual([ - {name: 'moveTo', args: [15, 20]}, - {name: 'lineTo', args: [35, 20]}, - {name: 'arcTo', args: [40, 20, 40, 25, 5]}, - {name: 'lineTo', args: [40, 55]}, - {name: 'arcTo', args: [40, 60, 35, 60, 5]}, - {name: 'lineTo', args: [15, 60]}, - {name: 'arcTo', args: [10, 60, 10, 55, 5]}, - {name: 'lineTo', args: [10, 25]}, - {name: 'arcTo', args: [10, 20, 15, 20, 5]}, + {name: 'moveTo', args: [10, 25]}, + {name: 'arc', args: [15, 25, 5, -Math.PI, -Math.PI / 2]}, + {name: 'arc', args: [35, 25, 5, -Math.PI / 2, 0]}, + {name: 'arc', args: [35, 55, 5, 0, Math.PI / 2]}, + {name: 'arc', args: [15, 55, 5, Math.PI / 2, Math.PI]}, + {name: 'closePath', args: []}, + {name: 'moveTo', args: [10, 20]} + ]); + }); + it('should optimize path if radius is exactly half of height', function() { + var context = window.createMockContext(); + + helpers.canvas.roundedRect(context, 10, 20, 40, 30, 15); + + expect(context.getCalls()).toEqual([ + {name: 'moveTo', args: [10, 35]}, + {name: 'moveTo', args: [25, 20]}, + {name: 'arc', args: [35, 35, 15, -Math.PI / 2, Math.PI / 2]}, + {name: 'arc', args: [25, 35, 15, Math.PI / 2, Math.PI * 3 / 2]}, + {name: 'closePath', args: []}, + {name: 'moveTo', args: [10, 20]} + ]); + }); + it('should optimize path if radius is exactly half of width', function() { + var context = window.createMockContext(); + + helpers.canvas.roundedRect(context, 10, 20, 30, 40, 15); + + expect(context.getCalls()).toEqual([ + {name: 'moveTo', args: [10, 35]}, + {name: 'arc', args: [25, 35, 15, -Math.PI, 0]}, + {name: 'arc', args: [25, 45, 15, 0, Math.PI]}, + {name: 'closePath', args: []}, + {name: 'moveTo', args: [10, 20]} + ]); + }); + it('should optimize path if radius is exactly half of width and height', function() { + var context = window.createMockContext(); + + helpers.canvas.roundedRect(context, 10, 20, 30, 30, 15); + + expect(context.getCalls()).toEqual([ + {name: 'moveTo', args: [10, 35]}, + {name: 'arc', args: [25, 35, 15, -Math.PI, Math.PI]}, {name: 'closePath', args: []}, {name: 'moveTo', args: [10, 20]} ]); From bbca2fc78982e09414330f5addd7be5e0de57527 Mon Sep 17 00:00:00 2001 From: jedrekdomanski Date: Wed, 28 Nov 2018 07:56:41 +0100 Subject: [PATCH 030/137] Enhance documentation for bar specific scale options (#5854) --- docs/charts/bar.md | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/docs/charts/bar.md b/docs/charts/bar.md index e71c38e24ba..945539ea59a 100644 --- a/docs/charts/bar.md +++ b/docs/charts/bar.md @@ -125,9 +125,8 @@ The interaction with each bar can be controlled with the following properties: All these values, if `undefined`, fallback to the associated [`elements.point.*`](../configuration/elements.md#point-configuration) options. -## Configuration Options - -The bar chart defines the following configuration options. These options are merged with the global chart configuration options, `Chart.defaults.global`, to form the options passed to the chart. +## Scale Configuration +The bar chart accepts the following configuration from the associated `scale` options: | Name | Type | Default | Description | ---- | ---- | ------- | ----------- @@ -138,22 +137,16 @@ The bar chart defines the following configuration options. These options are mer | `minBarLength` | `Number` | | Set this to ensure that bars have a minimum length in pixels. | `gridLines.offsetGridLines` | `Boolean` | `true` | If true, the bars for a particular data point fall between the grid lines. The grid line will move to the left by one half of the tick interval. If false, the grid line will go right down the middle of the bars. [more...](#offsetgridlines) -### barThickness -If this value is a number, it is applied to the width of each bar, in pixels. When this is enforced, `barPercentage` and `categoryPercentage` are ignored. - -If set to `'flex'`, the base sample widths are calculated automatically based on the previous and following samples so that they take the full available widths without overlap. Then, bars are sized using `barPercentage` and `categoryPercentage`. There is no gap when the percentage options are 1. This mode generates bars with different widths when data are not evenly spaced. - -If not set (default), the base sample widths are calculated using the smallest interval that prevents bar overlapping, and bars are sized using `barPercentage` and `categoryPercentage`. This mode always generates bars equally sized. - -### offsetGridLines -If true, the bars for a particular data point fall between the grid lines. The grid line will move to the left by one half of the tick interval, which is the space between the grid lines. If false, the grid line will go right down the middle of the bars. This is set to true for a category scale in a bar chart while false for other scales or chart types by default. - -This setting applies to the axis configuration. If axes are added to the chart, this setting will need to be set for each new axis. +### Example Usage ```javascript options = { scales: { xAxes: [{ + barPercentage: 0.5, + barThickness: 6, + maxBarThickness: 8, + minBarLength: 2, gridLines: { offsetGridLines: true } @@ -161,6 +154,15 @@ options = { } } ``` +### barThickness +If this value is a number, it is applied to the width of each bar, in pixels. When this is enforced, `barPercentage` and `categoryPercentage` are ignored. + +If set to `'flex'`, the base sample widths are calculated automatically based on the previous and following samples so that they take the full available widths without overlap. Then, bars are sized using `barPercentage` and `categoryPercentage`. There is no gap when the percentage options are 1. This mode generates bars with different widths when data are not evenly spaced. + +If not set (default), the base sample widths are calculated using the smallest interval that prevents bar overlapping, and bars are sized using `barPercentage` and `categoryPercentage`. This mode always generates bars equally sized. + +### offsetGridLines +If true, the bars for a particular data point fall between the grid lines. The grid line will move to the left by one half of the tick interval, which is the space between the grid lines. If false, the grid line will go right down the middle of the bars. This is set to true for a category scale in a bar chart while false for other scales or chart types by default. ## Default Options @@ -287,6 +289,6 @@ var myBarChart = new Chart(ctx, { ``` ## Config Options -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 configuration options for the horizontal bar chart are the same as for the [bar chart](#scale-configuration). 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`. From d6ac7d8a80a80866d64845c99cbfaec66b377ddd Mon Sep 17 00:00:00 2001 From: Akihiko Kusanagi Date: Wed, 28 Nov 2018 15:35:15 +0800 Subject: [PATCH 031/137] Fix cut off tick labels in radial scale (#5848) Fix the issue that the topmost tick label and the bottom of the chart area are cut off with a radial scale. --- src/scales/scale.radialLinear.js | 35 +++++++++++++----- .../fixtures/controller.radar/point-style.png | Bin 7070 -> 15089 bytes .../fill-radar-boundary-origin-spline.png | Bin 15638 -> 10472 bytes .../fill-radar-boundary-origin.png | Bin 12866 -> 9297 bytes test/specs/controller.doughnut.tests.js | 8 ++-- test/specs/controller.polarArea.tests.js | 16 ++++---- test/specs/controller.radar.tests.js | 18 ++++----- test/specs/scale.radialLinear.tests.js | 8 ++-- 8 files changed, 51 insertions(+), 34 deletions(-) diff --git a/src/scales/scale.radialLinear.js b/src/scales/scale.radialLinear.js index ffd76dd43f4..71e156e0f09 100644 --- a/src/scales/scale.radialLinear.js +++ b/src/scales/scale.radialLinear.js @@ -79,6 +79,16 @@ module.exports = function(Chart) { }; } + function getTickFontSize(scale) { + var opts = scale.options; + var tickOpts = opts.ticks; + + if (tickOpts.display && opts.display) { + return helpers.valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); + } + return 0; + } + function measureLabelSize(ctx, fontSize, label) { if (helpers.isArray(label)) { return { @@ -145,6 +155,7 @@ module.exports = function(Chart) { */ var plFont = getPointLabelFontOptions(scale); + var paddingTop = getTickFontSize(scale) / 2; // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width. // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points @@ -194,6 +205,11 @@ module.exports = function(Chart) { } } + if (paddingTop && -paddingTop < furthestLimits.t) { + furthestLimits.t = -paddingTop; + furthestAngles.t = 0; + } + scale.setReductions(largestPossibleRadius, furthestLimits, furthestAngles); } @@ -201,9 +217,10 @@ module.exports = function(Chart) { * Helper function to fit a radial linear scale with no point labels */ function fit(scale) { - var largestPossibleRadius = Math.min(scale.height / 2, scale.width / 2); - scale.drawingArea = Math.round(largestPossibleRadius); - scale.setCenterPoint(0, 0, 0, 0); + var paddingTop = getTickFontSize(scale) / 2; + var largestPossibleRadius = Math.min((scale.height - paddingTop) / 2, scale.width / 2); + scale.drawingArea = Math.floor(largestPossibleRadius); + scale.setCenterPoint(0, 0, paddingTop, 0); } function getTextAlignForAngle(angle) { @@ -341,8 +358,8 @@ module.exports = function(Chart) { // Set the unconstrained dimension before label rotation me.width = me.maxWidth; me.height = me.maxHeight; - me.xCenter = Math.round(me.width / 2); - me.yCenter = Math.round(me.height / 2); + me.xCenter = Math.floor(me.width / 2); + me.yCenter = Math.floor(me.height / 2); var minSize = helpers.min([me.height, me.width]); var tickFontSize = helpers.valueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize); @@ -416,8 +433,8 @@ module.exports = function(Chart) { radiusReductionBottom = numberOrZero(radiusReductionBottom); me.drawingArea = Math.min( - Math.round(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2), - Math.round(largestPossibleRadius - (radiusReductionTop + radiusReductionBottom) / 2)); + Math.floor(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2), + Math.floor(largestPossibleRadius - (radiusReductionTop + radiusReductionBottom) / 2)); me.setCenterPoint(radiusReductionLeft, radiusReductionRight, radiusReductionTop, radiusReductionBottom); }, setCenterPoint: function(leftMovement, rightMovement, topMovement, bottomMovement) { @@ -427,8 +444,8 @@ module.exports = function(Chart) { var maxTop = topMovement + me.drawingArea; var maxBottom = me.height - bottomMovement - me.drawingArea; - me.xCenter = Math.round(((maxLeft + maxRight) / 2) + me.left); - me.yCenter = Math.round(((maxTop + maxBottom) / 2) + me.top); + me.xCenter = Math.floor(((maxLeft + maxRight) / 2) + me.left); + me.yCenter = Math.floor(((maxTop + maxBottom) / 2) + me.top); }, getIndexAngle: function(index) { diff --git a/test/fixtures/controller.radar/point-style.png b/test/fixtures/controller.radar/point-style.png index 3f73ff96f91a02142de3504a59531b8f954d59d2..c5437ed24affbc9078ab004a6d0db2c5f7fdf5a0 100644 GIT binary patch literal 15089 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%0DJ=3Iv)NF#Pciave~c_$lC^ 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 IUKSGmKWDH5cK`qY literal 15638 zcmeHuWmjBH(Cy6N?(XjHPH+hloZu1&PH=(^?(PmDxVyVcfZ*;D+}+-J?)wk!TKC)i zG<(fD>zwN9?&@9DwZm1FWl<0b5di=IioBeZ8UO%5-<83EY6BNh_nDf(7=Q(CHdd>U$AL(6G&EnpbBNyGcFHgzV-XHFeWe6=@2JZ z51f=~RN9pDR;?>F@#-x*@#@mK*^c5$AE(eq?Dzx>piBS)TQAOYHhz+S0&WqnbBTzE za8;M9Ht<|Lc3wO@pSZuG!rtb?t3t2f0*mZqmEn{z^5DUkfNW@Ne_k0drZ^E(E^0QV zI0ASQ7$HtXC6166ovacHc?w$m5Fniw4FS_bW5tri@T#CZXE5dT5CnWT+n>!>ho%Qk z3y5QUH-mtN+Uuif22c{oj$A`t)w{9F?!Yd|sR^7~(ipc%6Fehkge+N-Lhe?;QQM zoi}Pk(qVhwc;ngJfG|`@Dcj8ZYLz6J?<0$@d1bkg z9YR7l(Cq8YJgw1%o6in__U$rs6$j9|TLevxR|=c_!u6WIj@q6LG=nluBwxm7kAV%J z)mYyU;7WUD?P{OX2(Y1NrU9VzR?Q`o`Rb8+HUnYNJV$kF?iF1BT_j_WOZ9*$m;zMy zXU;SOFvX$nlngx(5{khAcwpJsP&1L%## z7ZX^}dJ|2oRh`VZ=XQ4Ahs3|C^2h_ZmdP)=dvb2!v-3g`v?^Su4~|jeiV4|4&)rA10XhH^*5LF zIWJLzhQ%9d6Q8?}zf8@!j62bhE~+EivOb2HsdmP|fQU0BfW-`JEz*9+Q8JeYKCT`J z?3;J1CCP(U-MytV@<9`B*Qq%t%v94CahR(2{|gqGA*vnp`O*FzN5CJ4zn8ft;S=YG zRUD2^{GZwm2D_avhqem~qlj^*0rhGYGGO0C^JS$TQz|UWt177EQ>)SN zV*3PpZ0XsXmIC#4pUPQ|leAaCy@%ymeDfg-Pnc0{8IRSUrPQ<1^P}^Do_T*MB1{Pv z(wo$;Pi=9S;)9EM-+>4CG0~jHyqekux|L6MH}~lxTqN5{Cb@MP$0=^iLYUdk*;h(< zywcewmU=C_5$MhWd#t`_n~EioMWSB|pz~DG_N-NcTG}@hef_bJn^8BeCoZxs#`rn4 zf=wA&C$Xsmg%WEsodwEk$$!8=Q5^`d=JKT1$wzv=;iN;4k~S9q9QNv`vXuRGR>L_$ z<##6ZI2mAS>}u$b2?oJnFo|O9_~P0ch@4dscg^s$J2%O)BbLD~(DP_GQ0|}06?wy! zs?PD~fPM$f6Qc={{1vwBI2^yoybEsjX|emucQVchu0M)Uh_f&n8sAl-^C0kN0FKNR zk@?2L|8Nj_I}FSWTu@XoA{-NNa{bt;Mr{`&JiGl}Pb$EUP8qgh`!{ZtAO-&*r=O!v zddb`VW@fsOVkc&`Y>E15UyOM_D~n8lH0WHmJ+|EsKE1?R9)#zVYeZHm1Wr z>uYFLv$Awxu3FNOH`eu?+(kW@DEejO?L3o3;N@UzzjZ8L$0&Yl#DKVE$~3_A8(P(* z&IiTlhR|e9o2@_RXnnToB-UVaK+i*oB#{5hRtTP?BrHN*6%t=xLmi=`U}|cX+chV) z&q##vp^(s_P_dxi2W@{eB?&F{_8Xvyio*;2-nYqLhK zovOF+PgV;iu|{Vo2lhedd9YU%GT-eq$8zoKv3Wqrrq#JtyS`w6Lll8%=f z`Qcz|CQcXoc4S+fX-HIM5_sn@e`?`SNb)csEDaYllHNxlj+sJ36p z0mvb;Ak2JfM0*>_`B*ESvP(WmHG1)8+5|Bno_=RE9G?mbMjwg@q z6yX(ca-&{=CpeiaK9`4>@!Ae3aideFRr~Hd^7DDNxS8kYRJWVjDwn{EIQ6gRr;s%~ zIx7yk1D_4!!s{2KWxs+C8Ncy?GrDFd=B3zMYv#EHXh-DXHUjn_fQFapI>&4&ThsDSFJ{QfKt2xaFJnNR@p1NNB@ub?khe_&9Io~8$hfSBZ>2260v%+&8 zja2mc5Q=5!uAKo0^-!oU$a*|kYo>16W(K4V7@dg^U?pUcS z4(i5VwHok~$b%sP>F`9pMq$-n5mYjqy!q07^?o4d7qs^{yU&HGLwJB_+Ru!>7$Ct6 zYboSYPmow@`*>#HaF(&;?dHrxg1Dnx^EdNnI<=iFU|`Oj&rPhXC~<1An9c3aX%4}3 zq*~-?tt$n)TIGp@$Pf)@6#wjgCd;9?`3U|am7n{b6O$dxxv{)0w9AkEWjE+cOhdx` zo>B`GzD|b~*Q3kA_@5or)=jNGqIpDKLGwT2dnnZnvbkss!hyDe%~rk_nO@^Y6lDlW zuy#jB7I~HMTQRAd{niAp%;wB|d(I0;_UFV1>GGN=3IW#SUE>0y4$} zbRYMl<4qMx6?0l#Z>$m%`h)Yc(W2P4Odt;gwlfVTBcp``o{k%#3)^aUTVEd9#xK}Q zEGV3y1S&roo8EVuH~+WpC9=3#Ev{q?3RWMi&5k*_^wki+x&Dhtk=FO>=I+~jxnFdD zu}iz|IzT$vuaWlJ?AFaVD7dC01dmi=!cNOU3$3{NB<=Ab^@u@qhuyBW+Ofnvc zp(b*G*sFQzyLcra(dT2e`{N$9lIn4RkxwrUxxhcKN*hpaHsLh1=Hf2a6suYh{D49(oVoW%M5ze4G0quLr)3b>frQqlKm7tn21Ka54nkqO|^5 z2!zdPSpJkTl9^&F!u2QX29?-+#w5h@)M9gjE#LLf!DR0*fRQm@b?X2_zrD7LgFwE} zz0bxXDFg=a_9iqwM%q<_c7OMEqUE5@)5kz*%6F&bCd$rWCR@CBRQ2`8W z8U=Zjq|AO?7S-&}Rp+c!;cfcrW~{50u*QSSF{CWAlO=mmH}x%)?kS3TuniyC*#3>B z7#=t#VN+QRbGKjH!huSxJ~lr;yzN`0=VbZsb)movkt@5sSU{&xnKWzX!1=vyB~PYL z)`>zptuC4gBv^uBUi`JJhQH6dQ%a>}==KUPiE?bWd4ba%ns1n8c9jZK!yHxrx&+%G41C<)1d9KlM8;g(t=#mp zv7}1$C=oF6`gMisF1Vh=hYua9srX5|2gm1?j4W3Ji%6u2MjYN3X|qkuVR^+LP!{pD zIHB!3NE^);@90jWW^k9IdS{(Wj5z7y{+SCwd#|+H&$`3#?O&lm9inlsug--|z?nF} z3qT(c%vbBs=X!TJU&NjjFJzLP9@WW)X``d6+Uk_TG+b}%yRa}6cV|)GguAiSj+pszi=%YH6$*z8FO6UAa`SL#HK(<&n z1^eJymQxOPBL}ZD>ntm7qWPW$=3f>$4z7U{jJc@~njf7Qij}aHh@}Gk_pd>giWZ-n zJQhw-3i;K~&+jw?=`b>SsZ>#AqQd&##6)j@B3C9WSIjM)kVsVQBZ$j)i6!)3tRWpX zX=a3_J2s1mimkDP57qgLPfR+_Y~X}tWX0PS@-{wprcd}QZkvqN^r?%T5abT*YD@FG zNY=Q{8Ev3_cma+vDOyS3#ogd*68$`!n0H5X#u>Oo`Bvgl?-&J@_ykND7N;AfCr9|W z%l?e)%#auVux4*GLbgmA7j^4!=0&pf##LEUYxl`+(r(5NfUIVxU4XvlYlOqK|7cy) zd-zr!9S**lOP+_jM6E>cJYT2zi4HyCtwdz_A{pX?(6wg*T&-2^27$BfN9igr&?HJZ zwASDOf!_v3FR0~J$@@m4@(jCt^BVfn)w^>X`R3ed3n9yx|4Nq5EqVMPG16U!3QNcu z6=Zq?TTqpM6(SXM7A+%WaAd2b{N*k!NMVIo=iTLz*m9yHLJigae+c@#1j@=@N_biWP&kADTn989!N3ZqNO za-JRGQm#z+Jg`*~L@MtU&V3nA)W&kCF$2h`N3@e&h?2}JQlw8zo4xLg2U;2{e&bkx zaG0go_hCg~{q^0)E>=6V*^$0456hdP%jZcL?*cp?9xFRHJ@EY7hm2e*2QS7a#cw#Q zyf(ORm*w^!)7Q}Aal#P=10*f~!hH%KeSeLx=PRP2DyltU`4vtmjuD;x{cX9Y2IkRF z*YhMnn#v4GNy+3b>aD7;+S>Z#1r>VDeo%Zl_e~rxJT{3*#t@|!?L)|V6Se^fxHc8w zir4?t$kyGI$#*lPCMX9Dw^rIyV=DymYE_7XA zl^_b>uuV7UiwVi9q@VXh*`Ipej$Kgw$SJe?1Sqrx+LLtbcisPMtjq5UNJX_zfp(S; zP?aROuWJ2f`SF_;!$B6*Yf?qdvtI0UNFZzcy)T|QxEVUo5i67pDkXqW@UIoouJ5De zd{QL~d=Hu!hkqv>k5-{=SL^S7?ABJJM4Ec9%b|ezW*uI$D#D1(4s1g98wp)KIG*P( z^(dA>`78J?rV81yl@(h2v)4l<4C?|3SLch~1*S@Gf(nm} z*gTVLl3+qocMr|5%;Q*z4)#^hYjVa@D^Ra#=%` zu~WZIkxd~>rSUTx3^S~&g-#6m$8A4#R$QmCMi?ME`Rk=vEicXEm6&K3m&2b-MgIXy z=Ar!)xUvXpYPd(WZ_FFUbLz5^6e^5or!j+G*70A;+g925Q=9aghm&qoL+N;Gj5Qv?yWLX<7O|}TcUfr1~lD~I(EW1&H+w*((1|E$_H`m9Dg=1DIv^GHf`@xeW zWGb~4d2NMxpRijhd#7uhN*l^+t!6Nn-{Ompp~)4Oc$fRm`wrhOnP`X9R&0nsIw#oF z!-rxXM?E#vI)rWp7FVp&B9eXhf@ajr7HiI+@)S!cj((PB}0Zx&d>)CW}xeIH#M0~{8}+{(T%x-=-2#s zK$X)QUiQ#G*fyJ}SHm-SfY!0ATSFIB)^-b9>mcm!pO6xWIG>lf$6vLD0zKBtKH%sMBg>pI9{7_cUw zspcfJIB149PD4=|_pT9!MJ?z{6mPSe{X@F#rvP;b%``u4 zV(rzxbLhuhtX=(esU!`Q%y;ZAjG^cg!c{6gJItog{=9x7Lc=x(P1?r%tDx$9USaP` z{wa&t>nvk~b8~nTB^d9@baPHpJvK^A*l=c?4eM&zbXJJS9$G_rv}y9G#>V&;t=Hwx zL71)UsFvLdn=eWQIo5RENGqU{V|Xb?J3i1q-IdzgttXZ2ZaTr~V`?g^Zd0yRmmFzr zNLO z(KN0wGrX)wDD~s?>oN*$?92nnrS=jTy=Z&M0PYV?!>+0(VmoO-xo&{r)2#NryiDnL z_VYcpSSo?;##5gB6@`)}5!nxqp1(L1V{7y2qsuRp@flizuoyE%40n_NYNUCIO0}d#mko(X@FU;fr)xt=byhAeOOQeLbeBE49Jvz5RV%CJ}sTcA0a)g zdr@u;j27N2=iWiisud9ad;8b_xbf9toC|>USxD^lH8fKqQ&L*poF=u6c=YK^sTM?z z$lWfIeT@i*KeHyYp!wV;tl%>XRw>~sIKF3wT^pS#M1-i*0?n@Tp9T+& zCX1P-nmh2t;rDd)*E|dwpK^`+2D0}u)Svsen_AFYp}EYS+MI9Ruj#QbPzC;8RlS5b zsQNK=jLAn!_g4*nQ&KuLh`;KSHo$w2PXCMH&s=pL!*Usps#~p~E?DReW#_~iOe+Iw zauZ9Ap!pfR5v)W5&ShW?@pWsm-;(QB`54tPbtErms3Pu%2$jR?3>cz`R9U&x+~XBR zE@Sw5aF&Yd3a5HT4vhDuYLMZ&;sTlQhkfV!%jWeCDWy0f{*{t#!L|OppVxwR#Fl-i zGZxYU`kbj4t`6!wdIgxlvWg0W;u9PBs*0V;tX4OKTK7)V6O%s|BndcPLI)3QV$LNTLWLsgW665)6M{eH5|C>v6k5<0_SCcXy#0NsVg)PMfB7FH>yE4Q&*3o zNo7{)>T$`}9qlkRx1wD544F>f#|v6GhGrC5T6sY2HKw&iUN^?-0o>dN)w+k}qkZ96lyj+j(W+Q8(9SQ&q-t%@Gfs)+5K)5nTp< ziCz%;0qRcu{#8x}w;|EjL)B&F_R+fIv+99j5=_>Zc=*Dz)jf3-pOthdEM(Ai@g+2p z!xWSE;;-6|mBk95A*G7T{txP!_mz`3wk|Q^qiBx>5O(hB3?$|7`bMwNEPZO=JU;n8 zS{K8feHFBr`nl3Jyn=xD;~$%#<(=_NJ970m%(oo$xzUM+u=hdwpG;kAZkMHBg79GD zN>)qTqYw((#vNKkAytO1C^n*(zaNh>>=7ieyagQ7oeC+xS79Zt5<=xRr25vA^(Q@!PY0!Jh-y5tdiFukqp_n%0ZINGQRD=eCjO0;RO z?{A9^F##tCOR4w@#D)6y^P%BnW;S)f;ElyVphb5gGulaf&Y7Uzo#TL{?aQrHZV((< zx}uxt(+#Uq_2`KI2J_Esq<=08b`1rD-&hX!!y+DLy~lbT!-SLZe{|8hdx>PL zgt(u^g10$U@~5x@7QX<>kJj`CiU#T1$_a}}p7yA8(%9!s6vc(#bZW?6wjD+qe-|ly z--~RxLUb%(CejA}!NixsEviucx901X!L`4zJRqdrarN*XgLO<&LLj_MS-)ZfU6#2>Rf+s1hJA+t!zwD4*gz9`6=xY9a@V4d)8k)sU2M=W3mT-q%;# zdp2l?9&Z;WU4C1xAbY@k-i8YXS%oy&lIp=1B~f9X1|@rcd*4W_g0e>D=%43XK95^M_M?D}AZ{ zA+VU_SyaowIfYB6wGOwbq6&ngr|Q&bH;A6G(da?yUtfsV{{);R6p*VOWV3Z~5nn`C zdp}|Ieo|q~i`Ce6+Go>?Nkfd)PopsmMC;(?e0ogQlZGltvY`SiuYv@#Oj_(DFkZYg z_bMc*%6);>-0Pt_m!ixJwHeH7wHJ$;QGNfXOcx7`vs-1;f$k^jei+i(_$g!v-l&6F z6!h0shmamOdy6T(OuC#d9YNVXYcZ>|Rv4F+)a}N>tr{zz0HYHR?GvnSu-=joMPCVj zh$#obz1g;mn3|I*N}T4fhsfJ=`C!RD!dCfX**wZazXCAAMRuat@9mbimmZ^&*%|;K z{`+4qfFmSbNUh%FqepmNU1TJ|%jCK~C}H|mGw39wzc}o779H$LF^v?L#=lkszm~#o zn}VH{f}F8QYKl_~fDQcNQkr0}2nPAVV`cqT>mleHN0;oyf*8&nH>IftH5BrGik( z9_oOShF32q~|q7QLw16 zOmLFpYaU5`p|(&%weAvXRz%{@p7=M~K5~}QVHmilQDpBKGffQhb}ajP8Gx=Zwz~G( z7gZKOe4D&ig3yhPWr+I5oC2%AMLQ|CG>O&b(;1U)5VKsmx6UOYhB8tdX04KZs)(_o zQ$W5kz>Kqh+7g_9q?9*KW?I&aLJwvLGp5ZMN?+`by%-ljD{&Pp``TTI0R9a!>p4|J zs*TP4Xc4P6438W2N48b2_P;8OQ5N{;!szOTttj>;{BrwZZJP z{Wq=pu+D)d7#C{DIK%`T{7ExbxX~p~Mu>o4oolIR5Bej=UY&iYzb5!_yQRKtXA&5i z!HQ<0hy^C|y}m9__^@|{oj6VV0jb+Qi*KYi%VG9YzaAY|N{-2>Vynur*>yR?3C7{@-mI5!!kCxC*nBfznEj)W(N#n{BuoZn#1$7N7g#%9{!Aa2%uNjOc^N47U*gg>2kNx1ORhZqr zuIZj>xb@l3y*1Zr_&tueH9^oWmVdP?Cl?mV_lO4q$A9+}?rf=H@MG%Ma8CFo7K5jF z21<*+ekK&uEyww)+&M3m=QOsUX7O{g!Di<@e4~NMZg&<{os<9HpFy9=uPYjAsqu)g zHWUt=k)HLXJ&E1PZ|ydtDW_>p$+uTipOE3{*P^}ag)xo7!E-;xUEK-BdyRPm?b?VD zoG!=v;&X=1oH2kQ-l(5hQOpEUojDQp6ng9^HSQg6dMM_(9vKrq=^mxf016lU#*EvW zZ`Wc3hHu}Gs=m9={VBF98d&>oDSB2nzhYneT%o}phqs|NqQh*uF{UshD3YOGr2m0q zwA__8=8?Tp7Hq`1~+4f364F)X--nQ%^Rj z)|L7&-AHx0RTk4ObMR+Y2lQt)SJqn7CpI0}J{RDO;4r>F*0P4f(0Psl8CegsWGF_( zh8cAPW*nYF_g4AGHPLVj?uj0Lwov37W2o4a5p0f;fT~?cF%80&bjOlp9pK~>5KB*@ zsvdQ5GAaG&!0rTXXK(Bfea0;`&IKPS`1<8XJOi@f70VA@v*kC&CqxT!+-MMWnlvsY zRiyz8x zmwVsRgNY0)Xb_CeKye`j$XmjKKM%H!_RnbWtvy9oNn@Kg1xLFS&@*5xWd{UPmrezdtu zYaBzo^k26%Sks1R_OgYwpO4w3(~INcbGnE7Sb{S(GmO^;bB5<+M9^$%?C4iFp%fN| z%?2hzt7VlvJk_uR@)Qv=@@CM@iv-lVb|+A8pkvZ9JFwTJ>r7WSK8bfr+VEN*nBaK$ z&B$|m6w@F;W6)o(a;3$4?oPxa`yb5#AZgXxv{++vqqV;Y|CEW02c!Hr0Svtm;Pf|Mp;HO} z4ohIO^Pu03L8j9(rN?(>zG&Y^6iBfrLbUS^L#PG9rev|iv3c{Pao0^spQd-jobkXR z>T-q9JMP(14O2H?q) zI?-(xvZJ>Im>mD@Nolm2tr$Nrf8d$Iq^nVK{zD@>{KI3XdizMvX>5#%O1@{-!8(3b zB}ceWu@M_c++SOXoR%S>KaW_F@J#m0T^&BAo#UyEegDR^)p}}>$0t$%o?_>;Kq@Et zTeyMvWU`U_wF5f-Uml0p=OU6?&GO!_&(o6YAp9ffoL1sM#lileN7}*s6_l^8<*#ERV?fP=5^oyk27M zCIue%7br!_1Wf?M)bM`o18DRN{feh)cy&&h?CJJxZPr|Co!q%^bX?N-5fnHGGC z=PhBp_=ehcZowFPEeAN4(v0!LYjYh@RJv`@!}6fiJh zj9+VTLRop^oI+&GPC%iIFw03hb@wzjeLO0ZXh#pQ6lltir9XLr@>?TTg*sLST}KFz zg1*2lRj6Nv(0fT?g-J(kw&cn^>HYoimzBUc;%gkR2kMTKNcXeNuyh5;5|z11UpoSs zswleXa{|{lr>q}?@gwB&$a?fO1N!MKVOi4HQM6yU(-BSo)|ei&FxVkws>g)Um?^B; zORB_1tb&;x_k2X6E7O=O#Q}S9)&|xNoq=6iqFkm$pE}wxn`}u%S7A<1*=WT$>L`qS zFkqBsoRW&#G#@&)GfULctw?^OtxIWRaczs;f=@t*NUO2`{vL`v!{ zWx24fBt#2JX*}?5jmW3Z=9vs;Wc)YXR2d>Dk}AP5a90Z@1f%UmYOg(km8{;1O(J;m?sqjh z1#NTY_T?yne(yW6w%^715U*NHnO$_|1n4=m)?8GStuhPc*54LmiR!^nwzNlVp#;!a zDCn6UM2VQoJGVZQ&>9ZM97Qf^mh+LVgXYWK{o=@J2p5bm{dL;W; z$?_=e-(~)*_&@7Q*Y254T?OAM7Tg7T*!I+%jgwb6_wam{fVq74l5@E4Iq$)(0 zQ{d4XhE2cxN5?I0o(y1IOFQGLIj2rz^&U5i*et#_q1qSDUWp+7>#}=Sz~N5^ckGq0-5Lyje%v3e_4gVy`eLxF)cV8&-Zzc+F%n`s1O4ODmdY z=ZCOYDer~26@tfef~_}^MvX3l_+6CP*WHUD6koje>OVniBE_q___c4u-F2OYoYLFT zoiS8nlncVQCS<&S=FBllT!hW_Y8VL4XhWnGPO4Z`qGI)t?$aja87XDPbjU$!P z>qS&xnUGg4c?$D%zc=*P0e%a`T3*QA!y9~e;aBDUMxTK#W#VBjKAc!c4-#sWfx}3; zq95b=*%-FFq82BrspVFVQ_wolOIqwLk>8spD&{M3#*jai^%D*~LD8=|C0R)MEOX{8 z^}93}eXnQzvY+i0b7>qX?Q@n`Q|}ikwo)#a$ccEvzV8=qS^drA3+Z3W?=#_nmJ91T zBmdMIDYZVa+bc-7?->%MoGc}ATl}>5NEu6~{9qFGG6a`~7#IG*z;8H$-=dE>x4g^! zZMyqPW%O2Vb2CH+N6H0|PHiI4LHX_%@Rj+ydrlG;GzNXoJ? zG|1+~iEZguHlOgrk_h^*R|HW#Gh4qyp`K5FqEeHLTbgXuF?zHwah%}tu}EoaEA?T-$Dz zGXlFYy#n5mNN#LUBCXMISP#IKn>NtA3NN;dApQ`uH{`T7k{AYmpV^u%7}nOs+0Fyqab?uI12$sEZ@h-a|%D;Es=kMRM7zexD3c9 zb~j$>NP8SlP;$JiL>%2VE8k3)c|9iqJ-}7_p*H+#Yiqlsuf#;diQTWXb@_QdaHhtz z9s_XoviJwDBrIn~p1RtHY?F;h>lnHQM|^aP4C67%FLraia!5FBNOg-F6?X1Bl1dj( z&?F7dFWhp<$rTx7wchD;c>y6Vd80-tbiSouSchT{ynO~dZTg2|&^g*n4Qa;c3sgvcaJV~hTwXc{~lbYI_yb)vO%013ySnSr+ z-M_E+2NsXs7aozCYJSIsE+{2;LZ+s*+HNPJ$`J=fl6Gdx0!n@Bxbvhc%Uo09?*|8VrC$%xH}6|x`TsxEcn%`eh% zwPHF@-fB^clxKhve*0vwSE^p{M+$(_2 zL%Nw{^5i;TU24R2xi;l(o8F*vt1|%Bb&uuHd&PcDSPQnZL&g={9j@KEn|%IPJo$ea zwd_$(x*PkyXW#%ZzK#R(CqUMgGX(S?mDixMbbG#u zXR7Mh9z7rMbi_qm4&vGxXRNbRCEcL()-vIr&_*(Ebqp|W3fMfBO(N?ZwX4AN*N6)C z3nTk?5ToGyE5N9f5u{>p|88@*Pj!Ko=hvn9Qn~^rt+vbUEfjGo;K+rIDe*e^)f<-? z0e~lk9BbLc{F-kwql<3_knP#XWnr?e3=n4HRX+{e_c#n z9z7ovth(l73E7W~8UL1YGe{9>w#4LX-ZUHYsjdk5wsi?(5R)IE3JIg;!v8hR^E*m1 zFO_9~qL!1#*x^JIebs(s`cq}M0ct`L|GP(K<9f&T*tf+E8`*}GI#a6f*LGYb-CtLT zR8(^oMS8Y_z77poX$h9yQR1rN3E-sxR*b1oLaiF#5EzpmKyFEG1&ZTZpc!&xMJ+-E z4nn(!S}x{GUwDl1kSd@-ED!oU_J{Jq20zMcRzd*Ej|7G-6&dqJ4BN~MsJWhvrOq50 zeZKn-Q8b~s;|h2i+%aNFA61q*52-76~N zp|IT?*QdsneTSOHHt8NSkw*~UO}`hc$pv;ewbA&0dWU=eu3e}Jwtjzi3Znd$F~A^q z?s(2XH;&hX4u$Hc?@A_EDu*C$CjE-pk_%-{_r@RUQ^ohA#r7-fe6}y6N!_=Z@E0OH zvYG@Bo;OxUknhzRj`{eY5;caOeGcqi**6}2BQjstEEgNo;)I8^ z;Wg)W#MBfA*}+qRPNlb0(<}X-(QW=x{wM7*oE=60IxL&VD7M`|VEu-yGEpY`8*)s&sOpj9cmH7djGTJ;fgT{fCwNJ-pPuBxGd1DymV($bT31t1-3&~bB=EL z@hFcR^6_Mdx2?Kul;7;2tHeHm;H!nJQ9-oJzEEYTPqWqbp*-Kz8~^;|lQDcBZ!dQA zDJUju-NCZ-e<)I=ESBt)o^A6{01#67663Q2tUYEseS-FgI$t`h;g$>ldYmhc1|e-X zWq3kt4poY)ci$U&BKVy@po99GOShx$_=e7vA-wqc!V1H#C}ut1<~t&PLf%%&uOhDT{K_Z6ct z9}tbPRQ>nqAXSfuuALae10VGb{xasA_Y%G0q{Rqc%^b$yQD`-VnJ!3eyvI`STtOwJ ziWmDAv?_`<&)gMMNPkZv4>yG{Oh-R}^Ru6yDO5#2K+i9bq|*aFgH!l)nV7g#nD~nL zv=o_glIc;UTVUCK+)KbY3KLA6q`~Tp>7fBCi>Mffa-ISrV)M1XKF?dWiidGN@(@tf7QFSu zUGX*a^cUwO0AXH~LMw1Q+#Fi;C#ZzNe*1-$d>4wEuLnKX_ihsL-r(QB0?HLgpbL=L z)1S3(GKo-Q;i1C``YZuVA@;0AVz+eYo@yup+{zk%c_6d|tC+L_kL}7> z8Gzj42GtXX+ny(Zi*JF_G}!=P6oUjmsbh}06B-;4w7@p!*&bY-4`f-k$w zssg6QsPubu#E85!#)LbHH2Q-L2^A>TjB{TI3FM#;a%nszGmkuBMDc5(VAC{k^n&1_ zaC^;gr>F|8SM3(?X~3`yucjy1*Ah#pzh!SQ3oTxV`OT*k7Lx*D@6b381vAFVuz-3&I zq&O*N-3V16wb~=**9DwrfAtIdMaU$V1Y-d>rUi7Ap;6OBq1UYQ(i%>1|C4wIChM_^ zf}zc00L6RhZ@eDl4KGDf|NTFIPX)dMy+rIGj<5m`yXK z-Zg*=2cUKy%@wNI4GKlx`0Jwo%#dp^M8s)O8i*)~F^vm(c4bG&s{sBVA_TUn!Qq5Z z=CMEopbdkW_c9pMI1uPkh{}j8Ev^RE^->zVTDLq zT>C$mELg&h4YY#@PxDwd8u%i>RS?By9+Ru82|Z{9qEr>v>#%ab1{zR6p!RWIpDJ_% zL_!K~@|fMA2enB+VAb5G;C%S+kq|5Gu8KOpK)d{Zga6-nxaR|o2Ax%yZxaYnJ_7*q M(#ld*62<}l1Et}jfdBvi diff --git a/test/fixtures/plugin.filler/fill-radar-boundary-origin.png b/test/fixtures/plugin.filler/fill-radar-boundary-origin.png index 2c07f7f0b2c2bd996ed549ee512779905de26883..66c6e563a796d1c50039329dafff1e339a841f53 100644 GIT binary patch literal 9297 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 diff --git a/test/specs/controller.doughnut.tests.js b/test/specs/controller.doughnut.tests.js index b1969e2817a..2f9602bda73 100644 --- a/test/specs/controller.doughnut.tests.js +++ b/test/specs/controller.doughnut.tests.js @@ -195,10 +195,10 @@ describe('Chart.controllers.doughnut', function() { {c: Math.PI / 8, s: Math.PI, e: Math.PI + Math.PI / 8}, {c: 3 * Math.PI / 8, s: Math.PI + Math.PI / 8, e: Math.PI + Math.PI / 2} ].forEach(function(expected, i) { - expect(meta.data[i]._model.x).toBeCloseToPixel(510); - expect(meta.data[i]._model.y).toBeCloseToPixel(510); - expect(meta.data[i]._model.outerRadius).toBeCloseToPixel(509); - expect(meta.data[i]._model.innerRadius).toBeCloseToPixel(381); + expect(meta.data[i]._model.x).toBeCloseToPixel(511); + expect(meta.data[i]._model.y).toBeCloseToPixel(511); + expect(meta.data[i]._model.outerRadius).toBeCloseToPixel(510); + expect(meta.data[i]._model.innerRadius).toBeCloseToPixel(382); expect(meta.data[i]._model.circumference).toBeCloseTo(expected.c, 8); expect(meta.data[i]._model.startAngle).toBeCloseTo(expected.s, 8); expect(meta.data[i]._model.endAngle).toBeCloseTo(expected.e, 8); diff --git a/test/specs/controller.polarArea.tests.js b/test/specs/controller.polarArea.tests.js index b706f8376a6..1023bf0789b 100644 --- a/test/specs/controller.polarArea.tests.js +++ b/test/specs/controller.polarArea.tests.js @@ -99,13 +99,13 @@ describe('Chart.controllers.polarArea', function() { expect(meta.data.length).toBe(4); [ - {o: 179, s: -0.5 * Math.PI, e: 0}, - {o: 243, s: 0, e: 0.5 * Math.PI}, + {o: 177, s: -0.5 * Math.PI, e: 0}, + {o: 240, s: 0, e: 0.5 * Math.PI}, {o: 51, s: 0.5 * Math.PI, e: Math.PI}, {o: 0, s: Math.PI, e: 1.5 * Math.PI} ].forEach(function(expected, i) { expect(meta.data[i]._model.x).toBeCloseToPixel(256); - expect(meta.data[i]._model.y).toBeCloseToPixel(256); + expect(meta.data[i]._model.y).toBeCloseToPixel(259); expect(meta.data[i]._model.innerRadius).toBeCloseToPixel(0); expect(meta.data[i]._model.outerRadius).toBeCloseToPixel(expected.o); expect(meta.data[i]._model.startAngle).toBe(expected.s); @@ -141,9 +141,9 @@ describe('Chart.controllers.polarArea', function() { chart.update(); expect(meta.data[0]._model.x).toBeCloseToPixel(256); - expect(meta.data[0]._model.y).toBeCloseToPixel(256); + expect(meta.data[0]._model.y).toBeCloseToPixel(259); expect(meta.data[0]._model.innerRadius).toBeCloseToPixel(0); - expect(meta.data[0]._model.outerRadius).toBeCloseToPixel(179); + expect(meta.data[0]._model.outerRadius).toBeCloseToPixel(177); expect(meta.data[0]._model).toEqual(jasmine.objectContaining({ startAngle: -0.5 * Math.PI, endAngle: 0, @@ -183,13 +183,13 @@ describe('Chart.controllers.polarArea', function() { expect(meta.data.length).toBe(4); [ - {o: 179, s: 0, e: 0.5 * Math.PI}, - {o: 243, s: 0.5 * Math.PI, e: Math.PI}, + {o: 177, s: 0, e: 0.5 * Math.PI}, + {o: 240, s: 0.5 * Math.PI, e: Math.PI}, {o: 51, s: Math.PI, e: 1.5 * Math.PI}, {o: 0, s: 1.5 * Math.PI, e: 2.0 * Math.PI} ].forEach(function(expected, i) { expect(meta.data[i]._model.x).toBeCloseToPixel(256); - expect(meta.data[i]._model.y).toBeCloseToPixel(256); + expect(meta.data[i]._model.y).toBeCloseToPixel(259); expect(meta.data[i]._model.innerRadius).toBeCloseToPixel(0); expect(meta.data[i]._model.outerRadius).toBeCloseToPixel(expected.o); expect(meta.data[i]._model.startAngle).toBe(expected.s); diff --git a/test/specs/controller.radar.tests.js b/test/specs/controller.radar.tests.js index 7e85e9e7eac..b71a2e47398 100644 --- a/test/specs/controller.radar.tests.js +++ b/test/specs/controller.radar.tests.js @@ -154,10 +154,10 @@ describe('Chart.controllers.radar', function() { meta.controller.update(); [ - {x: 256, y: 117, cppx: 246, cppy: 117, cpnx: 272, cpny: 117}, - {x: 464, y: 256, cppx: 464, cppy: 248, cpnx: 464, cpny: 262}, - {x: 256, y: 256, cppx: 276.9, cppy: 256, cpnx: 250.4, cpny: 256}, - {x: 200, y: 256, cppx: 200, cppy: 259, cpnx: 200, cpny: 245}, + {x: 256, y: 116, cppx: 246, cppy: 116, cpnx: 273, cpny: 116}, + {x: 466, y: 256, cppx: 466, cppy: 248, cpnx: 466, cpny: 262}, + {x: 256, y: 256, cppx: 277, cppy: 256, cpnx: 250.4, cpny: 256}, + {x: 200, y: 256, cppx: 200, cppy: 260, cpnx: 200, cpny: 246}, ].forEach(function(expected, i) { expect(meta.data[i]._model.x).toBeCloseToPixel(expected.x); expect(meta.data[i]._model.y).toBeCloseToPixel(expected.y); @@ -211,8 +211,8 @@ describe('Chart.controllers.radar', function() { // Since tension is now 0, we don't care about the control points [ - {x: 256, y: 117}, - {x: 464, y: 256}, + {x: 256, y: 116}, + {x: 466, y: 256}, {x: 256, y: 256}, {x: 200, y: 256}, ].forEach(function(expected, i) { @@ -270,11 +270,11 @@ describe('Chart.controllers.radar', function() { })); expect(meta.data[0]._model.x).toBeCloseToPixel(256); - expect(meta.data[0]._model.y).toBeCloseToPixel(117); + expect(meta.data[0]._model.y).toBeCloseToPixel(116); expect(meta.data[0]._model.controlPointPreviousX).toBeCloseToPixel(241); - expect(meta.data[0]._model.controlPointPreviousY).toBeCloseToPixel(117); + expect(meta.data[0]._model.controlPointPreviousY).toBeCloseToPixel(116); expect(meta.data[0]._model.controlPointNextX).toBeCloseToPixel(281); - expect(meta.data[0]._model.controlPointNextY).toBeCloseToPixel(117); + expect(meta.data[0]._model.controlPointNextY).toBeCloseToPixel(116); expect(meta.data[0]._model).toEqual(jasmine.objectContaining({ radius: 2.2, backgroundColor: 'rgb(0, 1, 3)', diff --git a/test/specs/scale.radialLinear.tests.js b/test/specs/scale.radialLinear.tests.js index 5390ef77d5f..da19f9e000d 100644 --- a/test/specs/scale.radialLinear.tests.js +++ b/test/specs/scale.radialLinear.tests.js @@ -349,9 +349,9 @@ describe('Test the radial linear scale', function() { } }); - expect(chart.scale.drawingArea).toBe(233); + expect(chart.scale.drawingArea).toBe(232); expect(chart.scale.xCenter).toBe(256); - expect(chart.scale.yCenter).toBe(280); + expect(chart.scale.yCenter).toBe(279); }); it('should correctly get the label for a given data index', function() { @@ -397,7 +397,7 @@ describe('Test the radial linear scale', function() { }); expect(chart.scale.getDistanceFromCenterForValue(chart.scale.min)).toBe(0); - expect(chart.scale.getDistanceFromCenterForValue(chart.scale.max)).toBe(233); + expect(chart.scale.getDistanceFromCenterForValue(chart.scale.max)).toBe(232); expect(chart.scale.getPointPositionForValue(1, 5)).toEqual({ x: 270, y: 275, @@ -406,7 +406,7 @@ describe('Test the radial linear scale', function() { chart.scale.options.ticks.reverse = true; chart.update(); - expect(chart.scale.getDistanceFromCenterForValue(chart.scale.min)).toBe(233); + expect(chart.scale.getDistanceFromCenterForValue(chart.scale.min)).toBe(232); expect(chart.scale.getDistanceFromCenterForValue(chart.scale.max)).toBe(0); }); From aa652df240a98a5283381591bed0bca555cc5b55 Mon Sep 17 00:00:00 2001 From: Simon Brunel Date: Thu, 29 Nov 2018 07:56:20 +0100 Subject: [PATCH 032/137] Deprecate Chart.{Type} classes (#5868) It looks like these classes are a legacy from version 1 but we actually never promoted their usage. Instead, the regular way to create a chart is to set the type in the config, for example: `new Chart(ctx, {type: 'bar'})`. Some types are actually missing (no `Chart.HorizontalBar` or `Chart.Pie`) but it's also not scalable because it can easily conflict with other classes scoped under the `Chart` namespace. --- src/chart.js | 35 ++++++++++++++++++------- src/charts/Chart.Bar.js | 11 -------- src/charts/Chart.Bubble.js | 10 ------- src/charts/Chart.Doughnut.js | 11 -------- src/charts/Chart.Line.js | 11 -------- src/charts/Chart.PolarArea.js | 11 -------- src/charts/Chart.Radar.js | 11 -------- src/charts/Chart.Scatter.js | 8 ------ test/specs/global.deprecations.tests.js | 27 +++++++++++++++++++ 9 files changed, 53 insertions(+), 82 deletions(-) delete mode 100644 src/charts/Chart.Bar.js delete mode 100644 src/charts/Chart.Bubble.js delete mode 100644 src/charts/Chart.Doughnut.js delete mode 100644 src/charts/Chart.Line.js delete mode 100644 src/charts/Chart.PolarArea.js delete mode 100644 src/charts/Chart.Radar.js delete mode 100644 src/charts/Chart.Scatter.js diff --git a/src/chart.js b/src/chart.js index 1f6982ce47e..7b393e6b069 100644 --- a/src/chart.js +++ b/src/chart.js @@ -42,14 +42,6 @@ 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); -require('./charts/Chart.Doughnut')(Chart); -require('./charts/Chart.Line')(Chart); -require('./charts/Chart.PolarArea')(Chart); -require('./charts/Chart.Radar')(Chart); -require('./charts/Chart.Scatter')(Chart); - // Loading built-in plugins var plugins = require('./plugins'); for (var k in plugins) { @@ -116,8 +108,33 @@ Chart.canvasHelpers = Chart.helpers.canvas; /** * Provided for backward compatibility, use Chart.layouts instead. * @namespace Chart.layoutService - * @deprecated since version 2.8.0 + * @deprecated since version 2.7.3 * @todo remove at version 3 * @private */ Chart.layoutService = Chart.layouts; + +/** + * Provided for backward compatibility, instead we should create a new Chart + * by setting the type in the config (`new Chart(id, {type: '{chart-type}'}`). + * @deprecated since version 2.8.0 + * @todo remove at version 3 + */ +Chart.helpers.each( + [ + 'Bar', + 'Bubble', + 'Doughnut', + 'Line', + 'PolarArea', + 'Radar', + 'Scatter' + ], + function(klass) { + Chart[klass] = function(ctx, cfg) { + return new Chart(ctx, Chart.helpers.merge(cfg || {}, { + type: klass.charAt(0).toLowerCase() + klass.slice(1) + })); + }; + } +); diff --git a/src/charts/Chart.Bar.js b/src/charts/Chart.Bar.js deleted file mode 100644 index e1ad7962f9a..00000000000 --- a/src/charts/Chart.Bar.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -module.exports = function(Chart) { - - Chart.Bar = function(context, config) { - config.type = 'bar'; - - return new Chart(context, config); - }; - -}; diff --git a/src/charts/Chart.Bubble.js b/src/charts/Chart.Bubble.js deleted file mode 100644 index 2de4a1047ec..00000000000 --- a/src/charts/Chart.Bubble.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict'; - -module.exports = function(Chart) { - - Chart.Bubble = function(context, config) { - config.type = 'bubble'; - return new Chart(context, config); - }; - -}; diff --git a/src/charts/Chart.Doughnut.js b/src/charts/Chart.Doughnut.js deleted file mode 100644 index e1e8ce54b34..00000000000 --- a/src/charts/Chart.Doughnut.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -module.exports = function(Chart) { - - Chart.Doughnut = function(context, config) { - config.type = 'doughnut'; - - return new Chart(context, config); - }; - -}; diff --git a/src/charts/Chart.Line.js b/src/charts/Chart.Line.js deleted file mode 100644 index e89662ff7c1..00000000000 --- a/src/charts/Chart.Line.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -module.exports = function(Chart) { - - Chart.Line = function(context, config) { - config.type = 'line'; - - return new Chart(context, config); - }; - -}; diff --git a/src/charts/Chart.PolarArea.js b/src/charts/Chart.PolarArea.js deleted file mode 100644 index e07e4bac5a1..00000000000 --- a/src/charts/Chart.PolarArea.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -module.exports = function(Chart) { - - Chart.PolarArea = function(context, config) { - config.type = 'polarArea'; - - return new Chart(context, config); - }; - -}; diff --git a/src/charts/Chart.Radar.js b/src/charts/Chart.Radar.js deleted file mode 100644 index d17bd5d8108..00000000000 --- a/src/charts/Chart.Radar.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -module.exports = function(Chart) { - - Chart.Radar = function(context, config) { - config.type = 'radar'; - - return new Chart(context, config); - }; - -}; diff --git a/src/charts/Chart.Scatter.js b/src/charts/Chart.Scatter.js deleted file mode 100644 index 9006e571293..00000000000 --- a/src/charts/Chart.Scatter.js +++ /dev/null @@ -1,8 +0,0 @@ -'use strict'; - -module.exports = function(Chart) { - Chart.Scatter = function(context, config) { - config.type = 'scatter'; - return new Chart(context, config); - }; -}; diff --git a/test/specs/global.deprecations.tests.js b/test/specs/global.deprecations.tests.js index 11d25d1d9b6..6992f23cf1e 100644 --- a/test/specs/global.deprecations.tests.js +++ b/test/specs/global.deprecations.tests.js @@ -1,5 +1,32 @@ describe('Deprecations', function() { describe('Version 2.8.0', function() { + [ + ['Bar', 'bar'], + ['Bubble', 'bubble'], + ['Doughnut', 'doughnut'], + ['Line', 'line'], + ['PolarArea', 'polarArea'], + ['Radar', 'radar'], + ['Scatter', 'scatter'] + ].forEach(function(descriptor) { + var klass = descriptor[0]; + var type = descriptor[1]; + + describe('Chart.' + klass, function() { + it('should be defined as a function', function() { + expect(Chart[klass]).toBeDefined(); + expect(typeof Chart[klass]).toBe('function'); + }); + it('should create a chart of type "' + type + '"', function() { + var chart = new Chart[klass]('foo', {data: {}}); + expect(chart instanceof Chart.Controller).toBeTruthy(); + expect(chart.config.type).toBe(type); + }); + }); + }); + }); + + describe('Version 2.7.3', function() { describe('Chart.layoutService', function() { it('should be defined and an alias of Chart.layouts', function() { expect(Chart.layoutService).toBeDefined(); From ecfa7b24c64d873befe625d21b64c826a304e10e Mon Sep 17 00:00:00 2001 From: Akihiko Kusanagi Date: Thu, 29 Nov 2018 20:52:56 +0800 Subject: [PATCH 033/137] Add support for CanvasPattern and CanvasGradient in tooltip (#5869) --- src/core/core.tooltip.js | 47 ++++++----- test/fixtures/core.tooltip/opacity.js | 104 +++++++++++++++++++++++++ test/fixtures/core.tooltip/opacity.png | Bin 0 -> 11800 bytes test/specs/core.tooltip.tests.js | 2 + 4 files changed, 128 insertions(+), 25 deletions(-) create mode 100644 test/fixtures/core.tooltip/opacity.js create mode 100644 test/fixtures/core.tooltip/opacity.png diff --git a/src/core/core.tooltip.js b/src/core/core.tooltip.js index c529812cc61..c3245d9cf1e 100644 --- a/src/core/core.tooltip.js +++ b/src/core/core.tooltip.js @@ -168,14 +168,6 @@ var positioners = { } }; -/** - * 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) { if (toPush) { @@ -734,7 +726,7 @@ var exports = module.exports = Element.extend({ return {x1: x1, x2: x2, x3: x3, y1: y1, y2: y2, y3: y3}; }, - drawTitle: function(pt, vm, ctx, opacity) { + drawTitle: function(pt, vm, ctx) { var title = vm.title; if (title.length) { @@ -744,7 +736,7 @@ var exports = module.exports = Element.extend({ var titleFontSize = vm.titleFontSize; var titleSpacing = vm.titleSpacing; - ctx.fillStyle = mergeOpacity(vm.titleFontColor, opacity); + ctx.fillStyle = vm.titleFontColor; ctx.font = helpers.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily); var i, len; @@ -759,7 +751,7 @@ var exports = module.exports = Element.extend({ } }, - drawBody: function(pt, vm, ctx, opacity) { + drawBody: function(pt, vm, ctx) { var bodyFontSize = vm.bodyFontSize; var bodySpacing = vm.bodySpacing; var body = vm.body; @@ -776,7 +768,7 @@ var exports = module.exports = Element.extend({ }; // Before body lines - ctx.fillStyle = mergeOpacity(vm.bodyFontColor, opacity); + ctx.fillStyle = vm.bodyFontColor; helpers.each(vm.beforeBody, fillLineOfText); var drawColorBoxes = vm.displayColors; @@ -784,7 +776,7 @@ var exports = module.exports = Element.extend({ // Draw body lines now helpers.each(body, function(bodyItem, i) { - var textColor = mergeOpacity(vm.labelTextColors[i], opacity); + var textColor = vm.labelTextColors[i]; ctx.fillStyle = textColor; helpers.each(bodyItem.before, fillLineOfText); @@ -792,16 +784,16 @@ var exports = module.exports = Element.extend({ // Draw Legend-like boxes if needed if (drawColorBoxes) { // Fill a white rect so that colours merge nicely if the opacity is < 1 - ctx.fillStyle = mergeOpacity(vm.legendColorBackground, opacity); + ctx.fillStyle = vm.legendColorBackground; ctx.fillRect(pt.x, pt.y, bodyFontSize, bodyFontSize); // Border ctx.lineWidth = 1; - ctx.strokeStyle = mergeOpacity(vm.labelColors[i].borderColor, opacity); + ctx.strokeStyle = vm.labelColors[i].borderColor; ctx.strokeRect(pt.x, pt.y, bodyFontSize, bodyFontSize); // Inner square - ctx.fillStyle = mergeOpacity(vm.labelColors[i].backgroundColor, opacity); + ctx.fillStyle = vm.labelColors[i].backgroundColor; ctx.fillRect(pt.x + 1, pt.y + 1, bodyFontSize - 2, bodyFontSize - 2); ctx.fillStyle = textColor; } @@ -820,7 +812,7 @@ var exports = module.exports = Element.extend({ pt.y -= bodySpacing; // Remove last body spacing }, - drawFooter: function(pt, vm, ctx, opacity) { + drawFooter: function(pt, vm, ctx) { var footer = vm.footer; if (footer.length) { @@ -829,7 +821,7 @@ var exports = module.exports = Element.extend({ ctx.textAlign = vm._footerAlign; ctx.textBaseline = 'top'; - ctx.fillStyle = mergeOpacity(vm.footerFontColor, opacity); + ctx.fillStyle = vm.footerFontColor; ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily); helpers.each(footer, function(line) { @@ -839,9 +831,9 @@ var exports = module.exports = Element.extend({ } }, - drawBackground: function(pt, vm, ctx, tooltipSize, opacity) { - ctx.fillStyle = mergeOpacity(vm.backgroundColor, opacity); - ctx.strokeStyle = mergeOpacity(vm.borderColor, opacity); + drawBackground: function(pt, vm, ctx, tooltipSize) { + ctx.fillStyle = vm.backgroundColor; + ctx.strokeStyle = vm.borderColor; ctx.lineWidth = vm.borderWidth; var xAlign = vm.xAlign; var yAlign = vm.yAlign; @@ -906,21 +898,26 @@ var exports = module.exports = Element.extend({ var hasTooltipContent = vm.title.length || vm.beforeBody.length || vm.body.length || vm.afterBody.length || vm.footer.length; if (this._options.enabled && hasTooltipContent) { + ctx.save(); + ctx.globalAlpha = opacity; + // Draw Background - this.drawBackground(pt, vm, ctx, tooltipSize, opacity); + this.drawBackground(pt, vm, ctx, tooltipSize); // Draw Title, Body, and Footer pt.x += vm.xPadding; pt.y += vm.yPadding; // Titles - this.drawTitle(pt, vm, ctx, opacity); + this.drawTitle(pt, vm, ctx); // Body - this.drawBody(pt, vm, ctx, opacity); + this.drawBody(pt, vm, ctx); // Footer - this.drawFooter(pt, vm, ctx, opacity); + this.drawFooter(pt, vm, ctx); + + ctx.restore(); } }, diff --git a/test/fixtures/core.tooltip/opacity.js b/test/fixtures/core.tooltip/opacity.js new file mode 100644 index 00000000000..8b872e75d73 --- /dev/null +++ b/test/fixtures/core.tooltip/opacity.js @@ -0,0 +1,104 @@ +var patternCanvas = document.createElement('canvas'); +var patternContext = patternCanvas.getContext('2d'); + +patternCanvas.width = 6; +patternCanvas.height = 6; +patternContext.fillStyle = '#ff0000'; +patternContext.fillRect(0, 0, 6, 6); +patternContext.fillStyle = '#ffff00'; +patternContext.fillRect(0, 0, 4, 4); + +var pattern = patternContext.createPattern(patternCanvas, 'repeat'); + +var gradient; + +module.exports = { + config: { + type: 'line', + data: { + datasets: [{ + data: [8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8], + pointBorderColor: '#ff0000', + pointBackgroundColor: '#00ff00', + showLine: false + }, { + label: '', + data: [4, 4, 4, 4, 4, 5, 3, 4, 4, 4, 4], + pointBorderColor: pattern, + pointBackgroundColor: pattern, + showLine: false + }, { + label: '', + data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + showLine: false + }], + labels: ['', '', '', '', '', '', '', '', '', '', ''] + }, + options: { + legend: false, + title: false, + scales: { + xAxes: [{display: false}], + yAxes: [{display: false}] + }, + elements: { + line: { + fill: false + } + }, + tooltips: { + mode: 'nearest', + intersect: false, + callbacks: { + label: function() { + return '\u200b'; + } + } + }, + layout: { + padding: 15 + } + }, + plugins: [{ + beforeDatasetsUpdate: function(chart) { + if (!gradient) { + gradient = chart.ctx.createLinearGradient(0, 0, 512, 256); + gradient.addColorStop(0, '#ff0000'); + gradient.addColorStop(1, '#0000ff'); + } + chart.config.data.datasets[2].pointBorderColor = gradient; + chart.config.data.datasets[2].pointBackgroundColor = gradient; + + return true; + }, + afterDraw: function(chart) { + var canvas = chart.canvas; + var rect = canvas.getBoundingClientRect(); + var point, event; + + for (var i = 0; i < 3; ++i) { + for (var j = 0; j < 11; ++j) { + point = chart.getDatasetMeta(i).data[j]; + event = { + type: 'mousemove', + target: canvas, + clientX: rect.left + point._model.x, + clientY: rect.top + point._model.y + }; + chart.handleEvent(event); + chart.tooltip.handleEvent(event); + chart.tooltip.transition(1); + chart.tooltip._view.opacity = j / 10; + chart.tooltip.draw(); + } + } + } + }] + }, + options: { + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/core.tooltip/opacity.png b/test/fixtures/core.tooltip/opacity.png new file mode 100644 index 0000000000000000000000000000000000000000..142dcc05f60fb6f966594da191fb7c8b109c23ea GIT binary patch literal 11800 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 Date: Fri, 30 Nov 2018 04:05:36 +0800 Subject: [PATCH 034/137] Fix test failures on Windows (#5872) --- test/specs/controller.radar.tests.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/specs/controller.radar.tests.js b/test/specs/controller.radar.tests.js index b71a2e47398..6a447a1ccdb 100644 --- a/test/specs/controller.radar.tests.js +++ b/test/specs/controller.radar.tests.js @@ -154,10 +154,10 @@ describe('Chart.controllers.radar', function() { meta.controller.update(); [ - {x: 256, y: 116, cppx: 246, cppy: 116, cpnx: 273, cpny: 116}, - {x: 466, y: 256, cppx: 466, cppy: 248, cpnx: 466, cpny: 262}, - {x: 256, y: 256, cppx: 277, cppy: 256, cpnx: 250.4, cpny: 256}, - {x: 200, y: 256, cppx: 200, cppy: 260, cpnx: 200, cpny: 246}, + {x: 256, y: 117, cppx: 246, cppy: 117, cpnx: 272, cpny: 117}, + {x: 464, y: 256, cppx: 464, cppy: 248, cpnx: 464, cpny: 262}, + {x: 256, y: 256, cppx: 277, cppy: 256, cpnx: 250, cpny: 256}, + {x: 200, y: 256, cppx: 200, cppy: 259, cpnx: 200, cpny: 245}, ].forEach(function(expected, i) { expect(meta.data[i]._model.x).toBeCloseToPixel(expected.x); expect(meta.data[i]._model.y).toBeCloseToPixel(expected.y); @@ -211,8 +211,8 @@ describe('Chart.controllers.radar', function() { // Since tension is now 0, we don't care about the control points [ - {x: 256, y: 116}, - {x: 466, y: 256}, + {x: 256, y: 117}, + {x: 464, y: 256}, {x: 256, y: 256}, {x: 200, y: 256}, ].forEach(function(expected, i) { @@ -270,11 +270,11 @@ describe('Chart.controllers.radar', function() { })); expect(meta.data[0]._model.x).toBeCloseToPixel(256); - expect(meta.data[0]._model.y).toBeCloseToPixel(116); + expect(meta.data[0]._model.y).toBeCloseToPixel(117); expect(meta.data[0]._model.controlPointPreviousX).toBeCloseToPixel(241); - expect(meta.data[0]._model.controlPointPreviousY).toBeCloseToPixel(116); + expect(meta.data[0]._model.controlPointPreviousY).toBeCloseToPixel(117); expect(meta.data[0]._model.controlPointNextX).toBeCloseToPixel(281); - expect(meta.data[0]._model.controlPointNextY).toBeCloseToPixel(116); + expect(meta.data[0]._model.controlPointNextY).toBeCloseToPixel(117); expect(meta.data[0]._model).toEqual(jasmine.objectContaining({ radius: 2.2, backgroundColor: 'rgb(0, 1, 3)', From be8d78a900b560a1dbd465a29736063e4e8fa4ef Mon Sep 17 00:00:00 2001 From: Simon Brunel Date: Thu, 29 Nov 2018 21:06:34 +0100 Subject: [PATCH 035/137] Make Chart.controllers.* importable (#5871) `controllers.*.js` and `core.datasetController.js` are now importable (no more function export), that's why there is so many changes mainly due to one indentation level removed. Split code for `bar/horizontalBar` and `doughnut/pie` in separate files, added a global controllers import (`src/controllers/index.js`) and add tests to check that all dataset controllers are correctly registered under `chart.controllers.{type}`. --- src/chart.js | 13 +- src/controllers/controller.bar.js | 699 +++++++++----------- src/controllers/controller.bubble.js | 263 ++++---- src/controllers/controller.doughnut.js | 309 +++++---- src/controllers/controller.horizontalBar.js | 79 +++ src/controllers/controller.line.js | 564 ++++++++-------- src/controllers/controller.pie.js | 13 + src/controllers/controller.polarArea.js | 254 ++++--- src/controllers/controller.radar.js | 284 ++++---- src/controllers/controller.scatter.js | 9 +- src/controllers/index.js | 18 + src/core/core.controller.js | 6 +- src/core/core.datasetController.js | 585 ++++++++-------- test/specs/controller.bar.tests.js | 5 + test/specs/controller.bubble.tests.js | 4 + test/specs/controller.doughnut.tests.js | 5 + test/specs/controller.line.tests.js | 4 + test/specs/controller.polarArea.tests.js | 8 +- test/specs/controller.radar.tests.js | 4 + test/specs/controller.scatter.test.js | 4 + 20 files changed, 1576 insertions(+), 1554 deletions(-) create mode 100644 src/controllers/controller.horizontalBar.js create mode 100644 src/controllers/controller.pie.js create mode 100644 src/controllers/index.js diff --git a/src/chart.js b/src/chart.js index 7b393e6b069..acb57862c50 100644 --- a/src/chart.js +++ b/src/chart.js @@ -10,6 +10,8 @@ require('./core/core.helpers')(Chart); Chart.Animation = require('./core/core.animation'); Chart.animationService = require('./core/core.animations'); +Chart.controllers = require('./controllers/index'); +Chart.DatasetController = require('./core/core.datasetController'); Chart.defaults = require('./core/core.defaults'); Chart.Element = require('./core/core.element'); Chart.elements = require('./elements/index'); @@ -23,7 +25,6 @@ Chart.Ticks = require('./core/core.ticks'); Chart.Tooltip = require('./core/core.tooltip'); require('./core/core.controller')(Chart); -require('./core/core.datasetController')(Chart); require('./scales/scale.linearbase')(Chart); require('./scales/scale.category')(Chart); @@ -32,16 +33,6 @@ require('./scales/scale.logarithmic')(Chart); require('./scales/scale.radialLinear')(Chart); require('./scales/scale.time')(Chart); -// Controllers must be loaded after elements -// See Chart.core.datasetController.dataElementType -require('./controllers/controller.bar')(Chart); -require('./controllers/controller.bubble')(Chart); -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); - // Loading built-in plugins var plugins = require('./plugins'); for (var k in plugins) { diff --git a/src/controllers/controller.bar.js b/src/controllers/controller.bar.js index 36f7c7e6e37..760fd8887ef 100644 --- a/src/controllers/controller.bar.js +++ b/src/controllers/controller.bar.js @@ -1,5 +1,6 @@ 'use strict'; +var DatasetController = require('../core/core.datasetController'); var defaults = require('../core/core.defaults'); var elements = require('../elements/index'); var helpers = require('../helpers/index'); @@ -12,15 +13,9 @@ defaults._set('bar', { scales: { xAxes: [{ type: 'category', - - // Specific to Bar Controller categoryPercentage: 0.8, barPercentage: 0.9, - - // offset settings offset: true, - - // grid line settings gridLines: { offsetGridLines: true } @@ -32,69 +27,6 @@ defaults._set('bar', { } }); -defaults._set('horizontalBar', { - hover: { - mode: 'index', - axis: 'y' - }, - - scales: { - xAxes: [{ - type: 'linear', - position: 'bottom' - }], - - yAxes: [{ - position: 'left', - type: 'category', - - // Specific to Horizontal Bar Controller - categoryPercentage: 0.8, - barPercentage: 0.9, - - // offset settings - offset: true, - - // 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; - } - }, - mode: 'index', - axis: 'y' - } -}); - /** * Computes the "optimal" sample size to maintain bars equally sized while preventing overlap. * @private @@ -182,349 +114,330 @@ function computeFlexCategoryTraits(index, ruler, options) { }; } -module.exports = function(Chart) { +module.exports = DatasetController.extend({ - Chart.controllers.bar = Chart.DatasetController.extend({ + dataElementType: elements.Rectangle, - dataElementType: elements.Rectangle, + initialize: function() { + var me = this; + var meta; - initialize: function() { - var me = this; - var meta; + DatasetController.prototype.initialize.apply(me, arguments); - Chart.DatasetController.prototype.initialize.apply(me, arguments); + meta = me.getMeta(); + meta.stack = me.getDataset().stack; + meta.bar = true; + }, - meta = me.getMeta(); - meta.stack = me.getDataset().stack; - meta.bar = true; - }, + update: function(reset) { + var me = this; + var rects = me.getMeta().data; + var i, ilen; - update: function(reset) { - var me = this; - var rects = me.getMeta().data; - var i, ilen; + me._ruler = me.getRuler(); - me._ruler = me.getRuler(); + for (i = 0, ilen = rects.length; i < ilen; ++i) { + me.updateElement(rects[i], i, reset); + } + }, - for (i = 0, ilen = rects.length; i < ilen; ++i) { - me.updateElement(rects[i], i, reset); - } - }, - - updateElement: function(rectangle, index, reset) { - var me = this; - var meta = me.getMeta(); - var dataset = me.getDataset(); - var options = me._resolveElementOptions(rectangle, index); - - rectangle._xScale = me.getScaleForId(meta.xAxisID); - rectangle._yScale = me.getScaleForId(meta.yAxisID); - rectangle._datasetIndex = me.index; - rectangle._index = index; - rectangle._model = { - backgroundColor: options.backgroundColor, - borderColor: options.borderColor, - borderSkipped: options.borderSkipped, - borderWidth: options.borderWidth, - datasetLabel: dataset.label, - label: me.chart.data.labels[index] - }; - - me._updateElementGeometry(rectangle, index, reset); - - rectangle.pivot(); - }, - - /** - * @private - */ - _updateElementGeometry: function(rectangle, index, reset) { - var me = this; - var model = rectangle._model; - var vscale = me.getValueScale(); - var base = vscale.getBasePixel(); - var horizontal = vscale.isHorizontal(); - var ruler = me._ruler || me.getRuler(); - var vpixels = me.calculateBarValuePixels(me.index, index); - var ipixels = me.calculateBarIndexPixels(me.index, index, ruler); - - model.horizontal = horizontal; - model.base = reset ? base : vpixels.base; - model.x = horizontal ? reset ? base : vpixels.head : ipixels.center; - model.y = horizontal ? ipixels.center : reset ? base : vpixels.head; - model.height = horizontal ? ipixels.size : undefined; - model.width = horizontal ? undefined : ipixels.size; - }, - - /** - * @private - */ - getValueScaleId: function() { - return this.getMeta().yAxisID; - }, - - /** - * @private - */ - getIndexScaleId: function() { - return this.getMeta().xAxisID; - }, - - /** - * @private - */ - getValueScale: function() { - return this.getScaleForId(this.getValueScaleId()); - }, - - /** - * @private - */ - getIndexScale: function() { - return this.getScaleForId(this.getIndexScaleId()); - }, - - /** - * Returns the stacks based on groups and bar visibility. - * @param {Number} [last] - The dataset index - * @returns {Array} The stack list - * @private - */ - _getStacks: function(last) { - var me = this; - var chart = me.chart; - var scale = me.getIndexScale(); - var stacked = scale.options.stacked; - var ilen = last === undefined ? chart.data.datasets.length : last + 1; - var stacks = []; - var i, meta; - - for (i = 0; i < ilen; ++i) { - meta = chart.getDatasetMeta(i); - if (meta.bar && chart.isDatasetVisible(i) && - (stacked === false || - (stacked === true && stacks.indexOf(meta.stack) === -1) || - (stacked === undefined && (meta.stack === undefined || stacks.indexOf(meta.stack) === -1)))) { - stacks.push(meta.stack); - } - } + updateElement: function(rectangle, index, reset) { + var me = this; + var meta = me.getMeta(); + var dataset = me.getDataset(); + var options = me._resolveElementOptions(rectangle, index); + + rectangle._xScale = me.getScaleForId(meta.xAxisID); + rectangle._yScale = me.getScaleForId(meta.yAxisID); + rectangle._datasetIndex = me.index; + rectangle._index = index; + rectangle._model = { + backgroundColor: options.backgroundColor, + borderColor: options.borderColor, + borderSkipped: options.borderSkipped, + borderWidth: options.borderWidth, + datasetLabel: dataset.label, + label: me.chart.data.labels[index] + }; + + me._updateElementGeometry(rectangle, index, reset); + + rectangle.pivot(); + }, + + /** + * @private + */ + _updateElementGeometry: function(rectangle, index, reset) { + var me = this; + var model = rectangle._model; + var vscale = me.getValueScale(); + var base = vscale.getBasePixel(); + var horizontal = vscale.isHorizontal(); + var ruler = me._ruler || me.getRuler(); + var vpixels = me.calculateBarValuePixels(me.index, index); + var ipixels = me.calculateBarIndexPixels(me.index, index, ruler); + + model.horizontal = horizontal; + model.base = reset ? base : vpixels.base; + model.x = horizontal ? reset ? base : vpixels.head : ipixels.center; + model.y = horizontal ? ipixels.center : reset ? base : vpixels.head; + model.height = horizontal ? ipixels.size : undefined; + model.width = horizontal ? undefined : ipixels.size; + }, + + /** + * @private + */ + getValueScaleId: function() { + return this.getMeta().yAxisID; + }, + + /** + * @private + */ + getIndexScaleId: function() { + return this.getMeta().xAxisID; + }, + + /** + * @private + */ + getValueScale: function() { + return this.getScaleForId(this.getValueScaleId()); + }, - return stacks; - }, - - /** - * Returns the effective number of stacks based on groups and bar visibility. - * @private - */ - getStackCount: function() { - return this._getStacks().length; - }, - - /** - * Returns the stack index for the given dataset based on groups and bar visibility. - * @param {Number} [datasetIndex] - The dataset index - * @param {String} [name] - The stack name to find - * @returns {Number} The stack index - * @private - */ - getStackIndex: function(datasetIndex, name) { - var stacks = this._getStacks(datasetIndex); - var index = (name !== undefined) - ? stacks.indexOf(name) - : -1; // indexOf returns -1 if element is not present - - return (index === -1) - ? stacks.length - 1 - : index; - }, - - /** - * @private - */ - getRuler: function() { - var me = this; - var scale = me.getIndexScale(); - var stackCount = me.getStackCount(); - var datasetIndex = me.index; - var isHorizontal = scale.isHorizontal(); - var start = isHorizontal ? scale.left : scale.top; - var end = start + (isHorizontal ? scale.width : scale.height); - var pixels = []; - var i, ilen, min; - - for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) { - pixels.push(scale.getPixelForValue(null, i, datasetIndex)); + /** + * @private + */ + getIndexScale: function() { + return this.getScaleForId(this.getIndexScaleId()); + }, + + /** + * Returns the stacks based on groups and bar visibility. + * @param {Number} [last] - The dataset index + * @returns {Array} The stack list + * @private + */ + _getStacks: function(last) { + var me = this; + var chart = me.chart; + var scale = me.getIndexScale(); + var stacked = scale.options.stacked; + var ilen = last === undefined ? chart.data.datasets.length : last + 1; + var stacks = []; + var i, meta; + + for (i = 0; i < ilen; ++i) { + meta = chart.getDatasetMeta(i); + if (meta.bar && chart.isDatasetVisible(i) && + (stacked === false || + (stacked === true && stacks.indexOf(meta.stack) === -1) || + (stacked === undefined && (meta.stack === undefined || stacks.indexOf(meta.stack) === -1)))) { + stacks.push(meta.stack); } + } + + return stacks; + }, + + /** + * Returns the effective number of stacks based on groups and bar visibility. + * @private + */ + getStackCount: function() { + return this._getStacks().length; + }, + + /** + * Returns the stack index for the given dataset based on groups and bar visibility. + * @param {Number} [datasetIndex] - The dataset index + * @param {String} [name] - The stack name to find + * @returns {Number} The stack index + * @private + */ + getStackIndex: function(datasetIndex, name) { + var stacks = this._getStacks(datasetIndex); + var index = (name !== undefined) + ? stacks.indexOf(name) + : -1; // indexOf returns -1 if element is not present + + return (index === -1) + ? stacks.length - 1 + : index; + }, + + /** + * @private + */ + getRuler: function() { + var me = this; + var scale = me.getIndexScale(); + var stackCount = me.getStackCount(); + var datasetIndex = me.index; + var isHorizontal = scale.isHorizontal(); + var start = isHorizontal ? scale.left : scale.top; + var end = start + (isHorizontal ? scale.width : scale.height); + var pixels = []; + var i, ilen, min; + + for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) { + pixels.push(scale.getPixelForValue(null, i, datasetIndex)); + } + + min = helpers.isNullOrUndef(scale.options.barThickness) + ? computeMinSampleSize(scale, pixels) + : -1; + + return { + min: min, + pixels: pixels, + start: start, + end: end, + stackCount: stackCount, + scale: scale + }; + }, - min = helpers.isNullOrUndef(scale.options.barThickness) - ? computeMinSampleSize(scale, pixels) - : -1; - - return { - min: min, - pixels: pixels, - start: start, - end: end, - stackCount: stackCount, - scale: scale - }; - }, - - /** - * Note: pixel values are not clamped to the scale area. - * @private - */ - calculateBarValuePixels: function(datasetIndex, index) { - var me = this; - var chart = me.chart; - var meta = me.getMeta(); - var scale = me.getValueScale(); - var isHorizontal = scale.isHorizontal(); - var datasets = chart.data.datasets; - var value = scale.getRightValue(datasets[datasetIndex].data[index]); - var minBarLength = scale.options.minBarLength; - var stacked = scale.options.stacked; - var stack = meta.stack; - var start = 0; - var i, imeta, ivalue, base, head, size; - - if (stacked || (stacked === undefined && stack !== undefined)) { - for (i = 0; i < datasetIndex; ++i) { - imeta = chart.getDatasetMeta(i); - - if (imeta.bar && - imeta.stack === stack && - imeta.controller.getValueScaleId() === scale.id && - chart.isDatasetVisible(i)) { - - ivalue = scale.getRightValue(datasets[i].data[index]); - if ((value < 0 && ivalue < 0) || (value >= 0 && ivalue > 0)) { - start += ivalue; - } + /** + * Note: pixel values are not clamped to the scale area. + * @private + */ + calculateBarValuePixels: function(datasetIndex, index) { + var me = this; + var chart = me.chart; + var meta = me.getMeta(); + var scale = me.getValueScale(); + var isHorizontal = scale.isHorizontal(); + var datasets = chart.data.datasets; + var value = scale.getRightValue(datasets[datasetIndex].data[index]); + var minBarLength = scale.options.minBarLength; + var stacked = scale.options.stacked; + var stack = meta.stack; + var start = 0; + var i, imeta, ivalue, base, head, size; + + if (stacked || (stacked === undefined && stack !== undefined)) { + for (i = 0; i < datasetIndex; ++i) { + imeta = chart.getDatasetMeta(i); + + if (imeta.bar && + imeta.stack === stack && + imeta.controller.getValueScaleId() === scale.id && + chart.isDatasetVisible(i)) { + + ivalue = scale.getRightValue(datasets[i].data[index]); + if ((value < 0 && ivalue < 0) || (value >= 0 && ivalue > 0)) { + start += ivalue; } } } + } - base = scale.getPixelForValue(start); - head = scale.getPixelForValue(start + value); - size = (head - base) / 2; + base = scale.getPixelForValue(start); + head = scale.getPixelForValue(start + value); + size = (head - base) / 2; - if (minBarLength !== undefined && Math.abs(size) < minBarLength) { - size = minBarLength; - if (value >= 0 && !isHorizontal || value < 0 && isHorizontal) { - head = base - minBarLength; - } else { - head = base + minBarLength; - } + if (minBarLength !== undefined && Math.abs(size) < minBarLength) { + size = minBarLength; + if (value >= 0 && !isHorizontal || value < 0 && isHorizontal) { + head = base - minBarLength; + } else { + head = base + minBarLength; } + } - return { - size: size, - base: base, - head: head, - center: head + size / 2 - }; - }, - - /** - * @private - */ - calculateBarIndexPixels: function(datasetIndex, index, ruler) { - var me = this; - var options = ruler.scale.options; - var range = options.barThickness === 'flex' - ? computeFlexCategoryTraits(index, ruler, options) - : computeFitCategoryTraits(index, ruler, options); - - var stackIndex = me.getStackIndex(datasetIndex, me.getMeta().stack); - var center = range.start + (range.chunk * stackIndex) + (range.chunk / 2); - var size = Math.min( - helpers.valueOrDefault(options.maxBarThickness, Infinity), - range.chunk * range.ratio); - - return { - base: center - size / 2, - head: center + size / 2, - center: center, - size: size - }; - }, - - draw: function() { - var me = this; - var chart = me.chart; - var scale = me.getValueScale(); - var rects = me.getMeta().data; - var dataset = me.getDataset(); - var ilen = rects.length; - var i = 0; - - helpers.canvas.clipArea(chart.ctx, chart.chartArea); - - for (; i < ilen; ++i) { - if (!isNaN(scale.getRightValue(dataset.data[i]))) { - rects[i].draw(); - } - } + return { + size: size, + base: base, + head: head, + center: head + size / 2 + }; + }, - helpers.canvas.unclipArea(chart.ctx); - }, - - /** - * @private - */ - _resolveElementOptions: function(rectangle, index) { - var me = this; - var chart = me.chart; - var datasets = chart.data.datasets; - var dataset = datasets[me.index]; - var custom = rectangle.custom || {}; - var options = chart.options.elements.rectangle; - var resolve = helpers.options.resolve; - var values = {}; - var i, ilen, key; - - // Scriptable options - var context = { - chart: chart, - dataIndex: index, - dataset: dataset, - datasetIndex: me.index - }; - - var keys = [ - 'backgroundColor', - 'borderColor', - 'borderSkipped', - 'borderWidth' - ]; - - for (i = 0, ilen = keys.length; i < ilen; ++i) { - key = keys[i]; - values[key] = resolve([ - custom[key], - dataset[key], - options[key] - ], context, index); - } + /** + * @private + */ + calculateBarIndexPixels: function(datasetIndex, index, ruler) { + var me = this; + var options = ruler.scale.options; + var range = options.barThickness === 'flex' + ? computeFlexCategoryTraits(index, ruler, options) + : computeFitCategoryTraits(index, ruler, options); + + var stackIndex = me.getStackIndex(datasetIndex, me.getMeta().stack); + var center = range.start + (range.chunk * stackIndex) + (range.chunk / 2); + var size = Math.min( + helpers.valueOrDefault(options.maxBarThickness, Infinity), + range.chunk * range.ratio); + + return { + base: center - size / 2, + head: center + size / 2, + center: center, + size: size + }; + }, + + draw: function() { + var me = this; + var chart = me.chart; + var scale = me.getValueScale(); + var rects = me.getMeta().data; + var dataset = me.getDataset(); + var ilen = rects.length; + var i = 0; + + helpers.canvas.clipArea(chart.ctx, chart.chartArea); - return values; + for (; i < ilen; ++i) { + if (!isNaN(scale.getRightValue(dataset.data[i]))) { + rects[i].draw(); + } } - }); - - Chart.controllers.horizontalBar = Chart.controllers.bar.extend({ - /** - * @private - */ - getValueScaleId: function() { - return this.getMeta().xAxisID; - }, - - /** - * @private - */ - getIndexScaleId: function() { - return this.getMeta().yAxisID; + + helpers.canvas.unclipArea(chart.ctx); + }, + + /** + * @private + */ + _resolveElementOptions: function(rectangle, index) { + var me = this; + var chart = me.chart; + var datasets = chart.data.datasets; + var dataset = datasets[me.index]; + var custom = rectangle.custom || {}; + var options = chart.options.elements.rectangle; + var resolve = helpers.options.resolve; + var values = {}; + var i, ilen, key; + + // Scriptable options + var context = { + chart: chart, + dataIndex: index, + dataset: dataset, + datasetIndex: me.index + }; + + var keys = [ + 'backgroundColor', + 'borderColor', + 'borderSkipped', + 'borderWidth' + ]; + + for (i = 0, ilen = keys.length; i < ilen; ++i) { + key = keys[i]; + values[key] = resolve([ + custom[key], + dataset[key], + options[key] + ], context, index); } - }); -}; + + return values; + } +}); diff --git a/src/controllers/controller.bubble.js b/src/controllers/controller.bubble.js index ed1e2045c71..77956c9dc7b 100644 --- a/src/controllers/controller.bubble.js +++ b/src/controllers/controller.bubble.js @@ -1,5 +1,6 @@ 'use strict'; +var DatasetController = require('../core/core.datasetController'); var defaults = require('../core/core.defaults'); var elements = require('../elements/index'); var helpers = require('../helpers/index'); @@ -37,140 +38,136 @@ defaults._set('bubble', { } }); +module.exports = DatasetController.extend({ + /** + * @protected + */ + dataElementType: elements.Point, + + /** + * @protected + */ + update: function(reset) { + var me = this; + var meta = me.getMeta(); + var points = meta.data; + + // Update Points + helpers.each(points, function(point, index) { + me.updateElement(point, index, reset); + }); + }, -module.exports = function(Chart) { - - Chart.controllers.bubble = Chart.DatasetController.extend({ - /** - * @protected - */ - dataElementType: elements.Point, - - /** - * @protected - */ - update: function(reset) { - var me = this; - var meta = me.getMeta(); - var points = meta.data; - - // Update Points - helpers.each(points, function(point, index) { - me.updateElement(point, index, reset); - }); - }, - - /** - * @protected - */ - updateElement: function(point, index, reset) { - var me = this; - var meta = me.getMeta(); - var custom = point.custom || {}; - var xScale = me.getScaleForId(meta.xAxisID); - var yScale = me.getScaleForId(meta.yAxisID); - var options = me._resolveElementOptions(point, index); - var data = me.getDataset().data[index]; - var dsIndex = me.index; - - var x = reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(typeof data === 'object' ? data : NaN, index, dsIndex); - var y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, dsIndex); - - point._xScale = xScale; - point._yScale = yScale; - point._options = options; - point._datasetIndex = dsIndex; - point._index = index; - point._model = { - backgroundColor: options.backgroundColor, - borderColor: options.borderColor, - borderWidth: options.borderWidth, - hitRadius: options.hitRadius, - pointStyle: options.pointStyle, - rotation: options.rotation, - radius: reset ? 0 : options.radius, - skip: custom.skip || isNaN(x) || isNaN(y), - x: x, - y: y, - }; - - point.pivot(); - }, - - /** - * @protected - */ - setHoverStyle: function(point) { - var model = point._model; - var options = point._options; - - point.$previousStyle = { - backgroundColor: model.backgroundColor, - borderColor: model.borderColor, - borderWidth: model.borderWidth, - radius: model.radius - }; - - model.backgroundColor = helpers.valueOrDefault(options.hoverBackgroundColor, helpers.getHoverColor(options.backgroundColor)); - model.borderColor = helpers.valueOrDefault(options.hoverBorderColor, helpers.getHoverColor(options.borderColor)); - model.borderWidth = helpers.valueOrDefault(options.hoverBorderWidth, options.borderWidth); - model.radius = options.radius + options.hoverRadius; - }, - - /** - * @private - */ - _resolveElementOptions: function(point, index) { - var me = this; - var chart = me.chart; - var datasets = chart.data.datasets; - var dataset = datasets[me.index]; - var custom = point.custom || {}; - var options = chart.options.elements.point; - var resolve = helpers.options.resolve; - var data = dataset.data[index]; - var values = {}; - var i, ilen, key; - - // Scriptable options - var context = { - chart: chart, - dataIndex: index, - dataset: dataset, - datasetIndex: me.index - }; - - var keys = [ - 'backgroundColor', - 'borderColor', - 'borderWidth', - 'hoverBackgroundColor', - 'hoverBorderColor', - 'hoverBorderWidth', - 'hoverRadius', - 'hitRadius', - 'pointStyle', - 'rotation' - ]; - - for (i = 0, ilen = keys.length; i < ilen; ++i) { - key = keys[i]; - values[key] = resolve([ - custom[key], - dataset[key], - options[key] - ], context, index); - } + /** + * @protected + */ + updateElement: function(point, index, reset) { + var me = this; + var meta = me.getMeta(); + var custom = point.custom || {}; + var xScale = me.getScaleForId(meta.xAxisID); + var yScale = me.getScaleForId(meta.yAxisID); + var options = me._resolveElementOptions(point, index); + var data = me.getDataset().data[index]; + var dsIndex = me.index; + + var x = reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(typeof data === 'object' ? data : NaN, index, dsIndex); + var y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, dsIndex); + + point._xScale = xScale; + point._yScale = yScale; + point._options = options; + point._datasetIndex = dsIndex; + point._index = index; + point._model = { + backgroundColor: options.backgroundColor, + borderColor: options.borderColor, + borderWidth: options.borderWidth, + hitRadius: options.hitRadius, + pointStyle: options.pointStyle, + rotation: options.rotation, + radius: reset ? 0 : options.radius, + skip: custom.skip || isNaN(x) || isNaN(y), + x: x, + y: y, + }; + + point.pivot(); + }, - // Custom radius resolution - values.radius = resolve([ - custom.radius, - data ? data.r : undefined, - dataset.radius, - options.radius - ], context, index); + /** + * @protected + */ + setHoverStyle: function(point) { + var model = point._model; + var options = point._options; + + point.$previousStyle = { + backgroundColor: model.backgroundColor, + borderColor: model.borderColor, + borderWidth: model.borderWidth, + radius: model.radius + }; + + model.backgroundColor = helpers.valueOrDefault(options.hoverBackgroundColor, helpers.getHoverColor(options.backgroundColor)); + model.borderColor = helpers.valueOrDefault(options.hoverBorderColor, helpers.getHoverColor(options.borderColor)); + model.borderWidth = helpers.valueOrDefault(options.hoverBorderWidth, options.borderWidth); + model.radius = options.radius + options.hoverRadius; + }, - return values; + /** + * @private + */ + _resolveElementOptions: function(point, index) { + var me = this; + var chart = me.chart; + var datasets = chart.data.datasets; + var dataset = datasets[me.index]; + var custom = point.custom || {}; + var options = chart.options.elements.point; + var resolve = helpers.options.resolve; + var data = dataset.data[index]; + var values = {}; + var i, ilen, key; + + // Scriptable options + var context = { + chart: chart, + dataIndex: index, + dataset: dataset, + datasetIndex: me.index + }; + + var keys = [ + 'backgroundColor', + 'borderColor', + 'borderWidth', + 'hoverBackgroundColor', + 'hoverBorderColor', + 'hoverBorderWidth', + 'hoverRadius', + 'hitRadius', + 'pointStyle', + 'rotation' + ]; + + for (i = 0, ilen = keys.length; i < ilen; ++i) { + key = keys[i]; + values[key] = resolve([ + custom[key], + dataset[key], + options[key] + ], context, index); } - }); -}; + + // Custom radius resolution + values.radius = resolve([ + custom.radius, + data ? data.r : undefined, + dataset.radius, + options.radius + ], context, index); + + return values; + } +}); diff --git a/src/controllers/controller.doughnut.js b/src/controllers/controller.doughnut.js index eb759fe60b8..b24a7a00e9f 100644 --- a/src/controllers/controller.doughnut.js +++ b/src/controllers/controller.doughnut.js @@ -1,5 +1,6 @@ 'use strict'; +var DatasetController = require('../core/core.datasetController'); var defaults = require('../core/core.defaults'); var elements = require('../elements/index'); var helpers = require('../helpers/index'); @@ -118,184 +119,176 @@ defaults._set('doughnut', { } }); -defaults._set('pie', helpers.clone(defaults.doughnut)); -defaults._set('pie', { - cutoutPercentage: 0 -}); - -module.exports = function(Chart) { +module.exports = DatasetController.extend({ - Chart.controllers.doughnut = Chart.controllers.pie = Chart.DatasetController.extend({ + dataElementType: elements.Arc, - dataElementType: elements.Arc, + linkScales: helpers.noop, - linkScales: helpers.noop, + // Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly + getRingIndex: function(datasetIndex) { + var ringIndex = 0; - // Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly - getRingIndex: function(datasetIndex) { - var ringIndex = 0; - - for (var j = 0; j < datasetIndex; ++j) { - if (this.chart.isDatasetVisible(j)) { - ++ringIndex; - } + for (var j = 0; j < datasetIndex; ++j) { + if (this.chart.isDatasetVisible(j)) { + ++ringIndex; } + } - return ringIndex; - }, - - update: function(reset) { - var me = this; - var chart = me.chart; - var chartArea = chart.chartArea; - var opts = chart.options; - var arcOpts = opts.elements.arc; - var availableWidth = chartArea.right - chartArea.left - arcOpts.borderWidth; - var availableHeight = chartArea.bottom - chartArea.top - arcOpts.borderWidth; - var minSize = Math.min(availableWidth, availableHeight); - var offset = {x: 0, y: 0}; - var meta = me.getMeta(); - var cutoutPercentage = opts.cutoutPercentage; - var circumference = opts.circumference; - - // If the chart's circumference isn't a full circle, calculate minSize as a ratio of the width/height of the arc - if (circumference < Math.PI * 2.0) { - var startAngle = opts.rotation % (Math.PI * 2.0); - startAngle += Math.PI * 2.0 * (startAngle >= Math.PI ? -1 : startAngle < -Math.PI ? 1 : 0); - 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 && 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); - var cutout = cutoutPercentage / 100.0; - var min = {x: contains180 ? -1 : Math.min(start.x * (start.x < 0 ? 1 : cutout), end.x * (end.x < 0 ? 1 : cutout)), y: contains270 ? -1 : Math.min(start.y * (start.y < 0 ? 1 : cutout), end.y * (end.y < 0 ? 1 : cutout))}; - var max = {x: contains0 ? 1 : Math.max(start.x * (start.x > 0 ? 1 : cutout), end.x * (end.x > 0 ? 1 : cutout)), y: contains90 ? 1 : Math.max(start.y * (start.y > 0 ? 1 : cutout), end.y * (end.y > 0 ? 1 : cutout))}; - var size = {width: (max.x - min.x) * 0.5, height: (max.y - min.y) * 0.5}; - minSize = Math.min(availableWidth / size.width, availableHeight / size.height); - offset = {x: (max.x + min.x) * -0.5, y: (max.y + min.y) * -0.5}; - } + return ringIndex; + }, - chart.borderWidth = me.getMaxBorderWidth(meta.data); - chart.outerRadius = Math.max((minSize - chart.borderWidth) / 2, 0); - chart.innerRadius = Math.max(cutoutPercentage ? (chart.outerRadius / 100) * (cutoutPercentage) : 0, 0); - chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount(); - chart.offsetX = offset.x * chart.outerRadius; - chart.offsetY = offset.y * chart.outerRadius; + update: function(reset) { + var me = this; + var chart = me.chart; + var chartArea = chart.chartArea; + var opts = chart.options; + var arcOpts = opts.elements.arc; + var availableWidth = chartArea.right - chartArea.left - arcOpts.borderWidth; + var availableHeight = chartArea.bottom - chartArea.top - arcOpts.borderWidth; + var minSize = Math.min(availableWidth, availableHeight); + var offset = {x: 0, y: 0}; + var meta = me.getMeta(); + var cutoutPercentage = opts.cutoutPercentage; + var circumference = opts.circumference; + + // If the chart's circumference isn't a full circle, calculate minSize as a ratio of the width/height of the arc + if (circumference < Math.PI * 2.0) { + var startAngle = opts.rotation % (Math.PI * 2.0); + startAngle += Math.PI * 2.0 * (startAngle >= Math.PI ? -1 : startAngle < -Math.PI ? 1 : 0); + 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 && 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); + var cutout = cutoutPercentage / 100.0; + var min = {x: contains180 ? -1 : Math.min(start.x * (start.x < 0 ? 1 : cutout), end.x * (end.x < 0 ? 1 : cutout)), y: contains270 ? -1 : Math.min(start.y * (start.y < 0 ? 1 : cutout), end.y * (end.y < 0 ? 1 : cutout))}; + var max = {x: contains0 ? 1 : Math.max(start.x * (start.x > 0 ? 1 : cutout), end.x * (end.x > 0 ? 1 : cutout)), y: contains90 ? 1 : Math.max(start.y * (start.y > 0 ? 1 : cutout), end.y * (end.y > 0 ? 1 : cutout))}; + var size = {width: (max.x - min.x) * 0.5, height: (max.y - min.y) * 0.5}; + minSize = Math.min(availableWidth / size.width, availableHeight / size.height); + offset = {x: (max.x + min.x) * -0.5, y: (max.y + min.y) * -0.5}; + } - meta.total = me.calculateTotal(); + chart.borderWidth = me.getMaxBorderWidth(meta.data); + chart.outerRadius = Math.max((minSize - chart.borderWidth) / 2, 0); + chart.innerRadius = Math.max(cutoutPercentage ? (chart.outerRadius / 100) * (cutoutPercentage) : 0, 0); + chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount(); + chart.offsetX = offset.x * chart.outerRadius; + chart.offsetY = offset.y * chart.outerRadius; - me.outerRadius = chart.outerRadius - (chart.radiusLength * me.getRingIndex(me.index)); - me.innerRadius = Math.max(me.outerRadius - chart.radiusLength, 0); + meta.total = me.calculateTotal(); - helpers.each(meta.data, function(arc, index) { - me.updateElement(arc, index, reset); - }); - }, + me.outerRadius = chart.outerRadius - (chart.radiusLength * me.getRingIndex(me.index)); + me.innerRadius = Math.max(me.outerRadius - chart.radiusLength, 0); - updateElement: function(arc, index, reset) { - var me = this; - var chart = me.chart; - var chartArea = chart.chartArea; - var opts = chart.options; - var animationOpts = opts.animation; - var centerX = (chartArea.left + chartArea.right) / 2; - var centerY = (chartArea.top + chartArea.bottom) / 2; - var startAngle = opts.rotation; // non reset case handled later - var endAngle = opts.rotation; // non reset case handled later - var dataset = me.getDataset(); - var circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / (2.0 * Math.PI)); - var innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius; - var outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius; - var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault; - - helpers.extend(arc, { - // Utility - _datasetIndex: me.index, - _index: index, - - // Desired view properties - _model: { - x: centerX + chart.offsetX, - y: centerY + chart.offsetY, - startAngle: startAngle, - endAngle: endAngle, - circumference: circumference, - outerRadius: outerRadius, - innerRadius: innerRadius, - label: valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index]) - } - }); - - var model = arc._model; - - // Resets the visual styles - var custom = arc.custom || {}; - var valueOrDefault = helpers.valueAtIndexOrDefault; - var elementOpts = this.chart.options.elements.arc; - model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : valueOrDefault(dataset.backgroundColor, index, elementOpts.backgroundColor); - model.borderColor = custom.borderColor ? custom.borderColor : valueOrDefault(dataset.borderColor, index, elementOpts.borderColor); - model.borderWidth = custom.borderWidth ? custom.borderWidth : valueOrDefault(dataset.borderWidth, index, elementOpts.borderWidth); - - // Set correct angles if not resetting - if (!reset || !animationOpts.animateRotate) { - if (index === 0) { - model.startAngle = opts.rotation; - } else { - model.startAngle = me.getMeta().data[index - 1]._model.endAngle; - } + helpers.each(meta.data, function(arc, index) { + me.updateElement(arc, index, reset); + }); + }, - model.endAngle = model.startAngle + model.circumference; + updateElement: function(arc, index, reset) { + var me = this; + var chart = me.chart; + var chartArea = chart.chartArea; + var opts = chart.options; + var animationOpts = opts.animation; + var centerX = (chartArea.left + chartArea.right) / 2; + var centerY = (chartArea.top + chartArea.bottom) / 2; + var startAngle = opts.rotation; // non reset case handled later + var endAngle = opts.rotation; // non reset case handled later + var dataset = me.getDataset(); + var circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / (2.0 * Math.PI)); + var innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius; + var outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius; + var valueAtIndexOrDefault = helpers.valueAtIndexOrDefault; + + helpers.extend(arc, { + // Utility + _datasetIndex: me.index, + _index: index, + + // Desired view properties + _model: { + x: centerX + chart.offsetX, + y: centerY + chart.offsetY, + startAngle: startAngle, + endAngle: endAngle, + circumference: circumference, + outerRadius: outerRadius, + innerRadius: innerRadius, + label: valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index]) + } + }); + + var model = arc._model; + + // Resets the visual styles + var custom = arc.custom || {}; + var valueOrDefault = helpers.valueAtIndexOrDefault; + var elementOpts = this.chart.options.elements.arc; + model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : valueOrDefault(dataset.backgroundColor, index, elementOpts.backgroundColor); + model.borderColor = custom.borderColor ? custom.borderColor : valueOrDefault(dataset.borderColor, index, elementOpts.borderColor); + model.borderWidth = custom.borderWidth ? custom.borderWidth : valueOrDefault(dataset.borderWidth, index, elementOpts.borderWidth); + + // Set correct angles if not resetting + if (!reset || !animationOpts.animateRotate) { + if (index === 0) { + model.startAngle = opts.rotation; + } else { + model.startAngle = me.getMeta().data[index - 1]._model.endAngle; } - arc.pivot(); - }, + model.endAngle = model.startAngle + model.circumference; + } - calculateTotal: function() { - var dataset = this.getDataset(); - var meta = this.getMeta(); - var total = 0; - var value; + arc.pivot(); + }, - helpers.each(meta.data, function(element, index) { - value = dataset.data[index]; - if (!isNaN(value) && !element.hidden) { - total += Math.abs(value); - } - }); + calculateTotal: function() { + var dataset = this.getDataset(); + var meta = this.getMeta(); + var total = 0; + var value; - /* if (total === 0) { - total = NaN; - }*/ + helpers.each(meta.data, function(element, index) { + value = dataset.data[index]; + if (!isNaN(value) && !element.hidden) { + total += Math.abs(value); + } + }); - return total; - }, + /* if (total === 0) { + total = NaN; + }*/ - calculateCircumference: function(value) { - var total = this.getMeta().total; - if (total > 0 && !isNaN(value)) { - return (Math.PI * 2.0) * (Math.abs(value) / total); - } - return 0; - }, + return total; + }, - // gets the max border or hover width to properly scale pie charts - getMaxBorderWidth: function(arcs) { - var max = 0; - var index = this.index; - var length = arcs.length; - var borderWidth; - var hoverWidth; + calculateCircumference: function(value) { + var total = this.getMeta().total; + if (total > 0 && !isNaN(value)) { + return (Math.PI * 2.0) * (Math.abs(value) / total); + } + return 0; + }, - for (var i = 0; i < length; i++) { - borderWidth = arcs[i]._model ? arcs[i]._model.borderWidth : 0; - hoverWidth = arcs[i]._chart ? arcs[i]._chart.config.data.datasets[index].hoverBorderWidth : 0; + // gets the max border or hover width to properly scale pie charts + getMaxBorderWidth: function(arcs) { + var max = 0; + var index = this.index; + var length = arcs.length; + var borderWidth; + var hoverWidth; - max = borderWidth > max ? borderWidth : max; - max = hoverWidth > max ? hoverWidth : max; - } - return max; + for (var i = 0; i < length; i++) { + borderWidth = arcs[i]._model ? arcs[i]._model.borderWidth : 0; + hoverWidth = arcs[i]._chart ? arcs[i]._chart.config.data.datasets[index].hoverBorderWidth : 0; + + max = borderWidth > max ? borderWidth : max; + max = hoverWidth > max ? hoverWidth : max; } - }); -}; + return max; + } +}); diff --git a/src/controllers/controller.horizontalBar.js b/src/controllers/controller.horizontalBar.js new file mode 100644 index 00000000000..b761c344403 --- /dev/null +++ b/src/controllers/controller.horizontalBar.js @@ -0,0 +1,79 @@ + +'use strict'; + +var BarController = require('./controller.bar'); +var defaults = require('../core/core.defaults'); + +defaults._set('horizontalBar', { + hover: { + mode: 'index', + axis: 'y' + }, + + scales: { + xAxes: [{ + type: 'linear', + position: 'bottom' + }], + + yAxes: [{ + type: 'category', + position: 'left', + categoryPercentage: 0.8, + barPercentage: 0.9, + offset: true, + 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; + } + }, + mode: 'index', + axis: 'y' + } +}); + +module.exports = BarController.extend({ + /** + * @private + */ + getValueScaleId: function() { + return this.getMeta().xAxisID; + }, + + /** + * @private + */ + getIndexScaleId: function() { + return this.getMeta().yAxisID; + } +}); + diff --git a/src/controllers/controller.line.js b/src/controllers/controller.line.js index 4a18bdadb3c..fffb1ce55d2 100644 --- a/src/controllers/controller.line.js +++ b/src/controllers/controller.line.js @@ -1,5 +1,6 @@ 'use strict'; +var DatasetController = require('../core/core.datasetController'); var defaults = require('../core/core.defaults'); var elements = require('../elements/index'); var helpers = require('../helpers/index'); @@ -24,321 +25,318 @@ defaults._set('line', { } }); -module.exports = function(Chart) { +function lineEnabled(dataset, options) { + return helpers.valueOrDefault(dataset.showLine, options.showLines); +} - function lineEnabled(dataset, options) { - return helpers.valueOrDefault(dataset.showLine, options.showLines); - } +module.exports = DatasetController.extend({ - Chart.controllers.line = Chart.DatasetController.extend({ + datasetElementType: elements.Line, - datasetElementType: elements.Line, + dataElementType: elements.Point, - dataElementType: elements.Point, + update: function(reset) { + var me = this; + var meta = me.getMeta(); + var line = meta.dataset; + var points = meta.data || []; + var options = me.chart.options; + var lineElementOptions = options.elements.line; + var scale = me.getScaleForId(meta.yAxisID); + var i, ilen, custom; + var dataset = me.getDataset(); + var showLine = lineEnabled(dataset, options); - update: function(reset) { - var me = this; - var meta = me.getMeta(); - var line = meta.dataset; - var points = meta.data || []; - var options = me.chart.options; - var lineElementOptions = options.elements.line; - var scale = me.getScaleForId(meta.yAxisID); - var i, ilen, custom; - var dataset = me.getDataset(); - var showLine = lineEnabled(dataset, options); + // Update Line + if (showLine) { + custom = line.custom || {}; - // Update Line - if (showLine) { - custom = line.custom || {}; + // Compatibility: If the properties are defined with only the old name, use those values + if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) { + dataset.lineTension = dataset.tension; + } - // Compatibility: If the properties are defined with only the old name, use those values - if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) { - dataset.lineTension = dataset.tension; - } + // Utility + line._scale = scale; + line._datasetIndex = me.index; + // Data + line._children = points; + // Model + line._model = { + // Appearance + // The default behavior of lines is to break at null values, according + // to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158 + // This option gives lines the ability to span gaps + spanGaps: dataset.spanGaps ? dataset.spanGaps : options.spanGaps, + tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, lineElementOptions.tension), + backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor), + borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth), + borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor), + borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle), + borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash), + borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset), + borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle), + fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill), + steppedLine: custom.steppedLine ? custom.steppedLine : helpers.valueOrDefault(dataset.steppedLine, lineElementOptions.stepped), + cubicInterpolationMode: custom.cubicInterpolationMode ? custom.cubicInterpolationMode : helpers.valueOrDefault(dataset.cubicInterpolationMode, lineElementOptions.cubicInterpolationMode), + }; - // Utility - line._scale = scale; - line._datasetIndex = me.index; - // Data - line._children = points; - // Model - line._model = { - // Appearance - // The default behavior of lines is to break at null values, according - // to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158 - // This option gives lines the ability to span gaps - spanGaps: dataset.spanGaps ? dataset.spanGaps : options.spanGaps, - tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, lineElementOptions.tension), - backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor), - borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth), - borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor), - borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle), - borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash), - borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset), - borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle), - fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill), - steppedLine: custom.steppedLine ? custom.steppedLine : helpers.valueOrDefault(dataset.steppedLine, lineElementOptions.stepped), - cubicInterpolationMode: custom.cubicInterpolationMode ? custom.cubicInterpolationMode : helpers.valueOrDefault(dataset.cubicInterpolationMode, lineElementOptions.cubicInterpolationMode), - }; - - line.pivot(); - } + line.pivot(); + } - // Update Points - for (i = 0, ilen = points.length; i < ilen; ++i) { - me.updateElement(points[i], i, reset); - } + // Update Points + for (i = 0, ilen = points.length; i < ilen; ++i) { + me.updateElement(points[i], i, reset); + } - if (showLine && line._model.tension !== 0) { - me.updateBezierControlPoints(); - } + if (showLine && line._model.tension !== 0) { + me.updateBezierControlPoints(); + } - // Now pivot the point for animation - for (i = 0, ilen = points.length; i < ilen; ++i) { - points[i].pivot(); - } - }, - - getPointBackgroundColor: function(point, index) { - var backgroundColor = this.chart.options.elements.point.backgroundColor; - var dataset = this.getDataset(); - var custom = point.custom || {}; - - if (custom.backgroundColor) { - backgroundColor = custom.backgroundColor; - } else if (dataset.pointBackgroundColor) { - backgroundColor = helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, backgroundColor); - } else if (dataset.backgroundColor) { - backgroundColor = dataset.backgroundColor; - } + // Now pivot the point for animation + for (i = 0, ilen = points.length; i < ilen; ++i) { + points[i].pivot(); + } + }, - return backgroundColor; - }, + getPointBackgroundColor: function(point, index) { + var backgroundColor = this.chart.options.elements.point.backgroundColor; + var dataset = this.getDataset(); + var custom = point.custom || {}; - getPointBorderColor: function(point, index) { - var borderColor = this.chart.options.elements.point.borderColor; - var dataset = this.getDataset(); - var custom = point.custom || {}; + if (custom.backgroundColor) { + backgroundColor = custom.backgroundColor; + } else if (dataset.pointBackgroundColor) { + backgroundColor = helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, backgroundColor); + } else if (dataset.backgroundColor) { + backgroundColor = dataset.backgroundColor; + } - if (custom.borderColor) { - borderColor = custom.borderColor; - } else if (dataset.pointBorderColor) { - borderColor = helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, borderColor); - } else if (dataset.borderColor) { - borderColor = dataset.borderColor; - } + return backgroundColor; + }, - return borderColor; - }, + getPointBorderColor: function(point, index) { + var borderColor = this.chart.options.elements.point.borderColor; + var dataset = this.getDataset(); + var custom = point.custom || {}; - getPointBorderWidth: function(point, index) { - var borderWidth = this.chart.options.elements.point.borderWidth; - var dataset = this.getDataset(); - var custom = point.custom || {}; + if (custom.borderColor) { + borderColor = custom.borderColor; + } else if (dataset.pointBorderColor) { + borderColor = helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, borderColor); + } else if (dataset.borderColor) { + borderColor = dataset.borderColor; + } - if (!isNaN(custom.borderWidth)) { - borderWidth = custom.borderWidth; - } else if (!isNaN(dataset.pointBorderWidth) || helpers.isArray(dataset.pointBorderWidth)) { - borderWidth = helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, borderWidth); - } else if (!isNaN(dataset.borderWidth)) { - borderWidth = dataset.borderWidth; - } + return borderColor; + }, - return borderWidth; - }, + getPointBorderWidth: function(point, index) { + var borderWidth = this.chart.options.elements.point.borderWidth; + var dataset = this.getDataset(); + var custom = point.custom || {}; - getPointRotation: function(point, index) { - var pointRotation = this.chart.options.elements.point.rotation; - var dataset = this.getDataset(); - var custom = point.custom || {}; + if (!isNaN(custom.borderWidth)) { + borderWidth = custom.borderWidth; + } else if (!isNaN(dataset.pointBorderWidth) || helpers.isArray(dataset.pointBorderWidth)) { + borderWidth = helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, borderWidth); + } else if (!isNaN(dataset.borderWidth)) { + borderWidth = dataset.borderWidth; + } - if (!isNaN(custom.rotation)) { - pointRotation = custom.rotation; - } else if (!isNaN(dataset.pointRotation) || helpers.isArray(dataset.pointRotation)) { - pointRotation = helpers.valueAtIndexOrDefault(dataset.pointRotation, index, pointRotation); - } - return pointRotation; - }, - - updateElement: function(point, index, reset) { - var me = this; - var meta = me.getMeta(); - var custom = point.custom || {}; - var dataset = me.getDataset(); - var datasetIndex = me.index; - var value = dataset.data[index]; - var yScale = me.getScaleForId(meta.yAxisID); - var xScale = me.getScaleForId(meta.xAxisID); - var pointOptions = me.chart.options.elements.point; - var x, y; + return borderWidth; + }, - // Compatibility: If the properties are defined with only the old name, use those values - if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) { - dataset.pointRadius = dataset.radius; - } - if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) { - dataset.pointHitRadius = dataset.hitRadius; - } + getPointRotation: function(point, index) { + var pointRotation = this.chart.options.elements.point.rotation; + var dataset = this.getDataset(); + var custom = point.custom || {}; + + if (!isNaN(custom.rotation)) { + pointRotation = custom.rotation; + } else if (!isNaN(dataset.pointRotation) || helpers.isArray(dataset.pointRotation)) { + pointRotation = helpers.valueAtIndexOrDefault(dataset.pointRotation, index, pointRotation); + } + return pointRotation; + }, - x = xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex); - y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex); + updateElement: function(point, index, reset) { + var me = this; + var meta = me.getMeta(); + var custom = point.custom || {}; + var dataset = me.getDataset(); + var datasetIndex = me.index; + var value = dataset.data[index]; + var yScale = me.getScaleForId(meta.yAxisID); + var xScale = me.getScaleForId(meta.xAxisID); + var pointOptions = me.chart.options.elements.point; + var x, y; + + // Compatibility: If the properties are defined with only the old name, use those values + if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) { + dataset.pointRadius = dataset.radius; + } + if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) { + dataset.pointHitRadius = dataset.hitRadius; + } + + x = xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex); + y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex); + + // Utility + point._xScale = xScale; + point._yScale = yScale; + point._datasetIndex = datasetIndex; + point._index = index; + + // Desired view properties + point._model = { + x: x, + y: y, + skip: custom.skip || isNaN(x) || isNaN(y), + // Appearance + radius: custom.radius || helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointOptions.radius), + pointStyle: custom.pointStyle || helpers.valueAtIndexOrDefault(dataset.pointStyle, index, pointOptions.pointStyle), + rotation: me.getPointRotation(point, index), + backgroundColor: me.getPointBackgroundColor(point, index), + borderColor: me.getPointBorderColor(point, index), + borderWidth: me.getPointBorderWidth(point, index), + tension: meta.dataset._model ? meta.dataset._model.tension : 0, + steppedLine: meta.dataset._model ? meta.dataset._model.steppedLine : false, + // Tooltip + hitRadius: custom.hitRadius || helpers.valueAtIndexOrDefault(dataset.pointHitRadius, index, pointOptions.hitRadius) + }; + }, - // Utility - point._xScale = xScale; - point._yScale = yScale; - point._datasetIndex = datasetIndex; - point._index = index; - - // Desired view properties - point._model = { - x: x, - y: y, - skip: custom.skip || isNaN(x) || isNaN(y), - // Appearance - radius: custom.radius || helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointOptions.radius), - pointStyle: custom.pointStyle || helpers.valueAtIndexOrDefault(dataset.pointStyle, index, pointOptions.pointStyle), - rotation: me.getPointRotation(point, index), - backgroundColor: me.getPointBackgroundColor(point, index), - borderColor: me.getPointBorderColor(point, index), - borderWidth: me.getPointBorderWidth(point, index), - tension: meta.dataset._model ? meta.dataset._model.tension : 0, - steppedLine: meta.dataset._model ? meta.dataset._model.steppedLine : false, - // Tooltip - hitRadius: custom.hitRadius || helpers.valueAtIndexOrDefault(dataset.pointHitRadius, index, pointOptions.hitRadius) - }; - }, - - calculatePointY: function(value, index, datasetIndex) { - var me = this; - var chart = me.chart; - var meta = me.getMeta(); - var yScale = me.getScaleForId(meta.yAxisID); - var sumPos = 0; - var sumNeg = 0; - var i, ds, dsMeta; - - if (yScale.options.stacked) { - for (i = 0; i < datasetIndex; i++) { - ds = chart.data.datasets[i]; - dsMeta = chart.getDatasetMeta(i); - if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i)) { - var stackedRightValue = Number(yScale.getRightValue(ds.data[index])); - if (stackedRightValue < 0) { - sumNeg += stackedRightValue || 0; - } else { - sumPos += stackedRightValue || 0; - } + calculatePointY: function(value, index, datasetIndex) { + var me = this; + var chart = me.chart; + var meta = me.getMeta(); + var yScale = me.getScaleForId(meta.yAxisID); + var sumPos = 0; + var sumNeg = 0; + var i, ds, dsMeta; + + if (yScale.options.stacked) { + for (i = 0; i < datasetIndex; i++) { + ds = chart.data.datasets[i]; + dsMeta = chart.getDatasetMeta(i); + if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i)) { + var stackedRightValue = Number(yScale.getRightValue(ds.data[index])); + if (stackedRightValue < 0) { + sumNeg += stackedRightValue || 0; + } else { + sumPos += stackedRightValue || 0; } } - - var rightValue = Number(yScale.getRightValue(value)); - if (rightValue < 0) { - return yScale.getPixelForValue(sumNeg + rightValue); - } - return yScale.getPixelForValue(sumPos + rightValue); } - return yScale.getPixelForValue(value); - }, - - updateBezierControlPoints: function() { - var me = this; - var meta = me.getMeta(); - var area = me.chart.chartArea; - var points = (meta.data || []); - var i, ilen, point, model, controlPoints; - - // Only consider points that are drawn in case the spanGaps option is used - if (meta.dataset._model.spanGaps) { - points = points.filter(function(pt) { - return !pt._model.skip; - }); + var rightValue = Number(yScale.getRightValue(value)); + if (rightValue < 0) { + return yScale.getPixelForValue(sumNeg + rightValue); } + return yScale.getPixelForValue(sumPos + rightValue); + } - function capControlPoint(pt, min, max) { - return Math.max(Math.min(pt, max), min); - } + return yScale.getPixelForValue(value); + }, - if (meta.dataset._model.cubicInterpolationMode === 'monotone') { - helpers.splineCurveMonotone(points); - } else { - for (i = 0, ilen = points.length; i < ilen; ++i) { - point = points[i]; - model = point._model; - controlPoints = helpers.splineCurve( - helpers.previousItem(points, i)._model, - model, - helpers.nextItem(points, i)._model, - meta.dataset._model.tension - ); - model.controlPointPreviousX = controlPoints.previous.x; - model.controlPointPreviousY = controlPoints.previous.y; - model.controlPointNextX = controlPoints.next.x; - model.controlPointNextY = controlPoints.next.y; - } + updateBezierControlPoints: function() { + var me = this; + var meta = me.getMeta(); + var area = me.chart.chartArea; + var points = (meta.data || []); + var i, ilen, point, model, controlPoints; + + // Only consider points that are drawn in case the spanGaps option is used + if (meta.dataset._model.spanGaps) { + points = points.filter(function(pt) { + return !pt._model.skip; + }); + } + + function capControlPoint(pt, min, max) { + return Math.max(Math.min(pt, max), min); + } + + if (meta.dataset._model.cubicInterpolationMode === 'monotone') { + helpers.splineCurveMonotone(points); + } else { + for (i = 0, ilen = points.length; i < ilen; ++i) { + point = points[i]; + model = point._model; + controlPoints = helpers.splineCurve( + helpers.previousItem(points, i)._model, + model, + helpers.nextItem(points, i)._model, + meta.dataset._model.tension + ); + model.controlPointPreviousX = controlPoints.previous.x; + model.controlPointPreviousY = controlPoints.previous.y; + model.controlPointNextX = controlPoints.next.x; + model.controlPointNextY = controlPoints.next.y; } + } - if (me.chart.options.elements.line.capBezierPoints) { - for (i = 0, ilen = points.length; i < ilen; ++i) { - model = points[i]._model; - model.controlPointPreviousX = capControlPoint(model.controlPointPreviousX, area.left, area.right); - model.controlPointPreviousY = capControlPoint(model.controlPointPreviousY, area.top, area.bottom); - model.controlPointNextX = capControlPoint(model.controlPointNextX, area.left, area.right); - model.controlPointNextY = capControlPoint(model.controlPointNextY, area.top, area.bottom); - } - } - }, - - draw: function() { - var me = this; - var chart = me.chart; - var meta = me.getMeta(); - var points = meta.data || []; - var area = chart.chartArea; - var ilen = points.length; - var halfBorderWidth; - var i = 0; - - if (lineEnabled(me.getDataset(), chart.options)) { - halfBorderWidth = (meta.dataset._model.borderWidth || 0) / 2; - - helpers.canvas.clipArea(chart.ctx, { - left: area.left, - right: area.right, - top: area.top - halfBorderWidth, - bottom: area.bottom + halfBorderWidth - }); - - meta.dataset.draw(); - - helpers.canvas.unclipArea(chart.ctx); + if (me.chart.options.elements.line.capBezierPoints) { + for (i = 0, ilen = points.length; i < ilen; ++i) { + model = points[i]._model; + model.controlPointPreviousX = capControlPoint(model.controlPointPreviousX, area.left, area.right); + model.controlPointPreviousY = capControlPoint(model.controlPointPreviousY, area.top, area.bottom); + model.controlPointNextX = capControlPoint(model.controlPointNextX, area.left, area.right); + model.controlPointNextY = capControlPoint(model.controlPointNextY, area.top, area.bottom); } + } + }, - // Draw the points - for (; i < ilen; ++i) { - points[i].draw(area); - } - }, - - setHoverStyle: function(element) { - // Point - var dataset = this.chart.data.datasets[element._datasetIndex]; - var index = element._index; - var custom = element.custom || {}; - var model = element._model; - - element.$previousStyle = { - backgroundColor: model.backgroundColor, - borderColor: model.borderColor, - borderWidth: model.borderWidth, - radius: model.radius - }; + draw: function() { + var me = this; + var chart = me.chart; + var meta = me.getMeta(); + var points = meta.data || []; + var area = chart.chartArea; + var ilen = points.length; + var halfBorderWidth; + var i = 0; + + if (lineEnabled(me.getDataset(), chart.options)) { + halfBorderWidth = (meta.dataset._model.borderWidth || 0) / 2; + + helpers.canvas.clipArea(chart.ctx, { + left: area.left, + right: area.right, + top: area.top - halfBorderWidth, + bottom: area.bottom + halfBorderWidth + }); + + meta.dataset.draw(); + + helpers.canvas.unclipArea(chart.ctx); + } + + // Draw the points + for (; i < ilen; ++i) { + points[i].draw(area); + } + }, - model.backgroundColor = custom.hoverBackgroundColor || helpers.valueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor)); - model.borderColor = custom.hoverBorderColor || helpers.valueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor)); - model.borderWidth = custom.hoverBorderWidth || helpers.valueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth); - model.radius = custom.hoverRadius || helpers.valueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius); - }, - }); -}; + setHoverStyle: function(element) { + // Point + var dataset = this.chart.data.datasets[element._datasetIndex]; + var index = element._index; + var custom = element.custom || {}; + var model = element._model; + + element.$previousStyle = { + backgroundColor: model.backgroundColor, + borderColor: model.borderColor, + borderWidth: model.borderWidth, + radius: model.radius + }; + + model.backgroundColor = custom.hoverBackgroundColor || helpers.valueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor)); + model.borderColor = custom.hoverBorderColor || helpers.valueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor)); + model.borderWidth = custom.hoverBorderWidth || helpers.valueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth); + model.radius = custom.hoverRadius || helpers.valueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius); + } +}); diff --git a/src/controllers/controller.pie.js b/src/controllers/controller.pie.js new file mode 100644 index 00000000000..2f50c9d4d88 --- /dev/null +++ b/src/controllers/controller.pie.js @@ -0,0 +1,13 @@ +'use strict'; + +var DoughnutController = require('./controller.doughnut'); +var defaults = require('../core/core.defaults'); +var helpers = require('../helpers/index'); + +defaults._set('pie', helpers.clone(defaults.doughnut)); +defaults._set('pie', { + cutoutPercentage: 0 +}); + +// Pie charts are Doughnut chart with different defaults +module.exports = DoughnutController; diff --git a/src/controllers/controller.polarArea.js b/src/controllers/controller.polarArea.js index 663a9534d55..fb045e30271 100644 --- a/src/controllers/controller.polarArea.js +++ b/src/controllers/controller.polarArea.js @@ -1,5 +1,6 @@ 'use strict'; +var DatasetController = require('../core/core.datasetController'); var defaults = require('../core/core.defaults'); var elements = require('../elements/index'); var helpers = require('../helpers/index'); @@ -108,148 +109,145 @@ defaults._set('polarArea', { } }); -module.exports = function(Chart) { +module.exports = DatasetController.extend({ - Chart.controllers.polarArea = Chart.DatasetController.extend({ + dataElementType: elements.Arc, - dataElementType: elements.Arc, + linkScales: helpers.noop, - linkScales: helpers.noop, + update: function(reset) { + var me = this; + var dataset = me.getDataset(); + var meta = me.getMeta(); + var start = me.chart.options.startAngle || 0; + var starts = me._starts = []; + var angles = me._angles = []; + var i, ilen, angle; - update: function(reset) { - var me = this; - var dataset = me.getDataset(); - var meta = me.getMeta(); - var start = me.chart.options.startAngle || 0; - var starts = me._starts = []; - var angles = me._angles = []; - var i, ilen, angle; + me._updateRadius(); - me._updateRadius(); + meta.count = me.countVisibleElements(); - meta.count = me.countVisibleElements(); - - for (i = 0, ilen = dataset.data.length; i < ilen; i++) { - starts[i] = start; - angle = me._computeAngle(i); - angles[i] = angle; - start += angle; - } - - helpers.each(meta.data, function(arc, index) { - me.updateElement(arc, index, reset); - }); - }, - - /** - * @private - */ - _updateRadius: function() { - var me = this; - var chart = me.chart; - var chartArea = chart.chartArea; - var opts = chart.options; - var arcOpts = opts.elements.arc; - var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top); - - chart.outerRadius = Math.max((minSize - arcOpts.borderWidth / 2) / 2, 0); - chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0); - chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount(); - - me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index); - me.innerRadius = me.outerRadius - chart.radiusLength; - }, + for (i = 0, ilen = dataset.data.length; i < ilen; i++) { + starts[i] = start; + angle = me._computeAngle(i); + angles[i] = angle; + start += angle; + } - updateElement: function(arc, index, reset) { - var me = this; - var chart = me.chart; - var dataset = me.getDataset(); - var opts = chart.options; - var animationOpts = opts.animation; - var scale = chart.scale; - var labels = chart.data.labels; - - var centerX = scale.xCenter; - var centerY = scale.yCenter; - - // var negHalfPI = -0.5 * Math.PI; - var datasetStartAngle = opts.startAngle; - var distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); - var startAngle = me._starts[index]; - var endAngle = startAngle + (arc.hidden ? 0 : me._angles[index]); - - var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); - - helpers.extend(arc, { - // Utility - _datasetIndex: me.index, - _index: index, - _scale: scale, - - // Desired view properties - _model: { - x: centerX, - y: centerY, - innerRadius: 0, - outerRadius: reset ? resetRadius : distance, - startAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle, - endAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle, - label: helpers.valueAtIndexOrDefault(labels, index, labels[index]) - } - }); + helpers.each(meta.data, function(arc, index) { + me.updateElement(arc, index, reset); + }); + }, - // Apply border and fill style - var elementOpts = this.chart.options.elements.arc; - var custom = arc.custom || {}; - var valueOrDefault = helpers.valueAtIndexOrDefault; - var model = arc._model; + /** + * @private + */ + _updateRadius: function() { + var me = this; + var chart = me.chart; + var chartArea = chart.chartArea; + var opts = chart.options; + var arcOpts = opts.elements.arc; + var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top); + + chart.outerRadius = Math.max((minSize - arcOpts.borderWidth / 2) / 2, 0); + chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0); + chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount(); + + me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index); + me.innerRadius = me.outerRadius - chart.radiusLength; + }, - model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : valueOrDefault(dataset.backgroundColor, index, elementOpts.backgroundColor); - model.borderColor = custom.borderColor ? custom.borderColor : valueOrDefault(dataset.borderColor, index, elementOpts.borderColor); - model.borderWidth = custom.borderWidth ? custom.borderWidth : valueOrDefault(dataset.borderWidth, index, elementOpts.borderWidth); + updateElement: function(arc, index, reset) { + var me = this; + var chart = me.chart; + var dataset = me.getDataset(); + var opts = chart.options; + var animationOpts = opts.animation; + var scale = chart.scale; + var labels = chart.data.labels; + + var centerX = scale.xCenter; + var centerY = scale.yCenter; + + // var negHalfPI = -0.5 * Math.PI; + var datasetStartAngle = opts.startAngle; + var distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); + var startAngle = me._starts[index]; + var endAngle = startAngle + (arc.hidden ? 0 : me._angles[index]); + + var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); + + helpers.extend(arc, { + // Utility + _datasetIndex: me.index, + _index: index, + _scale: scale, + + // Desired view properties + _model: { + x: centerX, + y: centerY, + innerRadius: 0, + outerRadius: reset ? resetRadius : distance, + startAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle, + endAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle, + label: helpers.valueAtIndexOrDefault(labels, index, labels[index]) + } + }); - arc.pivot(); - }, + // Apply border and fill style + var elementOpts = this.chart.options.elements.arc; + var custom = arc.custom || {}; + var valueOrDefault = helpers.valueAtIndexOrDefault; + var model = arc._model; - countVisibleElements: function() { - var dataset = this.getDataset(); - var meta = this.getMeta(); - var count = 0; + model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : valueOrDefault(dataset.backgroundColor, index, elementOpts.backgroundColor); + model.borderColor = custom.borderColor ? custom.borderColor : valueOrDefault(dataset.borderColor, index, elementOpts.borderColor); + model.borderWidth = custom.borderWidth ? custom.borderWidth : valueOrDefault(dataset.borderWidth, index, elementOpts.borderWidth); - helpers.each(meta.data, function(element, index) { - if (!isNaN(dataset.data[index]) && !element.hidden) { - count++; - } - }); + arc.pivot(); + }, - return count; - }, + countVisibleElements: function() { + var dataset = this.getDataset(); + var meta = this.getMeta(); + var count = 0; - /** - * @private - */ - _computeAngle: function(index) { - var me = this; - var count = this.getMeta().count; - var dataset = me.getDataset(); - var meta = me.getMeta(); - - if (isNaN(dataset.data[index]) || meta.data[index].hidden) { - return 0; + helpers.each(meta.data, function(element, index) { + if (!isNaN(dataset.data[index]) && !element.hidden) { + count++; } + }); - // Scriptable options - var context = { - chart: me.chart, - dataIndex: index, - dataset: dataset, - datasetIndex: me.index - }; - - return helpers.options.resolve([ - me.chart.options.elements.arc.angle, - (2 * Math.PI) / count - ], context, index); + return count; + }, + + /** + * @private + */ + _computeAngle: function(index) { + var me = this; + var count = this.getMeta().count; + var dataset = me.getDataset(); + var meta = me.getMeta(); + + if (isNaN(dataset.data[index]) || meta.data[index].hidden) { + return 0; } - }); -}; + + // Scriptable options + var context = { + chart: me.chart, + dataIndex: index, + dataset: dataset, + datasetIndex: me.index + }; + + return helpers.options.resolve([ + me.chart.options.elements.arc.angle, + (2 * Math.PI) / count + ], context, index); + } +}); diff --git a/src/controllers/controller.radar.js b/src/controllers/controller.radar.js index dc2b86c617b..4d69928d87b 100644 --- a/src/controllers/controller.radar.js +++ b/src/controllers/controller.radar.js @@ -1,5 +1,6 @@ 'use strict'; +var DatasetController = require('../core/core.datasetController'); var defaults = require('../core/core.defaults'); var elements = require('../elements/index'); var helpers = require('../helpers/index'); @@ -15,158 +16,157 @@ defaults._set('radar', { } }); -module.exports = function(Chart) { +module.exports = DatasetController.extend({ - Chart.controllers.radar = Chart.DatasetController.extend({ + datasetElementType: elements.Line, - datasetElementType: elements.Line, + dataElementType: elements.Point, - dataElementType: elements.Point, + linkScales: helpers.noop, - linkScales: helpers.noop, + update: function(reset) { + var me = this; + var meta = me.getMeta(); + var line = meta.dataset; + var points = meta.data || []; + var custom = line.custom || {}; + var dataset = me.getDataset(); + var lineElementOptions = me.chart.options.elements.line; + var scale = me.chart.scale; + var i, ilen; - update: function(reset) { - var me = this; - var meta = me.getMeta(); - var line = meta.dataset; - var points = meta.data || []; - var custom = line.custom || {}; - var dataset = me.getDataset(); - var lineElementOptions = me.chart.options.elements.line; - var scale = me.chart.scale; - var i, ilen; + // Compatibility: If the properties are defined with only the old name, use those values + if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) { + dataset.lineTension = dataset.tension; + } - // Compatibility: If the properties are defined with only the old name, use those values - if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) { - dataset.lineTension = dataset.tension; + helpers.extend(meta.dataset, { + // Utility + _datasetIndex: me.index, + _scale: scale, + // Data + _children: points, + _loop: true, + // Model + _model: { + // Appearance + tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, lineElementOptions.tension), + backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor), + borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth), + borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor), + fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill), + borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle), + borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash), + borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset), + borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle), } + }); - helpers.extend(meta.dataset, { - // Utility - _datasetIndex: me.index, - _scale: scale, - // Data - _children: points, - _loop: true, - // Model - _model: { - // Appearance - tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, lineElementOptions.tension), - backgroundColor: custom.backgroundColor ? custom.backgroundColor : (dataset.backgroundColor || lineElementOptions.backgroundColor), - borderWidth: custom.borderWidth ? custom.borderWidth : (dataset.borderWidth || lineElementOptions.borderWidth), - borderColor: custom.borderColor ? custom.borderColor : (dataset.borderColor || lineElementOptions.borderColor), - fill: custom.fill ? custom.fill : (dataset.fill !== undefined ? dataset.fill : lineElementOptions.fill), - borderCapStyle: custom.borderCapStyle ? custom.borderCapStyle : (dataset.borderCapStyle || lineElementOptions.borderCapStyle), - borderDash: custom.borderDash ? custom.borderDash : (dataset.borderDash || lineElementOptions.borderDash), - borderDashOffset: custom.borderDashOffset ? custom.borderDashOffset : (dataset.borderDashOffset || lineElementOptions.borderDashOffset), - borderJoinStyle: custom.borderJoinStyle ? custom.borderJoinStyle : (dataset.borderJoinStyle || lineElementOptions.borderJoinStyle), - } - }); - - meta.dataset.pivot(); - - // Update Points - for (i = 0, ilen = points.length; i < ilen; i++) { - me.updateElement(points[i], i, reset); - } + meta.dataset.pivot(); - // Update bezier control points - me.updateBezierControlPoints(); + // Update Points + for (i = 0, ilen = points.length; i < ilen; i++) { + me.updateElement(points[i], i, reset); + } - // Now pivot the point for animation - for (i = 0, ilen = points.length; i < ilen; i++) { - points[i].pivot(); - } - }, - updateElement: function(point, index, reset) { - var me = this; - var custom = point.custom || {}; - var dataset = me.getDataset(); - var scale = me.chart.scale; - var pointElementOptions = me.chart.options.elements.point; - var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]); - - // Compatibility: If the properties are defined with only the old name, use those values - if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) { - dataset.pointRadius = dataset.radius; - } - if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) { - dataset.pointHitRadius = dataset.hitRadius; - } + // Update bezier control points + me.updateBezierControlPoints(); - helpers.extend(point, { - // Utility - _datasetIndex: me.index, - _index: index, - _scale: scale, - - // Desired view properties - _model: { - x: reset ? scale.xCenter : pointPosition.x, // value not used in dataset scale, but we want a consistent API between scales - y: reset ? scale.yCenter : pointPosition.y, - - // Appearance - tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, me.chart.options.elements.line.tension), - radius: custom.radius ? custom.radius : helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius), - backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor), - borderColor: custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor), - borderWidth: custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth), - pointStyle: custom.pointStyle ? custom.pointStyle : helpers.valueAtIndexOrDefault(dataset.pointStyle, index, pointElementOptions.pointStyle), - rotation: custom.rotation ? custom.rotation : helpers.valueAtIndexOrDefault(dataset.pointRotation, index, pointElementOptions.rotation), - - // Tooltip - hitRadius: custom.hitRadius ? custom.hitRadius : helpers.valueAtIndexOrDefault(dataset.pointHitRadius, index, pointElementOptions.hitRadius) - } - }); - - point._model.skip = custom.skip ? custom.skip : (isNaN(point._model.x) || isNaN(point._model.y)); - }, - updateBezierControlPoints: function() { - var me = this; - var meta = me.getMeta(); - var area = me.chart.chartArea; - var points = meta.data || []; - var i, ilen, model, controlPoints; - - function capControlPoint(pt, min, max) { - return Math.max(Math.min(pt, max), min); - } + // Now pivot the point for animation + for (i = 0, ilen = points.length; i < ilen; i++) { + points[i].pivot(); + } + }, + + updateElement: function(point, index, reset) { + var me = this; + var custom = point.custom || {}; + var dataset = me.getDataset(); + var scale = me.chart.scale; + var pointElementOptions = me.chart.options.elements.point; + var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]); + + // Compatibility: If the properties are defined with only the old name, use those values + if ((dataset.radius !== undefined) && (dataset.pointRadius === undefined)) { + dataset.pointRadius = dataset.radius; + } + if ((dataset.hitRadius !== undefined) && (dataset.pointHitRadius === undefined)) { + dataset.pointHitRadius = dataset.hitRadius; + } - for (i = 0, ilen = points.length; i < ilen; i++) { - model = points[i]._model; - controlPoints = helpers.splineCurve( - helpers.previousItem(points, i, true)._model, - model, - helpers.nextItem(points, i, true)._model, - model.tension - ); - - // Prevent the bezier going outside of the bounds of the graph - model.controlPointPreviousX = capControlPoint(controlPoints.previous.x, area.left, area.right); - model.controlPointPreviousY = capControlPoint(controlPoints.previous.y, area.top, area.bottom); - model.controlPointNextX = capControlPoint(controlPoints.next.x, area.left, area.right); - model.controlPointNextY = capControlPoint(controlPoints.next.y, area.top, area.bottom); + helpers.extend(point, { + // Utility + _datasetIndex: me.index, + _index: index, + _scale: scale, + + // Desired view properties + _model: { + x: reset ? scale.xCenter : pointPosition.x, // value not used in dataset scale, but we want a consistent API between scales + y: reset ? scale.yCenter : pointPosition.y, + + // Appearance + tension: custom.tension ? custom.tension : helpers.valueOrDefault(dataset.lineTension, me.chart.options.elements.line.tension), + radius: custom.radius ? custom.radius : helpers.valueAtIndexOrDefault(dataset.pointRadius, index, pointElementOptions.radius), + backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.valueAtIndexOrDefault(dataset.pointBackgroundColor, index, pointElementOptions.backgroundColor), + borderColor: custom.borderColor ? custom.borderColor : helpers.valueAtIndexOrDefault(dataset.pointBorderColor, index, pointElementOptions.borderColor), + borderWidth: custom.borderWidth ? custom.borderWidth : helpers.valueAtIndexOrDefault(dataset.pointBorderWidth, index, pointElementOptions.borderWidth), + pointStyle: custom.pointStyle ? custom.pointStyle : helpers.valueAtIndexOrDefault(dataset.pointStyle, index, pointElementOptions.pointStyle), + rotation: custom.rotation ? custom.rotation : helpers.valueAtIndexOrDefault(dataset.pointRotation, index, pointElementOptions.rotation), + + // Tooltip + hitRadius: custom.hitRadius ? custom.hitRadius : helpers.valueAtIndexOrDefault(dataset.pointHitRadius, index, pointElementOptions.hitRadius) } - }, - - setHoverStyle: function(point) { - // Point - var dataset = this.chart.data.datasets[point._datasetIndex]; - var custom = point.custom || {}; - var index = point._index; - var model = point._model; - - point.$previousStyle = { - backgroundColor: model.backgroundColor, - borderColor: model.borderColor, - borderWidth: model.borderWidth, - radius: model.radius - }; - - model.radius = custom.hoverRadius ? custom.hoverRadius : helpers.valueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius); - model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.valueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor)); - model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.valueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor)); - model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.valueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth); - }, - }); -}; + }); + + point._model.skip = custom.skip ? custom.skip : (isNaN(point._model.x) || isNaN(point._model.y)); + }, + + updateBezierControlPoints: function() { + var me = this; + var meta = me.getMeta(); + var area = me.chart.chartArea; + var points = meta.data || []; + var i, ilen, model, controlPoints; + + function capControlPoint(pt, min, max) { + return Math.max(Math.min(pt, max), min); + } + + for (i = 0, ilen = points.length; i < ilen; i++) { + model = points[i]._model; + controlPoints = helpers.splineCurve( + helpers.previousItem(points, i, true)._model, + model, + helpers.nextItem(points, i, true)._model, + model.tension + ); + + // Prevent the bezier going outside of the bounds of the graph + model.controlPointPreviousX = capControlPoint(controlPoints.previous.x, area.left, area.right); + model.controlPointPreviousY = capControlPoint(controlPoints.previous.y, area.top, area.bottom); + model.controlPointNextX = capControlPoint(controlPoints.next.x, area.left, area.right); + model.controlPointNextY = capControlPoint(controlPoints.next.y, area.top, area.bottom); + } + }, + + setHoverStyle: function(point) { + // Point + var dataset = this.chart.data.datasets[point._datasetIndex]; + var custom = point.custom || {}; + var index = point._index; + var model = point._model; + + point.$previousStyle = { + backgroundColor: model.backgroundColor, + borderColor: model.borderColor, + borderWidth: model.borderWidth, + radius: model.radius + }; + + model.radius = custom.hoverRadius ? custom.hoverRadius : helpers.valueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius); + model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.valueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor)); + model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.valueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.getHoverColor(model.borderColor)); + model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.valueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, model.borderWidth); + } +}); diff --git a/src/controllers/controller.scatter.js b/src/controllers/controller.scatter.js index b2e2cf1f7e1..319296d33ba 100644 --- a/src/controllers/controller.scatter.js +++ b/src/controllers/controller.scatter.js @@ -1,5 +1,6 @@ 'use strict'; +var LineController = require('./controller.line'); var defaults = require('../core/core.defaults'); defaults._set('scatter', { @@ -34,9 +35,5 @@ defaults._set('scatter', { } }); -module.exports = function(Chart) { - - // Scatter charts use line controllers - Chart.controllers.scatter = Chart.controllers.line; - -}; +// Scatter charts use line controllers +module.exports = LineController; diff --git a/src/controllers/index.js b/src/controllers/index.js new file mode 100644 index 00000000000..d2fa6c96e3b --- /dev/null +++ b/src/controllers/index.js @@ -0,0 +1,18 @@ +'use strict'; + +// NOTE export a map in which the key represents the controller type, not +// the class, and so must be CamelCase in order to be correctly retrieved +// by the controller in core.controller.js (`controllers[meta.type]`). + +/* eslint-disable global-require */ +module.exports = { + bar: require('./controller.bar'), + bubble: require('./controller.bubble'), + doughnut: require('./controller.doughnut'), + horizontalBar: require('./controller.horizontalBar'), + line: require('./controller.line'), + polarArea: require('./controller.polarArea'), + pie: require('./controller.pie'), + radar: require('./controller.radar'), + scatter: require('./controller.scatter') +}; diff --git a/src/core/core.controller.js b/src/core/core.controller.js index 48365fb224d..117a6d576ea 100644 --- a/src/core/core.controller.js +++ b/src/core/core.controller.js @@ -2,6 +2,7 @@ var Animation = require('./core.animation'); var animations = require('./core.animations'); +var controllers = require('../controllers/index'); var defaults = require('./core.defaults'); var helpers = require('../helpers/index'); var Interaction = require('./core.interaction'); @@ -20,9 +21,6 @@ module.exports = function(Chart) { // Destroy method on the chart will remove the instance of the chart from this reference. Chart.instances = {}; - // Controllers available for dataset visualization eg. bar, line, slice, etc. - Chart.controllers = {}; - /** * Initializes the given config with global and chart default values. */ @@ -337,7 +335,7 @@ module.exports = function(Chart) { meta.controller.updateIndex(datasetIndex); meta.controller.linkScales(); } else { - var ControllerClass = Chart.controllers[meta.type]; + var ControllerClass = controllers[meta.type]; if (ControllerClass === undefined) { throw new Error('"' + meta.type + '" is not a chart type.'); } diff --git a/src/core/core.datasetController.js b/src/core/core.datasetController.js index 4928f98913e..8d2de1a1aaf 100644 --- a/src/core/core.datasetController.js +++ b/src/core/core.datasetController.js @@ -2,328 +2,325 @@ var helpers = require('../helpers/index'); -module.exports = function(Chart) { - - var arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift']; +var arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift']; + +/** + * Hooks the array methods that add or remove values ('push', pop', 'shift', 'splice', + * 'unshift') and notify the listener AFTER the array has been altered. Listeners are + * called on the 'onData*' callbacks (e.g. onDataPush, etc.) with same arguments. + */ +function listenArrayEvents(array, listener) { + if (array._chartjs) { + array._chartjs.listeners.push(listener); + return; + } - /** - * Hooks the array methods that add or remove values ('push', pop', 'shift', 'splice', - * 'unshift') and notify the listener AFTER the array has been altered. Listeners are - * called on the 'onData*' callbacks (e.g. onDataPush, etc.) with same arguments. - */ - function listenArrayEvents(array, listener) { - if (array._chartjs) { - array._chartjs.listeners.push(listener); - return; + Object.defineProperty(array, '_chartjs', { + configurable: true, + enumerable: false, + value: { + listeners: [listener] } + }); - Object.defineProperty(array, '_chartjs', { + arrayEvents.forEach(function(key) { + var method = 'onData' + key.charAt(0).toUpperCase() + key.slice(1); + var base = array[key]; + + Object.defineProperty(array, key, { configurable: true, enumerable: false, - value: { - listeners: [listener] + value: function() { + var args = Array.prototype.slice.call(arguments); + var res = base.apply(this, args); + + helpers.each(array._chartjs.listeners, function(object) { + if (typeof object[method] === 'function') { + object[method].apply(object, args); + } + }); + + return res; } }); + }); +} + +/** + * Removes the given array event listener and cleanup extra attached properties (such as + * the _chartjs stub and overridden methods) if array doesn't have any more listeners. + */ +function unlistenArrayEvents(array, listener) { + var stub = array._chartjs; + if (!stub) { + return; + } - arrayEvents.forEach(function(key) { - var method = 'onData' + key.charAt(0).toUpperCase() + key.slice(1); - var base = array[key]; - - Object.defineProperty(array, key, { - configurable: true, - enumerable: false, - value: function() { - var args = Array.prototype.slice.call(arguments); - var res = base.apply(this, args); - - helpers.each(array._chartjs.listeners, function(object) { - if (typeof object[method] === 'function') { - object[method].apply(object, args); - } - }); - - return res; - } - }); - }); + var listeners = stub.listeners; + var index = listeners.indexOf(listener); + if (index !== -1) { + listeners.splice(index, 1); + } + + if (listeners.length > 0) { + return; } + arrayEvents.forEach(function(key) { + delete array[key]; + }); + + delete array._chartjs; +} + +// Base class for all dataset controllers (line, bar, etc) +var DatasetController = module.exports = function(chart, datasetIndex) { + this.initialize(chart, datasetIndex); +}; + +helpers.extend(DatasetController.prototype, { + /** - * Removes the given array event listener and cleanup extra attached properties (such as - * the _chartjs stub and overridden methods) if array doesn't have any more listeners. + * Element type used to generate a meta dataset (e.g. Chart.element.Line). + * @type {Chart.core.element} */ - function unlistenArrayEvents(array, listener) { - var stub = array._chartjs; - if (!stub) { - return; - } + datasetElementType: null, - var listeners = stub.listeners; - var index = listeners.indexOf(listener); - if (index !== -1) { - listeners.splice(index, 1); + /** + * Element type used to generate a meta data (e.g. Chart.element.Point). + * @type {Chart.core.element} + */ + dataElementType: null, + + initialize: function(chart, datasetIndex) { + var me = this; + me.chart = chart; + me.index = datasetIndex; + me.linkScales(); + me.addElements(); + }, + + updateIndex: function(datasetIndex) { + this.index = datasetIndex; + }, + + linkScales: function() { + var me = this; + var meta = me.getMeta(); + var dataset = me.getDataset(); + + if (meta.xAxisID === null || !(meta.xAxisID in me.chart.scales)) { + meta.xAxisID = dataset.xAxisID || me.chart.options.scales.xAxes[0].id; } - - if (listeners.length > 0) { - return; + if (meta.yAxisID === null || !(meta.yAxisID in me.chart.scales)) { + meta.yAxisID = dataset.yAxisID || me.chart.options.scales.yAxes[0].id; } + }, + + getDataset: function() { + return this.chart.data.datasets[this.index]; + }, - arrayEvents.forEach(function(key) { - delete array[key]; + getMeta: function() { + return this.chart.getDatasetMeta(this.index); + }, + + getScaleForId: function(scaleID) { + return this.chart.scales[scaleID]; + }, + + reset: function() { + this.update(true); + }, + + /** + * @private + */ + destroy: function() { + if (this._data) { + unlistenArrayEvents(this._data, this); + } + }, + + createMetaDataset: function() { + var me = this; + var type = me.datasetElementType; + return type && new type({ + _chart: me.chart, + _datasetIndex: me.index + }); + }, + + createMetaData: function(index) { + var me = this; + var type = me.dataElementType; + return type && new type({ + _chart: me.chart, + _datasetIndex: me.index, + _index: index }); + }, - delete array._chartjs; - } + addElements: function() { + var me = this; + var meta = me.getMeta(); + var data = me.getDataset().data || []; + var metaData = meta.data; + var i, ilen; - // Base class for all dataset controllers (line, bar, etc) - Chart.DatasetController = function(chart, datasetIndex) { - this.initialize(chart, datasetIndex); - }; - - helpers.extend(Chart.DatasetController.prototype, { - - /** - * Element type used to generate a meta dataset (e.g. Chart.element.Line). - * @type {Chart.core.element} - */ - datasetElementType: null, - - /** - * Element type used to generate a meta data (e.g. Chart.element.Point). - * @type {Chart.core.element} - */ - dataElementType: null, - - initialize: function(chart, datasetIndex) { - var me = this; - me.chart = chart; - me.index = datasetIndex; - me.linkScales(); - me.addElements(); - }, - - updateIndex: function(datasetIndex) { - this.index = datasetIndex; - }, - - linkScales: function() { - var me = this; - var meta = me.getMeta(); - var dataset = me.getDataset(); - - if (meta.xAxisID === null || !(meta.xAxisID in me.chart.scales)) { - meta.xAxisID = dataset.xAxisID || me.chart.options.scales.xAxes[0].id; - } - if (meta.yAxisID === null || !(meta.yAxisID in me.chart.scales)) { - meta.yAxisID = dataset.yAxisID || me.chart.options.scales.yAxes[0].id; - } - }, - - getDataset: function() { - return this.chart.data.datasets[this.index]; - }, - - getMeta: function() { - return this.chart.getDatasetMeta(this.index); - }, - - getScaleForId: function(scaleID) { - return this.chart.scales[scaleID]; - }, - - reset: function() { - this.update(true); - }, - - /** - * @private - */ - destroy: function() { - if (this._data) { - unlistenArrayEvents(this._data, this); - } - }, - - createMetaDataset: function() { - var me = this; - var type = me.datasetElementType; - return type && new type({ - _chart: me.chart, - _datasetIndex: me.index - }); - }, - - createMetaData: function(index) { - var me = this; - var type = me.dataElementType; - return type && new type({ - _chart: me.chart, - _datasetIndex: me.index, - _index: index - }); - }, - - addElements: function() { - var me = this; - var meta = me.getMeta(); - var data = me.getDataset().data || []; - var metaData = meta.data; - var i, ilen; - - for (i = 0, ilen = data.length; i < ilen; ++i) { - metaData[i] = metaData[i] || me.createMetaData(i); - } + for (i = 0, ilen = data.length; i < ilen; ++i) { + metaData[i] = metaData[i] || me.createMetaData(i); + } - meta.dataset = meta.dataset || me.createMetaDataset(); - }, - - addElementAndReset: function(index) { - var element = this.createMetaData(index); - this.getMeta().data.splice(index, 0, element); - this.updateElement(element, index, true); - }, - - buildOrUpdateElements: function() { - var me = this; - var dataset = me.getDataset(); - var data = dataset.data || (dataset.data = []); - - // In order to correctly handle data addition/deletion animation (an thus simulate - // real-time charts), we need to monitor these data modifications and synchronize - // the internal meta data accordingly. - if (me._data !== data) { - if (me._data) { - // This case happens when the user replaced the data array instance. - unlistenArrayEvents(me._data, me); - } - - listenArrayEvents(data, me); - me._data = data; + meta.dataset = meta.dataset || me.createMetaDataset(); + }, + + addElementAndReset: function(index) { + var element = this.createMetaData(index); + this.getMeta().data.splice(index, 0, element); + this.updateElement(element, index, true); + }, + + buildOrUpdateElements: function() { + var me = this; + var dataset = me.getDataset(); + var data = dataset.data || (dataset.data = []); + + // In order to correctly handle data addition/deletion animation (an thus simulate + // real-time charts), we need to monitor these data modifications and synchronize + // the internal meta data accordingly. + if (me._data !== data) { + if (me._data) { + // This case happens when the user replaced the data array instance. + unlistenArrayEvents(me._data, me); } - // Re-sync meta data in case the user replaced the data array or if we missed - // any updates and so make sure that we handle number of datapoints changing. - me.resyncElements(); - }, + listenArrayEvents(data, me); + me._data = data; + } - update: helpers.noop, + // Re-sync meta data in case the user replaced the data array or if we missed + // any updates and so make sure that we handle number of datapoints changing. + me.resyncElements(); + }, - transition: function(easingValue) { - var meta = this.getMeta(); - var elements = meta.data || []; - var ilen = elements.length; - var i = 0; + update: helpers.noop, - for (; i < ilen; ++i) { - elements[i].transition(easingValue); - } + transition: function(easingValue) { + var meta = this.getMeta(); + var elements = meta.data || []; + var ilen = elements.length; + var i = 0; - if (meta.dataset) { - meta.dataset.transition(easingValue); - } - }, + for (; i < ilen; ++i) { + elements[i].transition(easingValue); + } - draw: function() { - var meta = this.getMeta(); - var elements = meta.data || []; - var ilen = elements.length; - var i = 0; + if (meta.dataset) { + meta.dataset.transition(easingValue); + } + }, - if (meta.dataset) { - meta.dataset.draw(); - } + draw: function() { + var meta = this.getMeta(); + var elements = meta.data || []; + var ilen = elements.length; + var i = 0; - for (; i < ilen; ++i) { - elements[i].draw(); - } - }, - - removeHoverStyle: function(element) { - helpers.merge(element._model, element.$previousStyle || {}); - delete element.$previousStyle; - }, - - setHoverStyle: function(element) { - var dataset = this.chart.data.datasets[element._datasetIndex]; - var index = element._index; - var custom = element.custom || {}; - var valueOrDefault = helpers.valueAtIndexOrDefault; - var getHoverColor = helpers.getHoverColor; - var model = element._model; - - element.$previousStyle = { - backgroundColor: model.backgroundColor, - borderColor: model.borderColor, - borderWidth: model.borderWidth - }; - - model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : valueOrDefault(dataset.hoverBackgroundColor, index, getHoverColor(model.backgroundColor)); - model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : valueOrDefault(dataset.hoverBorderColor, index, getHoverColor(model.borderColor)); - model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : valueOrDefault(dataset.hoverBorderWidth, index, model.borderWidth); - }, - - /** - * @private - */ - resyncElements: function() { - var me = this; - var meta = me.getMeta(); - var data = me.getDataset().data; - var numMeta = meta.data.length; - var numData = data.length; - - if (numData < numMeta) { - meta.data.splice(numData, numMeta - numData); - } else if (numData > numMeta) { - me.insertElements(numMeta, numData - numMeta); - } - }, - - /** - * @private - */ - insertElements: function(start, count) { - for (var i = 0; i < count; ++i) { - this.addElementAndReset(start + i); - } - }, - - /** - * @private - */ - onDataPush: function() { - this.insertElements(this.getDataset().data.length - 1, arguments.length); - }, - - /** - * @private - */ - onDataPop: function() { - this.getMeta().data.pop(); - }, - - /** - * @private - */ - onDataShift: function() { - this.getMeta().data.shift(); - }, - - /** - * @private - */ - onDataSplice: function(start, count) { - this.getMeta().data.splice(start, count); - this.insertElements(start, arguments.length - 2); - }, - - /** - * @private - */ - onDataUnshift: function() { - this.insertElements(0, arguments.length); + if (meta.dataset) { + meta.dataset.draw(); } - }); - Chart.DatasetController.extend = helpers.inherits; -}; + for (; i < ilen; ++i) { + elements[i].draw(); + } + }, + + removeHoverStyle: function(element) { + helpers.merge(element._model, element.$previousStyle || {}); + delete element.$previousStyle; + }, + + setHoverStyle: function(element) { + var dataset = this.chart.data.datasets[element._datasetIndex]; + var index = element._index; + var custom = element.custom || {}; + var valueOrDefault = helpers.valueAtIndexOrDefault; + var getHoverColor = helpers.getHoverColor; + var model = element._model; + + element.$previousStyle = { + backgroundColor: model.backgroundColor, + borderColor: model.borderColor, + borderWidth: model.borderWidth + }; + + model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : valueOrDefault(dataset.hoverBackgroundColor, index, getHoverColor(model.backgroundColor)); + model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : valueOrDefault(dataset.hoverBorderColor, index, getHoverColor(model.borderColor)); + model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : valueOrDefault(dataset.hoverBorderWidth, index, model.borderWidth); + }, + + /** + * @private + */ + resyncElements: function() { + var me = this; + var meta = me.getMeta(); + var data = me.getDataset().data; + var numMeta = meta.data.length; + var numData = data.length; + + if (numData < numMeta) { + meta.data.splice(numData, numMeta - numData); + } else if (numData > numMeta) { + me.insertElements(numMeta, numData - numMeta); + } + }, + + /** + * @private + */ + insertElements: function(start, count) { + for (var i = 0; i < count; ++i) { + this.addElementAndReset(start + i); + } + }, + + /** + * @private + */ + onDataPush: function() { + this.insertElements(this.getDataset().data.length - 1, arguments.length); + }, + + /** + * @private + */ + onDataPop: function() { + this.getMeta().data.pop(); + }, + + /** + * @private + */ + onDataShift: function() { + this.getMeta().data.shift(); + }, + + /** + * @private + */ + onDataSplice: function(start, count) { + this.getMeta().data.splice(start, count); + this.insertElements(start, arguments.length - 2); + }, + + /** + * @private + */ + onDataUnshift: function() { + this.insertElements(0, arguments.length); + } +}); + +DatasetController.extend = helpers.inherits; diff --git a/test/specs/controller.bar.tests.js b/test/specs/controller.bar.tests.js index 0e843e1435f..62ab0acd87a 100644 --- a/test/specs/controller.bar.tests.js +++ b/test/specs/controller.bar.tests.js @@ -1,6 +1,11 @@ describe('Chart.controllers.bar', function() { describe('auto', jasmine.fixture.specs('controller.bar')); + it('should be registered as dataset controller', function() { + expect(typeof Chart.controllers.bar).toBe('function'); + expect(typeof Chart.controllers.horizontalBar).toBe('function'); + }); + it('should be constructed', function() { var chart = window.acquireChart({ type: 'bar', diff --git a/test/specs/controller.bubble.tests.js b/test/specs/controller.bubble.tests.js index a5f5b89a5de..9591e65973c 100644 --- a/test/specs/controller.bubble.tests.js +++ b/test/specs/controller.bubble.tests.js @@ -1,6 +1,10 @@ describe('Chart.controllers.bubble', function() { describe('auto', jasmine.fixture.specs('controller.bubble')); + it('should be registered as dataset controller', function() { + expect(typeof Chart.controllers.bubble).toBe('function'); + }); + it('should be constructed', function() { var chart = window.acquireChart({ type: 'bubble', diff --git a/test/specs/controller.doughnut.tests.js b/test/specs/controller.doughnut.tests.js index 2f9602bda73..e7bc951fd57 100644 --- a/test/specs/controller.doughnut.tests.js +++ b/test/specs/controller.doughnut.tests.js @@ -1,4 +1,9 @@ describe('Chart.controllers.doughnut', function() { + it('should be registered as dataset controller', function() { + expect(typeof Chart.controllers.doughnut).toBe('function'); + expect(Chart.controllers.doughnut).toBe(Chart.controllers.pie); + }); + it('should be constructed', function() { var chart = window.acquireChart({ type: 'doughnut', diff --git a/test/specs/controller.line.tests.js b/test/specs/controller.line.tests.js index 25a9ed6b0f5..f3a3351de9c 100644 --- a/test/specs/controller.line.tests.js +++ b/test/specs/controller.line.tests.js @@ -1,6 +1,10 @@ describe('Chart.controllers.line', function() { describe('auto', jasmine.fixture.specs('controller.line')); + it('should be registered as dataset controller', function() { + expect(typeof Chart.controllers.line).toBe('function'); + }); + it('should be constructed', function() { var chart = window.acquireChart({ type: 'line', diff --git a/test/specs/controller.polarArea.tests.js b/test/specs/controller.polarArea.tests.js index 1023bf0789b..1feda5134b8 100644 --- a/test/specs/controller.polarArea.tests.js +++ b/test/specs/controller.polarArea.tests.js @@ -1,6 +1,10 @@ -describe('auto', jasmine.fixture.specs('controller.polarArea')); - describe('Chart.controllers.polarArea', function() { + describe('auto', jasmine.fixture.specs('controller.polarArea')); + + it('should be registered as dataset controller', function() { + expect(typeof Chart.controllers.polarArea).toBe('function'); + }); + it('should be constructed', function() { var chart = window.acquireChart({ type: 'polarArea', diff --git a/test/specs/controller.radar.tests.js b/test/specs/controller.radar.tests.js index 6a447a1ccdb..a013274bb78 100644 --- a/test/specs/controller.radar.tests.js +++ b/test/specs/controller.radar.tests.js @@ -1,6 +1,10 @@ describe('Chart.controllers.radar', function() { describe('auto', jasmine.fixture.specs('controller.radar')); + it('should be registered as dataset controller', function() { + expect(typeof Chart.controllers.radar).toBe('function'); + }); + it('Should be constructed', function() { var chart = window.acquireChart({ type: 'radar', diff --git a/test/specs/controller.scatter.test.js b/test/specs/controller.scatter.test.js index 435750928d9..6063caed766 100644 --- a/test/specs/controller.scatter.test.js +++ b/test/specs/controller.scatter.test.js @@ -1,4 +1,8 @@ describe('Chart.controllers.scatter', function() { + it('should be registered as dataset controller', function() { + expect(typeof Chart.controllers.scatter).toBe('function'); + }); + describe('showLines option', function() { it('should not draw a line if undefined', function() { var chart = window.acquireChart({ From 3cb2d7050e0976f92383beb48bee07e5d579a2c0 Mon Sep 17 00:00:00 2001 From: Simon Brunel Date: Sat, 1 Dec 2018 08:35:43 +0100 Subject: [PATCH 036/137] Remove gulp-connect and add jsdelivr/unpkg paths (#5875) --- .travis.yml | 2 +- gulpfile.js | 20 +------------------- package.json | 23 ++++++++++++++++------- 3 files changed, 18 insertions(+), 27 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9b38ab3c146..574bfe9a27a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: node_js node_js: - - "6" + - lts/* before_install: - "export CHROME_BIN=/usr/bin/google-chrome" diff --git a/gulpfile.js b/gulpfile.js index 06229d6f89c..c0a18cda225 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,6 +1,5 @@ var gulp = require('gulp'); var concat = require('gulp-concat'); -var connect = require('gulp-connect'); var eslint = require('gulp-eslint'); var file = require('gulp-file'); var insert = require('gulp-insert'); @@ -26,7 +25,7 @@ var argv = yargs .option('force-output', {default: false}) .option('silent-errors', {default: false}) .option('verbose', {default: false}) - .argv + .argv; var srcDir = './src/'; var outDir = './dist/'; @@ -53,15 +52,12 @@ gulp.task('lint-html', lintHtmlTask); gulp.task('lint-js', lintJsTask); gulp.task('lint', gulp.parallel('lint-html', 'lint-js')); gulp.task('docs', docsTask); -gulp.task('server', serverTask); gulp.task('unittest', unittestTask); gulp.task('test', gulp.parallel('lint', 'unittest')); gulp.task('library-size', librarySizeTask); gulp.task('module-sizes', moduleSizesTask); gulp.task('size', gulp.parallel('library-size', 'module-sizes')); -gulp.task('_open', _openTask); gulp.task('default', gulp.parallel('build', 'watch')); -gulp.task('dev', gulp.parallel('server', 'default')); /** * Generates the bower.json manifest file which will be pushed along release tags. @@ -136,7 +132,6 @@ function buildTask() { .pipe(gulp.dest(outDir)); return merge(bundled, nonBundled); - } function packageTask() { @@ -235,16 +230,3 @@ function moduleSizesTask() { function watchTask() { return gulp.watch('./src/**', gulp.parallel('build')); } - -function serverTask() { - connect.server({ - port: 8000 - }); -} - -// Convenience task for opening the project straight from the command line - -function _openTask() { - exec('open http://localhost:8000'); - exec('subl .'); -} diff --git a/package.json b/package.json index b8c72f99afb..23b75ffd67b 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,27 @@ { "name": "chart.js", - "homepage": "http://www.chartjs.org", + "homepage": "https://www.chartjs.org", "description": "Simple HTML5 charts using the canvas element.", "version": "2.7.3", "license": "MIT", + "jsdelivr": "dist/Chart.min.js", + "unpkg": "dist/Chart.min.js", "main": "src/chart.js", + "keywords": [ + "canvas", + "charts", + "data", + "graphs", + "html5", + "responsive" + ], "repository": { "type": "git", "url": "https://github.com/chartjs/Chart.js.git" }, + "bugs": { + "url": "https://github.com/chartjs/Chart.js/issues" + }, "devDependencies": { "browserify": "^16.2.3", "browserify-istanbul": "^3.0.1", @@ -21,7 +34,6 @@ "gitbook-cli": "^2.3.2", "gulp": "^4.0.0", "gulp-concat": "^2.6.0", - "gulp-connect": "^5.6.1", "gulp-eslint": "^5.0.0", "gulp-file": "^0.4.0", "gulp-htmllint": "^0.0.16", @@ -39,16 +51,13 @@ "karma-chrome-launcher": "^2.2.0", "karma-coverage": "^1.1.1", "karma-firefox-launcher": "^1.0.1", - "karma-jasmine": "^2.0.0", + "karma-jasmine": "^2.0.1", "karma-jasmine-html-reporter": "^1.4.0", "merge-stream": "^1.0.1", "pixelmatch": "^4.0.2", "vinyl-source-stream": "^2.0.0", "watchify": "^3.9.0", - "yargs": "^12.0.2" - }, - "spm": { - "main": "Chart.js" + "yargs": "^12.0.5" }, "dependencies": { "chartjs-color": "^2.1.0", From 5797e034218bebb5c9330407c391c87e812ae839 Mon Sep 17 00:00:00 2001 From: generic-github-user <40661852+generic-github-user@users.noreply.github.com> Date: Tue, 4 Dec 2018 03:07:35 -0500 Subject: [PATCH 037/137] Refactor data generation in scatter basic example (#5877) Replace repeated function call with compact function, generateData --- samples/charts/scatter/basic.html | 57 +++++++------------------------ 1 file changed, 13 insertions(+), 44 deletions(-) diff --git a/samples/charts/scatter/basic.html b/samples/charts/scatter/basic.html index 6ac227c3b95..ccf1059f8b8 100644 --- a/samples/charts/scatter/basic.html +++ b/samples/charts/scatter/basic.html @@ -21,59 +21,28 @@ - - - - - -

- - - - - diff --git a/samples/advanced/progress-bar.html b/samples/advanced/progress-bar.html index b9c72446e59..4736546ba48 100644 --- a/samples/advanced/progress-bar.html +++ b/samples/advanced/progress-bar.html @@ -2,7 +2,7 @@ Animation Callbacks - + + + + +
+
+ +
+ + + + diff --git a/samples/samples.js b/samples/samples.js index 29ed1ff8190..bb0463f7e6e 100644 --- a/samples/samples.js +++ b/samples/samples.js @@ -148,6 +148,9 @@ }, { title: 'Point style', path: 'legend/point-style.html' + }, { + title: 'Callbacks', + path: 'legend/callbacks.html' }] }, { title: 'Tooltip', diff --git a/src/plugins/plugin.legend.js b/src/plugins/plugin.legend.js index 2e832f98afd..2c6870b279d 100644 --- a/src/plugins/plugin.legend.js +++ b/src/plugins/plugin.legend.js @@ -30,6 +30,7 @@ defaults._set('global', { }, onHover: null, + onLeave: null, labels: { boxWidth: 40, @@ -106,6 +107,11 @@ var Legend = Element.extend({ // Contains hit boxes for each dataset (in dataset order) this.legendHitBoxes = []; + /** + * @private + */ + this._hoveredItem = null; + // Are we in doughnut mode which has a different data type this.doughnutMode = false; }, @@ -458,20 +464,42 @@ var Legend = Element.extend({ } }, + /** + * @private + */ + _getLegendItemAt: function(x, y) { + var me = this; + var i, hitBox, lh; + + if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) { + // See if we are touching one of the dataset boxes + lh = me.legendHitBoxes; + for (i = 0; i < lh.length; ++i) { + hitBox = lh[i]; + + if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) { + // Touching an element + return me.legendItems[i]; + } + } + } + + return null; + }, + /** * Handle an event * @private * @param {IEvent} event - The event to handle - * @return {boolean} true if a change occured */ handleEvent: function(e) { var me = this; var opts = me.options; var type = e.type === 'mouseup' ? 'click' : e.type; - var changed = false; + var hoveredItem; if (type === 'mousemove') { - if (!opts.onHover) { + if (!opts.onHover && !opts.onLeave) { return; } } else if (type === 'click') { @@ -483,33 +511,26 @@ var Legend = Element.extend({ } // Chart event already has relative position in it - var x = e.x; - var y = e.y; + hoveredItem = me._getLegendItemAt(e.x, e.y); - if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) { - // See if we are touching one of the dataset boxes - var lh = me.legendHitBoxes; - for (var i = 0; i < lh.length; ++i) { - var hitBox = lh[i]; - - if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) { - // Touching an element - if (type === 'click') { - // use e.native for backwards compatibility - opts.onClick.call(me, e.native, me.legendItems[i]); - changed = true; - break; - } else if (type === 'mousemove') { - // use e.native for backwards compatibility - opts.onHover.call(me, e.native, me.legendItems[i]); - changed = true; - break; - } + if (type === 'click') { + if (hoveredItem && opts.onClick) { + // use e.native for backwards compatibility + opts.onClick.call(me, e.native, hoveredItem); + } + } else { + if (opts.onLeave && hoveredItem !== me._hoveredItem) { + if (me._hoveredItem) { + opts.onLeave.call(me, e.native, me._hoveredItem); } + me._hoveredItem = hoveredItem; } - } - return changed; + if (opts.onHover && hoveredItem) { + // use e.native for backwards compatibility + opts.onHover.call(me, e.native, hoveredItem); + } + } } }); diff --git a/test/specs/plugin.legend.tests.js b/test/specs/plugin.legend.tests.js index fee715bdb13..e970c596a82 100644 --- a/test/specs/plugin.legend.tests.js +++ b/test/specs/plugin.legend.tests.js @@ -11,6 +11,7 @@ describe('Legend block tests', function() { // a callback that will handle onClick: jasmine.any(Function), onHover: null, + onLeave: null, labels: { boxWidth: 40, @@ -653,4 +654,53 @@ describe('Legend block tests', function() { expect(chart.legend.options).toEqual(jasmine.objectContaining(Chart.defaults.global.legend)); }); }); + + describe('callbacks', function() { + it('should call onClick, onHover and onLeave at the correct times', function() { + var clickItem = null; + var hoverItem = null; + var leaveItem = null; + + var chart = acquireChart({ + type: 'line', + data: { + labels: ['A', 'B', 'C', 'D'], + datasets: [{ + data: [10, 20, 30, 100] + }] + }, + options: { + legend: { + onClick: function(_, item) { + clickItem = item; + }, + onHover: function(_, item) { + hoverItem = item; + }, + onLeave: function(_, item) { + leaveItem = item; + } + } + } + }); + + var hb = chart.legend.legendHitBoxes[0]; + var el = { + x: hb.left + (hb.width / 2), + y: hb.top + (hb.height / 2) + }; + + jasmine.triggerMouseEvent(chart, 'click', el); + + expect(clickItem).toBe(chart.legend.legendItems[0]); + + jasmine.triggerMouseEvent(chart, 'mousemove', el); + + expect(hoverItem).toBe(chart.legend.legendItems[0]); + + jasmine.triggerMouseEvent(chart, 'mousemove', chart.getDatasetMeta(0).data[0]); + + expect(leaveItem).toBe(chart.legend.legendItems[0]); + }); + }); }); From 317cae11dc3358e816528229d05bea7c835c4ad3 Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Sun, 24 Feb 2019 01:59:21 -0800 Subject: [PATCH 118/137] Ignore invalid log scale min and max (#6058) --- src/scales/scale.logarithmic.js | 13 ++++-- test/specs/scale.logarithmic.tests.js | 62 +++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 4 deletions(-) diff --git a/src/scales/scale.logarithmic.js b/src/scales/scale.logarithmic.js index e52f714a81f..06b206df27c 100644 --- a/src/scales/scale.logarithmic.js +++ b/src/scales/scale.logarithmic.js @@ -62,6 +62,11 @@ var defaultConfig = { } }; +// TODO(v3): change this to positiveOrDefault +function nonNegativeOrDefault(value, defaultValue) { + return helpers.isFinite(value) && value >= 0 ? value : defaultValue; +} + module.exports = Scale.extend({ determineDataLimits: function() { var me = this; @@ -174,8 +179,8 @@ module.exports = Scale.extend({ var DEFAULT_MIN = 1; var DEFAULT_MAX = 10; - me.min = valueOrDefault(tickOpts.min, me.min); - me.max = valueOrDefault(tickOpts.max, me.max); + me.min = nonNegativeOrDefault(tickOpts.min, me.min); + me.max = nonNegativeOrDefault(tickOpts.max, me.max); if (me.min === me.max) { if (me.min !== 0 && me.min !== null) { @@ -211,8 +216,8 @@ module.exports = Scale.extend({ var reverse = !me.isHorizontal(); var generationOptions = { - min: tickOpts.min, - max: tickOpts.max + min: nonNegativeOrDefault(tickOpts.min), + max: nonNegativeOrDefault(tickOpts.max) }; var ticks = me.ticks = generateTicks(generationOptions, me); diff --git a/test/specs/scale.logarithmic.tests.js b/test/specs/scale.logarithmic.tests.js index cb44f108d36..dd7c7cce94a 100644 --- a/test/specs/scale.logarithmic.tests.js +++ b/test/specs/scale.logarithmic.tests.js @@ -477,6 +477,68 @@ describe('Logarithmic Scale tests', function() { expect(yScale.ticks[tickCount - 1]).toBe(10); }); + it('should ignore negative min and max options', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + data: [1, 1, 1, 2, 1, 0] + }], + labels: [] + }, + options: { + scales: { + yAxes: [{ + id: 'yScale', + type: 'logarithmic', + ticks: { + min: -10, + max: -1010, + callback: function(value) { + return value; + } + } + }] + } + } + }); + + var yScale = chart.scales.yScale; + expect(yScale.min).toBe(0); + expect(yScale.max).toBe(2); + }); + + it('should ignore invalid min and max options', function() { + var chart = window.acquireChart({ + type: 'bar', + data: { + datasets: [{ + data: [1, 1, 1, 2, 1, 0] + }], + labels: [] + }, + options: { + scales: { + yAxes: [{ + id: 'yScale', + type: 'logarithmic', + ticks: { + min: '', + max: false, + callback: function(value) { + return value; + } + } + }] + } + } + }); + + var yScale = chart.scales.yScale; + expect(yScale.min).toBe(0); + expect(yScale.max).toBe(2); + }); + it('should generate tick marks', function() { var chart = window.acquireChart({ type: 'bar', From b36d55d0936bf591d5ab58ae3d6a076f8f2d62e1 Mon Sep 17 00:00:00 2001 From: Jon Rimmer Date: Mon, 25 Feb 2019 07:59:48 +0000 Subject: [PATCH 119/137] Add onLeave to legend config docs (#6088) --- docs/configuration/legend.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/configuration/legend.md b/docs/configuration/legend.md index 30e3283b40d..c7d124868ed 100644 --- a/docs/configuration/legend.md +++ b/docs/configuration/legend.md @@ -12,6 +12,7 @@ The legend configuration is passed into the `options.legend` namespace. The glob | `fullWidth` | `boolean` | `true` | Marks that this box should take the full width of the canvas (pushing down other boxes). This is unlikely to need to be changed in day-to-day use. | `onClick` | `function` | | A callback that is called when a click event is registered on a label item. | `onHover` | `function` | | A callback that is called when a 'mousemove' event is registered on top of a label item. +| `onLeave` | `function` | | A callback that is called when a 'mousemove' event is registered outside of a previously hovered label item. | `reverse` | `boolean` | `false` | Legend will show datasets in reverse order. | `labels` | `object` | | See the [Legend Label Configuration](#legend-label-configuration) section below. From 0ec3f5569e6cf45b2d86886554bea200a71bd9c9 Mon Sep 17 00:00:00 2001 From: Jukka Kurkela Date: Mon, 25 Feb 2019 10:03:12 +0200 Subject: [PATCH 120/137] Add support for per side border width for rectangle (#6077) --- docs/charts/bar.md | 11 +- src/elements/element.rectangle.js | 239 +++++++++--------- .../controller.bar/borderSkipped/value.js | 5 + .../controller.bar/borderSkipped/value.png | Bin 4889 -> 1989 bytes .../borderWidth/indexable-object.js | 56 ++++ .../borderWidth/indexable-object.png | Bin 0 -> 1871 bytes .../controller.bar/borderWidth/indexable.png | Bin 4969 -> 1855 bytes .../controller.bar/borderWidth/negative.js | 49 ++++ .../controller.bar/borderWidth/negative.png | Bin 0 -> 1766 bytes .../controller.bar/borderWidth/object.js | 42 +++ .../controller.bar/borderWidth/object.png | Bin 0 -> 2113 bytes .../borderWidth/scriptable-object.js | 54 ++++ .../borderWidth/scriptable-object.png | Bin 0 -> 1640 bytes .../controller.bar/borderWidth/value.png | Bin 5109 -> 2064 bytes .../controller.bar/chart-area-clip.js | 42 +++ .../controller.bar/chart-area-clip.png | Bin 0 -> 1711 bytes .../controller.bar/horizontal-borders.js | 42 +++ .../controller.bar/horizontal-borders.png | Bin 0 -> 1522 bytes test/specs/element.rectangle.tests.js | 183 +------------- 19 files changed, 420 insertions(+), 303 deletions(-) create mode 100644 test/fixtures/controller.bar/borderWidth/indexable-object.js create mode 100644 test/fixtures/controller.bar/borderWidth/indexable-object.png create mode 100644 test/fixtures/controller.bar/borderWidth/negative.js create mode 100644 test/fixtures/controller.bar/borderWidth/negative.png create mode 100644 test/fixtures/controller.bar/borderWidth/object.js create mode 100644 test/fixtures/controller.bar/borderWidth/object.png create mode 100644 test/fixtures/controller.bar/borderWidth/scriptable-object.js create mode 100644 test/fixtures/controller.bar/borderWidth/scriptable-object.png create mode 100644 test/fixtures/controller.bar/chart-area-clip.js create mode 100644 test/fixtures/controller.bar/chart-area-clip.png create mode 100644 test/fixtures/controller.bar/horizontal-borders.js create mode 100644 test/fixtures/controller.bar/horizontal-borders.png diff --git a/docs/charts/bar.md b/docs/charts/bar.md index 3f6e5cdd202..02f080614a1 100644 --- a/docs/charts/bar.md +++ b/docs/charts/bar.md @@ -71,7 +71,7 @@ the color of the bars is generally set this way. | [`backgroundColor`](#styling) | [`Color`](../general/colors.md) | Yes | Yes | `'rgba(0, 0, 0, 0.1)'` | [`borderColor`](#styling) | [`Color`](../general/colors.md) | Yes | Yes | `'rgba(0, 0, 0, 0.1)'` | [`borderSkipped`](#borderskipped) | `string` | Yes | Yes | `'bottom'` -| [`borderWidth`](#styling) | `number` | Yes | Yes | `0` +| [`borderWidth`](#borderwidth) | number|object | Yes | Yes | `0` | [`data`](#data-structure) | `object[]` | - | - | **required** | [`hoverBackgroundColor`](#interactions) | [`Color`](../general/colors.md) | - | Yes | `undefined` | [`hoverBorderColor`](#interactions) | [`Color`](../general/colors.md) | - | Yes | `undefined` @@ -97,7 +97,7 @@ The style of each bar can be controlled with the following properties: | `backgroundColor` | The bar background color. | `borderColor` | The bar border color. | [`borderSkipped`](#borderskipped) | The edge to skip when drawing bar. -| `borderWidth` | The bar border width (in pixels). +| [`borderWidth`](#borderwidth) | The bar border width (in pixels). All these values, if `undefined`, fallback to the associated [`elements.rectangle.*`](../configuration/elements.md#rectangle-configuration) options. @@ -107,11 +107,18 @@ This setting is used to avoid drawing the bar stroke at the base of the fill. In general, this does not need to be changed except when creating chart types that derive from a bar chart. +**Note:** for negative bars in vertical chart, `top` and `bottom` are flipped. Same goes for `left` and `right` in horizontal chart. + Options are: * `'bottom'` * `'left'` * `'top'` * `'right'` +* `false` + +#### borderWidth + +If this value is a number, it is applied to all sides of the rectangle (left, top, right, bottom), except [`borderSkipped`](#borderskipped). If this value is an object, the `left` property defines the left border width. Similarly the `right`, `top` and `bottom` properties can also be specified. Omitted borders and [`borderSkipped`](#borderskipped) are skipped. ### Interactions diff --git a/src/elements/element.rectangle.js b/src/elements/element.rectangle.js index fe3702cda4e..5e5a2eac459 100644 --- a/src/elements/element.rectangle.js +++ b/src/elements/element.rectangle.js @@ -2,6 +2,7 @@ var defaults = require('../core/core.defaults'); var Element = require('../core/core.element'); +var helpers = require('../helpers/index'); var defaultColor = defaults.global.defaultColor; @@ -16,8 +17,8 @@ defaults._set('global', { } }); -function isVertical(bar) { - return bar._view.width !== undefined; +function isVertical(vm) { + return vm && vm.width !== undefined; } /** @@ -26,24 +27,21 @@ function isVertical(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; +function getBarBounds(vm) { + var x1, x2, y1, y2, half; + + if (isVertical(vm)) { + half = vm.width / 2; + x1 = vm.x - half; + x2 = vm.x + half; y1 = Math.min(vm.y, vm.base); y2 = Math.max(vm.y, vm.base); } else { - // horizontal bar - var halfHeight = vm.height / 2; + half = 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; + y1 = vm.y - half; + y2 = vm.y + half; } return { @@ -54,96 +52,107 @@ function getBarBounds(bar) { }; } -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 - 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'; - } +function swap(orig, v1, v2) { + return orig === v1 ? v2 : orig === v2 ? v1 : orig; +} - // 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; - } - } +function parseBorderSkipped(vm) { + var edge = vm.borderSkipped; + var res = {}; - 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; - } + if (!edge) { + return res; + } - function cornerAt(index) { - return corners[(startCorner + index) % 4]; + if (vm.horizontal) { + if (vm.base > vm.x) { + edge = swap(edge, 'left', 'right'); } + } else if (vm.base < vm.y) { + edge = swap(edge, 'bottom', 'top'); + } + + res[edge] = true; + return res; +} + +function parseBorderWidth(vm, maxW, maxH) { + var value = vm.borderWidth; + var skip = parseBorderSkipped(vm); + 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 { + t: skip.top || (t < 0) ? 0 : t > maxH ? maxH : t, + r: skip.right || (r < 0) ? 0 : r > maxW ? maxW : r, + b: skip.bottom || (b < 0) ? 0 : b > maxH ? maxH : b, + l: skip.left || (l < 0) ? 0 : l > maxW ? maxW : l + }; +} - // Draw rectangle from 'startCorner' - var corner = cornerAt(0); - ctx.moveTo(corner[0], corner[1]); +function boundingRects(vm) { + var bounds = getBarBounds(vm); + var width = bounds.right - bounds.left; + var height = bounds.bottom - bounds.top; + var border = parseBorderWidth(vm, width / 2, height / 2); - for (var i = 1; i < 4; i++) { - corner = cornerAt(i); - ctx.lineTo(corner[0], corner[1]); + return { + outer: { + x: bounds.left, + y: bounds.top, + w: width, + h: height + }, + inner: { + x: bounds.left + border.l, + y: bounds.top + border.t, + w: width - border.l - border.r, + h: height - border.t - border.b } + }; +} + +function inRange(vm, x, y) { + var skipX = x === null; + var skipY = y === null; + var bounds = !vm || (skipX && skipY) ? false : getBarBounds(vm); + + return bounds + && (skipX || x >= bounds.left && x <= bounds.right) + && (skipY || y >= bounds.top && y <= bounds.bottom); +} + +module.exports = Element.extend({ + draw: function() { + var ctx = this._chart.ctx; + var vm = this._view; + var rects = boundingRects(vm); + var outer = rects.outer; + var inner = rects.inner; + + ctx.fillStyle = vm.backgroundColor; + ctx.fillRect(outer.x, outer.y, outer.w, outer.h); - ctx.fill(); - if (borderWidth) { - ctx.stroke(); + if (outer.w === inner.w && outer.h === inner.h) { + return; } + + ctx.save(); + ctx.beginPath(); + ctx.rect(outer.x, outer.y, outer.w, outer.h); + ctx.clip(); + ctx.fillStyle = vm.borderColor; + ctx.rect(inner.x, inner.y, inner.w, inner.h); + ctx.fill('evenodd'); + ctx.restore(); }, height: function() { @@ -152,48 +161,28 @@ module.exports = Element.extend({ }, 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; + return inRange(this._view, mouseX, mouseY); }, 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; + var vm = this._view; + return isVertical(vm) + ? inRange(vm, mouseX, null) + : inRange(vm, null, mouseY); }, inXRange: function(mouseX) { - var bounds = getBarBounds(this); - return mouseX >= bounds.left && mouseX <= bounds.right; + return inRange(this._view, mouseX, null); }, inYRange: function(mouseY) { - var bounds = getBarBounds(this); - return mouseY >= bounds.top && mouseY <= bounds.bottom; + return inRange(this._view, null, mouseY); }, getCenterPoint: function() { var vm = this._view; var x, y; - if (isVertical(this)) { + if (isVertical(vm)) { x = vm.x; y = (vm.y + vm.base) / 2; } else { @@ -207,7 +196,7 @@ module.exports = Element.extend({ getArea: function() { var vm = this._view; - return isVertical(this) + return isVertical(vm) ? vm.width * Math.abs(vm.y - vm.base) : vm.height * Math.abs(vm.x - vm.base); }, diff --git a/test/fixtures/controller.bar/borderSkipped/value.js b/test/fixtures/controller.bar/borderSkipped/value.js index 139a2c68905..fed9592636b 100644 --- a/test/fixtures/controller.bar/borderSkipped/value.js +++ b/test/fixtures/controller.bar/borderSkipped/value.js @@ -22,6 +22,11 @@ module.exports = { { // option in element (fallback) data: [0, 5, -10, null], + }, + { + // option in dataset + data: [0, 5, -10, null], + borderSkipped: false } ] }, diff --git a/test/fixtures/controller.bar/borderSkipped/value.png b/test/fixtures/controller.bar/borderSkipped/value.png index 56ef05a41fe65a38ffee53a6b4a25964e0eee2e7..7f4179c87b13b21345439be6f2f7266b2b9b46d4 100644 GIT binary patch literal 1989 zcmeAS@N?(olHy`uVBq!ia0y~yU;;83893O0)X@p&(is@odpunnLn`LHy|yt|#*@M2 zqOZrc8rB0F%GBg;vee$>@_6piFlYDqqq>d@p4l$g>liMb{O2|=P&+=*@c*sNdHv^= z|9)*RmN|ZY|GwJp{QZCbz588X`~5)W-n)6*fB*gW)${w`UH9L|*YE%M)w8^AKT!0s z#Xj5Dz0WJ_e)iNeHgF39o!h`ILZt8X`?=R|zpZ1a`2JIv;d$jgyO-xkEtwJy+pDp-9^zS) RGZR#~db;|#taD0e0sxW#R#N}~ literal 4889 zcmeHLe^3*57Js{4=mvPjx)5>9LDQ#i#pklRzUaO^6Nw5|b5V%tz2?Cp< zpyvtpYz>t|&})kfsMwLh90>7%k+yPzs89kVskJ48A>oH4m_Y8k@&CKInRcdsB>!Z; zFQ0ku?fbs>c`xr+V!{T#ho1)k@HfWC{t*C2j~I~d=&S4Gzjp(8>o&&5ye&&M4!@E1 zo3DM?zUR&byduhI_5a7YzHOrJA$AkKuWL{Ji0@+m)iH^S+p9LJ>g~04-Fd-cab0-u z_noV))EAbQ9ZP721q`nc3}#JqF51jJ6XZjJ4SXW^UrK?0>wyg+P86>8qByyd??JNs zHPh84KWFW(QJK$3%iiEWd@jBTnZ4(w-;k-dVK0aPy zv)KkLQPIoG+GlO^k`=4$ANUqa%F4=eYx)g0&hty7XTrTYjHb&0Wj9pw1&)LEkLI)y zl?k~e9r`(g!SJm{qq#MgRX)=>9_IDpsW!tV4XrVPQRAH<8qyH8c!W z6uemOX%KY2Clwn+8Kw18U5VUqEKxRQ+ujG?}C1&i}rvhamNkZ_Icm_x|uIPRsPTVF*R`kxB z>A^Sycg`fWtYmamIIiBj%9{dQ+NYBp1*Hhb>7FfjhK>RSjUo3kd6ZcQYbm6iV7h5J z55wztR!?=>Ory{Tc{59BUd50Oum7PV3vMz;S)?|{=)*59A*uLa z8g6foyLf_&N%~IXX}}!rYOlk{7Dk1~C$DGKY4`%(;FoVa;>N;-SFh)Yoi%~7iG?Il z#PT49KEXo#{$-8pf=~c19&3CLC+z;NfVcwkE6$Mjv26X`Hm?rz_~hi|#DU29X7i3h z1xL0T?TY13xb;!d!OW5rncOs8Qd08MNT$UozI8UCrlmt~w$IGWJTMxKKZRC4IOtnk zAyzaEc+fPecECM?09R_YLo1S-YMWw4@SY{uFCE6CYYObMb zuj5x8;ZfugRQG??jYm~fzJ|&_wIdaAQ&b!Shdk09YdDT`Je#{7 z&qo~BX0r4rgd$@#e3ei@_r@|HO8JUZwh=@%JWM~|B=^G_dh&A9a3K%b&+Y?x#1T}m zf8wu;sXoo|4aZ?x0R=yP2+Hp`^0>L&Z}DeHV7Sy-O#UE5%tt#u9@3Yy;47GV3Bz}D zAnUt#&@Ycny*yIHL8|9^-woT2nCFxtP`TiS?tZNB8(Z`_7;}_Xo@AQUNw8fa(GF|1 z?QKowNtwM-YjQl6z9lJ6u|Bd|Wx0`+1$ILoYIAeh5yLwa&G#p*UusS51?IDw6yM}g zn_aKh%bPU$Hfb5x?JT)I4$HjFQc+h|*B&jq&h@+FLbEOvb??;6qt|(>kXO@)y0$Bt z1fUv|&jw?dG>W9kS0kcUKRh?dL}J*%IhQ-ah7a9tx%SojXfrYSAkt5H3;kd3-{o9lY}>#<{r$`>_~&RcygDx678dCN zXlqm2vW54t55ip%bIXZ~2hj>4a$@Vs#BB_0Zi#YkO75Y#e7yO+Pnf+J+KN0z8!lz52WZ^)Hvj+t diff --git a/test/fixtures/controller.bar/borderWidth/indexable-object.js b/test/fixtures/controller.bar/borderWidth/indexable-object.js new file mode 100644 index 00000000000..97f9af5d46f --- /dev/null +++ b/test/fixtures/controller.bar/borderWidth/indexable-object.js @@ -0,0 +1,56 @@ +module.exports = { + config: { + type: 'bar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + borderSkipped: false, + borderWidth: [ + {}, + {bottom: 1, left: 1, top: 1, right: 1}, + {bottom: 1, left: 2, top: 1, right: 2}, + {bottom: 1, left: 3, top: 1, right: 3}, + {bottom: 1, left: 4, top: 1, right: 4}, + {bottom: 1, left: 5, top: 1, right: 5} + ] + }, + { + // option in element (fallback) + data: [0, 5, 10, null, -10, -5], + } + ] + }, + options: { + legend: false, + title: false, + elements: { + rectangle: { + backgroundColor: 'transparent', + borderColor: '#80808080', + borderSkipped: false, + borderWidth: [ + {bottom: 1, left: 5, top: 1, right: 5}, + {bottom: 1, left: 4, top: 1, right: 4}, + {bottom: 1, left: 3, top: 1, right: 3}, + {bottom: 1, left: 2, top: 1, right: 2}, + {bottom: 1, left: 1, top: 1, right: 1}, + {} + ] + } + }, + scales: { + xAxes: [{display: false}], + yAxes: [{display: false}] + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/controller.bar/borderWidth/indexable-object.png b/test/fixtures/controller.bar/borderWidth/indexable-object.png new file mode 100644 index 0000000000000000000000000000000000000000..75471f52679ce285a93d1a4c780c7da6d9e4db48 GIT binary patch literal 1871 zcmeAS@N?(olHy`uVBq!ia0y~yU;;83893O0)X@p&(is@o#XMacLn`LHy|FMaCSAns z;)G3moO_#2JM}i7a$eJ9<2du_#o8}T+g1hN>)^*me|1v%hId0GR;WuyozfafChBH67y*=MPw)pVl$B)l5F*N8f zDljl`2mu|^pfJj$PB=U`Yu)$wpT+sRn`O(@-|djE3kHSky={-rGwiT``0nt>`-~q7 zj^6&xlrWEhA;N`$qd}92AwdwRXc-4XgDXn|!we+`1~EYfri89h1;aEPCW_bb`5pfKFOSR1?pXxPp2t`i7!E{#0fL`P9BhCY`pfse#};;TO&`5{`SSCZ-SfWc z9vA&@$@)OHQjuX_%Z~w0|N^u zu#j<>Fv_GtI5ga5JU%1?%h?bk_L0KNS1FiU9~bUHx3vIVCg!0QSyLm;e9( literal 0 HcmV?d00001 diff --git a/test/fixtures/controller.bar/borderWidth/indexable.png b/test/fixtures/controller.bar/borderWidth/indexable.png index 30011d3b722e474f7ae790f8a1c477f3679195b8..d3f1b85c8675193c6deceae1afe9da63a6bab7ff 100644 GIT binary patch literal 1855 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 ZVSYys`@d?L^aJp-ZL_y~=_c0EUjgZ(L^ diff --git a/test/fixtures/controller.bar/borderWidth/negative.js b/test/fixtures/controller.bar/borderWidth/negative.js new file mode 100644 index 00000000000..5a5189d7fbe --- /dev/null +++ b/test/fixtures/controller.bar/borderWidth/negative.js @@ -0,0 +1,49 @@ +module.exports = { + config: { + type: 'bar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + borderWidth: -2 + }, + { + // option in element (fallback) + data: [0, 5, 10, null, -10, -5], + }, + { + data: [0, 5, 10, null, -10, -5], + borderWidth: {left: -5, top: -5, bottom: -5, right: -5}, + borderSkipped: false + }, + { + data: [0, 5, 10, null, -10, -5], + borderWidth: {} + }, + ] + }, + options: { + legend: false, + title: false, + elements: { + rectangle: { + backgroundColor: '#888', + borderColor: '#f00', + borderWidth: -4 + } + }, + scales: { + xAxes: [{display: false}], + yAxes: [{display: false}] + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/controller.bar/borderWidth/negative.png b/test/fixtures/controller.bar/borderWidth/negative.png new file mode 100644 index 0000000000000000000000000000000000000000..ca2a445d99250cb59e9bc4d06b00b762801bfad4 GIT binary patch literal 1766 zcmeAS@N?(olHy`uVBq!ia0y~yU;;83893O0)X@p&(is@o)_S@)hE&XXd)+tpu!Dr_ z#RiVHAdc1q&DMA6X^C7%gSeIkaH&qnYf^aEf3>>r$=m1~lS=nr=KMEt>$T@mufLrz zeqOULzvj)2ikOFe%~;&Eor$1TN=H|5^0Jb!G?=U=h!Lm4<28k~h0m>d`wSU4FN6&M(r zSQ;1v7#JLx96)jkj0y}a3=R{3LQD(_9zY>R1_2cYmIg?KIgDx>fFNNIc*Ib^U@;n3 tLogjS%;#iaurOg%7%lN=QS$da;9h%i_KZWT^k*{wfv2mV%Q~loCID${;hF#d literal 0 HcmV?d00001 diff --git a/test/fixtures/controller.bar/borderWidth/object.js b/test/fixtures/controller.bar/borderWidth/object.js new file mode 100644 index 00000000000..9133b30aec5 --- /dev/null +++ b/test/fixtures/controller.bar/borderWidth/object.js @@ -0,0 +1,42 @@ +module.exports = { + config: { + type: 'bar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + borderSkipped: false, + borderWidth: {bottom: 1, left: 2, top: 3, right: 4} + }, + { + // option in element (fallback) + data: [0, 5, 10, null, -10, -5], + } + ] + }, + options: { + legend: false, + title: false, + elements: { + rectangle: { + backgroundColor: 'transparent', + borderColor: '#888', + borderSkipped: false, + borderWidth: {bottom: 4, left: 3, top: 2, right: 1} + } + }, + scales: { + xAxes: [{display: false}], + yAxes: [{display: false}] + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/controller.bar/borderWidth/object.png b/test/fixtures/controller.bar/borderWidth/object.png new file mode 100644 index 0000000000000000000000000000000000000000..04576006af463429104cd7595d23318d113cf6cd GIT binary patch literal 2113 zcmeAS@N?(olHy`uVBq!ia0y~yU;;83893O0)X@p&(is>yI6Pe(Ln`LHy|pnjHC&`M z@#wk(XCnT@ zzVF|&pJ9b61B2KPj)qtL5BTSO)nmxwWMKFw&Zxk!ij$#%m8F4Ug&I(S00Yy4Squyj zz^H1-Vq!>O991xM!lAK~@xzPBLJa(Nz?d@v#vBVIqXDS}{R}%^9R6Li{qb_{hVFNt zzSea!Ffcrb&)a_c`Rb&vWxH#4-TwIUknDWvd#^+78SbgSV@QyIn6RLevEba#Z!3P! zFW$F=#o?=L{gXMLbr=}heseSwpPT+JeQxo-rK}EL@Bh8`{l7IZmHqH%X<&G9Lj|Zr z7?_-rCj+y-A22=j`2w9~0W{Rg44C8|jw%}NLEzxWVDpIs64k&$2~^O7DTjQ9n7p#~W!Hm(3=AoPh`RKJGJ~EKQ^WFlhNn_zA9%NJ23ETa N44$rjF6*2UngBaj#$^Bi literal 0 HcmV?d00001 diff --git a/test/fixtures/controller.bar/borderWidth/scriptable-object.js b/test/fixtures/controller.bar/borderWidth/scriptable-object.js new file mode 100644 index 00000000000..5bd2903efa7 --- /dev/null +++ b/test/fixtures/controller.bar/borderWidth/scriptable-object.js @@ -0,0 +1,54 @@ +module.exports = { + config: { + type: 'bar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + borderSkipped: false, + borderWidth: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return {top: Math.abs(value)}; + } + }, + { + // option in element (fallback) + data: [0, 5, 10, null, -10, -5] + } + ] + }, + options: { + legend: false, + title: false, + elements: { + rectangle: { + backgroundColor: 'transparent', + borderColor: '#80808080', + borderSkipped: false, + borderWidth: function(ctx) { + return {left: ctx.dataIndex * 2}; + } + } + }, + scales: { + xAxes: [{display: false}], + yAxes: [ + { + display: false, + ticks: { + beginAtZero: true + } + } + ] + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/controller.bar/borderWidth/scriptable-object.png b/test/fixtures/controller.bar/borderWidth/scriptable-object.png new file mode 100644 index 0000000000000000000000000000000000000000..10e65f5ced56ec31adbd1f15db2e98ef96bd1f45 GIT binary patch literal 1640 zcmeAS@N?(olHy`uVBq!ia0y~yU;;83893O0)X@p&(is@o^gUf1Ln`LHy=K^z94^sv z(Q{6vpzv9-IaQqJ=4!n1ogr5Ka`KG}Y>Bscmxix6$GKEol3UjO;{ z@wDH6|6Mq6r1H*>r91ILMidh&K-dF-% z@J)u1fuS0xjA5M$P{jnGdPkrz8z%#U#4r?iu(0^t`{!2o?cevbQifFZ#z z%lP3E9{MVaLmLCbf>{hF1Q-}Xgc&567#gye6r33t0(==#fZ@4{vw??^!NHVqf+7RM z3N;2Jpkbq$s2mUs4fU1v_5a_-JU?!4ea_PU-|NG`y~#i|T?$_z%R znk#bW*LJKfXZn!B^k8f4H^p`LI1fzbY`FgIX8VpRbq0SmhPd15@{eB4Ww4pa08?z> zHJS|Qk{=ihJb;Cn0AFaA4j1DR-sJY;>jO0ZVTNPgg&ebxsLQ02i2Y AmH+?% literal 0 HcmV?d00001 diff --git a/test/fixtures/controller.bar/borderWidth/value.png b/test/fixtures/controller.bar/borderWidth/value.png index 3c81aff7f2dfb863751bc423a7c8db002e959871..af89232e9723602b3b02106ba12e409b2a81ac8b 100644 GIT binary patch literal 2064 zcmeAS@N?(olHy`uVBq!ia0y~yU;;83893O0)X@p&(is@ouY0;UhE&XXdwuW9m~e*H zz@zI9oRyfen$vuNoLW_Sz$&&oEk%v93t9c%y70^K_ZYtUdGlWOLC^I0GZWsG+t)Pw zpQs#yh8?>_y`{`U{V2MHzz1_px{rVQsS>+fWLp7VJ% z149G55YQnFj^|| zfq{(^==uW+j0y}45-OvL@q~u|`#Hsx-;dXBFFyD4$1^#3`HFt;Pv)Y)aDVU;sBHo; z1W)Z@bNHqBLx1*tR)=W}3=ixD8JH9t85pKiUSqI$UbFwnVK zFYkU|x%u~<&#Zh642%j4lYTQXENB7d(v!7}3;|9I91TzQF)(N-GO#!l#RC(O05CD_ z)MsGu;sB<$JK_ussw@o*9`Cq;)`HU0cUA@m&QW8AL)dI%dJyN%z+htuj7}C%>0EG| zq2f#P_sDmQ2POl{6bEoYzMYeSp&|>!F&Hh%sZp^lNat;^mIqdcVvGvF>hOyc1HTop gdY5wqW<~ek(#vKsK5(4q;K=|4p00i_>zopr0OWP4(f|Me literal 5109 zcmeHLX;c$g7JgM$>?DXLCZK{q2nq-;(8{JlN(IrDCCb_^ENY8(qoAN{0+kU36bBd^ zp^+tXP*6}on^q8k46?aE?1K!*8f6Cw$Sx2vuh7#!=kJ^|hx|#-SMPoA-tR8CucG;O z){1l$Isia%?;gto0HE+E0;r4OS4CW{7XYm1do6c32D%Nt-+$(K(F4lcl8&Nb;uAA) zyP(SJyQLntS(2T%ZH?}HeAT1>rS~0oU@*1Ty>vEGw21b+q{KotJwD*B=H4fh$@;pw zx&}w34s97P{*{VRTQr9o)N=ba(8@78TAM?gR%SOFL0J?w5$zBz8JbmkuA!mfBNmI( z#DHc>Dto30BkC#lzO5`x+3@epQTdXxzVmpCF6$-9?fE*oeRFyU}@3Q!yf4zAhKqxq3>Y-SS z3iL5HpEm3mqNqgqQJa_iStx%xbMlI!M>3MDg^$YQn%4s zbcLYB)M#inmfR=l*+JkoI1*)Mc0QV1bNF$#+YP*kK;K%RoA~R5Ko=y;bpD8`{|G5N z`auwC*&wt;d=qAI_kRRAGy0y5Q0k z^A9MPsh|@-B39{FL_-1 zVT2FV&pirVhpIx474NFQ*CDM6ZSFaN20-qBn%B)6F*cxxVk3Yc32NbYu75enQ*hj= zSX*!bp$J1UH{k&s66sC8{GGb}F|@6^PHn0c?(&c{&Ah@&)(XMJYc(3FdL(z2G;@;c zn@pg-O_tBau_U>~TA?$RwgkGoSTUr`#R+;$3z2SHewP7B+2$Q9=L2(MRWViY0w%|c z?>fiNks8D`ugs#ofeLlk&S{nwLWj3^9-TRW8AA5r8DtEU< zN6a0rhFPFxA0P>Efbt4{8PaD>-GrgqU>xqBO>(C^yVZ70bPBFI#rkZ)gMzUMjSX=; zZG$~HvU1BOn!6M9So>4q&>rDVNXj3JIW~V_(obVtY$+^{r5bS!uSpH?GsjZ24P0;} zY1>TXY&V&$an}a-F&jBT;Ej)0TaZ@uHd~mm3ZR??zqqwsfa>Uiur(O99Ohxt4TnTB zP@=Ut{YJEWDASr2eY1&{N>f{phj3fK#Ii3nN-$eBS2QPb^LF4!BcT%7>Edc7c1a5f4-XFx z3JRLJ*u8FO|7RJDsPJg)_DdoRR>;{`S=bPS4e4Yw8nwh61GxKS%E&#>ah<(k^2DgC zA@)YHsj0w_u&}V~slUtRF%1n3;_#PWW(MSLr=NACn%@9|Fp_F<)|LA7HTRb0IM^x? zyh0}_7w2$=!17n)RHpN|{BTWvn}6MO$a(~=fmzW3BAh~94Ih}Co{cU@27>D_yt`ZK zGcJEcj|@H%(-;K~q%pVhvX|*+j%QxyF zS&`qC|5@kb&nRXLG1ap!vC3aj|39iMba06sY3rG?Y>WFz|Gs5BXL;C`5bZ`N9$M1= zKfj?5CmMU;e*kQQTWW1x>m!xwg6k^NqjG4BM%YHZ%a52J{D#)6&!hmsHW>8ELR!vr z-qC|TO;fD1MhL13vFimn)(T(IXHCb_SsdRi*kMggJe@S0Z3b*r!?_Q&agPiF&0_|P zpZtcpgnaTtF)5o5RC=`U_rJJ$s`ylgeR(}b^@f=7$A{`0zM_3U$A;PwqEu4MmIW~v z#9YWsviScOWv*^Z073Y2zVpCqy}Fcps6@_Va&X}e+_kGr?|W{hnFEFM*r}EQG1qyl zOuWS_8(2PhwF-sPq5?JNu&dS7Z{0RZM76WoE=YD1@k~(2q4G#J#(O<4Ducsc&$)lj zQ|W_O@@6N%J=C=XL9wfp2&awrxwAHYnRNVkPKqW@;E#yBSC^e4(ofYoB2nH6n=vXw zSDE2ln0Iw}bIoshApXI*D*!<&&@#Oh&djg{;}(=#$hrUjf{@?Kj1S1&IyyHuH#Rad zq8yZlGWLJ#1aNYzs;X)p9UXP}+#nNoxVgLYF7*xNdU=)UjaXzQ?`|vZx2beQ?jD$2 z4I9=P%uX@KS{Fnc{;DQ34=g=#Xkxhh5}UwoX253C3#b1HnLSV&bp%`AweE%8OaTr< z)2QzUmHd*&a+)xqy$hq-%-0vU3=LfW7@j=iKk)4j<4E*B{s@k%-LUUp%=zQ(pJ#vmy8JyOIQI@n3LjpD1ce14 ft&4bu1OFfB`%joSi?^xoA;>aMS3j3^P6W2i)R*M+Z%ZfIVWtvURlqH5)vJneuEL*RSWJs{p z_k3(9wsaJu)6J|Bl+bm$E{hbEh}Bv&%T~=wd%gE|MxqjaxPQ)j&wJi;&hwm)1DP4i z<3>&%2>@|2sYDJCg^n1+MkDd4vVAka2%}80I7@Z*)9a*zQ?`s-_F_#%=bNJ<@8yXd zQPVHgvJa(RdwwBZXnD}VX`eOzgZy5}+t0Ns%B;#1jzUiU<5$|?30yRGIdQ1!>>bE0 z(Y1+TnyY{CYR^UhPE(OX#~c)5NismJom<60@Osxh9SVh3 z;wT5T!PQr%ZeOLQ8uS>L#3DFcgXC)t`3}*(Z63un^@#xPI?kKZ&H7LX_S?CY34ugU@TwWy6oJa9z z`4Tk)-#Cz0&#ltBP$DbJ_6yR)ELiPM7xWIsgGr80lUc0*Cjva#uJUNOVXu(d`b9tv zyJ&KJYSHGU8lsmaVTQm}&o9wU`2 zA}Kl?4u?=C(s{YR5%*sdM4_^KA`_37dM=}FhNS^j<3m61tkqSc*+erjFdqy>rN`&$ z0wG9b5!JaU?MjSqxrPj%KxNM=WRnll5|lD88MH-DY|FiF%cA~elRO(^zErb&KkSc1rk0xRm^hZ5W1RSXv9(?5^1Y+nii zxJjVhfSPAm_E8`;w}(q7P2EW+`Nv8R_kySSzO*{0OdX*TJ;rre-Pb+Y zv Date: Mon, 25 Feb 2019 00:17:37 -0800 Subject: [PATCH 121/137] Add instructions for image-based tests to the contributors guide (#6073) --- docs/developers/contributing.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/developers/contributing.md b/docs/developers/contributing.md index 71cb5533793..15fc938ae09 100644 --- a/docs/developers/contributing.md +++ b/docs/developers/contributing.md @@ -42,6 +42,26 @@ The following commands are now available from the repository root: More information can be found in [gulpfile.js](https://github.com/chartjs/Chart.js/blob/master/gulpfile.js). +### Image-Based Tests + +Some display-related functionality is difficult to test via typical Jasmine units. For this reason, we introduced image-based tests ([#3988](https://github.com/chartjs/Chart.js/pull/3988) and [#5777](https://github.com/chartjs/Chart.js/pull/5777)) to assert that a chart is drawn pixel-for-pixel matching an expected image. + +Generated charts in image-based tests should be **as minimal as possible** and focus only on the tested feature to prevent failure if another feature breaks (e.g. disable the title and legend when testing scales). + +You can create a new image-based test by following the steps below: +- Create a JS file ([example](https://github.com/chartjs/Chart.js/blob/f7b671006a86201808402c3b6fe2054fe834fd4a/test/fixtures/controller.bubble/radius-scriptable.js)) or JSON file ([example](https://github.com/chartjs/Chart.js/blob/4b421a50bfa17f73ac7aa8db7d077e674dbc148d/test/fixtures/plugin.filler/fill-line-dataset.json)) that defines chart config and generation options. +- Add this file in `test/fixtures/{spec.name}/{feature-name}.json`. +- Add a [describe line](https://github.com/chartjs/Chart.js/blob/4b421a50bfa17f73ac7aa8db7d077e674dbc148d/test/specs/plugin.filler.tests.js#L10) to the beginning of `test/specs/{spec.name}.tests.js` if it doesn't exist yet. +- Run `gulp unittest --watch --inputs=test/specs/{spec.name}.tests.js`. +- Click the *"Debug"* button (top/right): a test should fail with the associated canvas visible. +- Right click on the chart and *"Save image as..."* `test/fixtures/{spec.name}/{feature-name}.png` making sure not to activate the tooltip or any hover functionality +- Refresh the browser page (`CTRL+R`): test should now pass +- Verify test relevancy by changing the feature values *slightly* in the JSON file. + +Tests should pass in both browsers. In general, we've hidden all text in image tests since it's quite difficult to get them passing between different browsers. As a result, it is recommended to hide all scales in image-based tests. It is also recommended to disable animations. If tests still do not pass, adjust [`tolerance` and/or `threshold`](https://github.com/chartjs/Chart.js/blob/1ca0ffb5d5b6c2072176fd36fa85a58c483aa434/test/jasmine.matchers.js) at the beginning of the JSON file keeping them **as low as possible**. + +When a test fails, the expected and actual images are shown. If you'd like to see the images even when the tests pass, set `"debug": true` in the JSON file. + ## Bugs and Issues Please report these on the GitHub page - at
github.com/chartjs/Chart.js. Please do not use issues for support requests. For help using Chart.js, please take a look at the [`chartjs`](https://stackoverflow.com/questions/tagged/chartjs) tag on Stack Overflow. From 93f4e6e4e8431fdfbdec8654ec325ab9b51cb501 Mon Sep 17 00:00:00 2001 From: Vincent-Ip <40588938+Vincent-Ip@users.noreply.github.com> Date: Wed, 27 Feb 2019 17:06:54 -0500 Subject: [PATCH 122/137] New weight option for pie and doughnut charts (#5951) Add functionality to give pie & doughnut datasets a weight attribute, which affects the relative thickness of the dataset when there are multiple datasets in pie & doughnut charts. The default weight of each dataset is 1, providing any other numerical value will allow the pie or doughnut dataset to be drawn with a thickness relative to its default size. For example a weight of 2 will allow the dataset to be drawn double its typical dataset thickness. Note that the weight attribute will only affect a pie or doughnut chart if there is more than one visible dataset. Using weight on a pie or doughnut dataset when there is only one dataset on the chart will have no affect. --- docs/charts/doughnut.md | 2 + src/controllers/controller.doughnut.js | 40 ++++++++++++-- .../controller.doughnut/doughnut-weight.json | 50 +++++++++++++++++ .../controller.doughnut/doughnut-weight.png | Bin 0 -> 35512 bytes .../controller.doughnut/pie-weight.json | 52 ++++++++++++++++++ .../controller.doughnut/pie-weight.png | Bin 0 -> 28314 bytes 6 files changed, 140 insertions(+), 4 deletions(-) create mode 100644 test/fixtures/controller.doughnut/doughnut-weight.json create mode 100644 test/fixtures/controller.doughnut/doughnut-weight.png create mode 100644 test/fixtures/controller.doughnut/pie-weight.json create mode 100644 test/fixtures/controller.doughnut/pie-weight.png diff --git a/docs/charts/doughnut.md b/docs/charts/doughnut.md index ad1af3106ef..65c8767fa9f 100644 --- a/docs/charts/doughnut.md +++ b/docs/charts/doughnut.md @@ -63,6 +63,7 @@ The doughnut/pie chart allows a number of properties to be specified for each da | [`hoverBackgroundColor`](#interations) | [`Color`](../general/colors.md) | Yes | Yes | `undefined` | [`hoverBorderColor`](#interactions) | [`Color`](../general/colors.md) | Yes | Yes | `undefined` | [`hoverBorderWidth`](#interactions) | `number` | Yes | Yes | `undefined` +| [`weight`](#styling) | `number` | - | - | `1` ### Styling @@ -73,6 +74,7 @@ The style of each arc can be controlled with the following properties: | `backgroundColor` | arc background color. | `borderColor` | arc border color. | `borderWidth` | arc border width (in pixels). +| `weight` | The relative thickness of the dataset. Providing a value for weight will cause the pie or doughnut dataset to be drawn with a thickness relative to the sum of all the dataset weight values. All these values, if `undefined`, fallback to the associated [`elements.arc.*`](../configuration/elements.md#arc-configuration) options. diff --git a/src/controllers/controller.doughnut.js b/src/controllers/controller.doughnut.js index 21e23ebcae8..a6d4a63775a 100644 --- a/src/controllers/controller.doughnut.js +++ b/src/controllers/controller.doughnut.js @@ -6,6 +6,7 @@ var elements = require('../elements/index'); var helpers = require('../helpers/index'); var resolve = helpers.options.resolve; +var valueOrDefault = helpers.valueOrDefault; defaults._set('doughnut', { animation: { @@ -152,6 +153,7 @@ module.exports = DatasetController.extend({ var arcs = meta.data; var cutoutPercentage = opts.cutoutPercentage; var circumference = opts.circumference; + var chartWeight = me._getRingWeight(me.index); var i, ilen; // If the chart's circumference isn't a full circle, calculate minSize as a ratio of the width/height of the arc @@ -180,14 +182,14 @@ module.exports = DatasetController.extend({ chart.borderWidth = me.getMaxBorderWidth(); chart.outerRadius = Math.max((minSize - chart.borderWidth) / 2, 0); chart.innerRadius = Math.max(cutoutPercentage ? (chart.outerRadius / 100) * (cutoutPercentage) : 0, 0); - chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount(); + chart.radiusLength = (chart.outerRadius - chart.innerRadius) / (me._getVisibleDatasetWeightTotal() || 1); chart.offsetX = offset.x * chart.outerRadius; chart.offsetY = offset.y * chart.outerRadius; meta.total = me.calculateTotal(); - me.outerRadius = chart.outerRadius - (chart.radiusLength * me.getRingIndex(me.index)); - me.innerRadius = Math.max(me.outerRadius - chart.radiusLength, 0); + me.outerRadius = chart.outerRadius - chart.radiusLength * me._getRingWeightOffset(me.index); + me.innerRadius = Math.max(me.outerRadius - chart.radiusLength * chartWeight, 0); for (i = 0, ilen = arcs.length; i < ilen; ++i) { me.updateElement(arcs[i], i, reset); @@ -322,7 +324,6 @@ module.exports = DatasetController.extend({ var model = arc._model; var options = arc._options; var getHoverColor = helpers.getHoverColor; - var valueOrDefault = helpers.valueOrDefault; arc.$previousStyle = { backgroundColor: model.backgroundColor, @@ -375,5 +376,36 @@ module.exports = DatasetController.extend({ } return values; + }, + + /** + * Get radius length offset of the dataset in relation to the visible datasets weights. This allows determining the inner and outer radius correctly + * @private + */ + _getRingWeightOffset: function(datasetIndex) { + var ringWeightOffset = 0; + + for (var i = 0; i < datasetIndex; ++i) { + if (this.chart.isDatasetVisible(i)) { + ringWeightOffset += this._getRingWeight(i); + } + } + + return ringWeightOffset; + }, + + /** + * @private + */ + _getRingWeight: function(dataSetIndex) { + return Math.max(valueOrDefault(this.chart.data.datasets[dataSetIndex].weight, 1), 0); + }, + + /** + * Returns the sum of all visibile data set weights. This value can be 0. + * @private + */ + _getVisibleDatasetWeightTotal: function() { + return this._getRingWeightOffset(this.chart.data.datasets.length); } }); diff --git a/test/fixtures/controller.doughnut/doughnut-weight.json b/test/fixtures/controller.doughnut/doughnut-weight.json new file mode 100644 index 00000000000..769814b0ccb --- /dev/null +++ b/test/fixtures/controller.doughnut/doughnut-weight.json @@ -0,0 +1,50 @@ +{ + "config": { + "type": "doughnut", + "data": { + "datasets": [{ + "data": [ 1, 1 ], + "backgroundColor": [ + "rgba(255, 99, 132, 0.8)", + "rgba(54, 162, 235, 0.8)" + ], + "borderWidth": 0 + }, + { + "data": [ 2, 1 ], + "hidden": true, + "borderWidth": 0 + }, + { + "data": [ 3, 3 ], + "weight": 3, + "backgroundColor": [ + "rgba(255, 206, 86, 0.8)", + "rgba(75, 192, 192, 0.8)" + ], + "borderWidth": 0 + }, + { + "data": [ 4, 0 ], + "weight": 0, + "borderWidth": 0 + }, + { + "data": [ 5, 0 ], + "weight": -2, + "borderWidth": 0 + }], + "labels": [ "label0", "label1" ] + }, + "options": { + "legend": false, + "title": false + } + }, + "options": { + "canvas": { + "height": 500, + "width": 500 + } + } +} \ No newline at end of file diff --git a/test/fixtures/controller.doughnut/doughnut-weight.png b/test/fixtures/controller.doughnut/doughnut-weight.png new file mode 100644 index 0000000000000000000000000000000000000000..d6ab34c8a9af80411801c8bc1cd856413811e1e6 GIT binary patch literal 35512 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 literal 0 HcmV?d00001 diff --git a/test/fixtures/controller.doughnut/pie-weight.json b/test/fixtures/controller.doughnut/pie-weight.json new file mode 100644 index 00000000000..91d6c110ba9 --- /dev/null +++ b/test/fixtures/controller.doughnut/pie-weight.json @@ -0,0 +1,52 @@ +{ + "config": { + "type": "pie", + "data": { + "datasets": [ + { + "data": [ 1, 1 ], + "backgroundColor": [ + "rgba(255, 99, 132, 0.8)", + "rgba(54, 162, 235, 0.8)" + ], + "borderWidth": 0 + }, + { + "data": [ 2, 1 ], + "hidden": true, + "borderWidth": 0 + }, + { + "data": [ 3, 3 ], + "weight": 3, + "backgroundColor": [ + "rgba(255, 206, 86, 0.8)", + "rgba(75, 192, 192, 0.8)" + ], + "borderWidth": 0 + }, + { + "data": [ 4, 0 ], + "weight": 0, + "borderWidth": 0 + }, + { + "data": [ 5, 0 ], + "weight": -2, + "borderWidth": 0 + } + ], + "labels": [ "label0", "label1" ] + }, + "options": { + "legend": false, + "title": false + } + }, + "options": { + "canvas": { + "height": 500, + "width": 500 + } + } +} \ No newline at end of file diff --git a/test/fixtures/controller.doughnut/pie-weight.png b/test/fixtures/controller.doughnut/pie-weight.png new file mode 100644 index 0000000000000000000000000000000000000000..606ae0ebc59fe78c76af278b60b36029424024e4 GIT binary patch literal 28314 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%0A!_F%|a?ZY_F(f^&-Yv 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

!MM(KVXyqLl1E~uhg5&Z<8SsG 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 literal 0 HcmV?d00001 From b2f7adc2b4539ee8d5208ccef8e0f6ab1f9c8558 Mon Sep 17 00:00:00 2001 From: Simon Brunel Date: Fri, 1 Mar 2019 09:13:21 +0100 Subject: [PATCH 123/137] Revamp the README.md and add link to the awesome list (#6096) Integrate the upcoming awesome list and make our README.md welcome page more user friendly by displaying the Chart.js logo, adding the docs TOC and removing instructions that was already in the docs. --- README.md | 69 ++++++++++++-------------------------- docs/SUMMARY.md | 2 +- docs/notes/extensions.md | 72 ---------------------------------------- 3 files changed, 23 insertions(+), 120 deletions(-) delete mode 100644 docs/notes/extensions.md diff --git a/README.md b/README.md index 8673e30cb6a..5a522a5e915 100644 --- a/README.md +++ b/README.md @@ -1,56 +1,31 @@ -# Chart.js - -[![travis](https://img.shields.io/travis/chartjs/Chart.js.svg?style=flat-square&maxAge=60)](https://travis-ci.org/chartjs/Chart.js) [![coveralls](https://img.shields.io/coveralls/chartjs/Chart.js.svg?style=flat-square&maxAge=600)](https://coveralls.io/github/chartjs/Chart.js?branch=master) [![codeclimate](https://img.shields.io/codeclimate/maintainability/chartjs/Chart.js.svg?style=flat-square&maxAge=600)](https://codeclimate.com/github/chartjs/Chart.js) [![slack](https://img.shields.io/badge/slack-chartjs-blue.svg?style=flat-square&maxAge=3600)](https://chartjs-slack.herokuapp.com/) - -*Simple HTML5 Charts using the canvas element* [chartjs.org](https://www.chartjs.org) - -## Installation - -You can download the latest version of Chart.js from the [GitHub releases](https://github.com/chartjs/Chart.js/releases/latest) or use a [Chart.js CDN](https://cdnjs.com/libraries/Chart.js). - -To install via npm: - -```bash -npm install chart.js --save -``` - -To install via bower: -```bash -bower install chart.js --save -``` - -### Selecting the Correct Build - -Chart.js provides two different builds for you to choose: `Stand-Alone Build`, `Bundled Build`. - -#### Stand-Alone Build -Files: -* `dist/Chart.js` -* `dist/Chart.min.js` - -The stand-alone build includes Chart.js as well as the color parsing library. If this version is used, you are required to include [Moment.js](https://momentjs.com/) before Chart.js for the functionality of the time axis. - -#### Bundled Build -Files: -* `dist/Chart.bundle.js` -* `dist/Chart.bundle.min.js` - -The bundled build includes Moment.js in a single file. You should use this version if you require time axes and want to include a single file. You should not use this build if your application already included Moment.js. Otherwise, Moment.js will be included twice which results in increasing page load time and possible version compatibility issues. The Moment.js version in the bundled build is private to Chart.js so if you want to use Moment.js yourself, it's better to use Chart.js (non bundled) and import Moment.js manually. +

+
+ Simple yet flexible JavaScript charting for designers & developers +

+ +
## Documentation -You can find documentation at [www.chartjs.org/docs](https://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](https://www.chartjs.org/docs/latest/developers/#previous-versions). +- [Introduction](https://www.chartjs.org/docs/latest/) +- [Getting Started](https://www.chartjs.org/docs/latest/getting-started/) +- [General](https://www.chartjs.org/docs/latest/general/) +- [Configuration](https://www.chartjs.org/docs/latest/configuration/) +- [Charts](https://www.chartjs.org/docs/latest/charts/) +- [Axes](https://www.chartjs.org/docs/latest/axes/) +- [Developers](https://www.chartjs.org/docs/latest/developers/) +- [Popular Extensions](https://github.com/chartjs/awesome) +- [Samples](https://www.chartjs.org/samples/) ## Contributing -Before submitting an issue or a pull request, please take a moment to look over the [contributing guidelines](https://github.com/chartjs/Chart.js/blob/master/docs/developers/contributing.md) first. For support using Chart.js, please post questions with the [`chartjs` tag on Stack Overflow](https://stackoverflow.com/questions/tagged/chartjs). - -## Building -Instructions on building and testing Chart.js can be found in [the documentation](https://github.com/chartjs/Chart.js/blob/master/docs/developers/contributing.md#building-and-testing). - -## Thanks -- [BrowserStack](https://browserstack.com) for allowing our team to test on thousands of browsers. -- [@n8agrin](https://twitter.com/n8agrin) for the Twitter handle donation. +Instructions on building and testing Chart.js can be found in [the documentation](https://github.com/chartjs/Chart.js/blob/master/docs/developers/contributing.md#building-and-testing). Before submitting an issue or a pull request, please take a moment to look over the [contributing guidelines](https://github.com/chartjs/Chart.js/blob/master/docs/developers/contributing.md) first. For support, please post questions on [Stack Overflow](https://stackoverflow.com/questions/tagged/chartjs) with the `chartjs` tag. ## License diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 15265bd2b5a..f0beb678cdc 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -51,5 +51,5 @@ * [Contributing](developers/contributing.md) * [Additional Notes](notes/README.md) * [Comparison Table](notes/comparison.md) - * [Popular Extensions](notes/extensions.md) + * [Popular Extensions](https://github.com/chartjs/awesome) * [License](notes/license.md) diff --git a/docs/notes/extensions.md b/docs/notes/extensions.md deleted file mode 100644 index d526ff0c4fd..00000000000 --- a/docs/notes/extensions.md +++ /dev/null @@ -1,72 +0,0 @@ -# Popular Extensions - -Many extensions can be found on the [Chart.js GitHub organization](https://github.com/chartjs) or on the [npm registry](https://www.npmjs.com/search?q=chartjs-). - -## Charts - - - chartjs-chart-financial - Adds financial chart types such as a candlestick. - - Chart.BarFunnel.js - Adds a bar funnel chart type. - - Chart.LinearGauge.js - Adds a linear gauge chart type. - - Chart.Smith.js - Adds a smith chart type. - -In addition, many charts can be found on the [npm registry](https://www.npmjs.com/search?q=chartjs-chart-). - -## Plugins - - - chartjs-plugin-annotation - Draws lines and boxes on chart area. - - chartjs-plugin-colorschemes - Enables automatic coloring using predefined color schemes. - - chartjs-plugin-crosshair - Adds a data crosshair to line and scatter charts - - chartjs-plugin-datalabels - Displays labels on data for any type of charts. - - chartjs-plugin-deferred - Defers initial chart update until chart scrolls into viewport. - - chartjs-plugin-draggable - Makes select chart elements draggable with the mouse. - - chartjs-plugin-rough - Draws charts in a sketchy, hand-drawn-like style. - - chartjs-plugin-stacked100 - Draws 100% stacked bar chart. - - chartjs-plugin-streaming - Enables to create live streaming charts. - - chartjs-plugin-style - Provides more styling options. - - chartjs-plugin-waterfall - Enables easy use of waterfall charts. - - chartjs-plugin-zoom - Enables zooming and panning on charts. - -In addition, many plugins can be found on the [npm registry](https://www.npmjs.com/search?q=chartjs-plugin-). - -## Integrations - -### Angular (v2+) - - - emn178/angular2-chartjs - - valor-software/ng2-charts - -### Angular (v1) - - angular-chart.js - - tc-angular-chartjs - - angular-chartjs - - Angular Chart-js Directive - -### React - - react-chartjs2 - - react-chartjs-2 - -### Django - - Django JChart - - Django Chartjs - -### Ruby on Rails - - chartjs-ror - -### Laravel - - laravel-chartjs - -### Vue.js - - vue-chartjs - -### Java - - Chart.java - - Wicked-Charts - -### GWT (Google Web toolkit) - - Charba - -### Ember.js - - ember-cli-chart - -### Omi (v5+) - - omi-chart From 946c6d0617bfa0eb21e86f6f75d7f34b2bb1dc85 Mon Sep 17 00:00:00 2001 From: Akihiko Kusanagi Date: Sat, 2 Mar 2019 00:03:20 +0100 Subject: [PATCH 124/137] Fix document errors related to ticks (#6099) --- docs/axes/cartesian/README.md | 4 ++-- docs/axes/styling.md | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/axes/cartesian/README.md b/docs/axes/cartesian/README.md index 86611b2fdb4..2c4ab87ef9d 100644 --- a/docs/axes/cartesian/README.md +++ b/docs/axes/cartesian/README.md @@ -29,10 +29,10 @@ The following options are common to all cartesian axes but do not apply to other | `autoSkip` | `boolean` | `true` | If true, automatically calculates how many labels can be shown and hides labels accordingly. Labels will be rotated up to `maxRotation` before skipping any. Turn `autoSkip` off to show all labels no matter what. | `autoSkipPadding` | `number` | `0` | Padding between the ticks on the horizontal axis when `autoSkip` is enabled. | `labelOffset` | `number` | `0` | Distance in pixels to offset the label from the centre point of the tick (in the x direction for the x axis, and the y direction for the y axis). *Note: this can cause labels at the edges to be cropped by the edge of the canvas* -| `maxRotation` | `number` | `90` | Maximum rotation for tick labels when rotating to condense labels. Note: Rotation doesn't occur until necessary. *Note: Only applicable to horizontal scales.* +| `maxRotation` | `number` | `50` | Maximum rotation for tick labels when rotating to condense labels. Note: Rotation doesn't occur until necessary. *Note: Only applicable to horizontal scales.* | `minRotation` | `number` | `0` | Minimum rotation for tick labels. *Note: Only applicable to horizontal scales.* | `mirror` | `boolean` | `false` | Flips tick labels around axis, displaying the labels inside the chart instead of outside. *Note: Only applicable to vertical scales.* -| `padding` | `number` | `10` | Padding between the tick label and the axis. When set on a vertical axis, this applies in the horizontal (X) direction. When set on a horizontal axis, this applies in the vertical (Y) direction. +| `padding` | `number` | `0` | Padding between the tick label and the axis. When set on a vertical axis, this applies in the horizontal (X) direction. When set on a horizontal axis, this applies in the vertical (Y) direction. ### Axis ID The properties `dataset.xAxisID` or `dataset.yAxisID` have to match the scale properties `scales.xAxes.id` or `scales.yAxes.id`. This is especially needed if multi-axes charts are used. diff --git a/docs/axes/styling.md b/docs/axes/styling.md index e0089cb3725..23b212348da 100644 --- a/docs/axes/styling.md +++ b/docs/axes/styling.md @@ -54,10 +54,11 @@ The minorTick configuration is nested under the ticks configuration in the `mino | `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)). ## Major Tick Configuration -The majorTick configuration is nested under the ticks configuration in the `major` key. It defines options for the major tick marks that are generated by the axis. Omitted options are inherited from `ticks` configuration. +The majorTick configuration is nested under the ticks configuration in the `major` key. It defines options for the major tick marks that are generated by the axis. Omitted options are inherited from `ticks` configuration. These options are disabled by default. | Name | Type | Default | Description | ---- | ---- | ------- | ----------- +| `enabled` | `boolean` | `false` | If true, major tick options are used to show major ticks. | `callback` | `function` | | Returns the string representation of the tick value as it should be displayed on the chart. See [callback](../axes/labelling.md#creating-custom-tick-formats). | `fontColor` | `Color` | `'#666'` | Font color for tick labels. | `fontFamily` | `string` | `"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif"` | Font family for the tick labels, follows CSS font-family options. From 653e9a954eae83a8fcf157e6d402bcacaac51821 Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Sun, 3 Mar 2019 01:26:40 -0800 Subject: [PATCH 125/137] Add a note about how to include an example against master (#6107) --- .github/ISSUE_TEMPLATE/BUG.md | 11 +++++++++-- docs/developers/contributing.md | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/BUG.md b/.github/ISSUE_TEMPLATE/BUG.md index 90ebd643e2b..1d9ca8c2754 100644 --- a/.github/ISSUE_TEMPLATE/BUG.md +++ b/.github/ISSUE_TEMPLATE/BUG.md @@ -21,13 +21,20 @@ labels: 'type: bug' ## Possible Solution - - + ## Steps to Reproduce (for bugs) ## Context diff --git a/docs/developers/contributing.md b/docs/developers/contributing.md index 15fc938ae09..6180a3139bc 100644 --- a/docs/developers/contributing.md +++ b/docs/developers/contributing.md @@ -72,6 +72,6 @@ Guidelines for reporting bugs: - Check the issue search to see if it has already been reported - Isolate the problem to a simple test case - - Please include a demonstration of the bug on a website such as [JS Bin](https://jsbin.com/), [JS Fiddle](https://jsfiddle.net/), or [Codepen](https://codepen.io/pen/). ([Template](https://codepen.io/pen?template=JXVYzq)) + - Please include a demonstration of the bug on a website such as [JS Bin](https://jsbin.com/), [JS Fiddle](https://jsfiddle.net/), or [Codepen](https://codepen.io/pen/). ([Template](https://codepen.io/pen?template=JXVYzq)). If filing a bug against `master`, you may reference the latest code via https://www.chartjs.org/dist/master/Chart.min.js (changing the filename to point at the file you need as appropriate). Do not rely on these files for production purposes as they may be removed at any time. Please provide any additional details associated with the bug, if it's browser or screen density specific, or only happens with a certain configuration or data. From 0ac215b56a14373d3e49d927d39e9e4ccaff52cd Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Sun, 3 Mar 2019 06:00:24 -0800 Subject: [PATCH 126/137] Improve financial sample tooltips and interactions (#6089) --- samples/scales/time/financial.html | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/samples/scales/time/financial.html b/samples/scales/time/financial.html index bce7d587b93..875bb60e158 100644 --- a/samples/scales/time/financial.html +++ b/samples/scales/time/financial.html @@ -33,8 +33,8 @@ } function randomBar(date, lastClose) { - var open = randomNumber(lastClose * 0.95, lastClose * 1.05); - var close = randomNumber(open * 0.95, open * 1.05); + var open = randomNumber(lastClose * 0.95, lastClose * 1.05).toFixed(2); + var close = randomNumber(open * 0.95, open * 1.05).toFixed(2); return { t: date.valueOf(), y: close @@ -44,12 +44,10 @@ var dateFormat = 'MMMM DD YYYY'; var date = moment('April 01 2017', dateFormat); var data = [randomBar(date, 30)]; - var labels = [date]; while (data.length < 60) { date = date.clone().add(1, 'd'); if (date.isoWeekday() <= 5) { data.push(randomBar(date, data[data.length - 1].y)); - labels.push(date); } } @@ -61,7 +59,6 @@ var cfg = { type: 'bar', data: { - labels: labels, datasets: [{ label: 'CHRT - Chart.js Corporation', backgroundColor: color(window.chartColors.red).alpha(0.5).rgbString(), @@ -80,7 +77,8 @@ type: 'time', distribution: 'series', ticks: { - source: 'labels' + source: 'data', + autoSkip: true } }], yAxes: [{ @@ -89,9 +87,24 @@ labelString: 'Closing price ($)' } }] + }, + tooltips: { + intersect: false, + mode: 'index', + callbacks: { + label: function(tooltipItem, myData) { + var label = myData.datasets[tooltipItem.datasetIndex].label || ''; + if (label) { + label += ': '; + } + label += parseFloat(tooltipItem.value).toFixed(2); + return label; + } + } } } }; + var chart = new Chart(ctx, cfg); document.getElementById('update').addEventListener('click', function() { From 35273ee9488b9764485e6a0adfe92390fa01b130 Mon Sep 17 00:00:00 2001 From: Simon Brunel Date: Sun, 3 Mar 2019 15:19:11 +0100 Subject: [PATCH 127/137] Optimize the npm package by removing useless files (#6105) Explicitly target files that should be included in the npm package, making it 10x smaller by removing the docs, samples, scripts, sources, tests and other useless files. --- .npmignore | 13 ------------- karma.conf.js | 4 ++-- package.json | 6 ++++++ rollup.config.js | 2 +- src/{chart.js => index.js} | 0 src/scales/scale.category.js | 2 +- src/scales/scale.linear.js | 2 +- src/scales/scale.logarithmic.js | 2 +- src/scales/scale.radialLinear.js | 2 +- src/scales/scale.time.js | 2 +- 10 files changed, 14 insertions(+), 21 deletions(-) delete mode 100644 .npmignore rename src/{chart.js => index.js} (100%) diff --git a/.npmignore b/.npmignore deleted file mode 100644 index 47b4948ed27..00000000000 --- a/.npmignore +++ /dev/null @@ -1,13 +0,0 @@ -/.git -/.github -/coverage -/custom -/dist/*.zip -/docs/index.md -/node_modules - -.codeclimate.yml -.DS_Store -.gitignore -.idea -.travis.yml diff --git a/karma.conf.js b/karma.conf.js index 02878567571..d7aae642a2e 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -49,12 +49,12 @@ module.exports = function(karma) { {pattern: 'test/fixtures/**/*.png', included: false}, 'node_modules/moment/min/moment.min.js', 'test/index.js', - 'src/chart.js' + 'src/index.js' ].concat(args.inputs), preprocessors: { 'test/index.js': ['rollup'], - 'src/chart.js': ['sources'] + 'src/index.js': ['sources'] }, rollupPreprocessor: { diff --git a/package.json b/package.json index 72c359debf4..5d16f9323cc 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,12 @@ "bugs": { "url": "https://github.com/chartjs/Chart.js/issues" }, + "files": [ + "bower.json", + "composer.json", + "dist/*.css", + "dist/*.js" + ], "devDependencies": { "clean-css": "^4.2.1", "coveralls": "^3.0.0", diff --git a/rollup.config.js b/rollup.config.js index 1f0dbbacac5..fc53f1a7460 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -7,7 +7,7 @@ const optional = require('./rollup.plugins').optional; const stylesheet = require('./rollup.plugins').stylesheet; const pkg = require('./package.json'); -const input = 'src/chart.js'; +const input = 'src/index.js'; const banner = `/*! * Chart.js v${pkg.version} * ${pkg.homepage} diff --git a/src/chart.js b/src/index.js similarity index 100% rename from src/chart.js rename to src/index.js diff --git a/src/scales/scale.category.js b/src/scales/scale.category.js index d837a7365da..b9e51bcb162 100644 --- a/src/scales/scale.category.js +++ b/src/scales/scale.category.js @@ -130,5 +130,5 @@ module.exports = Scale.extend({ } }); -// INTERNAL: static default options, registered in src/chart.js +// INTERNAL: static default options, registered in src/index.js module.exports._defaults = defaultConfig; diff --git a/src/scales/scale.linear.js b/src/scales/scale.linear.js index 41a4e4168a3..a2199b66e78 100644 --- a/src/scales/scale.linear.js +++ b/src/scales/scale.linear.js @@ -186,5 +186,5 @@ module.exports = LinearScaleBase.extend({ } }); -// INTERNAL: static default options, registered in src/chart.js +// INTERNAL: static default options, registered in src/index.js module.exports._defaults = defaultConfig; diff --git a/src/scales/scale.logarithmic.js b/src/scales/scale.logarithmic.js index 06b206df27c..fd67f0b19a4 100644 --- a/src/scales/scale.logarithmic.js +++ b/src/scales/scale.logarithmic.js @@ -345,5 +345,5 @@ module.exports = Scale.extend({ } }); -// INTERNAL: static default options, registered in src/chart.js +// INTERNAL: static default options, registered in src/index.js module.exports._defaults = defaultConfig; diff --git a/src/scales/scale.radialLinear.js b/src/scales/scale.radialLinear.js index d19c03e99bc..ce605bde404 100644 --- a/src/scales/scale.radialLinear.js +++ b/src/scales/scale.radialLinear.js @@ -530,5 +530,5 @@ module.exports = LinearScaleBase.extend({ } }); -// INTERNAL: static default options, registered in src/chart.js +// INTERNAL: static default options, registered in src/index.js module.exports._defaults = defaultConfig; diff --git a/src/scales/scale.time.js b/src/scales/scale.time.js index 7d1df171e0f..0db15bb7859 100644 --- a/src/scales/scale.time.js +++ b/src/scales/scale.time.js @@ -764,5 +764,5 @@ module.exports = Scale.extend({ } }); -// INTERNAL: static default options, registered in src/chart.js +// INTERNAL: static default options, registered in src/index.js module.exports._defaults = defaultConfig; From 344628ba9c5fc95a1edbc9cf94488461d24a4b32 Mon Sep 17 00:00:00 2001 From: Jukka Kurkela Date: Mon, 4 Mar 2019 10:11:57 +0200 Subject: [PATCH 128/137] Fix animation regression introduced by #5331 (#6108) --- src/core/core.animations.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/core/core.animations.js b/src/core/core.animations.js index 54645ddd996..df6100f5b1e 100644 --- a/src/core/core.animations.js +++ b/src/core/core.animations.js @@ -93,20 +93,24 @@ module.exports = { */ advance: function() { var animations = this.animations; - var animation, chart; + var animation, chart, numSteps, nextStep; var i = 0; + // 1 animation per chart, so we are looping charts here while (i < animations.length) { animation = animations[i]; chart = animation.chart; + numSteps = animation.numSteps; - animation.currentStep = Math.floor((Date.now() - animation.startTime) / animation.duration * animation.numSteps); - animation.currentStep = Math.min(animation.currentStep, animation.numSteps); + // Make sure that currentStep starts at 1 + // https://github.com/chartjs/Chart.js/issues/6104 + nextStep = Math.floor((Date.now() - animation.startTime) / animation.duration * numSteps) + 1; + animation.currentStep = Math.min(nextStep, numSteps); helpers.callback(animation.render, [chart, animation], chart); helpers.callback(animation.onAnimationProgress, [animation], chart); - if (animation.currentStep >= animation.numSteps) { + if (animation.currentStep >= numSteps) { helpers.callback(animation.onAnimationComplete, [animation], chart); chart.animating = false; animations.splice(i, 1); From 858cc80a1fbbf33e70808e75aaae94c21968d325 Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Mon, 4 Mar 2019 00:15:29 -0800 Subject: [PATCH 129/137] Properly initialize variables if ticks aren't being displayed (#6100) --- src/core/core.scale.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/core/core.scale.js b/src/core/core.scale.js index 5384e323613..1f65bc564b6 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -171,6 +171,9 @@ module.exports = Element.extend({ top: 0, bottom: 0 }, margins); + + me._maxLabelLines = 0; + me.longestLabelWidth = 0; me.longestTextCache = me.longestTextCache || {}; // Dimensions @@ -419,20 +422,18 @@ module.exports = Element.extend({ } } - // Don't bother fitting the ticks if we are not showing them + // Don't bother fitting the ticks if we are not showing the labels if (tickOpts.display && display) { var largestTextWidth = helpers.longestText(me.ctx, tickFont.string, labels, me.longestTextCache); var tallestLabelHeightInLines = helpers.numberOfLabelLines(labels); var lineSpace = tickFont.size * 0.5; var tickPadding = me.options.ticks.padding; - // Store max number of lines used in labels for _autoSkip + // Store max number of lines and widest label for _autoSkip me._maxLabelLines = tallestLabelHeightInLines; + me.longestLabelWidth = largestTextWidth; if (isHorizontal) { - // A horizontal axis is more constrained by the height. - me.longestLabelWidth = largestTextWidth; - var angleRadians = helpers.toRadians(me.labelRotation); var cosRotation = Math.cos(angleRadians); var sinRotation = Math.sin(angleRadians); @@ -679,11 +680,11 @@ module.exports = Element.extend({ var cos = Math.abs(Math.cos(rot)); var sin = Math.abs(Math.sin(rot)); - var padding = optionTicks.autoSkipPadding; - var w = me.longestLabelWidth + padding || 0; + var padding = optionTicks.autoSkipPadding || 0; + var w = (me.longestLabelWidth + padding) || 0; var tickFont = helpers.options._parseFont(optionTicks); - var h = me._maxLabelLines * tickFont.lineHeight + padding; + var h = (me._maxLabelLines * tickFont.lineHeight + padding) || 0; // Calculate space needed for 1 tick in axis direction. return isHorizontal @@ -744,7 +745,7 @@ module.exports = Element.extend({ var isHorizontal = me.isHorizontal(); var parseFont = helpers.options._parseFont; - var ticks = optionTicks.autoSkip ? me._autoSkip(me.getTicks()) : me.getTicks(); + var ticks = optionTicks.display && optionTicks.autoSkip ? me._autoSkip(me.getTicks()) : me.getTicks(); var tickFontColor = valueOrDefault(optionTicks.fontColor, defaultFontColor); var tickFont = parseFont(optionTicks); var lineHeight = tickFont.lineHeight; From 31aebf3bab8a6a45edce853360bc469ed42d386e Mon Sep 17 00:00:00 2001 From: Simon Brunel Date: Mon, 4 Mar 2019 09:57:08 +0100 Subject: [PATCH 130/137] Include generated CSS in the GitHub releases --- .travis.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 574bfe9a27a..0be85778214 100644 --- a/.travis.yml +++ b/.travis.yml @@ -42,13 +42,12 @@ deploy: branch: release - provider: releases api_key: $GITHUB_AUTH_TOKEN - file: - - "./dist/Chart.bundle.js" - - "./dist/Chart.bundle.min.js" - - "./dist/Chart.js" - - "./dist/Chart.min.js" - - "./dist/Chart.js.zip" skip_cleanup: true + file_glob: true + file: + - ./dist/*.css + - ./dist/*.js + - ./dist/*.zip on: tags: true - provider: npm From f9f048a5c5ff460e300eec43d7ed6a1fb4f29809 Mon Sep 17 00:00:00 2001 From: Simon Brunel Date: Mon, 4 Mar 2019 09:58:08 +0100 Subject: [PATCH 131/137] Bump version to 2.8.0-rc.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5d16f9323cc..38990b0aec2 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "chart.js", "homepage": "https://www.chartjs.org", "description": "Simple HTML5 charts using the canvas element.", - "version": "2.7.3", + "version": "2.8.0-rc.1", "license": "MIT", "jsdelivr": "dist/Chart.min.js", "unpkg": "dist/Chart.min.js", From d3b7559b9b2e1ccb62cd315bd103cafbb34118a2 Mon Sep 17 00:00:00 2001 From: Jon Rimmer Date: Wed, 6 Mar 2019 08:11:24 +0000 Subject: [PATCH 132/137] Tighten check for detecting if Moment is installed (#6113) --- src/adapters/adapter.moment.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/adapters/adapter.moment.js b/src/adapters/adapter.moment.js index e028b52f3ae..5d7c1f3103d 100644 --- a/src/adapters/adapter.moment.js +++ b/src/adapters/adapter.moment.js @@ -18,7 +18,7 @@ var FORMATS = { year: 'YYYY' }; -adapters._date.override(moment ? { +adapters._date.override(typeof moment === 'function' ? { _id: 'moment', // DEBUG ONLY formats: function() { From 87a74f99a1e73fef3ebd314a11c322829c3d7cd1 Mon Sep 17 00:00:00 2001 From: Simon Brunel Date: Wed, 6 Mar 2019 09:12:29 +0100 Subject: [PATCH 133/137] Fix missing Chart.Chart (deprecated) alias (#6112) --- src/index.js | 9 +++++++++ test/specs/global.deprecations.tests.js | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/src/index.js b/src/index.js index 586dcc38295..a8286b302c1 100644 --- a/src/index.js +++ b/src/index.js @@ -51,6 +51,15 @@ if (typeof window !== 'undefined') { // DEPRECATIONS +/** + * Provided for backward compatibility, not available anymore + * @namespace Chart.Chart + * @deprecated since version 2.8.0 + * @todo remove at version 3 + * @private + */ +Chart.Chart = Chart; + /** * Provided for backward compatibility, not available anymore * @namespace Chart.Legend diff --git a/test/specs/global.deprecations.tests.js b/test/specs/global.deprecations.tests.js index d9e705a1c32..437a316fe0b 100644 --- a/test/specs/global.deprecations.tests.js +++ b/test/specs/global.deprecations.tests.js @@ -24,6 +24,12 @@ describe('Deprecations', function() { }); }); + describe('Chart.Chart', function() { + it('should be defined as an alias to Chart', function() { + expect(Chart.Chart).toBe(Chart); + }); + }); + describe('Chart.helpers.aliasPixel', function() { it('should be defined as a function', function() { expect(typeof Chart.helpers.aliasPixel).toBe('function'); From f5ff45693e79093279aa8e1463721976c86bea5c Mon Sep 17 00:00:00 2001 From: Roman Borovik <31778230+Starmordar@users.noreply.github.com> Date: Mon, 11 Mar 2019 11:06:50 +0300 Subject: [PATCH 134/137] Correct typo in a comment in test/index.js (#6122) --- test/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/index.js b/test/index.js index b3fa6cf3b60..c0ed1526801 100644 --- a/test/index.js +++ b/test/index.js @@ -34,7 +34,7 @@ var utils = require('./utils'); window.waitForResize = utils.waitForResize; window.createMockContext = createMockContext; - // some style initialization to limit differences between browsers across different plateforms. + // some style initialization to limit differences between browsers across different platforms. utils.injectCSS( '.chartjs-wrapper, .chartjs-wrapper canvas {' + 'border: 0;' + From eddd1f14ba657d64675c6e23a2451b807c7a609b Mon Sep 17 00:00:00 2001 From: Simon Brunel Date: Tue, 12 Mar 2019 08:35:39 +0100 Subject: [PATCH 135/137] Keep the previous extensions page link alive (#6127) Instead of a direct link, restore the extensions.md file which now redirects /notes/extensions.html to https://github.com/chartjs/awesome in case anyone bookmarked it / there were links to it. --- book.json | 10 +++++++++- docs/SUMMARY.md | 2 +- docs/notes/extensions.md | 1 + 3 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 docs/notes/extensions.md diff --git a/book.json b/book.json index 8b0f73970df..22acbde968c 100644 --- a/book.json +++ b/book.json @@ -3,7 +3,15 @@ "title": "Chart.js documentation", "author": "chartjs", "gitbook": "3.2.2", - "plugins": ["-lunr", "-search", "search-plus", "anchorjs", "chartjs", "ga"], + "plugins": [ + "-lunr", + "-search", + "search-plus", + "anchorjs", + "chartjs", + "ga", + "redirect" + ], "pluginsConfig": { "anchorjs": { "icon": "#", diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index f0beb678cdc..15265bd2b5a 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -51,5 +51,5 @@ * [Contributing](developers/contributing.md) * [Additional Notes](notes/README.md) * [Comparison Table](notes/comparison.md) - * [Popular Extensions](https://github.com/chartjs/awesome) + * [Popular Extensions](notes/extensions.md) * [License](notes/license.md) diff --git a/docs/notes/extensions.md b/docs/notes/extensions.md new file mode 100644 index 00000000000..d66989b1592 --- /dev/null +++ b/docs/notes/extensions.md @@ -0,0 +1 @@ +!REDIRECT "https://github.com/chartjs/awesome" From 152f1d9725eaf38973687899d69c21f296d5d3f2 Mon Sep 17 00:00:00 2001 From: Simon Brunel Date: Tue, 12 Mar 2019 11:29:11 +0100 Subject: [PATCH 136/137] Bump version to 2.8.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 38990b0aec2..9d72920c053 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "chart.js", "homepage": "https://www.chartjs.org", "description": "Simple HTML5 charts using the canvas element.", - "version": "2.8.0-rc.1", + "version": "2.8.0", "license": "MIT", "jsdelivr": "dist/Chart.min.js", "unpkg": "dist/Chart.min.js", From 75e76cffe5270a9d4cece59871d65ba758ec3dd5 Mon Sep 17 00:00:00 2001 From: Jukka Kurkela Date: Wed, 13 Mar 2019 11:36:10 +0200 Subject: [PATCH 137/137] Make decimalPlaces private and update CDN links (#6131) --- docs/README.md | 2 +- docs/getting-started/README.md | 2 +- src/core/core.helpers.js | 3 ++- src/scales/scale.linearbase.js | 2 +- test/specs/core.helpers.tests.js | 16 ++++++++-------- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/docs/README.md b/docs/README.md index d0b0544ab0c..9592ecf2c10 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,7 +4,7 @@ ## Installation -You can download the latest version of Chart.js from the [GitHub releases](https://github.com/chartjs/Chart.js/releases/latest) or use a [Chart.js CDN](https://cdnjs.com/libraries/Chart.js). Detailed installation instructions can be found on the [installation](./getting-started/installation.md) page. +You can download the latest version of Chart.js from the [GitHub releases](https://github.com/chartjs/Chart.js/releases/latest) or use a [Chart.js CDN](https://www.jsdelivr.com/package/npm/chart.js). Detailed installation instructions can be found on the [installation](./getting-started/installation.md) page. ## Creating a Chart diff --git a/docs/getting-started/README.md b/docs/getting-started/README.md index 5a879c4ce51..9dd7ed684d0 100644 --- a/docs/getting-started/README.md +++ b/docs/getting-started/README.md @@ -11,7 +11,7 @@ First, we need to have a canvas in our page. Now that we have a canvas we can use, we need to include Chart.js in our page. ```html - + ``` Now, we can create a chart. We add a script to our page: diff --git a/src/core/core.helpers.js b/src/core/core.helpers.js index 5b4aec34c7e..7922747dee9 100644 --- a/src/core/core.helpers.js +++ b/src/core/core.helpers.js @@ -125,8 +125,9 @@ module.exports = function() { * i.e. the number of digits after the decimal point, of the value of this Number. * @param {number} x - A number. * @returns {number} The number of decimal places. + * @private */ - helpers.decimalPlaces = function(x) { + helpers._decimalPlaces = function(x) { if (!helpers.isFinite(x)) { return; } diff --git a/src/scales/scale.linearbase.js b/src/scales/scale.linearbase.js index 24816e9225e..7f68279db6c 100644 --- a/src/scales/scale.linearbase.js +++ b/src/scales/scale.linearbase.js @@ -44,7 +44,7 @@ function generateTicks(generationOptions, dataRange) { if (stepSize || isNullOrUndef(precision)) { // If a precision is not specified, calculate factor based on spacing - factor = Math.pow(10, helpers.decimalPlaces(spacing)); + factor = Math.pow(10, helpers._decimalPlaces(spacing)); } else { // If the user specified a precision, round to that number of decimal places factor = Math.pow(10, precision); diff --git a/test/specs/core.helpers.tests.js b/test/specs/core.helpers.tests.js index b0c7431c859..a075c0e8d85 100644 --- a/test/specs/core.helpers.tests.js +++ b/test/specs/core.helpers.tests.js @@ -73,14 +73,14 @@ describe('Core helper tests', function() { }); it('should get the correct number of decimal places', function() { - expect(helpers.decimalPlaces(100)).toBe(0); - expect(helpers.decimalPlaces(1)).toBe(0); - expect(helpers.decimalPlaces(0)).toBe(0); - expect(helpers.decimalPlaces(0.01)).toBe(2); - expect(helpers.decimalPlaces(-0.01)).toBe(2); - expect(helpers.decimalPlaces('1')).toBe(undefined); - expect(helpers.decimalPlaces('')).toBe(undefined); - expect(helpers.decimalPlaces(undefined)).toBe(undefined); + expect(helpers._decimalPlaces(100)).toBe(0); + expect(helpers._decimalPlaces(1)).toBe(0); + expect(helpers._decimalPlaces(0)).toBe(0); + expect(helpers._decimalPlaces(0.01)).toBe(2); + expect(helpers._decimalPlaces(-0.01)).toBe(2); + expect(helpers._decimalPlaces('1')).toBe(undefined); + expect(helpers._decimalPlaces('')).toBe(undefined); + expect(helpers._decimalPlaces(undefined)).toBe(undefined); }); it('should get an angle from a point', function() {

+ Downloads + Builds + Coverage + Awesome + Slack +